兰宝车间质量管理系统-前端
疯狂的狮子Li
2023-12-22 cc9b67d4676dd51a6b2d88e92607aca2629ee54d
!72 发布 vue 版本 5.1.2 与 cloud 版本 2.1.2
Merge pull request !72 from 疯狂的狮子Li/temp
已修改25个文件
137 ■■■■ 文件已修改
.env.development 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.production 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/DictTag/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/IconSelect/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/SidebarItem.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/permission.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/tagsView.ts 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/user.ts 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/env.d.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/crypto.ts 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/jsencrypt.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/request.ts 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/online/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/register.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dept/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dict/data.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/menu/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/oss/config.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/userAvatar.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.development
@@ -20,6 +20,8 @@
# 接口加密传输 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'
.env.production
@@ -23,6 +23,8 @@
# 接口加密传输 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'
README.md
@@ -17,7 +17,9 @@
# 启动服务
npm run dev
# 构建生产环境 yarn build:prod
# 构建生产环境
npm run build:prod
# 前端访问地址 http://localhost:80
```
package.json
@@ -1,6 +1,6 @@
{
  "name": "ruoyi-vue-plus",
  "version": "5.1.1",
  "version": "5.1.2",
  "description": "RuoYi-Vue-Plus多租户管理系统",
  "author": "LionLi",
  "license": "MIT",
src/components/DictTag/index.vue
@@ -2,7 +2,7 @@
  <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>
@@ -11,7 +11,7 @@
          :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 + " " }}
src/components/IconSelect/index.vue
@@ -2,7 +2,7 @@
  <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>
src/layout/components/Sidebar/SidebarItem.vue
@@ -18,8 +18,8 @@
      </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)"
src/permission.ts
@@ -20,6 +20,8 @@
    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;
src/router/index.ts
@@ -139,7 +139,7 @@
    path: '/system/oss-config',
    component: Layout,
    hidden: true,
    permissions: ['system:oss:list'],
    permissions: ['system:ossConfig:list'],
    children: [
      {
        path: 'index',
src/store/modules/tagsView.ts
@@ -1,4 +1,4 @@
import { TagView } from 'vue-router';
import { TagView, RouteRecordNormalized } from 'vue-router';
export const useTagsViewStore = defineStore('tagsView', () => {
  const visitedViews = ref<TagView[]>([]);
@@ -35,7 +35,9 @@
  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]
@@ -177,6 +179,11 @@
    }
  };
  const isDynamicRoute = (view: any): boolean => {
    // 检查匹配的路由记录中是否有动态段
    return view.matched.some((m: RouteRecordNormalized) => m.path.includes(':'));
  };
  return {
    visitedViews,
    cachedViews,
src/store/modules/user.ts
@@ -63,6 +63,10 @@
    removeToken();
  };
  const setAvatar = (value: string) => {
    avatar.value = value;
  };
  return {
    userId,
    token,
@@ -72,7 +76,8 @@
    permissions,
    login,
    getInfo,
    logout
    logout,
    setAvatar
  };
});
src/types/env.d.ts
@@ -68,6 +68,7 @@
  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;
}
src/utils/crypto.ts
@@ -31,6 +31,13 @@
};
/**
 * 解密base64
 */
export const decryptBase64 = (str: string) => {
  return CryptoJS.enc.Base64.parse(str);
};
/**
 * 使用密钥对数据进行加密
 * @param message
 * @param aesKey
@@ -43,3 +50,17 @@
  });
  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);
};
src/utils/jsencrypt.ts
@@ -4,7 +4,7 @@
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) => {
src/utils/request.ts
@@ -8,9 +8,10 @@
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 };
@@ -78,7 +79,7 @@
    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
@@ -96,6 +97,20 @@
// 响应拦截器
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;
    // 获取错误信息
src/views/index.vue
@@ -33,7 +33,7 @@
          * 部署方式 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">&yen;免费开源</el-tag>
        </p>
@@ -78,7 +78,7 @@
          * 分布式监控 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">&yen;免费开源</el-tag>
        </p>
src/views/monitor/online/index.vue
@@ -63,6 +63,8 @@
<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"));
@@ -100,10 +102,12 @@
}
/** 强退按钮操作 */
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(() => {
src/views/register.vue
@@ -105,12 +105,12 @@
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));
src/views/system/dept/index.vue
@@ -70,7 +70,7 @@
      </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">
src/views/system/dict/data.vue
@@ -49,8 +49,8 @@
        <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" />
src/views/system/menu/index.vue
@@ -169,7 +169,7 @@
              <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>
src/views/system/oss/config.vue
@@ -29,13 +29,13 @@
      <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>
@@ -67,10 +67,10 @@
        <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>
src/views/system/role/index.vue
@@ -107,7 +107,7 @@
        <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>
              权限字符
src/views/system/user/profile/userAvatar.vue
@@ -1,6 +1,6 @@
<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' }">
@@ -62,7 +62,7 @@
import useUserStore from "@/store/modules/user";
interface Options {
  img: string | ArrayBuffer | null; // 裁剪图片的地址
  img: string | any; // 裁剪图片的地址
  autoCrop: boolean; // 是否默认生成截图框
  autoCropWidth: number; // 默认生成截图框宽度
  autoCropHeight: number; // 默认生成截图框高度
@@ -140,7 +140,7 @@
    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;
  });
src/views/tool/gen/index.vue
@@ -97,8 +97,8 @@
      <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">