diff --git a/.env.development b/.env.development
index 53ae057..ad47825 100644
--- a/.env.development
+++ b/.env.development
@@ -18,6 +18,8 @@ VITE_APP_POWERJOB_ADMIN = 'http://localhost:7700/'
VITE_APP_PORT = 80
+# 接口加密功能开关(如需关闭 后端也必须对应关闭)
+VITE_APP_ENCRYPT = true
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
diff --git a/.env.production b/.env.production
index 0e3b9fc..c15308f 100644
--- a/.env.production
+++ b/.env.production
@@ -21,6 +21,8 @@ VITE_BUILD_COMPRESS = gzip
VITE_APP_PORT = 80
+# 接口加密功能开关(如需关闭 后端也必须对应关闭)
+VITE_APP_ENCRYPT = true
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
diff --git a/package.json b/package.json
index 23a9cce..751bd6f 100644
--- a/package.json
+++ b/package.json
@@ -38,6 +38,7 @@
"file-saver": "2.0.5",
"fuse.js": "7.0.0",
"highlight.js": "11.9.0",
+ "image-conversion": "^2.1.1",
"js-cookie": "3.0.5",
"jsencrypt": "3.3.2",
"moddle": "6.2.3",
@@ -76,10 +77,10 @@
"eslint": "8.56.0",
"eslint-config-prettier": "9.1.0",
"eslint-define-config": "2.1.0",
- "eslint-plugin-import": "2.29.1",
- "eslint-plugin-node": "11.1.0",
"eslint-plugin-prettier": "5.1.3",
"eslint-plugin-promise": "6.1.1",
+ "eslint-plugin-node": "11.1.0",
+ "eslint-plugin-import": "2.29.1",
"eslint-plugin-vue": "9.20.1",
"fast-glob": "3.3.2",
"husky": "8.0.3",
diff --git a/src/components/DictTag/index.vue b/src/components/DictTag/index.vue
index b355a42..7442546 100644
--- a/src/components/DictTag/index.vue
+++ b/src/components/DictTag/index.vue
@@ -50,12 +50,13 @@ const values = computed(() => {
const unmatch = computed(() => {
if (props.options?.length == 0 || props.value === '' || props.value === null || typeof props.value === 'undefined') return false;
// 传入值为非数组
+ let unmatch = false; // 添加一个标志来判断是否有未匹配项
values.value.forEach((item) => {
if (!props.options.some((v) => v.value === item)) {
- return true; // 如果有未匹配项,将标志设置为true
+ unmatch = true; // 如果有未匹配项,将标志设置为true
}
});
- return false; // 返回标志的值
+ return unmatch; // 返回标志的值
});
const unmatchArray = computed(() => {
diff --git a/src/components/ImageUpload/index.vue b/src/components/ImageUpload/index.vue
index ae5cd45..1c6dcd1 100644
--- a/src/components/ImageUpload/index.vue
+++ b/src/components/ImageUpload/index.vue
@@ -44,6 +44,7 @@ import { listByIds, delOss } from '@/api/system/oss';
import { OssVO } from '@/api/system/oss/types';
import { propTypes } from '@/utils/propTypes';
import { globalHeaders } from '@/utils/request';
+import { compressAccurately } from 'image-conversion';
const props = defineProps({
modelValue: {
@@ -60,7 +61,14 @@ const props = defineProps({
isShowTip: {
type: Boolean,
default: true
- }
+ },
+ // 是否支持压缩,默认否
+ compressSupport: {
+ type: Boolean,
+ default: false
+ },
+ // 压缩目标大小,单位KB。默认300KB以上文件才压缩,并压缩至300KB以内
+ compressTargetSize: propTypes.number.def(300)
});
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -138,8 +146,16 @@ const handleBeforeUpload = (file: any) => {
return false;
}
}
- proxy?.$modal.loading('正在上传图片,请稍候...');
- number.value++;
+
+ //压缩图片,开启压缩并且大于指定的压缩大小时才压缩
+ if (props.compressSupport && file.size / 1024 > props.compressTargetSize) {
+ proxy?.$modal.loading('正在上传图片,请稍候...');
+ number.value++;
+ return compressAccurately(file, props.compressTargetSize);
+ } else {
+ proxy?.$modal.loading('正在上传图片,请稍候...');
+ number.value++;
+ }
};
// 文件个数超出
diff --git a/src/components/RoleSelect/index.vue b/src/components/RoleSelect/index.vue
index e190b20..ec5bcc7 100644
--- a/src/components/RoleSelect/index.vue
+++ b/src/components/RoleSelect/index.vue
@@ -36,7 +36,7 @@
:data="roleList"
:loading="loading"
:row-config="{ keyField: 'roleId' }"
- :checkbox-config="{ reserve: true, checkRowKeys: defaultSelectRoleIds }"
+ :checkbox-config="{ reserve: true, checkRowKeys: roleIds }"
highlight-current-row
@checkbox-all="handleCheckboxAll"
@checkbox-change="handleCheckboxChange"
@@ -63,41 +63,42 @@
v-model:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
- @pagination="pageList"
+ @pagination="getList"
/>
- 取消
+ 取消
确定
-
+
+onMounted(() => {
+ getList();
+});
+
\ No newline at end of file
diff --git a/src/components/UserSelect/index.vue b/src/components/UserSelect/index.vue
index d544330..f2ef014 100644
--- a/src/components/UserSelect/index.vue
+++ b/src/components/UserSelect/index.vue
@@ -47,7 +47,7 @@
-
+
{{ user.userName }}
@@ -60,8 +60,9 @@
show-overflow
:data="userList"
:loading="loading"
- :row-config="{ keyField: 'userId', isHover: true }"
- :checkbox-config="{ reserve: true, trigger: 'row', highlight: true, showHeader: prop.multiple }"
+ :row-config="{ keyField: 'userId' }"
+ :checkbox-config="{ reserve: true, checkRowKeys: userIds }"
+ highlight-current-row
@checkbox-all="handleCheckboxAll"
@checkbox-change="handleCheckboxChange"
>
@@ -89,14 +90,14 @@
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
:total="total"
- @pagination="pageList"
+ @pagination="getList"
/>
- 取消
+ 取消
确定
@@ -111,20 +112,18 @@ import { VxeTableInstance } from 'vxe-table';
import useDialog from '@/hooks/useDialog';
interface PropType {
- modelValue?: UserVO[] | UserVO | undefined;
- multiple?: boolean;
- data?: string | number | (string | number)[];
+ modelValue?: UserVO[];
}
const prop = withDefaults(defineProps(), {
- multiple: true,
- modelValue: undefined,
- data: undefined
+ modelValue: () => []
});
-const emit = defineEmits(['update:modelValue', 'confirmCallBack']);
+const emit = defineEmits(['update:modelValue']);
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs(proxy?.useDict('sys_normal_disable'));
+const userIds = computed(() => prop.modelValue.map((item) => item.userId as string));
+
const userList = ref();
const loading = ref(true);
const showSearch = ref(true);
@@ -152,8 +151,11 @@ const queryParams = ref({
roleId: ''
});
-const defaultSelectUserIds = computed(() => computedIds(prop.data));
-
+/** 通过条件过滤节点 */
+const filterNode = (value: string, data: any) => {
+ if (!value) return true;
+ return data.label.indexOf(value) !== -1;
+};
/** 根据名称筛选部门树 */
watchEffect(
() => {
@@ -165,30 +167,10 @@ watchEffect(
);
const confirm = () => {
- emit('update:modelValue', selectUserList.value);
- emit('confirmCallBack', selectUserList.value);
+ emit('update:modelValue', [...selectUserList.value]);
userDialog.closeDialog();
};
-const computedIds = (data) => {
- if (data instanceof Array) {
- return [...data];
- } else if (typeof data === 'string') {
- return data.split(',');
- } else if (typeof data === 'number') {
- return [data];
- } else {
- console.warn(' The data type of data should be array or string or number, but I received other');
- return [];
- }
-};
-
-/** 通过条件过滤节点 */
-const filterNode = (value: string, data: any) => {
- if (!value) return true;
- return data.label.indexOf(value) !== -1;
-};
-
/** 查询部门下拉树结构 */
const getTreeSelect = async () => {
const res = await api.deptTreeSelect();
@@ -204,14 +186,6 @@ const getList = async () => {
total.value = res.total;
};
-const pageList = async () => {
- await getList();
- const users = userList.value.filter((item) => {
- return selectUserList.value.some((user) => user.userId === item.userId);
- });
- await tableRef.value.setCheckboxRow(users, true);
-};
-
/** 节点单击事件 */
const handleNodeClick = (data: DeptVO) => {
queryParams.value.deptId = data.id;
@@ -234,10 +208,6 @@ const resetQuery = () => {
};
const handleCheckboxChange = (checked) => {
- if (!prop.multiple && checked.checked) {
- tableRef.value.setCheckboxRow(selectUserList.value, false);
- selectUserList.value = [];
- }
const row = checked.row;
if (checked.checked) {
selectUserList.value.push(row);
@@ -264,46 +234,22 @@ const handleCheckboxAll = (checked) => {
const handleCloseTag = (user: UserVO) => {
const userId = user.userId;
- // 使用split删除用户
const index = selectUserList.value.findIndex((item) => item.userId === userId);
const rows = selectUserList.value[index];
tableRef.value?.setCheckboxRow(rows, false);
selectUserList.value.splice(index, 1);
};
-
-const initSelectUser = async () => {
- if (defaultSelectUserIds.value.length > 0) {
- const { data } = await api.optionSelect(defaultSelectUserIds.value);
- selectUserList.value = data;
- const users = userList.value.filter((item) => {
- return defaultSelectUserIds.value.includes(String(item.userId));
- });
- await nextTick(() => {
- tableRef.value.setCheckboxRow(users, true);
- });
- }
-};
-const close = () => {
- userDialog.closeDialog();
-};
-
watch(
- () => userDialog.visible.value,
- (newValue: boolean) => {
- if (newValue) {
- initSelectUser();
- } else {
- tableRef.value.clearCheckboxReserve();
- tableRef.value.clearCheckboxRow();
- resetQuery();
- selectUserList.value = [];
- }
- }
+ () => prop.modelValue,
+ (newVal, oldValue) => {
+ Object.assign(selectUserList.value, newVal);
+ },
+ { deep: true }
);
onMounted(() => {
- getTreeSelect(); // 初始化部门数据
- getList(); // 初始化列表数据
+ getTreeSelect();
+ getList();
});
defineExpose({
@@ -312,4 +258,4 @@ defineExpose({
});
-
+
\ No newline at end of file
diff --git a/src/hooks/useDialog.ts b/src/hooks/useDialog.ts
index 2779979..68440bf 100644
--- a/src/hooks/useDialog.ts
+++ b/src/hooks/useDialog.ts
@@ -2,18 +2,15 @@ import { Ref } from 'vue';
interface Options {
title?: string;
- visible?: boolean;
}
-
interface Return {
title: Ref;
visible: Ref;
-
openDialog: () => void;
closeDialog: () => void;
}
export default (ops?: Options): Return => {
- const visible = ref(ops.visible !== undefined ? ops.visible : false);
+ const visible = ref(false);
const title = ref(ops.title || '');
const openDialog = () => {
@@ -31,4 +28,4 @@ export default (ops?: Options): Return => {
openDialog,
closeDialog
};
-};
+};
\ No newline at end of file
diff --git a/src/main.ts b/src/main.ts
index f0efc72..592de58 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -38,6 +38,10 @@ VXETable.config({
zIndex: 999999
});
+// 修改 el-dialog 默认点击遮照为不关闭
+import { ElDialog } from 'element-plus';
+ElDialog.props.closeOnClickModal.default = false;
+
const app = createApp(App);
app.use(HighLight);
diff --git a/src/types/env.d.ts b/src/types/env.d.ts
index 6667d05..5b18a8e 100644
--- a/src/types/env.d.ts
+++ b/src/types/env.d.ts
@@ -14,6 +14,7 @@ interface ImportMetaEnv {
VITE_APP_MONITRO_ADMIN: string;
VITE_APP_POWERJOB_ADMIN: string;
VITE_APP_ENV: string;
+ VITE_APP_ENCRYPT: string
VITE_APP_RSA_PUBLIC_KEY: string;
VITE_APP_RSA_PRIVATE_KEY: string;
VITE_APP_CLIENT_ID: string;
diff --git a/src/utils/request.ts b/src/utils/request.ts
index 3de2076..f2a69f0 100644
--- a/src/utils/request.ts
+++ b/src/utils/request.ts
@@ -76,12 +76,14 @@ service.interceptors.request.use(
}
}
}
- // 当开启参数加密
- if (isEncrypt && (config.method === 'post' || config.method === 'put')) {
- // 生成一个 AES 密钥
- const aesKey = generateAesKey();
- config.headers[encryptHeader] = encrypt(encryptBase64(aesKey));
- config.data = typeof config.data === 'object' ? encryptWithAes(JSON.stringify(config.data), aesKey) : encryptWithAes(config.data, aesKey);
+ if (import.meta.env.VITE_APP_ENCRYPT === 'true') {
+ // 当开启参数加密
+ if (isEncrypt && (config.method === 'post' || config.method === 'put')) {
+ // 生成一个 AES 密钥
+ const aesKey = generateAesKey();
+ 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
if (config.data instanceof FormData) {
@@ -97,19 +99,21 @@ service.interceptors.request.use(
// 响应拦截器
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);
+ if (import.meta.env.VITE_APP_ENCRYPT === 'true') {
+ // 加密后的 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;
diff --git a/src/views/register.vue b/src/views/register.vue
index 7f078c1..99ca62f 100644
--- a/src/views/register.vue
+++ b/src/views/register.vue
@@ -91,7 +91,8 @@ const registerRules: ElFormRules = {
],
password: [
{ required: true, trigger: 'blur', message: '请输入您的密码' },
- { min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' }
+ { min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' },
+ { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }
],
confirmPassword: [
{ required: true, trigger: 'blur', message: '请再次输入您的密码' },
diff --git a/src/views/system/oss/index.vue b/src/views/system/oss/index.vue
index 1cc582e..77c5dcd 100644
--- a/src/views/system/oss/index.vue
+++ b/src/views/system/oss/index.vue
@@ -197,11 +197,10 @@ const getList = async () => {
loading.value = false;
showTable.value = true;
};
-function checkFileSuffix(fileSuffix: string[]) {
- let arr = ['png', 'jpg', 'jpeg'];
- return arr.some((type) => {
- return fileSuffix.indexOf(type) > -1;
- });
+function checkFileSuffix(fileSuffix: string | string[]) {
+ const arr = [".png", ".jpg", ".jpeg"];
+ const suffixArray = Array.isArray(fileSuffix) ? fileSuffix : [fileSuffix];
+ return suffixArray.some(suffix => arr.includes(suffix.toLowerCase()));
}
/** 取消按钮 */
function cancel() {
diff --git a/src/views/system/user/authRole.vue b/src/views/system/user/authRole.vue
index 656ffc2..9b9927d 100644
--- a/src/views/system/user/authRole.vue
+++ b/src/views/system/user/authRole.vue
@@ -59,6 +59,7 @@ import { RoleVO } from '@/api/system/role/types';
import { getAuthRole, updateAuthRole } from '@/api/system/user';
import { UserForm } from '@/api/system/user/types';
import { RouteLocationNormalized } from 'vue-router';
+import { parseTime } from "@/utils/ruoyi";
const route = useRoute();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue
index 3ea2864..dfbd789 100644
--- a/src/views/system/user/index.vue
+++ b/src/views/system/user/index.vue
@@ -395,7 +395,8 @@ const initData: PageData = {
max: 20,
message: '用户密码长度必须介于 5 和 20 之间',
trigger: 'blur'
- }
+ },
+ { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }
],
email: [
{
@@ -504,7 +505,12 @@ const handleResetPwd = async (row: UserVO) => {
cancelButtonText: '取消',
closeOnClickModal: false,
inputPattern: /^.{5,20}$/,
- inputErrorMessage: '用户密码长度必须介于 5 和 20 之间'
+ inputErrorMessage: '用户密码长度必须介于 5 和 20 之间',
+ inputValidator: (value) => {
+ if (/<|>|"|'|\||\\/.test(value)) {
+ return "不能包含非法字符:< > \" ' \\\ |"
+ }
+ }
})
);
if (!err && res) {
diff --git a/src/views/system/user/profile/resetPwd.vue b/src/views/system/user/profile/resetPwd.vue
index 0a39fb1..6d536da 100644
--- a/src/views/system/user/profile/resetPwd.vue
+++ b/src/views/system/user/profile/resetPwd.vue
@@ -44,7 +44,8 @@ const rules = ref({
max: 20,
message: '长度在 6 到 20 个字符',
trigger: 'blur'
- }
+ },
+ { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }
],
confirmPassword: [
{ required: true, message: '确认密码不能为空', trigger: 'blur' },