!72 发布 vue 版本 5.1.2 与 cloud 版本 2.1.2
Merge pull request !72 from 疯狂的狮子Li/temp
| | |
| | | |
| | | # 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换 |
| | | VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==' |
| | | # 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换 |
| | | VITE_APP_RSA_PRIVATE_KEY = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=' |
| | | |
| | | # 客户端id |
| | | VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e' |
| | |
| | | |
| | | # 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换 |
| | | VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==' |
| | | # 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换 |
| | | VITE_APP_RSA_PRIVATE_KEY = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=' |
| | | |
| | | # 客户端id |
| | | VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e' |
| | |
| | | # 启动服务 |
| | | npm run dev |
| | | |
| | | # 构建生产环境 yarn build:prod |
| | | # 构建生产环境 |
| | | npm run build:prod |
| | | |
| | | # 前端访问地址 http://localhost:80 |
| | | ``` |
| | | |
| | |
| | | { |
| | | "name": "ruoyi-vue-plus", |
| | | "version": "5.1.1", |
| | | "version": "5.1.2", |
| | | "description": "RuoYi-Vue-Plus多租户管理系统", |
| | | "author": "LionLi", |
| | | "license": "MIT", |
| | |
| | | <div> |
| | | <template v-for="(item, index) in options"> |
| | | <template v-if="values.includes(item.value)"> |
| | | <span v-if="(item.elTagType == 'default' || item.elTagType == '') && (item.elTagClass == '' || item.elTagClass == null)" |
| | | <span v-if="(item.elTagType === 'default' || item.elTagType === '') && (item.elTagClass === '' || item.elTagClass == null)" |
| | | :key="item.value" :index="index" :class="item.elTagClass"> |
| | | {{ item.label + " " }} |
| | | </span> |
| | |
| | | :disable-transitions="true" |
| | | :key="item.value + ''" |
| | | :index="index" |
| | | :type="item.elTagType === 'primary' ? '' : item.elTagType" |
| | | :type="(item.elTagType === 'primary' || item.elTagType === 'default')? '' : item.elTagType" |
| | | :class="item.elTagClass" |
| | | > |
| | | {{ item.label + " " }} |
| | |
| | | <div class="relative" :style="{ width: width }"> |
| | | <el-input v-model="modelValue" readonly @click="visible = !visible" placeholder="点击选择图标"> |
| | | <template #prepend> |
| | | <svg-icon :icon-class="modelValue as string" /> |
| | | <svg-icon :icon-class="modelValue" /> |
| | | </template> |
| | | </el-input> |
| | | |
| | |
| | | </template> |
| | | |
| | | <sidebar-item |
| | | v-for="(child) in item.children as RouteOption[]" |
| | | :key="child.path" |
| | | v-for="(child, index) in item.children" |
| | | :key="child.path + index" |
| | | :is-nest="true" |
| | | :item="child" |
| | | :base-path="resolvePath(child.path)" |
| | |
| | | if (to.path === '/login') { |
| | | next({ path: '/' }); |
| | | NProgress.done(); |
| | | } else if (whiteList.indexOf(to.path) !== -1) { |
| | | next() |
| | | } else { |
| | | if (useUserStore().roles.length === 0) { |
| | | isRelogin.show = true; |
| | |
| | | path: '/system/oss-config', |
| | | component: Layout, |
| | | hidden: true, |
| | | permissions: ['system:oss:list'], |
| | | permissions: ['system:ossConfig:list'], |
| | | children: [ |
| | | { |
| | | path: 'index', |
| | |
| | | import { TagView } from 'vue-router'; |
| | | import { TagView, RouteRecordNormalized } from 'vue-router'; |
| | | |
| | | export const useTagsViewStore = defineStore('tagsView', () => { |
| | | const visitedViews = ref<TagView[]>([]); |
| | |
| | | const delView = (view: TagView): Promise<{ visitedViews: TagView[]; cachedViews: string[] }> => { |
| | | return new Promise((resolve) => { |
| | | delVisitedView(view); |
| | | delCachedView(view); |
| | | if (!isDynamicRoute(view)) { |
| | | delCachedView(view); |
| | | } |
| | | resolve({ |
| | | visitedViews: [...visitedViews.value], |
| | | cachedViews: [...cachedViews.value] |
| | |
| | | } |
| | | }; |
| | | |
| | | const isDynamicRoute = (view: any): boolean => { |
| | | // 检查匹配的路由记录中是否有动态段 |
| | | return view.matched.some((m: RouteRecordNormalized) => m.path.includes(':')); |
| | | }; |
| | | |
| | | return { |
| | | visitedViews, |
| | | cachedViews, |
| | |
| | | removeToken(); |
| | | }; |
| | | |
| | | const setAvatar = (value: string) => { |
| | | avatar.value = value; |
| | | }; |
| | | |
| | | return { |
| | | userId, |
| | | token, |
| | |
| | | permissions, |
| | | login, |
| | | getInfo, |
| | | logout |
| | | logout, |
| | | setAvatar |
| | | }; |
| | | }); |
| | | |
| | |
| | | VITE_APP_POWERJOB_ADMIN: string; |
| | | VITE_APP_ENV: string; |
| | | VITE_APP_RSA_PUBLIC_KEY: string; |
| | | VITE_APP_RSA_PRIVATE_KEY: string; |
| | | VITE_APP_CLIENT_ID: string; |
| | | VITE_APP_WEBSOCKET: string; |
| | | } |
| | |
| | | }; |
| | | |
| | | /** |
| | | * 解密base64 |
| | | */ |
| | | export const decryptBase64 = (str: string) => { |
| | | return CryptoJS.enc.Base64.parse(str); |
| | | }; |
| | | |
| | | /** |
| | | * 使用密钥对数据进行加密 |
| | | * @param message |
| | | * @param aesKey |
| | |
| | | }); |
| | | return encrypted.toString(); |
| | | }; |
| | | |
| | | /** |
| | | * 使用密钥对数据进行解密 |
| | | * @param message |
| | | * @param aesKey |
| | | * @returns {string} |
| | | */ |
| | | export const decryptWithAes = (message: string, aesKey: CryptoJS.lib.WordArray) => { |
| | | const decrypted = CryptoJS.AES.decrypt(message, aesKey, { |
| | | mode: CryptoJS.mode.ECB, |
| | | padding: CryptoJS.pad.Pkcs7 |
| | | }); |
| | | return decrypted.toString(CryptoJS.enc.Utf8); |
| | | }; |
| | |
| | | const publicKey = import.meta.env.VITE_APP_RSA_PUBLIC_KEY; |
| | | |
| | | // 前端不建议存放私钥 不建议解密数据 因为都是透明的意义不大 |
| | | const privateKey = '**********'; |
| | | const privateKey = import.meta.env.VITE_APP_RSA_PRIVATE_KEY; |
| | | |
| | | // 加密 |
| | | export const encrypt = (txt: string) => { |
| | |
| | | import { LoadingInstance } from 'element-plus/es/components/loading/src/loading'; |
| | | import FileSaver from 'file-saver'; |
| | | import { getLanguage } from '@/lang'; |
| | | import { encryptBase64, encryptWithAes, generateAesKey } from '@/utils/crypto'; |
| | | import { encrypt } from '@/utils/jsencrypt'; |
| | | import { encryptBase64, encryptWithAes, generateAesKey, decryptWithAes, decryptBase64 } from '@/utils/crypto'; |
| | | import { encrypt, decrypt } from '@/utils/jsencrypt'; |
| | | |
| | | const encryptHeader = 'encrypt-key'; |
| | | let downloadLoadingInstance: LoadingInstance; |
| | | // 是否显示重新登录 |
| | | export const isRelogin = { show: false }; |
| | |
| | | if (isEncrypt && (config.method === 'post' || config.method === 'put')) { |
| | | // 生成一个 AES 密钥 |
| | | const aesKey = generateAesKey(); |
| | | config.headers['encrypt-key'] = encrypt(encryptBase64(aesKey)); |
| | | config.headers[encryptHeader] = encrypt(encryptBase64(aesKey)); |
| | | config.data = typeof config.data === 'object' ? encryptWithAes(JSON.stringify(config.data), aesKey) : encryptWithAes(config.data, aesKey); |
| | | } |
| | | // FormData数据去请求头Content-Type |
| | |
| | | // 响应拦截器 |
| | | service.interceptors.response.use( |
| | | (res: AxiosResponse) => { |
| | | // 加密后的 AES 秘钥 |
| | | const keyStr = res.headers[encryptHeader]; |
| | | // 加密 |
| | | if (keyStr != null && keyStr != '') { |
| | | const data = res.data; |
| | | // 请求体 AES 解密 |
| | | const base64Str = decrypt(keyStr); |
| | | // base64 解码 得到请求头的 AES 秘钥 |
| | | const aesKey = decryptBase64(base64Str.toString()); |
| | | // aesKey 解码 data |
| | | const decryptData = decryptWithAes(data, aesKey); |
| | | // 将结果 (得到的是 JSON 字符串) 转为 JSON |
| | | res.data = JSON.parse(decryptData); |
| | | } |
| | | // 未设置状态码则默认成功状态 |
| | | const code = res.data.code || HttpStatus.SUCCESS; |
| | | // 获取错误信息 |
| | |
| | | * 部署方式 Docker 容器编排 一键部署业务集群<br /> |
| | | * 国际化 SpringMessage Spring标准国际化方案<br /> |
| | | </p> |
| | | <p><b>当前版本:</b> <span>v5.1.1</span></p> |
| | | <p><b>当前版本:</b> <span>v5.1.2</span></p> |
| | | <p> |
| | | <el-tag type="danger">¥免费开源</el-tag> |
| | | </p> |
| | |
| | | * 分布式监控 Prometheus、Grafana 全方位性能监控<br /> |
| | | * 其余与 Vue 版本一致<br /> |
| | | </p> |
| | | <p><b>当前版本:</b> <span>v2.1.1</span></p> |
| | | <p><b>当前版本:</b> <span>v2.1.2</span></p> |
| | | <p> |
| | | <el-tag type="danger">¥免费开源</el-tag> |
| | | </p> |
| | |
| | | <script setup name="Online" lang="ts"> |
| | | import { forceLogout, list as initData } from "@/api/monitor/online"; |
| | | import { OnlineQuery, OnlineVO } from "@/api/monitor/online/types"; |
| | | import api from "@/api/system/user"; |
| | | import {to} from "await-to-js"; |
| | | |
| | | const { proxy } = getCurrentInstance() as ComponentInternalInstance; |
| | | const { sys_device_type } = toRefs<any>(proxy?.useDict("sys_device_type")); |
| | |
| | | } |
| | | /** 强退按钮操作 */ |
| | | const handleForceLogout = async (row: OnlineVO) => { |
| | | await proxy?.$modal.confirm('是否确认强退名称为"' + row.userName + '"的用户?'); |
| | | await forceLogout(row.tokenId); |
| | | await getList(); |
| | | proxy?.$modal.msgSuccess("删除成功"); |
| | | const [err] = await to(proxy?.$modal.confirm('是否确认强退名称为"' + row.userName + '"的用户?') as any); |
| | | if (!err) { |
| | | await forceLogout(row.tokenId); |
| | | await getList(); |
| | | proxy?.$modal.msgSuccess("删除成功"); |
| | | } |
| | | } |
| | | |
| | | onMounted(() => { |
| | |
| | | const codeUrl = ref(""); |
| | | const loading = ref(false); |
| | | const captchaEnabled = ref(true); |
| | | const registerRef = ref(ElForm); |
| | | const registerRef = ref<ElFormInstance>(); |
| | | // 租户列表 |
| | | const tenantList = ref<TenantVO[]>([]); |
| | | |
| | | const handleRegister = () => { |
| | | registerRef.value.validate(async (valid: boolean) => { |
| | | registerRef.value?.validate(async (valid: boolean) => { |
| | | if (valid) { |
| | | loading.value = true; |
| | | const [err] = await to(register(registerForm.value)); |
| | |
| | | </el-table> |
| | | </el-card> |
| | | |
| | | <el-dialog :title="dialog.title" v-model="dialog.visible" destroy-on-close append-to-bod width="600px"> |
| | | <el-dialog :title="dialog.title" v-model="dialog.visible" destroy-on-close append-to-body width="600px"> |
| | | <el-form ref="deptFormRef" :model="form" :rules="rules" label-width="80px"> |
| | | <el-row> |
| | | <el-col :span="24" v-if="form.parentId !== 0"> |
| | |
| | | <el-table-column label="字典编码" align="center" prop="dictCode" v-if="false" /> |
| | | <el-table-column label="字典标签" align="center" prop="dictLabel"> |
| | | <template #default="scope"> |
| | | <span v-if="(scope.row.listClass == '' || scope.row.listClass == 'default') && (scope.row.cssClass == '' || scope.row.cssClass == null)">{{ scope.row.dictLabel }}</span> |
| | | <el-tag v-else :type="scope.row.listClass == 'primary' ? '' : scope.row.listClass" :class="scope.row.cssClass">{{ scope.row.dictLabel }}</el-tag> |
| | | <span v-if="(scope.row.listClass === '' || scope.row.listClass === 'default') && (scope.row.cssClass === '' || scope.row.cssClass == null)">{{ scope.row.dictLabel }}</span> |
| | | <el-tag v-else :type="(scope.row.listClass === 'primary' || scope.row.listClass === 'default') ? '' : scope.row.listClass" :class="scope.row.cssClass">{{ scope.row.dictLabel }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="字典键值" align="center" prop="dictValue" /> |
| | |
| | | <el-input v-model="form.perms" placeholder="请输入权限标识" maxlength="100" /> |
| | | <template #label> |
| | | <span> |
| | | <el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasPermi('system:user:list')`)" placement="top"> |
| | | <el-tooltip content="控制器中定义的权限字符,如:@SaCheckPermission('system:user:list')" placement="top"> |
| | | <el-icon> |
| | | <question-filled /> |
| | | </el-icon> |
| | |
| | | <template #header> |
| | | <el-row :gutter="10" class="mb8"> |
| | | <el-col :span="1.5"> |
| | | <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:oss:add']">新增</el-button> |
| | | <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:ossConfig:add']">新增</el-button> |
| | | </el-col> |
| | | <el-col :span="1.5"> |
| | | <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:oss:edit']">修改</el-button> |
| | | <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:ossConfig:edit']">修改</el-button> |
| | | </el-col> |
| | | <el-col :span="1.5"> |
| | | <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:oss:remove']"> |
| | | <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:ossConfig:remove']"> |
| | | 删除 |
| | | </el-button> |
| | | </el-col> |
| | |
| | | <el-table-column label="操作" fixed="right" align="center" width="150" class-name="small-padding"> |
| | | <template #default="scope"> |
| | | <el-tooltip content="修改" placement="top"> |
| | | <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:oss:edit']"></el-button> |
| | | <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:ossConfig:edit']"></el-button> |
| | | </el-tooltip> |
| | | <el-tooltip content="删除" placement="top"> |
| | | <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:oss:remove']"></el-button> |
| | | <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:ossConfig:remove']"></el-button> |
| | | </el-tooltip> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | <el-form-item prop="roleKey"> |
| | | <template #label> |
| | | <span> |
| | | <el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasRole('admin')`)" placement="top"> |
| | | <el-tooltip content="控制器中定义的权限字符,如:@SaCheckRole('admin')" placement="top"> |
| | | <el-icon><question-filled /></el-icon> |
| | | </el-tooltip> |
| | | 权限字符 |
| | |
| | | <template> |
| | | <div class="user-info-head" @click="editCropper()"> |
| | | <img :src="options.img as string" title="点击上传头像" class="img-circle img-lg" /> |
| | | <img :src="options.img" title="点击上传头像" class="img-circle img-lg" /> |
| | | <el-dialog :title="title" v-model="open" width="800px" append-to-body @opened="modalOpened" @close="closeDialog"> |
| | | <el-row> |
| | | <el-col :xs="24" :md="12" :style="{ height: '350px' }"> |
| | |
| | | import useUserStore from "@/store/modules/user"; |
| | | |
| | | interface Options { |
| | | img: string | ArrayBuffer | null; // 裁剪图片的地址 |
| | | img: string | any; // 裁剪图片的地址 |
| | | autoCrop: boolean; // 是否默认生成截图框 |
| | | autoCropWidth: number; // 默认生成截图框宽度 |
| | | autoCropHeight: number; // 默认生成截图框高度 |
| | |
| | | const res = await uploadAvatar(formData); |
| | | open.value = false; |
| | | options.img = res.data.imgUrl; |
| | | userStore.avatar = options.img as string |
| | | userStore.setAvatar(options.img as string) |
| | | proxy?.$modal.msgSuccess("修改成功"); |
| | | visible.value = false; |
| | | }); |
| | |
| | | <el-tabs v-model="preview.activeName"> |
| | | <el-tab-pane |
| | | v-for="(value, key) in preview.data" |
| | | :label="(key as any).substring((key as any).lastIndexOf('/') + 1, (key as any).indexOf('.vm'))" |
| | | :name="(key as any).substring((key as any).lastIndexOf('/') + 1, (key as any).indexOf('.vm'))" |
| | | :label="key.substring(key.lastIndexOf('/') + 1, key.indexOf('.vm'))" |
| | | :name="key.substring(key.lastIndexOf('/') + 1, key.indexOf('.vm'))" |
| | | :key="value" |
| | | > |
| | | <el-link :underline="false" icon="DocumentCopy" v-copyText="value" v-copyText:callback="copyTextSuccess" style="float:right"> |