merge 合并dev

This commit is contained in:
gssong 2024-03-05 21:09:56 +08:00
commit 63d07864f7
16 changed files with 127 additions and 196 deletions

View File

@ -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 私钥与后端加密公钥对应 如更换需前后端一同更换

View File

@ -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 私钥与后端加密公钥对应 如更换需前后端一同更换

View File

@ -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",

View File

@ -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(() => {

View File

@ -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
},
// KB300KB300KB
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++;
}
};
//

View File

@ -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"
/>
</el-card>
<template #footer>
<el-button @click="close">取消</el-button>
<el-button @click="roleDialog.closeDialog">取消</el-button>
<el-button type="primary" @click="confirm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
<script setup name="Role" lang="ts">
import { listRole } from '@/api/system/role';
import { RoleVO, RoleQuery } from '@/api/system/role/types';
import { VxeTableInstance } from 'vxe-table';
import useDialog from '@/hooks/useDialog';
import api from '@/api/system/role';
interface PropType {
modelValue?: RoleVO[] | RoleVO | undefined;
multiple?: boolean;
data?: string | number | (string | number)[];
modelValue?: RoleVO[];
}
const prop = withDefaults(defineProps<PropType>(), {
multiple: true,
modelValue: undefined,
data: undefined
modelValue: () => []
});
const emit = defineEmits(['update:modelValue', 'confirmCallBack']);
const emit = defineEmits(['update:modelValue']);
const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const roleIds = computed(() => prop.modelValue.map((item) => item.roleId as string));
const roleList = ref<RoleVO[]>();
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const selectRoleList = ref<RoleVO[]>([]);
@ -116,46 +117,22 @@ const queryParams = ref<RoleQuery>({
roleKey: '',
status: ''
});
const defaultSelectRoleIds = computed(() => computedIds(prop.data));
const confirm = () => {
emit('update:modelValue', selectRoleList.value);
emit('confirmCallBack', selectRoleList.value);
emit('update:modelValue', [...selectRoleList.value]);
roleDialog.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('<RoleSelect> The data type of data should be array or string or number, but I received other');
return [];
}
};
/**
* 查询角色列表
*/
const getList = () => {
loading.value = true;
api.listRole(proxy?.addDateRange(queryParams.value, dateRange.value)).then((res) => {
listRole(proxy?.addDateRange(queryParams.value, dateRange.value)).then((res) => {
roleList.value = res.rows;
total.value = res.total;
loading.value = false;
});
};
const pageList = async () => {
await getList();
const roles = roleList.value.filter((item) => {
return selectRoleList.value.some((role) => role.roleId === item.roleId);
});
await tableRef.value.setCheckboxRow(roles, true);
};
/**
* 搜索按钮操作
*/
@ -172,10 +149,6 @@ const resetQuery = () => {
};
const handleCheckboxChange = (checked) => {
if (!prop.multiple && checked.checked) {
tableRef.value.setCheckboxRow(selectRoleList.value, false);
selectRoleList.value = [];
}
const row = checked.row;
if (checked.checked) {
selectRoleList.value.push(row);
@ -208,43 +181,19 @@ const handleCloseTag = (user: RoleVO) => {
tableRef.value?.setCheckboxRow(rows, false);
selectRoleList.value.splice(index, 1);
};
/**
* 初始化选中数据
*/
const initSelectRole = async () => {
if (defaultSelectRoleIds.value.length > 0) {
const { data } = await api.optionSelect(defaultSelectRoleIds.value);
selectRoleList.value = data;
const users = roleList.value.filter((item) => {
return defaultSelectRoleIds.value.includes(String(item.roleId));
});
await nextTick(() => {
tableRef.value.setCheckboxRow(users, true);
});
}
};
const close = () => {
roleDialog.closeDialog();
};
watch(
() => roleDialog.visible.value,
(newValue: boolean) => {
if (newValue) {
initSelectRole();
} else {
tableRef.value.clearCheckboxReserve();
tableRef.value.clearCheckboxRow();
resetQuery();
selectRoleList.value = [];
}
}
() => prop.modelValue,
(newVal, oldValue) => {
Object.assign(selectRoleList.value, newVal);
},
{ deep: true }
);
onMounted(() => {
getList(); //
});
defineExpose({
open: roleDialog.openDialog,
close: roleDialog.closeDialog
});
</script>
onMounted(() => {
getList();
});
</script>

View File

@ -47,7 +47,7 @@
</transition>
<el-card shadow="hover">
<template v-if="prop.multiple" #header>
<template #header>
<el-tag v-for="user in selectUserList" :key="user.userId" closable style="margin: 2px" @close="handleCloseTag(user)">
{{ user.userName }}
</el-tag>
@ -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"
/>
</el-card>
</el-col>
</el-row>
<template #footer>
<el-button @click="close">取消</el-button>
<el-button @click="userDialog.closeDialog">取消</el-button>
<el-button type="primary" @click="confirm">确定</el-button>
</template>
</el-dialog>
@ -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<PropType>(), {
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<any>(proxy?.useDict('sys_normal_disable'));
const userIds = computed(() => prop.modelValue.map((item) => item.userId as string));
const userList = ref<UserVO[]>();
const loading = ref(true);
const showSearch = ref(true);
@ -152,8 +151,11 @@ const queryParams = ref<UserQuery>({
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('<UserSelect> 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({
});
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped></style>

View File

@ -2,18 +2,15 @@ import { Ref } from 'vue';
interface Options {
title?: string;
visible?: boolean;
}
interface Return {
title: Ref<string>;
visible: Ref<boolean>;
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
};
};
};

View File

@ -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);

1
src/types/env.d.ts vendored
View File

@ -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;

View File

@ -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;

View File

@ -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: '请再次输入您的密码' },

View File

@ -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() {

View File

@ -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;

View File

@ -395,7 +395,8 @@ const initData: PageData<UserForm, UserQuery> = {
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) {

View File

@ -44,7 +44,8 @@ const rules = ref({
max: 20,
message: '长度在 6 到 20 个字符',
trigger: 'blur'
}
},
{ pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }
],
confirmPassword: [
{ required: true, message: '确认密码不能为空', trigger: 'blur' },