This commit is contained in:
吴红兵
2026-03-07 01:34:48 +08:00
parent adc511cfdc
commit 94c3473958
1211 changed files with 599405 additions and 322105 deletions

View File

@@ -48,7 +48,7 @@
:data="state.dataList"
v-loading="state.loading"
style="width: 100%"
row-key="id"
row-key="id"
@selection-change="handleSelectionChange"
@sort-change="sortChangeHandle"
border
@@ -109,7 +109,7 @@ const resetQuery = () => {
// 导出excel
const exportExcel = () => {
downBlobFile('/admin/audit/export', Object.assign(state.queryForm,{ids:selectObjs}), 'audit.xlsx');
downBlobFile('/admin/audit/export', Object.assign(state.queryForm, { ids: selectObjs }), 'audit.xlsx');
};
// 多选事件

View File

@@ -164,7 +164,7 @@ const collapseActive = ref('1');
// 定义校验规则
const dataRules = ref({
clientId: [
{ validator: rule.overLength, trigger: 'blur' },
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '编号不能为空', trigger: 'blur' },
{ validator: rule.validatorLowercase, trigger: 'blur' },
{
@@ -175,11 +175,14 @@ const dataRules = ref({
},
],
clientSecret: [
{ validator: rule.overLength, trigger: 'blur' },
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '密钥不能为空', trigger: 'blur' },
{ validator: rule.validatorLower, trigger: 'blur' },
],
scope: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '域不能为空', trigger: 'blur' }],
scope: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '域不能为空', trigger: 'blur' },
],
authorizedGrantTypes: [{ required: true, message: '授权模式不能为空', trigger: 'blur' }],
accessTokenValidity: [
{ required: true, message: '令牌时效不能为空', trigger: 'blur' },
@@ -193,7 +196,10 @@ const dataRules = ref({
encFlag: [{ required: true, message: '是否开启密码加密传输', trigger: 'blur' }],
onlineQuantity: [{ required: true, message: '是否允许同时在线', trigger: 'blur' }],
autoapprove: [{ required: true, message: '自动放行不能为空', trigger: 'blur' }],
webServerRedirectUri: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '回调地址不能为空', trigger: 'blur' }],
webServerRedirectUri: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '回调地址不能为空', trigger: 'blur' },
],
});
// 打开弹窗

View File

@@ -44,7 +44,7 @@
:data="state.dataList"
@selection-change="handleSelectionChange"
style="width: 100%"
row-key="id"
row-key="id"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
@@ -127,7 +127,7 @@ const resetQuery = () => {
// 导出excel
const exportExcel = () => {
downBlobFile('/admin/client/export', Object.assign(state.queryForm,{ids:selectObjs}), 'client.xlsx');
downBlobFile('/admin/client/export', Object.assign(state.queryForm, { ids: selectObjs }), 'client.xlsx');
};
// 多选事件

View File

@@ -1,50 +1,46 @@
<template>
<el-dialog v-model="visible" :title="dataForm.id ? $t('common.editBtn') : $t('common.addBtn')" width="600">
<el-form ref="dicDialogFormRef" :model="dataForm" label-width="120px" :rules="dataRules" v-loading="loading">
<el-form-item :label="$t('dictItem.dictType')" prop="dictType">
<el-input v-model="dataForm.dictType" clearable disabled
:placeholder="$t('dictItem.inputDictTypeTip')"></el-input>
</el-form-item>
<el-form-item :label="$t('dictItem.label')" prop="label">
<el-input v-model="dataForm.label" :placeholder="$t('dictItem.inputLabelTip')" clearable></el-input>
</el-form-item>
<el-form-item :label="$t('dictItem.itemValue')" prop="value">
<el-input v-model="dataForm.value" :placeholder="$t('dictItem.inputItemValueTip')" clearable></el-input>
</el-form-item>
<el-form-item :label="$t('dictItem.description')" prop="description">
<el-input v-model="dataForm.description" :placeholder="$t('dictItem.inputDescriptionTip')" clearable></el-input>
</el-form-item>
<el-form-item :label="$t('dictItem.sortOrder')" prop="sortOrder">
<el-input-number v-model="dataForm.sortOrder" :placeholder="$t('dictItem.inputSortOrderTip')"
clearable></el-input-number>
</el-form-item>
<el-form-item :label="$t('dictItem.remarks')" prop="remarks">
<el-input type="textarea" maxlength="100" :rows="3" v-model="dataForm.remarks"
:placeholder="$t('dictItem.inputRemarksTip')"></el-input>
</el-form-item>
<el-form-item label="状态颜色" prop="fontCss">
<el-input maxlength="100" v-model="dataForm.fontCss"
placeholder="状态颜色"></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-dialog v-model="visible" :title="dataForm.id ? $t('common.editBtn') : $t('common.addBtn')" width="600">
<el-form ref="dicDialogFormRef" :model="dataForm" label-width="120px" :rules="dataRules" v-loading="loading">
<el-form-item :label="$t('dictItem.dictType')" prop="dictType">
<el-input v-model="dataForm.dictType" clearable disabled :placeholder="$t('dictItem.inputDictTypeTip')"></el-input>
</el-form-item>
<el-form-item :label="$t('dictItem.label')" prop="label">
<el-input v-model="dataForm.label" :placeholder="$t('dictItem.inputLabelTip')" clearable></el-input>
</el-form-item>
<el-form-item :label="$t('dictItem.itemValue')" prop="value">
<el-input v-model="dataForm.value" :placeholder="$t('dictItem.inputItemValueTip')" clearable></el-input>
</el-form-item>
<el-form-item :label="$t('dictItem.description')" prop="description">
<el-input v-model="dataForm.description" :placeholder="$t('dictItem.inputDescriptionTip')" clearable></el-input>
</el-form-item>
<el-form-item :label="$t('dictItem.sortOrder')" prop="sortOrder">
<el-input-number v-model="dataForm.sortOrder" :placeholder="$t('dictItem.inputSortOrderTip')" clearable></el-input-number>
</el-form-item>
<el-form-item :label="$t('dictItem.remarks')" prop="remarks">
<el-input type="textarea" maxlength="100" :rows="3" v-model="dataForm.remarks" :placeholder="$t('dictItem.inputRemarksTip')"></el-input>
</el-form-item>
<el-form-item label="状态颜色" prop="fontCss">
<el-input maxlength="100" v-model="dataForm.fontCss" placeholder="状态颜色"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
</span>
</template>
</el-dialog>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="dict-item-form">
import {useI18n} from 'vue-i18n';
import {getItemObj, addItemObj, putItemObj, validateDictItemLabel} from '/@/api/admin/dict';
import {useMessage} from '/@/hooks/message';
import {rule} from "/@/utils/validate";
import { useI18n } from 'vue-i18n';
import { getItemObj, addItemObj, putItemObj, validateDictItemLabel } from '/@/api/admin/dict';
import { useMessage } from '/@/hooks/message';
import { rule } from '/@/utils/validate';
// 定义子组件向父组件传值/事件
const emit = defineEmits(['refresh']);
const {t} = useI18n();
const { t } = useI18n();
// 定义变量内容
const dicDialogFormRef = ref();
@@ -53,86 +49,97 @@ const visible = ref(false);
const loading = ref(false);
const dataForm = reactive({
id: '',
dictId: '',
dictType: '',
value: '',
label: '',
description: '',
sortOrder: 0,
remarks: '',
fontCss: '',
id: '',
dictId: '',
dictType: '',
value: '',
label: '',
description: '',
sortOrder: 0,
remarks: '',
fontCss: '',
});
const dataRules = reactive({
dictType: [{validator: rule.overLength, trigger: 'blur'}, {
required: true,
message: '请点选左侧字典项',
trigger: 'blur'
}],
value: [{validator: rule.overLength, trigger: 'blur'}, {required: true, message: '数据值不能为空', trigger: 'blur'}],
label: [
{validator: rule.overLength, trigger: 'blur'},
{required: true, message: '标签不能为空', trigger: 'blur'},
{
validator: (rule: any, value: any, callback: any) => {
validateDictItemLabel(rule, value, callback, dataForm.dictType, dataForm.id !== '');
},
trigger: 'blur',
},
],
description: [{validator: rule.overLength, trigger: 'blur'}, {
required: true,
message: '描述不能为空',
trigger: 'blur'
}],
sortOrder: [{validator: rule.overLength, trigger: 'blur'}, {
required: true,
message: '排序不能为空',
trigger: 'blur'
}],
dictType: [
{ validator: rule.overLength, trigger: 'blur' },
{
required: true,
message: '请点选左侧字典项',
trigger: 'blur',
},
],
value: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '数据值不能为空', trigger: 'blur' },
],
label: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '标签不能为空', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
validateDictItemLabel(rule, value, callback, dataForm.dictType, dataForm.id !== '');
},
trigger: 'blur',
},
],
description: [
{ validator: rule.overLength, trigger: 'blur' },
{
required: true,
message: '描述不能为空',
trigger: 'blur',
},
],
sortOrder: [
{ validator: rule.overLength, trigger: 'blur' },
{
required: true,
message: '排序不能为空',
trigger: 'blur',
},
],
});
// 打开弹窗
const openDialog = (row: any, dictForm: any) => {
visible.value = true;
dataForm.id = '';
visible.value = true;
dataForm.id = '';
nextTick(() => {
dicDialogFormRef.value?.resetFields();
if (dictForm) {
dataForm.dictId = dictForm.dictId;
dataForm.dictType = dictForm.dictType;
}
});
if (row?.id) {
getItemObj(row.id).then((res) => {
Object.assign(dataForm, res.data);
});
}
nextTick(() => {
dicDialogFormRef.value?.resetFields();
if (dictForm) {
dataForm.dictId = dictForm.dictId;
dataForm.dictType = dictForm.dictType;
}
});
if (row?.id) {
getItemObj(row.id).then((res) => {
Object.assign(dataForm, res.data);
});
}
};
// 提交
const onSubmit = async () => {
const valid = await dicDialogFormRef.value.validate().catch(() => {
});
if (!valid) return false;
const valid = await dicDialogFormRef.value.validate().catch(() => {});
if (!valid) return false;
try {
loading.value = true;
dataForm.id ? await putItemObj(dataForm) : await addItemObj(dataForm);
useMessage().success(t(dataForm.id ? 'common.editSuccessText' : 'common.addSuccessText'));
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
try {
loading.value = true;
dataForm.id ? await putItemObj(dataForm) : await addItemObj(dataForm);
useMessage().success(t(dataForm.id ? 'common.editSuccessText' : 'common.addSuccessText'));
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// 暴露变量
defineExpose({
openDialog,
openDialog,
});
</script>

View File

@@ -54,7 +54,7 @@ const dataForm = reactive({
const dataRules = reactive({
dictType: [
{ validator: rule.overLength, trigger: 'blur' },
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '类型不能为空', trigger: 'blur' },
{ validator: rule.validatorNameCn, trigger: 'blur' },
{
@@ -65,7 +65,10 @@ const dataRules = reactive({
},
],
systemFlag: [{ required: true, message: '字典类型不能为空', trigger: 'blur' }],
description: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '描述不能为空', trigger: 'blur' }],
description: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '描述不能为空', trigger: 'blur' },
],
});
// 打开弹窗

View File

@@ -46,7 +46,7 @@ const form = reactive({
// 定义校验规则
const dataRules = ref({
name: [
{ validator: rule.overLength, trigger: 'blur' },
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: 'name不能为空', trigger: 'blur' },
{ validator: rule.noChinese, trigger: 'blur' },
{
@@ -57,7 +57,7 @@ const dataRules = ref({
},
],
zhCn: [
{ validator: rule.overLength, trigger: 'blur' },
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '中文不能为空', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
@@ -67,7 +67,7 @@ const dataRules = ref({
},
],
en: [
{ validator: rule.overLength, trigger: 'blur' },
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '英文不能为空', trigger: 'blur' },
{ validator: rule.letter, trigger: 'blur' },
{

View File

@@ -55,7 +55,7 @@
@selection-change="handleSelectionChange"
@sort-change="sortChangeHandle"
style="width: 100%"
row-key="id"
row-key="id"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
@@ -139,7 +139,7 @@ const handleRefreshCache = () => {
// 导出excel
const exportExcel = () => {
downBlobFile('/admin/i18n/export', Object.assign(state.queryForm,{ids:selectObjs}), 'i18n.xlsx');
downBlobFile('/admin/i18n/export', Object.assign(state.queryForm, { ids: selectObjs }), 'i18n.xlsx');
};
// 多选事件

View File

@@ -1,86 +1,87 @@
<template>
<el-drawer v-model="visible" :title="data.title" size="30%">
<div class="w-full">
<div class="coding inverse-toggle px-5 pt-4 shadow-lg text-gray-100 text-sm font-mono subpixel-antialiased
bg-gray-800 pb-6 pt-4 rounded-lg leading-normal overflow-hidden">
<div class="top mb-2 flex">
<div class="h-3 w-3 bg-red-500 rounded-full"></div>
<div class="ml-2 h-3 w-3 bg-orange-300 rounded-full"></div>
<div class="ml-2 h-3 w-3 bg-green-500 rounded-full"></div>
</div>
<div class="mt-4 flex">
<span class="text-green-400">{{ $t('syslog.createTime') }}: </span>
<p class="flex-1 typing items-center pl-2 whitespace-normal overflow-hidden break-all">
{{ data.createTime }}
<br>
</p>
</div>
<div class="mt-4 flex">
<span class="text-green-400">{{ $t('syslog.createBy') }}: </span>
<p class="flex-1 typing items-center pl-2 whitespace-normal overflow-hidden break-all">
{{ data.createBy }}
<br>
</p>
</div>
<div class="mt-4 flex">
<span class="text-green-400">{{ $t('syslog.requestUri') }}: </span>
<p class="flex-1 typing items-center pl-2 whitespace-normal overflow-hidden break-all">
{{ data.requestUri }}
<br>
</p>
</div>
<div class="mt-4 flex">
<span class="text-green-400">{{ $t('syslog.remoteAddr') }}: </span>
<p class="flex-1 typing items-center pl-2 whitespace-normal overflow-hidden break-all">
{{ data.remoteAddr }}
<br>
</p>
</div>
<div class="mt-4 flex">
<span class="text-green-400">{{ $t('syslog.method') }}: </span>
<p class="flex-1 typing items-center pl-2 whitespace-normal overflow-hidden break-all">
{{ data.method }}
<br>
</p>
</div>
<div class="mt-4 flex">
<span class="text-green-400">{{ $t('syslog.serviceId') }}: </span>
<p class="flex-1 typing items-center pl-2 whitespace-normal overflow-hidden break-all">
{{ data.serviceId }}
<br>
</p>
</div>
<div class="mt-4 flex">
<span class="text-green-400">{{ $t('syslog.time') }}: </span>
<p class="flex-1 typing items-center pl-2 whitespace-normal overflow-hidden break-all">
{{ data.time }}/ms
<br>
</p>
</div>
<div class="mt-4 flex">
<span class="text-green-400">{{ $t('syslog.ua') }}: </span>
<p class="flex-1 typing items-center pl-2 whitespace-normal overflow-hidden break-all">
{{ data.userAgent }}
<br>
</p>
</div>
<div class="mt-4 flex" v-if="data.params">
<span class="text-green-400">{{ $t('syslog.params') }}: </span>
<p class="flex-1 typing items-center pl-2 whitespace-normal overflow-hidden break-all">
{{ data.params }}
<br>
</p>
</div>
<div class="mt-4 flex" v-if="data.exception">
<span class="text-green-400">{{ data.logType === '0' ? $t('syslog.result') : $t('syslog.exception') }}: </span>
<p class="flex-1 typing items-center pl-2 whitespace-normal overflow-hidden break-all">
{{ data.exception }}
<br>
</p>
</div>
</div>
</div>
</el-drawer>
<el-drawer v-model="visible" :title="data.title" size="30%">
<div class="w-full">
<div
class="coding inverse-toggle px-5 pt-4 shadow-lg text-gray-100 text-sm font-mono subpixel-antialiased bg-gray-800 pb-6 pt-4 rounded-lg leading-normal overflow-hidden"
>
<div class="top mb-2 flex">
<div class="h-3 w-3 bg-red-500 rounded-full"></div>
<div class="ml-2 h-3 w-3 bg-orange-300 rounded-full"></div>
<div class="ml-2 h-3 w-3 bg-green-500 rounded-full"></div>
</div>
<div class="mt-4 flex">
<span class="text-green-400">{{ $t('syslog.createTime') }}: </span>
<p class="flex-1 typing items-center pl-2 whitespace-normal overflow-hidden break-all">
{{ data.createTime }}
<br />
</p>
</div>
<div class="mt-4 flex">
<span class="text-green-400">{{ $t('syslog.createBy') }}: </span>
<p class="flex-1 typing items-center pl-2 whitespace-normal overflow-hidden break-all">
{{ data.createBy }}
<br />
</p>
</div>
<div class="mt-4 flex">
<span class="text-green-400">{{ $t('syslog.requestUri') }}: </span>
<p class="flex-1 typing items-center pl-2 whitespace-normal overflow-hidden break-all">
{{ data.requestUri }}
<br />
</p>
</div>
<div class="mt-4 flex">
<span class="text-green-400">{{ $t('syslog.remoteAddr') }}: </span>
<p class="flex-1 typing items-center pl-2 whitespace-normal overflow-hidden break-all">
{{ data.remoteAddr }}
<br />
</p>
</div>
<div class="mt-4 flex">
<span class="text-green-400">{{ $t('syslog.method') }}: </span>
<p class="flex-1 typing items-center pl-2 whitespace-normal overflow-hidden break-all">
{{ data.method }}
<br />
</p>
</div>
<div class="mt-4 flex">
<span class="text-green-400">{{ $t('syslog.serviceId') }}: </span>
<p class="flex-1 typing items-center pl-2 whitespace-normal overflow-hidden break-all">
{{ data.serviceId }}
<br />
</p>
</div>
<div class="mt-4 flex">
<span class="text-green-400">{{ $t('syslog.time') }}: </span>
<p class="flex-1 typing items-center pl-2 whitespace-normal overflow-hidden break-all">
{{ data.time }}/ms
<br />
</p>
</div>
<div class="mt-4 flex">
<span class="text-green-400">{{ $t('syslog.ua') }}: </span>
<p class="flex-1 typing items-center pl-2 whitespace-normal overflow-hidden break-all">
{{ data.userAgent }}
<br />
</p>
</div>
<div class="mt-4 flex" v-if="data.params">
<span class="text-green-400">{{ $t('syslog.params') }}: </span>
<p class="flex-1 typing items-center pl-2 whitespace-normal overflow-hidden break-all">
{{ data.params }}
<br />
</p>
</div>
<div class="mt-4 flex" v-if="data.exception">
<span class="text-green-400">{{ data.logType === '0' ? $t('syslog.result') : $t('syslog.exception') }}: </span>
<p class="flex-1 typing items-center pl-2 whitespace-normal overflow-hidden break-all">
{{ data.exception }}
<br />
</p>
</div>
</div>
</div>
</el-drawer>
</template>
<script setup lang="ts" name="log-detail">
@@ -89,12 +90,12 @@ const visible = ref(false);
const data = reactive({} as any);
const openDialog = (row: any) => {
visible.value = true;
Object.assign(data, row);
visible.value = true;
Object.assign(data, row);
};
// 暴露变量
defineExpose({
openDialog,
openDialog,
});
</script>

View File

@@ -1,22 +1,22 @@
export default {
syslog: {
index: '#',
logType: 'logType',
title: 'title',
remoteAddr: 'remoteAddr',
method: 'method',
ua: 'browser',
serviceId: 'serviceId',
time: 'time',
params: 'params',
createTime: 'createTime',
requestUri: 'requestUri',
exception: 'exception',
createBy: 'createBy',
action: 'action',
inputLogTypeTip: 'select logType',
inputStartPlaceholderTip: 'Start Time',
inputEndPlaceholderTip: 'End TIme',
result: 'result'
},
syslog: {
index: '#',
logType: 'logType',
title: 'title',
remoteAddr: 'remoteAddr',
method: 'method',
ua: 'browser',
serviceId: 'serviceId',
time: 'time',
params: 'params',
createTime: 'createTime',
requestUri: 'requestUri',
exception: 'exception',
createBy: 'createBy',
action: 'action',
inputLogTypeTip: 'select logType',
inputStartPlaceholderTip: 'Start Time',
inputEndPlaceholderTip: 'End TIme',
result: 'result',
},
};

View File

@@ -1,22 +1,22 @@
export default {
syslog: {
index: '#',
logType: '类型',
title: '标题',
remoteAddr: 'IP地址',
method: '请求方式',
ua: '浏览器',
serviceId: '客户端',
time: '耗时',
params: '请求参数',
createTime: '请求时间',
requestUri: '请求地址',
exception: '异常信息',
createBy: '操作人',
action: '操作',
inputLogTypeTip: '请选择类型',
inputStartPlaceholderTip: '开始时间',
inputEndPlaceholderTip: '结束时间',
result: '结果',
},
syslog: {
index: '#',
logType: '类型',
title: '标题',
remoteAddr: 'IP地址',
method: '请求方式',
ua: '浏览器',
serviceId: '客户端',
time: '耗时',
params: '请求参数',
createTime: '请求时间',
requestUri: '请求地址',
exception: '异常信息',
createBy: '操作人',
action: '操作',
inputLogTypeTip: '请选择类型',
inputStartPlaceholderTip: '开始时间',
inputEndPlaceholderTip: '结束时间',
result: '结果',
},
};

View File

@@ -1,114 +1,108 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 顶部折线图-->
<log-line-chart/>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 顶部折线图-->
<log-line-chart />
<el-row class="mt-4 ml10" v-show="showSearch">
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
<el-form-item :label="$t('syslog.logType')" prop="logType">
<el-select :placeholder="$t('syslog.inputLogTypeTip')" clearable
v-model="state.queryForm.logType">
<el-option :key="item.value" :label="item.label" :value="item.value" v-for="item in log_type"/>
</el-select>
</el-form-item>
<el-form-item :label="$t('syslog.createTime')" prop="createTime">
<el-date-picker
:end-placeholder="$t('syslog.inputEndPlaceholderTip')"
:start-placeholder="$t('syslog.inputStartPlaceholderTip')"
range-separator="To"
type="datetimerange"
v-model="state.queryForm.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button @click="getDataList" icon="Search" type="primary">{{ $t('common.queryBtn') }}</el-button>
<el-button @click="resetQuery" icon="Refresh">{{ $t('common.resetBtn') }}</el-button>
</el-form-item>
</el-form>
</el-row>
<el-row class="mt-4 ml10" v-show="showSearch">
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
<el-form-item :label="$t('syslog.logType')" prop="logType">
<el-select :placeholder="$t('syslog.inputLogTypeTip')" clearable v-model="state.queryForm.logType">
<el-option :key="item.value" :label="item.label" :value="item.value" v-for="item in log_type" />
</el-select>
</el-form-item>
<el-form-item :label="$t('syslog.createTime')" prop="createTime">
<el-date-picker
:end-placeholder="$t('syslog.inputEndPlaceholderTip')"
:start-placeholder="$t('syslog.inputStartPlaceholderTip')"
range-separator="To"
type="datetimerange"
v-model="state.queryForm.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button @click="getDataList" icon="Search" type="primary">{{ $t('common.queryBtn') }}</el-button>
<el-button @click="resetQuery" icon="Refresh">{{ $t('common.resetBtn') }}</el-button>
</el-form-item>
</el-form>
</el-row>
<el-row>
<div class="mb-2" style="width: 100%">
<el-button :disabled="multiple" v-auth="'sys_log_del'" @click="handleDelete(selectObjs)" class="ml10"
icon="Delete" type="primary">
{{ $t('common.delBtn') }}
</el-button>
<right-toolbar
:export="'sys_log_export'"
@exportExcel="exportExcel"
@queryTable="getDataList"
class="ml10"
style="float: right; margin-right: 20px"
v-model:showSearch="showSearch"
></right-toolbar>
</div>
</el-row>
<el-table
ref="tableRef"
:data="state.dataList"
@selection-change="handleSelectionChange"
@sort-change="sortChangeHandle"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column align="center" type="selection" width="40"/>
<el-table-column :label="$t('syslog.index')" type="index" width="60"/>
<el-table-column :label="$t('syslog.logType')" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="log_type" :value="scope.row.logType"></dict-tag>
</template>
</el-table-column>
<el-table-column :label="$t('syslog.title')" prop="title" show-overflow-tooltip></el-table-column>
<el-table-column :label="$t('syslog.remoteAddr')" prop="remoteAddr" show-overflow-tooltip></el-table-column>
<el-table-column :label="$t('syslog.method')" prop="method" show-overflow-tooltip></el-table-column>
<el-table-column :label="$t('syslog.time')" prop="time" show-overflow-tooltip>
<template #default="scope">
<span v-if="scope.row.time">{{ scope.row.time }}/ms</span>
</template>
</el-table-column>
<el-table-column :label="$t('syslog.createTime')" prop="createTime" show-overflow-tooltip sortable="custom"
width="200"></el-table-column>
<el-table-column :label="$t('syslog.createBy')" prop="createBy" show-overflow-tooltip sortable="custom"
width="200"></el-table-column>
<el-table-column :label="$t('common.action')" width="150">
<template #default="scope">
<el-button icon="view" @click="LogDetailRef.openDialog(scope.row)" size="small" text type="primary">
{{ $t('common.detailBtn') }}
</el-button>
<el-button v-auth="'sys_log_del'" icon="delete" @click="handleDelete([scope.row.id])" size="small" text
type="primary">
{{ $t('common.delBtn') }}
</el-button>
</template>
</el-table-column>
</el-table>
<el-row>
<div class="mb-2" style="width: 100%">
<el-button :disabled="multiple" v-auth="'sys_log_del'" @click="handleDelete(selectObjs)" class="ml10" icon="Delete" type="primary">
{{ $t('common.delBtn') }}
</el-button>
<right-toolbar
:export="'sys_log_export'"
@exportExcel="exportExcel"
@queryTable="getDataList"
class="ml10"
style="float: right; margin-right: 20px"
v-model:showSearch="showSearch"
></right-toolbar>
</div>
</el-row>
<el-table
ref="tableRef"
:data="state.dataList"
@selection-change="handleSelectionChange"
@sort-change="sortChangeHandle"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column align="center" type="selection" width="40" />
<el-table-column :label="$t('syslog.index')" type="index" width="60" />
<el-table-column :label="$t('syslog.logType')" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="log_type" :value="scope.row.logType"></dict-tag>
</template>
</el-table-column>
<el-table-column :label="$t('syslog.title')" prop="title" show-overflow-tooltip></el-table-column>
<el-table-column :label="$t('syslog.remoteAddr')" prop="remoteAddr" show-overflow-tooltip></el-table-column>
<el-table-column :label="$t('syslog.method')" prop="method" show-overflow-tooltip></el-table-column>
<el-table-column :label="$t('syslog.time')" prop="time" show-overflow-tooltip>
<template #default="scope">
<span v-if="scope.row.time">{{ scope.row.time }}/ms</span>
</template>
</el-table-column>
<el-table-column :label="$t('syslog.createTime')" prop="createTime" show-overflow-tooltip sortable="custom" width="200"></el-table-column>
<el-table-column :label="$t('syslog.createBy')" prop="createBy" show-overflow-tooltip sortable="custom" width="200"></el-table-column>
<el-table-column :label="$t('common.action')" width="150">
<template #default="scope">
<el-button icon="view" @click="LogDetailRef.openDialog(scope.row)" size="small" text type="primary">
{{ $t('common.detailBtn') }}
</el-button>
<el-button v-auth="'sys_log_del'" icon="delete" @click="handleDelete([scope.row.id])" size="small" text type="primary">
{{ $t('common.delBtn') }}
</el-button>
</template>
</el-table-column>
</el-table>
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle"
v-bind="state.pagination"></pagination>
<log-detail ref="LogDetailRef"></log-detail>
</div>
</div>
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination"></pagination>
<log-detail ref="LogDetailRef"></log-detail>
</div>
</div>
</template>
<script lang="ts" setup>
import {BasicTableProps, useTable} from '/@/hooks/table';
import {delObj, pageList} from '/@/api/admin/log';
import {useI18n} from 'vue-i18n';
import {useMessage, useMessageBox} from '/@/hooks/message';
import {useDict} from '/@/hooks/dict';
import { BasicTableProps, useTable } from '/@/hooks/table';
import { delObj, pageList } from '/@/api/admin/log';
import { useI18n } from 'vue-i18n';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { useDict } from '/@/hooks/dict';
const LogDetail = defineAsyncComponent(() => import('./detail.vue'));
const LogLineChart = defineAsyncComponent(() => import('./line-chart.vue'));
const LogDetailRef = ref();
const {log_type} = useDict('log_type');
const { log_type } = useDict('log_type');
const {t} = useI18n();
const { t } = useI18n();
// 定义变量内容
const queryRef = ref();
@@ -121,82 +115,75 @@ const multiple = ref(true);
let tableRef = ref(null);
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
logType: '',
createTime: '',
serviceId: '',
},
selectObjs: [],
pageList: pageList,
descs: ['create_time'],
createdIsNeed: false,
queryForm: {
logType: '',
createTime: '',
serviceId: '',
},
selectObjs: [],
pageList: pageList,
descs: ['create_time'],
createdIsNeed: false,
});
// table hook
const {
downBlobFile,
getDataList,
currentChangeHandle: baseCurrentChangeHandle,
sortChangeHandle,
sizeChangeHandle,
tableStyle
} = useTable(state);
const { downBlobFile, getDataList, currentChangeHandle: baseCurrentChangeHandle, sortChangeHandle, sizeChangeHandle, tableStyle } = useTable(state);
// 分页事件
const currentChangeHandle = (page: number) => {
// Reset table scroll position to top
tableRef.value?.setScrollTop(0);
// Call the original handler
baseCurrentChangeHandle(page);
// Reset table scroll position to top
tableRef.value?.setScrollTop(0);
// Call the original handler
baseCurrentChangeHandle(page);
};
// 清空搜索条件
const resetQuery = () => {
queryRef.value?.resetFields();
getDataList();
queryRef.value?.resetFields();
getDataList();
};
// 导出excel
const exportExcel = () => {
downBlobFile('/admin/log/export', state.queryForm, 'log.xlsx');
downBlobFile('/admin/log/export', state.queryForm, 'log.xlsx');
};
// 多选事件
const handleSelectionChange = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({id}) => id);
multiple.value = !objs.length;
selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length;
};
// 删除操作
const handleDelete = async (ids: string[]) => {
try {
await useMessageBox().confirm(t('common.delConfirmText'));
} catch {
return;
}
try {
await useMessageBox().confirm(t('common.delConfirmText'));
} catch {
return;
}
try {
await delObj(ids);
getDataList();
useMessage().success(t('common.delSuccessText'));
} catch (err: any) {
useMessage().error(err.msg);
}
try {
await delObj(ids);
getDataList();
useMessage().success(t('common.delSuccessText'));
} catch (err: any) {
useMessage().error(err.msg);
}
};
// onMounted 通过路由参数给 serviceId 赋值
const route = useRoute();
onMounted(() => {
const {serviceId} = route.query;
if (serviceId) {
state.queryForm.serviceId = serviceId;
}
getDataList();
const { serviceId } = route.query;
if (serviceId) {
state.queryForm.serviceId = serviceId;
}
getDataList();
});
</script>
<style lang="scss" scoped>
pre code.hljs {
width: 65%;
width: 65%;
}
</style>

View File

@@ -95,7 +95,7 @@ const form = reactive({
// 定义校验规则
const dataRules = reactive({
publicName: [
{ validator: rule.overLength, trigger: 'blur' },
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '名称不能为空', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
@@ -105,7 +105,7 @@ const dataRules = reactive({
},
],
publicKey: [
{ validator: rule.overLength, trigger: 'blur' },
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '参数键不能为空', trigger: 'blur' },
{ validator: rule.validatorCapital, trigger: 'blur' },
{
@@ -115,7 +115,10 @@ const dataRules = reactive({
trigger: 'blur',
},
],
publicValue: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '参数值不能为空', trigger: 'blur' }],
publicValue: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '参数值不能为空', trigger: 'blur' },
],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
publicType: [{ required: true, message: '类型不能为空', trigger: 'blur' }],
systemFlag: [{ required: true, message: '类型不能为空', trigger: 'blur' }],

View File

@@ -58,7 +58,7 @@
:data="state.dataList"
@selection-change="handleSelectionChange"
style="width: 100%"
row-key="publicId"
row-key="publicId"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
@@ -158,7 +158,7 @@ const handleSelectable = (row: any) => {
// 导出excel
const exportExcel = () => {
downBlobFile('/admin/param/export', Object.assign(state.queryForm,{ids:selectObjs}), 'param.xlsx');
downBlobFile('/admin/param/export', Object.assign(state.queryForm, { ids: selectObjs }), 'param.xlsx');
};
const handleRefreshCache = () => {

View File

@@ -1,137 +1,136 @@
<template>
<el-dialog :title="form.sensitiveId ? '编辑' : '新增'" v-model="visible"
:close-on-click-modal="false" draggable>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="敏感词" prop="sensitiveWord">
<el-input v-model="form.sensitiveWord" placeholder="请输入敏感词"/>
</el-form-item>
</el-col>
<el-dialog :title="form.sensitiveId ? '编辑' : '新增'" v-model="visible" :close-on-click-modal="false" draggable>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="敏感词" prop="sensitiveWord">
<el-input v-model="form.sensitiveWord" placeholder="请输入敏感词" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="类型" prop="sensitiveType">
<el-radio-group v-model="form.sensitiveType">
<el-radio :label="item.value" v-for="(item, index) in sensitive_type" border :key="index">{{ item.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="类型" prop="sensitiveType">
<el-radio-group v-model="form.sensitiveType">
<el-radio :label="item.value" v-for="(item, index) in sensitive_type" border :key="index">{{ item.label }} </el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">确认</el-button>
</span>
</template>
</el-dialog>
<el-col :span="24" class="mb20">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">确认</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="SysSensitiveWordDialog">
import {useDict} from '/@/hooks/dict';
import {useMessage} from "/@/hooks/message";
import {getObj, addObj, putObj, validateWord} from '/@/api/admin/sensitive'
import {rule} from '/@/utils/validate';
import { useDict } from '/@/hooks/dict';
import { useMessage } from '/@/hooks/message';
import { getObj, addObj, putObj, validateWord } from '/@/api/admin/sensitive';
import { rule } from '/@/utils/validate';
const emit = defineEmits(['refresh']);
// 定义变量内容
const dataFormRef = ref();
const visible = ref(false)
const loading = ref(false)
const visible = ref(false);
const loading = ref(false);
// 定义字典
const {sensitive_type} = useDict('sensitive_type')
const { sensitive_type } = useDict('sensitive_type');
// 提交表单数据
const form = reactive({
sensitiveId: '',
sensitiveWord: '',
sensitiveType: '0',
remark: '',
sensitiveId: '',
sensitiveWord: '',
sensitiveType: '0',
remark: '',
});
// 定义校验规则
const dataRules = ref({
sensitiveWord: [{validator: rule.overLength, trigger: 'blur'}
, {
validator: (rule: any, value: any, callback: any) => {
validateWord(rule, value, callback, form.sensitiveId !== '');
}
, trigger: 'blur'
}
, {
required: true,
message: '敏感词不能为空',
trigger: 'blur'
}],
sensitiveType: [{required: true, message: '类型不能为空', trigger: 'blur'}],
remark: [{validator: rule.overLength, trigger: 'blur'}],
})
sensitiveWord: [
{ validator: rule.overLength, trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
validateWord(rule, value, callback, form.sensitiveId !== '');
},
trigger: 'blur',
},
{
required: true,
message: '敏感词不能为空',
trigger: 'blur',
},
],
sensitiveType: [{ required: true, message: '类型不能为空', trigger: 'blur' }],
remark: [{ validator: rule.overLength, trigger: 'blur' }],
});
// 打开弹窗
const openDialog = (sensitiveId: string) => {
visible.value = true
form.sensitiveId = ''
visible.value = true;
form.sensitiveId = '';
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
});
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
});
// 获取sysSensitiveWord信息
if (sensitiveId) {
form.sensitiveId = sensitiveId
getsysSensitiveWordData(sensitiveId)
}
// 获取sysSensitiveWord信息
if (sensitiveId) {
form.sensitiveId = sensitiveId;
getsysSensitiveWordData(sensitiveId);
}
};
// 提交
const onSubmit = async () => {
// 立即设置 loading防止重复点击
if (loading.value) return;
loading.value = true;
// 立即设置 loading防止重复点击
if (loading.value) return;
loading.value = true;
try {
const valid = await dataFormRef.value.validate().catch(() => {
});
if (!valid) {
loading.value = false;
return false;
}
try {
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
form.sensitiveId ? await putObj(form) : await addObj(form);
useMessage().success(form.sensitiveId ? '修改成功' : '添加成功');
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
form.sensitiveId ? await putObj(form) : await addObj(form);
useMessage().success(form.sensitiveId ? '修改成功' : '添加成功');
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// 初始化表单数据
const getsysSensitiveWordData = (sensitiveId: string) => {
// 获取数据
loading.value = true
getObj({sensitiveId}).then((res: any) => {
Object.assign(form, res.data)
}).finally(() => {
loading.value = false
})
// 获取数据
loading.value = true;
getObj({ sensitiveId })
.then((res: any) => {
Object.assign(form, res.data);
})
.finally(() => {
loading.value = false;
});
};
// 暴露变量
defineExpose({
openDialog
openDialog,
});
</script>

View File

@@ -1,165 +1,167 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="敏感词" prop="sensitiveWord">
<el-input placeholder="请输入敏感词" v-model="state.queryForm.sensitiveWord"/>
</el-form-item>
<el-form-item label="类型" prop="sensitiveType">
<el-radio-group v-model="state.queryForm.sensitiveType">
<el-radio :label="item.value" v-for="(item, index) in sensitive_type" border :key="index">{{ item.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList">
查询
</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<el-row>
<div class="mb8" style="width: 100%">
<el-button icon="folder-add" type="primary" class="ml10" @click="formDialogRef.openDialog()"
v-auth="'admin_sysSensitiveWord_add'">
</el-button>
<el-button plain :disabled="multiple" icon="Delete" type="primary"
v-auth="'admin_sysSensitiveWord_del'" @click="handleDelete(selectObjs)">
</el-button>
<el-button plain icon="Check" type="primary"
v-auth="'admin_sysSensitiveWord_del'" @click="matchDialogRef.openDialog()">
匹配测试
</el-button>
<el-button plain @click="handleRefreshCache()" class="ml10" icon="refresh-left" type="primary">
{{ $t('common.refreshCacheBtn') }}
</el-button>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item label="敏感词" prop="sensitiveWord">
<el-input placeholder="请输入敏感词" v-model="state.queryForm.sensitiveWord" />
</el-form-item>
<el-form-item label="类型" prop="sensitiveType">
<el-radio-group v-model="state.queryForm.sensitiveType">
<el-radio :label="item.value" v-for="(item, index) in sensitive_type" border :key="index">{{ item.label }} </el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList"> 查询 </el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-row>
<el-row>
<div class="mb8" style="width: 100%">
<el-button icon="folder-add" type="primary" class="ml10" @click="formDialogRef.openDialog()" v-auth="'admin_sysSensitiveWord_add'">
</el-button>
<el-button plain :disabled="multiple" icon="Delete" type="primary" v-auth="'admin_sysSensitiveWord_del'" @click="handleDelete(selectObjs)">
</el-button>
<el-button plain icon="Check" type="primary" v-auth="'admin_sysSensitiveWord_del'" @click="matchDialogRef.openDialog()">
匹配测试
</el-button>
<el-button plain @click="handleRefreshCache()" class="ml10" icon="refresh-left" type="primary">
{{ $t('common.refreshCacheBtn') }}
</el-button>
<right-toolbar v-model:showSearch="showSearch" :export="'admin_sysSensitiveWord_export'"
@exportExcel="exportExcel" class="ml10 mr20" style="float: right;"
@queryTable="getDataList"></right-toolbar>
</div>
</el-row>
<el-table :data="state.dataList" v-loading="state.loading" border
:cell-style="tableStyle.cellStyle" :header-cell-style="tableStyle.headerCellStyle"
@selection-change="selectionChangHandle"
@sort-change="sortChangeHandle">
<el-table-column type="selection" width="40" align="center"/>
<el-table-column type="index" label="#" width="40"/>
<el-table-column prop="sensitiveWord" label="敏感词" show-overflow-tooltip/>
<el-table-column prop="sensitiveType" label="类型" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="sensitive_type" :value="scope.row.sensitiveType"></dict-tag>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" width="200" show-overflow-tooltip/>
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button icon="edit-pen" text type="primary" v-auth="'admin_sysSensitiveWord_edit'"
@click="formDialogRef.openDialog(scope.row.sensitiveId)">编辑
</el-button>
<el-button icon="delete" text type="primary" v-auth="'admin_sysSensitiveWord_del'"
@click="handleDelete([scope.row.sensitiveId])">删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination"/>
</div>
<right-toolbar
v-model:showSearch="showSearch"
:export="'admin_sysSensitiveWord_export'"
@exportExcel="exportExcel"
class="ml10 mr20"
style="float: right"
@queryTable="getDataList"
></right-toolbar>
</div>
</el-row>
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@selection-change="selectionChangHandle"
@sort-change="sortChangeHandle"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column type="index" label="#" width="40" />
<el-table-column prop="sensitiveWord" label="敏感词" show-overflow-tooltip />
<el-table-column prop="sensitiveType" label="类型" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="sensitive_type" :value="scope.row.sensitiveType"></dict-tag>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" width="200" show-overflow-tooltip />
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button
icon="edit-pen"
text
type="primary"
v-auth="'admin_sysSensitiveWord_edit'"
@click="formDialogRef.openDialog(scope.row.sensitiveId)"
>编辑
</el-button>
<el-button icon="delete" text type="primary" v-auth="'admin_sysSensitiveWord_del'" @click="handleDelete([scope.row.sensitiveId])"
>删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
<!-- 编辑新增 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)"/>
<!-- 编辑新增 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
<!-- 匹配测试 -->
<match-dialog ref="matchDialogRef" @refresh="getDataList(false)"/>
</div>
<!-- 匹配测试 -->
<match-dialog ref="matchDialogRef" @refresh="getDataList(false)" />
</div>
</template>
<script setup lang="ts" name="systemSysSensitiveWord">
import {BasicTableProps, useTable} from "/@/hooks/table";
import {fetchList, delObjs, refreshObj} from "/@/api/admin/sensitive";
import {useMessage, useMessageBox} from "/@/hooks/message";
import {useDict} from '/@/hooks/dict';
import { BasicTableProps, useTable } from '/@/hooks/table';
import { fetchList, delObjs, refreshObj } from '/@/api/admin/sensitive';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { useDict } from '/@/hooks/dict';
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const MatchDialog = defineAsyncComponent(() => import('./match.vue'));
// 定义查询字典
const {sensitive_type} = useDict('sensitive_type')
const { sensitive_type } = useDict('sensitive_type');
// 定义变量内容
const formDialogRef = ref()
const matchDialogRef = ref()
const formDialogRef = ref();
const matchDialogRef = ref();
// 搜索变量
const queryRef = ref()
const showSearch = ref(true)
const queryRef = ref();
const showSearch = ref(true);
// 多选变量
const selectObjs = ref([]) as any
const multiple = ref(true)
const selectObjs = ref([]) as any;
const multiple = ref(true);
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {},
pageList: fetchList
})
queryForm: {},
pageList: fetchList,
});
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile,
tableStyle
} = useTable(state)
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
// 清空搜索条件
const resetQuery = () => {
// 清空搜索条件
queryRef.value?.resetFields()
// 清空多选
selectObjs.value = []
getDataList()
}
// 清空搜索条件
queryRef.value?.resetFields();
// 清空多选
selectObjs.value = [];
getDataList();
};
// 导出excel
const exportExcel = () => {
downBlobFile('/admin/sysSensitiveWord/export', Object.assign(state.queryForm, {ids: selectObjs}), 'sysSensitiveWord.xlsx')
}
downBlobFile('/admin/sysSensitiveWord/export', Object.assign(state.queryForm, { ids: selectObjs }), 'sysSensitiveWord.xlsx');
};
// 多选事件
const selectionChangHandle = (objs: { sensitiveId: string }[]) => {
selectObjs.value = objs.map(({sensitiveId}) => sensitiveId);
multiple.value = !objs.length;
selectObjs.value = objs.map(({ sensitiveId }) => sensitiveId);
multiple.value = !objs.length;
};
// 删除操作
const handleDelete = async (ids: string[]) => {
try {
await useMessageBox().confirm('此操作将永久删除');
} catch {
return;
}
try {
await useMessageBox().confirm('此操作将永久删除');
} catch {
return;
}
try {
await delObjs(ids);
getDataList();
useMessage().success('删除成功');
} catch (err: any) {
useMessage().error(err.msg);
}
try {
await delObjs(ids);
getDataList();
useMessage().success('删除成功');
} catch (err: any) {
useMessage().error(err.msg);
}
};
// 刷新缓存
const handleRefreshCache = async () => {
try {
await refreshObj();
getDataList();
useMessage().success('刷新成功');
} catch (err: any) {
useMessage().error(err.msg);
}
}
try {
await refreshObj();
getDataList();
useMessage().success('刷新成功');
} catch (err: any) {
useMessage().error(err.msg);
}
};
</script>

View File

@@ -1,127 +1,128 @@
<template>
<el-dialog title="匹配测试" v-model="visible"
:close-on-click-modal="false" draggable>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="敏感词" prop="sensitiveWord">
<el-input type="textarea" rows="3" v-model="form.sensitiveWord" placeholder="请输入敏感词"
@blur="onSubmit"/>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item prop="result">
<template #label>
匹配结果
<tip content="可点击敏感词加入白名单"/>
</template>
<div v-html="matchResult" @click="handleChildClick"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">确认</el-button>
</span>
</template>
</el-dialog>
<el-dialog title="匹配测试" v-model="visible" :close-on-click-modal="false" draggable>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="敏感词" prop="sensitiveWord">
<el-input type="textarea" rows="3" v-model="form.sensitiveWord" placeholder="请输入敏感词" @blur="onSubmit" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item prop="result">
<template #label>
匹配结果
<tip content="可点击敏感词加入白名单" />
</template>
<div v-html="matchResult" @click="handleChildClick" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">确认</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="SysSensitiveWordDialog">
import {useMessage} from "/@/hooks/message";
import {testObj, addObj, getObj} from '/@/api/admin/sensitive'
import {rule} from '/@/utils/validate';
import { useMessage } from '/@/hooks/message';
import { testObj, addObj, getObj } from '/@/api/admin/sensitive';
import { rule } from '/@/utils/validate';
const emit = defineEmits(['refresh']);
// 定义变量内容
const dataFormRef = ref();
const visible = ref(false)
const loading = ref(false)
const matchResult = ref('')
const visible = ref(false);
const loading = ref(false);
const matchResult = ref('');
// 提交表单数据
const form = reactive({
sensitiveId: '',
sensitiveWord: '',
sensitiveId: '',
sensitiveWord: '',
});
// 定义校验规则
const dataRules = ref({
sensitiveWord: [{validator: rule.overLength, trigger: 'blur'}, {
required: true,
message: '敏感词不能为空',
trigger: 'blur'
}]
})
sensitiveWord: [
{ validator: rule.overLength, trigger: 'blur' },
{
required: true,
message: '敏感词不能为空',
trigger: 'blur',
},
],
});
// 打开弹窗
const openDialog = (id: string) => {
visible.value = true
form.sensitiveId = ''
visible.value = true;
form.sensitiveId = '';
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
});
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
});
};
const handleChildClick = async (event: any) => {
try {
if (event.target.tagName.toLowerCase() === 'a' && event.target.classList.contains('link-error')) {
const {data} = await getObj({sensitiveWord: event.target.innerText, sensitiveType: '1'})
if (data) {
useMessage().error('数据已存在,请勿重新添加');
return
}
await addObj({sensitiveWord: event.target.innerText, sensitiveType: '1'})
useMessage().success('白名单添加成功');
emit('refresh');
}
} catch (err: any) {
useMessage().error(err.msg);
}
}
try {
if (event.target.tagName.toLowerCase() === 'a' && event.target.classList.contains('link-error')) {
const { data } = await getObj({ sensitiveWord: event.target.innerText, sensitiveType: '1' });
if (data) {
useMessage().error('数据已存在,请勿重新添加');
return;
}
await addObj({ sensitiveWord: event.target.innerText, sensitiveType: '1' });
useMessage().success('白名单添加成功');
emit('refresh');
}
} catch (err: any) {
useMessage().error(err.msg);
}
};
// 提交
const onSubmit = async () => {
const valid = await dataFormRef.value.validate().catch(() => {
});
if (!valid) return false;
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) return false;
try {
loading.value = true;
const {data} = await testObj(form);
// 要处理的字符串
matchResult.value = data;
// 遍历关键词数组,并进行替换
matchResult.value = matchResult.value.map((item: string) => {
let modifiedItem = item;
data.forEach((word: string) => {
let regex = new RegExp(word, 'g');
modifiedItem = modifiedItem.replace(regex, `
try {
loading.value = true;
const { data } = await testObj(form);
// 要处理的字符串
matchResult.value = data;
// 遍历关键词数组,并进行替换
matchResult.value = matchResult.value.map((item: string) => {
let modifiedItem = item;
data.forEach((word: string) => {
let regex = new RegExp(word, 'g');
modifiedItem = modifiedItem.replace(
regex,
`
<div class="tooltip tooltip-open tooltip-bottom" data-tip="触发敏感词">
<a class="link link-error" @click="$emit('click-child','${word}')">${word}</a>
</div>
`);
});
return modifiedItem;
});
useMessage().success('操作成功');
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
`
);
});
return modifiedItem;
});
useMessage().success('操作成功');
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// 暴露变量
defineExpose({
openDialog
openDialog,
});
</script>

View File

@@ -81,14 +81,23 @@ const form = reactive({
// 定义校验规则
const dataRules = ref({
type: [{ required: true, message: '类型不能为空', trigger: 'blur' }],
appId: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: 'appId不能为空', trigger: 'blur' }],
remark: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '描述不能为空', trigger: 'blur' }],
appId: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: 'appId不能为空', trigger: 'blur' },
],
remark: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '描述不能为空', trigger: 'blur' },
],
redirectUrl: [
{ validator: rule.overLength, trigger: 'blur' },
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '回调地址不能为空', trigger: 'blur' },
{ validator: rule.url, trigger: 'blur' },
],
appSecret: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: 'appSecret不能为空', trigger: 'blur' }],
appSecret: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: 'appSecret不能为空', trigger: 'blur' },
],
});
// 打开弹窗

View File

@@ -45,7 +45,7 @@
v-loading="state.loading"
:data="state.dataList"
style="width: 100%"
row-key="id"
row-key="id"
@selection-change="handleSelectionChange"
border
:cell-style="tableStyle.cellStyle"
@@ -120,7 +120,7 @@ const resetQuery = () => {
// 导出excel
const exportExcel = () => {
downBlobFile('/admin/social/export', Object.assign(state.queryForm,{ids:selectObjs}), 'social.xlsx');
downBlobFile('/admin/social/export', Object.assign(state.queryForm, { ids: selectObjs }), 'social.xlsx');
};
// 多选事件

View File

@@ -1,200 +1,191 @@
<template>
<el-dialog :title="form.id ? $t('common.editBtn') : $t('common.addBtn')" v-model="visible"
:close-on-click-modal="false" draggable width="600">
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
<el-row>
<el-col :span="24" class="mb20" v-if="!form.id">
<el-form-item :label="t('area.pid')" prop="pid">
<china-area class="w-full" :placeholder="t('area.inputPidByTip')" :plus="true" @change="handleChange"/>
</el-form-item>
</el-col>
<el-dialog :title="form.id ? $t('common.editBtn') : $t('common.addBtn')" v-model="visible" :close-on-click-modal="false" draggable width="600">
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
<el-row>
<el-col :span="24" class="mb20" v-if="!form.id">
<el-form-item :label="t('area.pid')" prop="pid">
<china-area class="w-full" :placeholder="t('area.inputPidByTip')" :plus="true" @change="handleChange" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item :label="t('area.name')" prop="name">
<el-input v-model="form.name" :placeholder="t('area.inputNameByTip')"/>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item :label="t('area.name')" prop="name">
<el-input v-model="form.name" :placeholder="t('area.inputNameByTip')" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item :label="t('area.adcode')" prop="adcode">
<el-input-number v-model="form.adcode" :placeholder="t('area.inputAdCodeByTip')" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item :label="t('area.adcode')" prop="adcode">
<el-input-number v-model="form.adcode" :placeholder="t('area.inputAdCodeByTip')"/>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item :label="t('area.areaType')" prop="areaType">
<el-select v-model="form.areaType">
<el-option :key="item.value" :label="item.label" :value="item.value" v-for="item in area_type_dict"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item :label="t('area.areaType')" prop="areaType">
<el-select v-model="form.areaType">
<el-option :key="item.value" :label="item.label" :value="item.value"
v-for="item in area_type_dict"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item :label="t('area.areaSort')" prop="areaSort">
<el-input-number v-model="form.areaSort" :placeholder="t('area.inputAreaSortByTip')" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item :label="t('area.areaSort')" prop="areaSort">
<el-input-number v-model="form.areaSort" :placeholder="t('area.inputAreaSortByTip')"/>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item :label="t('area.hot')" prop="hot">
<el-radio-group v-model="form.hot">
<el-radio :key="index" :label="item.value" border v-for="(item, index) in yes_no_type">{{ item.label }} </el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item :label="t('area.hot')" prop="hot">
<el-radio-group v-model="form.hot">
<el-radio :key="index" :label="item.value" border v-for="(item, index) in yes_no_type">{{
item.label
}}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item :label="t('area.areaStatus')" prop="areaStatus">
<el-radio-group v-model="form.areaStatus">
<el-radio :key="index" :label="item.value" border v-for="(item, index) in yes_no_type">{{
item.label
}}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
<el-button @click="onSubmit" type="primary" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
</span>
</template>
</el-dialog>
<el-col :span="24" class="mb20">
<el-form-item :label="t('area.areaStatus')" prop="areaStatus">
<el-radio-group v-model="form.areaStatus">
<el-radio :key="index" :label="item.value" border v-for="(item, index) in yes_no_type">{{ item.label }} </el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
<el-button @click="onSubmit" type="primary" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="SysAreaDialog">
import {useMessage} from "/@/hooks/message";
import {getObj, addObj, putObj, validateExist} from '/@/api/admin/sysArea'
import {useDict} from "/@/hooks/dict";
import {useI18n} from "vue-i18n";
import {rule} from "/@/utils/validate";
import { useMessage } from '/@/hooks/message';
import { getObj, addObj, putObj, validateExist } from '/@/api/admin/sysArea';
import { useDict } from '/@/hooks/dict';
import { useI18n } from 'vue-i18n';
import { rule } from '/@/utils/validate';
const ChinaArea = defineAsyncComponent(() => import("/@/components/ChinaArea/index.vue"));
const ChinaArea = defineAsyncComponent(() => import('/@/components/ChinaArea/index.vue'));
const emit = defineEmits(['refresh']);
const { t } = useI18n();
// 定义变量内容
const dataFormRef = ref();
const visible = ref(false)
const loading = ref(false)
const visible = ref(false);
const loading = ref(false);
// 定义字典
const {yes_no_type} = useDict('yes_no_type')
const { yes_no_type } = useDict('yes_no_type');
const area_type_dict = [
{value: '0', label: '国家'},
{value: '1', label: '省份'},
{value: '2', label: '城市'},
{value: '3', label: '县区'},
{value: '4', label: '街道'}
]
{ value: '0', label: '国家' },
{ value: '1', label: '省份' },
{ value: '2', label: '城市' },
{ value: '3', label: '县区' },
{ value: '4', label: '街道' },
];
// 提交表单数据
const form = reactive({
id: '',
pid: 100000,
name: '',
letter: '',
adcode: 0,
location: '',
areaSort: 0,
areaStatus: '1',
areaType: '2',
hot: '0',
cityCode: '',
id: '',
pid: 100000,
name: '',
letter: '',
adcode: 0,
location: '',
areaSort: 0,
areaStatus: '1',
areaType: '2',
hot: '0',
cityCode: '',
});
// 定义校验规则
const dataRules = ref({
name: [
{required: true, message: '地区名称不能为空', trigger: 'blur'},
{min: 2, max: 20, message: '长度在 3 到 30 个字符', trigger: 'blur'},
{
validator: (rule: any, value: any, callback: any) => {
validateExist(rule, value, callback, form.id !== '');
},
trigger: 'blur',
},
],
adcode: [
{ validator: rule.overLength, trigger: 'blur' },
{required: true, message: '编码不能为空', trigger: 'blur'},
{
validator: (rule: any, value: any, callback: any) => {
validateExist(rule, value, callback, form.id !== '');
},
trigger: 'blur',
},
]
name: [
{ required: true, message: '地区名称不能为空', trigger: 'blur' },
{ min: 2, max: 20, message: '长度在 3 到 30 个字符', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
validateExist(rule, value, callback, form.id !== '');
},
trigger: 'blur',
},
],
adcode: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '编码不能为空', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
validateExist(rule, value, callback, form.id !== '');
},
trigger: 'blur',
},
],
});
// 打开弹窗
const openDialog = (id: string) => {
visible.value = true
form.id = ''
visible.value = true;
form.id = '';
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
});
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
});
// 获取sysArea信息
if (id) {
form.id = id
getsysAreaData(id)
}
// 获取sysArea信息
if (id) {
form.id = id;
getsysAreaData(id);
}
};
// 提交
const onSubmit = async () => {
// 立即设置 loading防止重复点击
if (loading.value) return;
loading.value = true;
// 立即设置 loading防止重复点击
if (loading.value) return;
loading.value = true;
try {
const valid = await dataFormRef.value.validate().catch(() => {
});
if (!valid) {
loading.value = false;
return false;
}
try {
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
form.id ? await putObj(form) : await addObj(form);
useMessage().success(form.id ? t('common.editSuccessText') : t('common.addSuccessText'));
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
form.id ? await putObj(form) : await addObj(form);
useMessage().success(form.id ? t('common.editSuccessText') : t('common.addSuccessText'));
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// 初始化表单数据
const getsysAreaData = (id: string) => {
// 获取数据
loading.value = true
getObj({id: id}).then((res: any) => {
Object.assign(form, res.data)
}).finally(() => {
loading.value = false
})
// 获取数据
loading.value = true;
getObj({ id: id })
.then((res: any) => {
Object.assign(form, res.data);
})
.finally(() => {
loading.value = false;
});
};
// 地区选择
const handleChange = (data: string) => {
let dataArray = data.split(",");
form.pid = dataArray[dataArray.length - 1];
}
let dataArray = data.split(',');
form.pid = dataArray[dataArray.length - 1];
};
// 暴露变量
defineExpose({
openDialog
openDialog,
});
</script>

View File

@@ -4,11 +4,11 @@ export default {
id: 'id',
pid: 'pid',
name: 'name',
adcode:'adcode',
areaType:'areaType',
areaSort:'areaSort',
hot:'hot',
areaStatus:'areaStatus',
adcode: 'adcode',
areaType: 'areaType',
areaSort: 'areaSort',
hot: 'hot',
areaStatus: 'areaStatus',
inputAdCodeByTip: 'input adcode',
inputPidByTip: 'input pid',
inputNameByTip: 'input name',

View File

@@ -4,11 +4,11 @@ export default {
id: '主键',
pid: '父级地区',
name: '名称',
adcode:'编码',
areaType:'类型',
areaSort:'排序值',
hot:'热门',
areaStatus:'有效',
adcode: '编码',
areaType: '类型',
areaSort: '排序值',
hot: '热门',
areaStatus: '有效',
inputAdCodeByTip: '请选择编码',
inputPidByTip: '请选择父级地区',
inputNameByTip: '请输入地区名称',

View File

@@ -1,174 +1,173 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item :label="t('area.pid')" prop="adcode">
<china-area :type="3" :placeholder="t('area.inputPidByTip')" v-model="pid" :plus="true" @change="handleChange"/>
</el-form-item>
<el-form-item :label="t('area.name')" prop="name">
<el-input :placeholder="t('area.inputNameByTip')" v-model="state.queryForm.name"/>
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList">
{{ $t('common.queryBtn') }}
</el-button>
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
</el-form-item>
</el-form>
</el-row>
<el-row>
<div class="mb8" style="width: 100%">
<el-button icon="folder-add" type="primary" class="ml10" @click="formDialogRef.openDialog()"
v-auth="'sys_sysArea_add'">
{{ $t('common.addBtn') }}
</el-button>
<el-button plain :disabled="multiple" icon="Delete" type="primary"
v-auth="'sys_sysArea_del'" @click="handleDelete(selectObjs)">
{{ $t('common.delBtn') }}
</el-button>
<right-toolbar v-model:showSearch="showSearch" :export="'sys_sysArea_export'"
@exportExcel="exportExcel" class="ml10 mr20" style="float: right;"
@queryTable="getDataList"></right-toolbar>
</div>
</el-row>
<el-table :data="state.dataList" v-loading="state.loading" border
:cell-style="tableStyle.cellStyle" :header-cell-style="tableStyle.headerCellStyle"
@selection-change="selectionChangHandle"
@sort-change="sortChangeHandle">
<el-table-column type="selection" width="40" align="center"/>
<el-table-column type="index" label="#" width="40"/>
<el-table-column prop="name" :label="t('area.name')" show-overflow-tooltip/>
<el-table-column prop="adcode" :label="t('area.adcode')" show-overflow-tooltip/>
<el-table-column prop="areaType" :label="t('area.areaType')" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="area_type_dict" :value="scope.row.areaType"></dict-tag>
</template>
</el-table-column>
<el-table-column prop="hot" :label="t('area.hot')" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="yes_no_type" :value="scope.row.hot"></dict-tag>
</template>
</el-table-column>
<el-table-column prop="areaStatus" :label="t('area.areaStatus')" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="yes_no_type" :value="scope.row.areaStatus"></dict-tag>
</template>
</el-table-column>
<el-table-column prop="areaSort" :label="t('area.areaSort')" width="100" sortable="custom"
show-overflow-tooltip/>
<el-table-column :label="$t('common.action')" width="150">
<template #default="scope">
<el-button icon="edit-pen" text type="primary" v-auth="'sys_sysArea_edit'"
@click="formDialogRef.openDialog(scope.row.id)">{{ $t('common.editBtn') }}
</el-button>
<el-button icon="delete" text type="primary" v-auth="'sys_sysArea_del'"
@click="handleDelete([scope.row.id])">{{ $t('common.delBtn') }}
</el-button>
</template>
</el-table-column>
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination"/>
</div>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch">
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
<el-form-item :label="t('area.pid')" prop="adcode">
<china-area :type="3" :placeholder="t('area.inputPidByTip')" v-model="pid" :plus="true" @change="handleChange" />
</el-form-item>
<el-form-item :label="t('area.name')" prop="name">
<el-input :placeholder="t('area.inputNameByTip')" v-model="state.queryForm.name" />
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList">
{{ $t('common.queryBtn') }}
</el-button>
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
</el-form-item>
</el-form>
</el-row>
<el-row>
<div class="mb8" style="width: 100%">
<el-button icon="folder-add" type="primary" class="ml10" @click="formDialogRef.openDialog()" v-auth="'sys_sysArea_add'">
{{ $t('common.addBtn') }}
</el-button>
<el-button plain :disabled="multiple" icon="Delete" type="primary" v-auth="'sys_sysArea_del'" @click="handleDelete(selectObjs)">
{{ $t('common.delBtn') }}
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
:export="'sys_sysArea_export'"
@exportExcel="exportExcel"
class="ml10 mr20"
style="float: right"
@queryTable="getDataList"
></right-toolbar>
</div>
</el-row>
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@selection-change="selectionChangHandle"
@sort-change="sortChangeHandle"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column type="index" label="#" width="40" />
<el-table-column prop="name" :label="t('area.name')" show-overflow-tooltip />
<el-table-column prop="adcode" :label="t('area.adcode')" show-overflow-tooltip />
<el-table-column prop="areaType" :label="t('area.areaType')" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="area_type_dict" :value="scope.row.areaType"></dict-tag>
</template>
</el-table-column>
<el-table-column prop="hot" :label="t('area.hot')" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="yes_no_type" :value="scope.row.hot"></dict-tag>
</template>
</el-table-column>
<el-table-column prop="areaStatus" :label="t('area.areaStatus')" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="yes_no_type" :value="scope.row.areaStatus"></dict-tag>
</template>
</el-table-column>
<el-table-column prop="areaSort" :label="t('area.areaSort')" width="100" sortable="custom" show-overflow-tooltip />
<el-table-column :label="$t('common.action')" width="150">
<template #default="scope">
<el-button icon="edit-pen" text type="primary" v-auth="'sys_sysArea_edit'" @click="formDialogRef.openDialog(scope.row.id)"
>{{ $t('common.editBtn') }}
</el-button>
<el-button icon="delete" text type="primary" v-auth="'sys_sysArea_del'" @click="handleDelete([scope.row.id])"
>{{ $t('common.delBtn') }}
</el-button>
</template>
</el-table-column>
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
<!-- 编辑新增 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)"/>
</div>
<!-- 编辑新增 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
</div>
</template>
<script setup lang="ts" name="systemSysArea">
import {BasicTableProps, useTable} from "/@/hooks/table";
import {delObjs, fetchList} from "/@/api/admin/sysArea";
import {useMessage, useMessageBox} from "/@/hooks/message";
import {useDict} from '/@/hooks/dict';
import {useI18n} from "vue-i18n";
import { BasicTableProps, useTable } from '/@/hooks/table';
import { delObjs, fetchList } from '/@/api/admin/sysArea';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { useDict } from '/@/hooks/dict';
import { useI18n } from 'vue-i18n';
const {t} = useI18n();
const { t } = useI18n();
// 省市区查询组件
const ChinaArea = defineAsyncComponent(() => import("/@/components/ChinaArea/index.vue"));
const ChinaArea = defineAsyncComponent(() => import('/@/components/ChinaArea/index.vue'));
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
// 定义查询字典
const {yes_no_type} = useDict('yes_no_type');
const { yes_no_type } = useDict('yes_no_type');
const area_type_dict = [
{value: '0', label: '国家'},
{value: '1', label: '省份'},
{value: '2', label: '城市'},
{value: '3', label: '县区'},
{value: '4', label: '街道'}
]
{ value: '0', label: '国家' },
{ value: '1', label: '省份' },
{ value: '2', label: '城市' },
{ value: '3', label: '县区' },
{ value: '4', label: '街道' },
];
// 定义变量内容
const formDialogRef = ref()
const formDialogRef = ref();
// 搜索变量
const queryRef = ref()
const showSearch = ref(true)
const queryRef = ref();
const showSearch = ref(true);
// 多选变量
const selectObjs = ref([]) as any
const multiple = ref(true)
const pid = ref()
const selectObjs = ref([]) as any;
const multiple = ref(true);
const pid = ref();
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
adcode: '',
name: ''
},
ascs: ['adcode'],
pageList: fetchList
})
queryForm: {
adcode: '',
name: '',
},
ascs: ['adcode'],
pageList: fetchList,
});
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
downBlobFile,
tableStyle
} = useTable(state)
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
// 清空搜索条件
const resetQuery = () => {
// 清空搜索条件
queryRef.value?.resetFields()
pid.value = ''
// 清空多选
selectObjs.value = []
getDataList()
}
// 清空搜索条件
queryRef.value?.resetFields();
pid.value = '';
// 清空多选
selectObjs.value = [];
getDataList();
};
// 导出excel
const exportExcel = () => {
downBlobFile('/admin/sysArea/export', Object.assign(state.queryForm, {ids: selectObjs}), 'sysArea.xlsx')
}
downBlobFile('/admin/sysArea/export', Object.assign(state.queryForm, { ids: selectObjs }), 'sysArea.xlsx');
};
// 多选事件
const selectionChangHandle = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({id}) => id);
multiple.value = !objs.length;
selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length;
};
// 删除操作
const handleDelete = async (ids: string[]) => {
try {
await useMessageBox().confirm(t('common.delConfirmText'));
} catch {
return;
}
try {
await useMessageBox().confirm(t('common.delConfirmText'));
} catch {
return;
}
try {
await delObjs(ids);
getDataList();
useMessage().success(t('common.delSuccessText'));
} catch (err: any) {
useMessage().error(err.msg);
}
try {
await delObjs(ids);
getDataList();
useMessage().success(t('common.delSuccessText'));
} catch (err: any) {
useMessage().error(err.msg);
}
};
// 地区查询
const handleChange = (data: string) => {
let dataArray = data.split(",");
state.queryForm.adcode = dataArray[dataArray.length - 1];
}
let dataArray = data.split(',');
state.queryForm.adcode = dataArray[dataArray.length - 1];
};
</script>

View File

@@ -33,7 +33,7 @@
import { useI18n } from 'vue-i18n';
import { getObj, deptTree, addObj, putObj } from '/@/api/admin/dept';
import { useMessage } from '/@/hooks/message';
import {rule} from "/@/utils/validate";
import { rule } from '/@/utils/validate';
// 定义子组件向父组件传值/事件
const emit = defineEmits(['refresh']);
@@ -52,7 +52,10 @@ const loading = ref(false);
const dataRules = ref({
parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }],
name: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
name: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '部门名称不能为空', trigger: 'blur' },
],
sortOrder: [{ required: true, message: '排序不能为空', trigger: 'blur' }],
});

View File

@@ -12,9 +12,9 @@ export default {
inputLeaderIdTip: 'input leader',
inputsortOrderTip: 'input sortOrder',
importTip: 'import dept',
addNodeText:'add dept',
editNodeText:'edit dept',
delNodeText:'delete dept',
addNodeText: 'add dept',
editNodeText: 'edit dept',
delNodeText: 'delete dept',
view: 'tree/table view',
tenantNodeErrorText: 'The current node cannot be operated. You need to maintain it in tenant management',
},

View File

@@ -1,21 +1,21 @@
export default {
sysdept: {
name: '部门名称',
parentId: '上级部门',
createTime: '创建时间',
weight: '排序',
sortOrder: '排序',
leaderId: '部门负责人',
inputdeptNameTip: '请输入部门名称',
inputnameTip: '请输入部门名称',
inputLeaderIdTip: '请输入部门领导',
inputparentIdTip: '请选择上级部门',
inputsortOrderTip: '请输入排序',
importTip: '导入部门',
addNodeText: '添加部门',
editNodeText: '编辑部门',
delNodeText: '删除部门',
tenantNodeErrorText: '当前节点不可操作,请在租户管理功能中维护',
view: '树/表视图'
},
sysdept: {
name: '部门名称',
parentId: '上级部门',
createTime: '创建时间',
weight: '排序',
sortOrder: '排序',
leaderId: '部门负责人',
inputdeptNameTip: '请输入部门名称',
inputnameTip: '请输入部门名称',
inputLeaderIdTip: '请输入部门领导',
inputparentIdTip: '请选择上级部门',
inputsortOrderTip: '请输入排序',
importTip: '导入部门',
addNodeText: '添加部门',
editNodeText: '编辑部门',
delNodeText: '删除部门',
tenantNodeErrorText: '当前节点不可操作,请在租户管理功能中维护',
view: '树/表视图',
},
};

View File

@@ -130,6 +130,6 @@ const resetQuery = () => {
</script>
<style scoped>
:deep(.el-table__body tr td) {
text-align: left !important;
text-align: left !important;
}
</style>
</style>

View File

@@ -1,50 +1,45 @@
<template>
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
style="width: 100%"
row-key="id"
default-expand-all
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle?.headerCellStyle"
>
<el-table-column :label="$t('sysdept.name')" prop="name" width="400" show-overflow-tooltip></el-table-column>
<el-table-column :label="$t('sysdept.weight')" prop="weight" show-overflow-tooltip width="80"></el-table-column>
<el-table-column prop="createTime" :label="$t('sysdept.createTime')" show-overflow-tooltip></el-table-column>
<el-table-column :label="$t('common.action')" show-overflow-tooltip width="250">
<template #default="scope">
<el-button text type="primary" icon="folder-add" @click="deptDialogRef.openDialog('add', scope.row?.id)"
v-auth="'sys_dept_add'">
{{ $t('common.addBtn') }}
</el-button
>
<el-button text type="primary" icon="edit-pen" @click="deptDialogRef.openDialog('edit', scope.row?.id)"
v-auth="'sys_dept_edit'">{{
$t('common.editBtn')
}}
</el-button>
<el-button text type="primary" icon="delete" @click="handleDelete(scope.row)" v-auth="'sys_dept_del'">
{{ $t('common.delBtn') }}
</el-button
>
</template>
</el-table-column>
</el-table>
<dept-form ref="deptDialogRef" @refresh="getDataList()"/>
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
style="width: 100%"
row-key="id"
default-expand-all
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle?.headerCellStyle"
>
<el-table-column :label="$t('sysdept.name')" prop="name" width="400" show-overflow-tooltip></el-table-column>
<el-table-column :label="$t('sysdept.weight')" prop="weight" show-overflow-tooltip width="80"></el-table-column>
<el-table-column prop="createTime" :label="$t('sysdept.createTime')" show-overflow-tooltip></el-table-column>
<el-table-column :label="$t('common.action')" show-overflow-tooltip width="250">
<template #default="scope">
<el-button text type="primary" icon="folder-add" @click="deptDialogRef.openDialog('add', scope.row?.id)" v-auth="'sys_dept_add'">
{{ $t('common.addBtn') }}
</el-button>
<el-button text type="primary" icon="edit-pen" @click="deptDialogRef.openDialog('edit', scope.row?.id)" v-auth="'sys_dept_edit'"
>{{ $t('common.editBtn') }}
</el-button>
<el-button text type="primary" icon="delete" @click="handleDelete(scope.row)" v-auth="'sys_dept_del'">
{{ $t('common.delBtn') }}
</el-button>
</template>
</el-table-column>
</el-table>
<dept-form ref="deptDialogRef" @refresh="getDataList()" />
</template>
<script setup lang="ts" name="systemDept">
import {BasicTableProps, useTable} from '/@/hooks/table';
import {deptTree, delObj} from '/@/api/admin/dept';
import {useMessage, useMessageBox} from '/@/hooks/message';
import {useI18n} from 'vue-i18n';
import { BasicTableProps, useTable } from '/@/hooks/table';
import { deptTree, delObj } from '/@/api/admin/dept';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { useI18n } from 'vue-i18n';
// 引入组件
const DeptForm = defineAsyncComponent(() => import('./form.vue'));
const {t} = useI18n();
const { t } = useI18n();
// 定义变量内容
const tableRef = ref(); // 表格引用
const deptDialogRef = ref(); // 部门对话框引用
@@ -58,33 +53,33 @@ const isExpand = ref(false); // 是否展开
* @returns Promise&lt;any&gt;
*/
const queryDeptTree = (params?: any) => {
return deptTree(params);
return deptTree(params);
};
/**
* 定义响应式表格数据
*/
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: queryDeptTree, // 页面列表数据
queryForm: {
deptName: '', // 部门名称
},
isPage: false, // 是否分页
descs: ['create_time'], // 排序字段
pageList: queryDeptTree, // 页面列表数据
queryForm: {
deptName: '', // 部门名称
},
isPage: false, // 是否分页
descs: ['create_time'], // 排序字段
});
/**
* 使用 useTable 定义表格相关操作
*/
const {getDataList, tableStyle} = useTable(state);
const { getDataList, tableStyle } = useTable(state);
/**
* 展开/折叠部门树方法
*/
const handleExpand = async () => {
isExpand.value = !isExpand.value;
const dataList = await deptTree();
toggleExpand(dataList.data, isExpand.value);
isExpand.value = !isExpand.value;
const dataList = await deptTree();
toggleExpand(dataList.data, isExpand.value);
};
/**
@@ -93,12 +88,12 @@ const handleExpand = async () => {
* @param unfold - 是否展开
*/
const toggleExpand = (children: any[], unfold = true) => {
for (const key in children) {
tableRef.value?.toggleRowExpansion(children[key], unfold);
if (children[key].children) {
toggleExpand(children[key].children!, unfold);
}
}
for (const key in children) {
tableRef.value?.toggleRowExpansion(children[key], unfold);
if (children[key].children) {
toggleExpand(children[key].children!, unfold);
}
}
};
/**
@@ -106,32 +101,32 @@ const toggleExpand = (children: any[], unfold = true) => {
* @param row - 当前行数据
*/
const handleDelete = async (row: any) => {
try {
await useMessageBox().confirm(t('common.delConfirmText'));
} catch {
return;
}
try {
await useMessageBox().confirm(t('common.delConfirmText'));
} catch {
return;
}
try {
await delObj(row.id);
getDataList();
useMessage().success(t('common.delSuccessText'));
} catch (err: any) {
useMessage().error(err.msg);
}
try {
await delObj(row.id);
getDataList();
useMessage().success(t('common.delSuccessText'));
} catch (err: any) {
useMessage().error(err.msg);
}
};
const handleAdd = ()=>{
deptDialogRef.value.openDialog('add')
}
const handleAdd = () => {
deptDialogRef.value.openDialog('add');
};
/**
* 暴露组件中的一些方法和变量
*/
defineExpose({
handleAdd, // 新增时间
state, // 响应式表格数据
getDataList, // 获取列表数据方法
handleExpand // 展开/折叠部门树方法
handleAdd, // 新增时间
state, // 响应式表格数据
getDataList, // 获取列表数据方法
handleExpand, // 展开/折叠部门树方法
});
</script>

View File

@@ -1,127 +1,134 @@
<template>
<el-dialog
:title="state.ruleForm.menuId ? $t('common.editBtn') : $t('common.addBtn')"
v-model="visible"
width="600"
:close-on-click-modal="false"
draggable
>
<el-form ref="menuDialogFormRef" :model="state.ruleForm" :rules="dataRules" label-width="100px" v-loading="loading">
<el-form-item :label="$t('sysmenu.menuType')" prop="menuType">
<el-radio-group v-model="state.ruleForm.menuType">
<el-radio border label="0">菜单</el-radio>
<el-radio border label="1">按钮</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('sysmenu.parentId')" prop="parentId">
<el-tree-select
v-model="state.ruleForm.parentId"
:data="state.parentData"
:render-after-expand="false"
:props="{ value: 'id', label: 'name', children: 'children' }"
class="w100"
clearable
check-strictly
:placeholder="$t('sysmenu.inputParentIdTip')"
>
</el-tree-select>
</el-form-item>
<el-form-item prop="name">
<template #label>
{{ state.ruleForm.menuType === '0' ? t('sysmenu.name') : t('sysmenu.buttonName') }}
</template>
<el-input v-model="state.ruleForm.name" clearable :placeholder="$t('sysmenu.inputNameTip')"></el-input>
</el-form-item>
<el-form-item :label="$t('sysmenu.path')" prop="path" v-if="state.ruleForm.menuType === '0'">
<el-input v-model="state.ruleForm.path" :placeholder="$t('sysmenu.inputPathTip')"/>
</el-form-item>
<el-form-item :label="$t('sysmenu.permission')" prop="permission" v-if="state.ruleForm.menuType === '1'">
<template #label>
{{ t('sysmenu.permission') }}
<tip content="对应后台接口@PreAuthorize注解入参字符串"></tip>
</template>
<el-input v-model="state.ruleForm.permission" maxlength="100" :placeholder="$t('sysmenu.inputPermissionTip')"/>
</el-form-item>
<el-form-item :label="$t('sysmenu.sortOrder')" prop="sortOrder">
<el-input-number v-model="state.ruleForm.sortOrder" :min="0" controls-position="right"/>
</el-form-item>
<el-form-item :label="$t('sysmenu.icon')" prop="icon" v-if="state.ruleForm.menuType === '0'">
<IconSelector :placeholder="$t('sysmenu.inputIconTip')" v-model="state.ruleForm.icon"/>
</el-form-item>
<el-row>
<el-col :span="12">
<el-form-item prop="keepAlive" v-if="state.ruleForm.menuType === '0'">
<template #label> {{ $t('sysmenu.keepAlive') }}
<tip content="组件保留状态,避免重新渲染"/>
</template>
<el-radio-group v-model="state.ruleForm.keepAlive">
<el-radio border label="0"></el-radio>
<el-radio border label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="visible" v-if="state.ruleForm.menuType === '0'">
<template #label> {{ $t('sysmenu.visible') }}
<tip content="左侧菜单树是否显示"/>
</template>
<el-radio-group v-model="state.ruleForm.visible">
<el-radio border label="0"></el-radio>
<el-radio border label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row class="mt-4">
<el-col :span="12">
<el-form-item prop="param" v-if="state.ruleForm.menuType === '0'">
<template #label> {{ $t('sysmenu.param') }}
<tip content="多个路径指向同一个组件"/>
</template>
<el-radio-group v-model="state.ruleForm.param">
<el-radio border label="0"></el-radio>
<el-radio border label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="embedded"
v-if="state.ruleForm.menuType === '0' && state.ruleForm.path?.startsWith('http')">
<template #label> {{ $t('sysmenu.embedded') }}
<tip content="iframe嵌套还是打开独立的Tab"/>
</template>
<el-radio-group v-model="state.ruleForm.embedded">
<el-radio border label="0"></el-radio>
<el-radio border label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-dialog
:title="state.ruleForm.menuId ? $t('common.editBtn') : $t('common.addBtn')"
v-model="visible"
width="600"
:close-on-click-modal="false"
draggable
>
<el-form ref="menuDialogFormRef" :model="state.ruleForm" :rules="dataRules" label-width="100px" v-loading="loading">
<el-form-item :label="$t('sysmenu.menuType')" prop="menuType">
<el-radio-group v-model="state.ruleForm.menuType">
<el-radio border label="0">菜单</el-radio>
<el-radio border label="1">按钮</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('sysmenu.parentId')" prop="parentId">
<el-tree-select
v-model="state.ruleForm.parentId"
:data="state.parentData"
:render-after-expand="false"
:props="{ value: 'id', label: 'name', children: 'children' }"
class="w100"
clearable
check-strictly
:placeholder="$t('sysmenu.inputParentIdTip')"
>
</el-tree-select>
</el-form-item>
<el-form-item prop="name">
<template #label>
{{ state.ruleForm.menuType === '0' ? t('sysmenu.name') : t('sysmenu.buttonName') }}
</template>
<el-input v-model="state.ruleForm.name" clearable :placeholder="$t('sysmenu.inputNameTip')"></el-input>
</el-form-item>
<el-form-item :label="$t('sysmenu.path')" prop="path" v-if="state.ruleForm.menuType === '0'">
<el-input v-model="state.ruleForm.path" :placeholder="$t('sysmenu.inputPathTip')" />
</el-form-item>
<el-form-item :label="$t('sysmenu.permission')" prop="permission" v-if="state.ruleForm.menuType === '1'">
<template #label>
{{ t('sysmenu.permission') }}
<tip content="对应后台接口@PreAuthorize注解入参字符串"></tip>
</template>
<el-input v-model="state.ruleForm.permission" maxlength="100" :placeholder="$t('sysmenu.inputPermissionTip')" />
</el-form-item>
<el-form-item :label="$t('sysmenu.sortOrder')" prop="sortOrder">
<el-input-number v-model="state.ruleForm.sortOrder" :min="0" controls-position="right" />
</el-form-item>
<el-form-item :label="$t('sysmenu.icon')" prop="icon" v-if="state.ruleForm.menuType === '0'">
<IconSelector :placeholder="$t('sysmenu.inputIconTip')" v-model="state.ruleForm.icon" />
</el-form-item>
<el-row>
<el-col :span="12">
<el-form-item prop="keepAlive" v-if="state.ruleForm.menuType === '0'">
<template #label>
{{ $t('sysmenu.keepAlive') }}
<tip content="组件保留状态,避免重新渲染" />
</template>
<el-radio-group v-model="state.ruleForm.keepAlive">
<el-radio border label="0"></el-radio>
<el-radio border label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="visible" v-if="state.ruleForm.menuType === '0'">
<template #label>
{{ $t('sysmenu.visible') }}
<tip content="左侧菜单树是否显示" />
</template>
<el-radio-group v-model="state.ruleForm.visible">
<el-radio border label="0"></el-radio>
<el-radio border label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row class="mt-4">
<el-col :span="12">
<el-form-item prop="param" v-if="state.ruleForm.menuType === '0'">
<template #label>
{{ $t('sysmenu.param') }}
<tip content="多个路径指向同一个组件" />
</template>
<el-radio-group v-model="state.ruleForm.param">
<el-radio border label="0"></el-radio>
<el-radio border label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="embedded" v-if="state.ruleForm.menuType === '0' && state.ruleForm.path?.startsWith('http')">
<template #label>
{{ $t('sysmenu.embedded') }}
<tip content="iframe嵌套还是打开独立的Tab" />
</template>
<el-radio-group v-model="state.ruleForm.embedded">
<el-radio border label="0"></el-radio>
<el-radio border label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-form-item class="mt-4" :label="$t('sysmenu.component')" prop="component" v-if="state.ruleForm.menuType === '0'
&& state.ruleForm.param === '1'">
<el-input v-model="state.ruleForm.component" :placeholder="$t('sysmenu.inputComponentTip')"/>
</el-form-item>
</el-form>
<template #footer>
<el-form-item
class="mt-4"
:label="$t('sysmenu.component')"
prop="component"
v-if="state.ruleForm.menuType === '0' && state.ruleForm.param === '1'"
>
<el-input v-model="state.ruleForm.component" :placeholder="$t('sysmenu.inputComponentTip')" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
</span>
</template>
</el-dialog>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="systemMenuDialog">
import {useI18n} from 'vue-i18n';
import {getObj, pageList, putObj, addObj, validateExist} from '/@/api/admin/menu';
import {useMessage} from '/@/hooks/message';
import {rule, validateNull} from "/@/utils/validate";
import Tip from "/@/components/Tip/index.vue";
import { useI18n } from 'vue-i18n';
import { getObj, pageList, putObj, addObj, validateExist } from '/@/api/admin/menu';
import { useMessage } from '/@/hooks/message';
import { rule, validateNull } from '/@/utils/validate';
import Tip from '/@/components/Tip/index.vue';
// 定义子组件向父组件传值/事件
const emit = defineEmits(['refresh']);
const {t} = useI18n();
const { t } = useI18n();
// 引入组件
const IconSelector = defineAsyncComponent(() => import('/@/components/IconSelector/index.vue'));
@@ -132,148 +139,160 @@ const menuDialogFormRef = ref();
const originalName = ref(''); // To store the original menu name for comparison during edits
// 定义需要的数据
const state = reactive({
ruleForm: {
menuId: '',
name: '',
permission: '',
parentId: '',
icon: '',
path: '',
param: '0',
component: '',
sortOrder: 0,
menuType: '1',
keepAlive: '0',
visible: '1',
embedded: '0',
},
parentData: [] as any[], // 上级菜单数据
ruleForm: {
menuId: '',
name: '',
permission: '',
parentId: '',
icon: '',
path: '',
param: '0',
component: '',
sortOrder: 0,
menuType: '1',
keepAlive: '0',
visible: '1',
embedded: '0',
},
parentData: [] as any[], // 上级菜单数据
});
// 表单校验规则
const dataRules = reactive({
menuType: [{required: true, message: '菜单类型不能为空', trigger: 'blur'}],
parentId: [{required: true, message: '上级菜单不能为空', trigger: 'blur'}],
name: [{validator: rule.overLength, trigger: 'blur'}, {
required: true,
message: '菜单不能为空',
trigger: 'blur'
}, {
validator: (rule: any, value: any, callback: any) => {
// 如果是按钮类型菜单,跳过名称唯一性校验
if (state.ruleForm.menuType === '1') {
callback();
return;
}
// 如果是编辑状态且菜单名称未改变,跳过校验
if (state.ruleForm.menuId !== '' && value === originalName.value) {
callback();
return;
}
// 其他情况下,验证菜单名称唯一性
validateExist(rule, value, callback, false);
},
trigger: 'blur',
}],
path: [{validator: rule.overLength, trigger: 'blur'}, {required: true, message: '路径不能为空', trigger: 'blur'}],
permission: [{validator: rule.overLength, trigger: 'blur'}, {
required: true,
message: '权限代码不能为空',
trigger: 'blur'
}, {
validator: (rule: any, value: any, callback: any) => {
validateExist(rule, value, callback, state.ruleForm.menuId !== '');
},
trigger: 'blur',
}],
sortOrder: [{required: true, message: '排序不能为空', trigger: 'blur'}],
component: [{min: 5, max: 255, message: '组件名称长度必须介于 5 和 255 之间', trigger: 'blur'},
{
validator: (rule: any, value: any, callback: any) => {
if (state.ruleForm.menuType === '0' && state.ruleForm.param === '1' && validateNull(state.ruleForm.component)) {
callback(new Error('请输入组件名称'));
} else {
return callback();
}
},
trigger: 'blur',
}],
menuType: [{ required: true, message: '菜单类型不能为空', trigger: 'blur' }],
parentId: [{ required: true, message: '上级菜单不能为空', trigger: 'blur' }],
name: [
{ validator: rule.overLength, trigger: 'blur' },
{
required: true,
message: '菜单不能为空',
trigger: 'blur',
},
{
validator: (rule: any, value: any, callback: any) => {
// 如果是按钮类型菜单,跳过名称唯一性校验
if (state.ruleForm.menuType === '1') {
callback();
return;
}
// 如果是编辑状态且菜单名称未改变,跳过校验
if (state.ruleForm.menuId !== '' && value === originalName.value) {
callback();
return;
}
// 其他情况下,验证菜单名称唯一性
validateExist(rule, value, callback, false);
},
trigger: 'blur',
},
],
path: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '路径不能为空', trigger: 'blur' },
],
permission: [
{ validator: rule.overLength, trigger: 'blur' },
{
required: true,
message: '权限代码不能为空',
trigger: 'blur',
},
{
validator: (rule: any, value: any, callback: any) => {
validateExist(rule, value, callback, state.ruleForm.menuId !== '');
},
trigger: 'blur',
},
],
sortOrder: [{ required: true, message: '排序不能为空', trigger: 'blur' }],
component: [
{ min: 5, max: 255, message: '组件名称长度必须介于 5 和 255 之间', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
if (state.ruleForm.menuType === '0' && state.ruleForm.param === '1' && validateNull(state.ruleForm.component)) {
callback(new Error('请输入组件名称'));
} else {
return callback();
}
},
trigger: 'blur',
},
],
});
// 打开弹窗
const openDialog = (type: string, row?: any) => {
state.ruleForm.menuId = '';
visible.value = true;
originalName.value = ''; // Reset the original name
state.ruleForm.menuId = '';
visible.value = true;
originalName.value = ''; // Reset the original name
nextTick(() => {
menuDialogFormRef.value?.resetFields();
state.ruleForm.parentId = row?.id || '-1';
});
nextTick(() => {
menuDialogFormRef.value?.resetFields();
state.ruleForm.parentId = row?.id || '-1';
});
if (row?.id && type === 'edit') {
state.ruleForm.menuId = row.id;
// 获取当前节点菜单信息
getMenuDetail(row.id);
}
// 渲染上级菜单列表树
getAllMenuData();
if (row?.id && type === 'edit') {
state.ruleForm.menuId = row.id;
// 获取当前节点菜单信息
getMenuDetail(row.id);
}
// 渲染上级菜单列表树
getAllMenuData();
};
// 获取菜单节点的详细信息
const getMenuDetail = (id: string) => {
getObj({menuId: id}).then((res) => {
if (res.data[0].component) {
state.ruleForm.param = '1'
}
originalName.value = res.data[0].name; // Store the original name
Object.assign(state.ruleForm, res.data[0]);
});
getObj({ menuId: id }).then((res) => {
if (res.data[0].component) {
state.ruleForm.param = '1';
}
originalName.value = res.data[0].name; // Store the original name
Object.assign(state.ruleForm, res.data[0]);
});
};
// 从后端获取菜单信息(含层级)
const getAllMenuData = () => {
state.parentData = [];
pageList({
type: '0',
}).then((res) => {
let menu = {
id: '-1',
name: '根菜单',
children: [],
};
menu.children = res.data;
state.parentData.push(menu);
});
state.parentData = [];
pageList({
type: '0',
}).then((res) => {
let menu = {
id: '-1',
name: '根菜单',
children: [],
};
menu.children = res.data;
state.parentData.push(menu);
});
};
// 保存数据
const onSubmit = async () => {
// 立即设置 loading防止重复点击
if (loading.value) return;
loading.value = true;
try {
const valid = await menuDialogFormRef.value.validate().catch(() => {
});
if (!valid) {
loading.value = false;
return false;
}
// 立即设置 loading防止重复点击
if (loading.value) return;
loading.value = true;
state.ruleForm.menuId ? await putObj(state.ruleForm) : await addObj(state.ruleForm);
useMessage().success(t(state.ruleForm.menuId ? 'common.editSuccessText' : 'common.addSuccessText'));
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
try {
const valid = await menuDialogFormRef.value.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
state.ruleForm.menuId ? await putObj(state.ruleForm) : await addObj(state.ruleForm);
useMessage().success(t(state.ruleForm.menuId ? 'common.editSuccessText' : 'common.addSuccessText'));
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// 暴露变量 只有暴漏出来的变量 父组件才能使用
defineExpose({
openDialog,
openDialog,
});
</script>

View File

@@ -1,37 +1,37 @@
export default {
sysmenu: {
index: '#',
name: 'menu name',
buttonName: 'button name',
sortOrder: 'sortOrder',
path: 'path',
menuType: 'menuType',
keepAlive: 'keepAlive',
permission: 'permission',
inputNameTip: 'input name',
parentId: 'parent menu',
embedded: 'embedded',
param: 'param',
component: 'component',
visible: 'visible',
icon: 'icon',
inputMenuIdTip: 'input menuId',
inputPermissionTip: 'input permission',
inputPathTip: 'input path',
inputParentIdTip: 'input parentId',
inputIconTip: 'input icon',
inputVisibleTip: 'input visible',
inputSortOrderTip: 'input sortOrder',
inputKeepAliveTip: 'input keepAlive',
inputMenuTypeTip: 'input menuType',
inputCreateByTip: 'input createBy',
inputCreateTimeTip: 'input createTime',
inputUpdateByTip: 'input updateBy',
inputUpdateTimeTip: 'input updateTime',
inputDelFlagTip: 'input delFlag',
inputTenantIdTip: 'input tenantId',
inputEmbeddedTip: 'input embedded',
inputComponentTip: 'input component',
deleteDisabledTip: 'menu inclusion subordinates cannot be deleted',
},
sysmenu: {
index: '#',
name: 'menu name',
buttonName: 'button name',
sortOrder: 'sortOrder',
path: 'path',
menuType: 'menuType',
keepAlive: 'keepAlive',
permission: 'permission',
inputNameTip: 'input name',
parentId: 'parent menu',
embedded: 'embedded',
param: 'param',
component: 'component',
visible: 'visible',
icon: 'icon',
inputMenuIdTip: 'input menuId',
inputPermissionTip: 'input permission',
inputPathTip: 'input path',
inputParentIdTip: 'input parentId',
inputIconTip: 'input icon',
inputVisibleTip: 'input visible',
inputSortOrderTip: 'input sortOrder',
inputKeepAliveTip: 'input keepAlive',
inputMenuTypeTip: 'input menuType',
inputCreateByTip: 'input createBy',
inputCreateTimeTip: 'input createTime',
inputUpdateByTip: 'input updateBy',
inputUpdateTimeTip: 'input updateTime',
inputDelFlagTip: 'input delFlag',
inputTenantIdTip: 'input tenantId',
inputEmbeddedTip: 'input embedded',
inputComponentTip: 'input component',
deleteDisabledTip: 'menu inclusion subordinates cannot be deleted',
},
};

View File

@@ -1,31 +1,31 @@
export default {
sysmenu: {
index: '#',
name: '菜单名称',
buttonName: '按钮名称',
sortOrder: '排序',
path: '路由',
menuType: '类型',
keepAlive: '缓冲',
permission: '权限标识',
inputNameTip: '请输入菜单名称',
parentId: '上级菜单',
embedded: '内嵌',
param: '带参',
component: '组件',
visible: '显示',
icon: '图标',
inputMenuIdTip: '',
inputPermissionTip: '请输入权限标识',
inputPathTip: '请输入路由路径',
inputParentIdTip: '请选择上级菜单',
inputIconTip: '请选择图标',
inputVisibleTip: '请选择是否显示',
inputSortOrderTip: '请输入排序',
inputKeepAliveTip: '请选择是否缓冲',
inputMenuTypeTip: '请选择菜单类型',
inputEmbeddedTip: '请选择是否内嵌',
inputComponentTip: '请输入组件名称',
deleteDisabledTip: '菜单包含下级不能删除',
},
sysmenu: {
index: '#',
name: '菜单名称',
buttonName: '按钮名称',
sortOrder: '排序',
path: '路由',
menuType: '类型',
keepAlive: '缓冲',
permission: '权限标识',
inputNameTip: '请输入菜单名称',
parentId: '上级菜单',
embedded: '内嵌',
param: '带参',
component: '组件',
visible: '显示',
icon: '图标',
inputMenuIdTip: '',
inputPermissionTip: '请输入权限标识',
inputPathTip: '请输入路由路径',
inputParentIdTip: '请选择上级菜单',
inputIconTip: '请选择图标',
inputVisibleTip: '请选择是否显示',
inputSortOrderTip: '请输入排序',
inputKeepAliveTip: '请选择是否缓冲',
inputMenuTypeTip: '请选择菜单类型',
inputEmbeddedTip: '请选择是否内嵌',
inputComponentTip: '请输入组件名称',
deleteDisabledTip: '菜单包含下级不能删除',
},
};

View File

@@ -198,6 +198,6 @@ const handleDelete = async (row: any) => {
<style scoped>
:deep(.el-table__body tr td) {
text-align: left !important;
text-align: left !important;
}
</style>

View File

@@ -27,7 +27,7 @@
import { useMessage } from '/@/hooks/message';
import { getObj, addObj, putObj, validatePostCode, validatePostName } from '/@/api/admin/post';
import { useI18n } from 'vue-i18n';
import {rule} from "/@/utils/validate";
import { rule } from '/@/utils/validate';
// 定义子组件向父组件传值/事件
const emit = defineEmits(['refresh']);
@@ -56,7 +56,7 @@ const form = reactive({
// 定义校验规则
const dataRules = ref({
postCode: [
{ validator: rule.overLength, trigger: 'blur' },
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '岗位编码不能为空', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
@@ -66,7 +66,7 @@ const dataRules = ref({
},
],
postName: [
{ validator: rule.overLength, trigger: 'blur' },
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '岗位名称不能为空', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
@@ -75,8 +75,14 @@ const dataRules = ref({
trigger: 'blur',
},
],
postSort: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '岗位排序不能为空', trigger: 'blur' }],
remark: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '岗位描述不能为空', trigger: 'blur' }],
postSort: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '岗位排序不能为空', trigger: 'blur' },
],
remark: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '岗位描述不能为空', trigger: 'blur' },
],
});
// 打开弹窗

View File

@@ -40,7 +40,7 @@
@selection-change="handleSelectionChange"
style="width: 100%"
v-loading="state.loading"
row-key="postId"
row-key="postId"
border
:cell-style="tableStyle?.cellStyle"
:header-cell-style="tableStyle?.headerCellStyle"
@@ -115,7 +115,7 @@ const resetQuery = () => {
// 导出excel
const exportExcel = () => {
downBlobFile('/admin/post/export', Object.assign(state.queryForm,{ids:selectObjs}), 'post.xlsx');
downBlobFile('/admin/post/export', Object.assign(state.queryForm, { ids: selectObjs }), 'post.xlsx');
};
// 多选事件

View File

@@ -1,178 +1,169 @@
<template>
<el-dialog
v-model="visible"
:title="dialogTitle"
width="80%"
:show-close="false"
:close-on-click-modal="false"
:close-on-press-escape="false"
:before-close="handleBeforeClose"
center
>
<el-form>
<el-form-item class="role-form-item">
<el-radio-group v-model="radio" class="role-radio-group" @change="handleChangeRole">
<template v-for="(roles, groupName) in allRoleGroups" :key="groupName">
<el-card class="role-group-card" shadow="hover">
<template #header>
<span class="group-name">{{ groupName }}</span>
</template>
<div class="role-group">
<el-radio-button
v-for="item in roles"
:key="item.roleCode"
:label="item.roleCode"
size="small"
>
{{ item.roleName }}
</el-radio-button>
</div>
</el-card>
</template>
</el-radio-group>
</el-form-item>
</el-form>
<el-dialog
v-model="visible"
:title="dialogTitle"
width="80%"
:show-close="false"
:close-on-click-modal="false"
:close-on-press-escape="false"
:before-close="handleBeforeClose"
center
>
<el-form>
<el-form-item class="role-form-item">
<el-radio-group v-model="radio" class="role-radio-group" @change="handleChangeRole">
<template v-for="(roles, groupName) in allRoleGroups" :key="groupName">
<el-card class="role-group-card" shadow="hover">
<template #header>
<span class="group-name">{{ groupName }}</span>
</template>
<div class="role-group">
<el-radio-button v-for="item in roles" :key="item.roleCode" :label="item.roleCode" size="small">
{{ item.roleName }}
</el-radio-button>
</div>
</el-card>
</template>
</el-radio-group>
</el-form-item>
</el-form>
<template v-if="!requireSelectToClose" #footer>
<el-button @click="handleFooterClose"> </el-button>
</template>
</el-dialog>
<template v-if="!requireSelectToClose" #footer>
<el-button @click="handleFooterClose"> </el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed, toRef } from 'vue'
import { listAllRole } from '/@/api/admin/role'
import { Local, Session } from '/@/utils/storage'
import { useMessage } from '/@/hooks/message'
import { ref, computed, toRef } from 'vue';
import { listAllRole } from '/@/api/admin/role';
import { Local, Session } from '/@/utils/storage';
import { useMessage } from '/@/hooks/message';
/** 弹框标题,如「角色切换」「登录角色选择」 */
const props = withDefaults(
defineProps<{ title?: string; requireSelectToClose?: boolean }>(),
{ title: '角色切换', requireSelectToClose: false }
)
const dialogTitle = computed(() => props.title)
const props = withDefaults(defineProps<{ title?: string; requireSelectToClose?: boolean }>(), { title: '角色切换', requireSelectToClose: false });
const dialogTitle = computed(() => props.title);
const visible = ref(false)
const radio = ref('')
const visible = ref(false);
const radio = ref('');
/** 按分组名分组的角色列表:{ "未分组": [{ roleId, roleName, roleCode, ... }], ... } */
const allRoleGroups = ref<Record<string, any[]>>({})
const requireSelectToClose = toRef(props, 'requireSelectToClose')
const allRoleGroups = ref<Record<string, any[]>>({});
const requireSelectToClose = toRef(props, 'requireSelectToClose');
const open = () => {
if (visible.value) return
visible.value = true
listAllRole().then((res) => {
allRoleGroups.value = res.data && typeof res.data === 'object' && !Array.isArray(res.data)
? res.data
: { '未分组': Array.isArray(res.data) ? res.data : [] }
radio.value = Local.get('roleCode')
})
}
if (visible.value) return;
visible.value = true;
listAllRole().then((res) => {
allRoleGroups.value =
res.data && typeof res.data === 'object' && !Array.isArray(res.data) ? res.data : { 未分组: Array.isArray(res.data) ? res.data : [] };
radio.value = Local.get('roleCode');
});
};
/** 根据 roleCode 从分组数据中查找角色 */
const findRoleByCode = (code: string) => {
for (const roles of Object.values(allRoleGroups.value)) {
if (!Array.isArray(roles)) continue
const found = roles.find((r: any) => r.roleCode === code)
if (found) return found
}
return null
}
for (const roles of Object.values(allRoleGroups.value)) {
if (!Array.isArray(roles)) continue;
const found = roles.find((r: any) => r.roleCode === code);
if (found) return found;
}
return null;
};
const canClose = () => {
if (!radio.value) {
useMessage().warning('请选择一个角色')
return false
}
return true
}
if (!radio.value) {
useMessage().warning('请选择一个角色');
return false;
}
return true;
};
const handleBeforeClose = (done: () => void) => {
if (requireSelectToClose.value) {
useMessage().warning('请先选择登录角色')
return
}
if (!canClose()) return
done()
}
if (requireSelectToClose.value) {
useMessage().warning('请先选择登录角色');
return;
}
if (!canClose()) return;
done();
};
const handleFooterClose = () => {
if (!canClose()) return
visible.value = false
}
if (!canClose()) return;
visible.value = false;
};
const handleChangeRole = (label: string) => {
const obj = findRoleByCode(label)
if (!obj) return
Local.set('roleCode', obj.roleCode)
Local.set('roleName', obj.roleName)
Local.set('roleId', obj.roleId)
useMessage().success('操作成功')
// 清掉 tags 缓存,重载后只保留首页 tag
Session.remove('tagsViewList')
// 清除 pinia 持久化的 tagsView 路由,避免重载后先恢复旧角色菜单再被新路由覆盖前就初始化出“不存在的 tag”
try {
window.localStorage.removeItem('tagsViewRoutes')
} catch (_) {}
setTimeout(() => {
window.location.hash = '#/home'
window.location.reload()
}, 500)
}
const obj = findRoleByCode(label);
if (!obj) return;
Local.set('roleCode', obj.roleCode);
Local.set('roleName', obj.roleName);
Local.set('roleId', obj.roleId);
useMessage().success('操作成功');
// 清掉 tags 缓存,重载后只保留首页 tag
Session.remove('tagsViewList');
// 清除 pinia 持久化的 tagsView 路由,避免重载后先恢复旧角色菜单再被新路由覆盖前就初始化出“不存在的 tag”
try {
window.localStorage.removeItem('tagsViewRoutes');
} catch (_) {}
setTimeout(() => {
window.location.hash = '#/home';
window.location.reload();
}, 500);
};
defineExpose({
open
})
open,
});
</script>
<style scoped lang="scss">
.role-form-item {
:deep(.el-form-item__content) {
flex-wrap: wrap;
}
:deep(.el-form-item__content) {
flex-wrap: wrap;
}
}
.role-radio-group {
display: flex;
flex-direction: column;
gap: 6px;
width: 100%;
display: flex;
flex-direction: column;
gap: 6px;
width: 100%;
:deep(.el-radio-button) {
margin: 0;
}
:deep(.el-radio-button__inner) {
border-radius: 6px !important;
border: 1px solid var(--el-border-color) !important;
margin-left: 0 !important;
line-height: 1.3;
}
:deep(.el-radio-button.is-active .el-radio-button__inner) {
border-color: var(--el-color-primary) !important;
}
:deep(.el-radio-button) {
margin: 0;
}
:deep(.el-radio-button__inner) {
border-radius: 6px !important;
border: 1px solid var(--el-border-color) !important;
margin-left: 0 !important;
line-height: 1.3;
}
:deep(.el-radio-button.is-active .el-radio-button__inner) {
border-color: var(--el-color-primary) !important;
}
}
.role-group-card {
width: 100%;
flex: 0 0 auto;
width: 100%;
flex: 0 0 auto;
:deep(.el-card__header) {
padding: 6px 12px;
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
}
:deep(.el-card__body) {
padding: 6px 12px 8px;
}
:deep(.el-card__header) {
padding: 6px 12px;
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
}
:deep(.el-card__body) {
padding: 6px 12px 8px;
}
}
.role-group {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px 8px;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px 8px;
}
.group-name {
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
}
</style>
</style>

View File

@@ -47,7 +47,6 @@
style="width: 100%"
row-key="roleId"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
@selection-change="handleSelectionChange"
border
:cell-style="tableStyle.cellStyle"
@@ -67,7 +66,7 @@
</template>
</el-table-column>
<el-table-column prop="roleSort" label="排序" width="80" align="center">
<template #default="scope">{{ scope.row._isGroup ? '—' : (scope.row.roleSort ?? 0) }}</template>
<template #default="scope">{{ scope.row._isGroup ? '—' : scope.row.roleSort ?? 0 }}</template>
</el-table-column>
<el-table-column prop="roleCode" :label="$t('sysrole.roleCode')" show-overflow-tooltip min-width="120">
<template #default="scope">{{ scope.row._isGroup ? '—' : scope.row.roleCode }}</template>
@@ -133,9 +132,18 @@
title="批量指定关联用户"
width="720px"
destroy-on-close
@close="assignUserKeyword = ''; assignUserType = ''; assignUserList = []; assignUserTree = []; assignSelectedIds = []">
@close="
assignUserKeyword = '';
assignUserType = '';
assignUserList = [];
assignUserTree = [];
assignSelectedIds = [];
"
>
<template v-if="assignCurrentRole">
<div class="mb12"><el-text type="info">当前角色{{ assignCurrentRole.roleName }}{{ assignCurrentRole.roleCode }}</el-text></div>
<div class="mb12">
<el-text type="info">当前角色{{ assignCurrentRole.roleName }}{{ assignCurrentRole.roleCode }}</el-text>
</div>
<el-form :inline="true" class="mb12">
<el-form-item label="用户类型" required>
<el-radio-group v-model="assignUserType">
@@ -161,7 +169,8 @@
v-loading="assignUserLoading"
max-height="360"
border
@selection-change="handleAssignUserSelectionChange">
@selection-change="handleAssignUserSelectionChange"
>
<el-table-column type="selection" width="50" align="left" :selectable="(row: any) => !row._isDept" />
<el-table-column prop="label" label="部门 / 姓名" min-width="200">
<template #default="{ row }">
@@ -173,20 +182,24 @@
<template #default="{ row }">{{ row._isDept ? '—' : row.username }}</template>
</el-table-column>
<el-table-column prop="deptName" label="部门" width="140">
<template #default="{ row }">{{ row._isDept ? '—' : (row.deptName || '—') }}</template>
<template #default="{ row }">{{ row._isDept ? '—' : row.deptName || '—' }}</template>
</el-table-column>
</el-table>
</template>
<template #footer>
<el-button @click="showAssignUserDialog = false">取消</el-button>
<el-button type="primary" :loading="assignSubmitLoading" :disabled="assignSelectedIds.length === 0" @click="handleAssignUsersSubmit">确定已选 {{ assignSelectedIds.length }} </el-button>
<el-button type="primary" :loading="assignSubmitLoading" :disabled="assignSelectedIds.length === 0" @click="handleAssignUsersSubmit"
>确定已选 {{ assignSelectedIds.length }} </el-button
>
</template>
</el-dialog>
<!-- 查看角色关联用户弹窗 -->
<el-dialog v-model="showRoleUsersDialog" title="关联用户" width="560px" destroy-on-close>
<template v-if="currentRoleForUsers">
<div class="mb12"><el-text type="info">角色{{ currentRoleForUsers.roleName }}{{ currentRoleForUsers.roleCode }}</el-text></div>
<div class="mb12">
<el-text type="info">角色{{ currentRoleForUsers.roleName }}{{ currentRoleForUsers.roleCode }}</el-text>
</div>
<el-table :data="roleUsersList" v-loading="roleUsersLoading" max-height="400" border>
<el-table-column prop="deptName" label="部门" min-width="140" show-overflow-tooltip />
<el-table-column prop="realName" label="姓名" width="100" show-overflow-tooltip />
@@ -298,9 +311,7 @@ const state: BasicTableProps = reactive<BasicTableProps>({
const kw = String(params.roleName).trim().toLowerCase();
if (kw) {
data = data.filter(
(r: any) =>
(r.roleName && r.roleName.toLowerCase().includes(kw)) ||
(r.roleCode && r.roleCode.toLowerCase().includes(kw))
(r: any) => (r.roleName && r.roleName.toLowerCase().includes(kw)) || (r.roleCode && r.roleCode.toLowerCase().includes(kw))
);
}
}
@@ -353,10 +364,10 @@ const dictType = ref([
label: '本级',
value: '3',
},
{
label: '本人',
value: '4',
},
{
label: '本人',
value: '4',
},
]);
// table hook无分页不暴露 currentChangeHandle/sizeChangeHandle
@@ -370,7 +381,7 @@ const resetQuery = () => {
// 导出excel
const exportExcel = () => {
downBlobFile('/admin/role/export',Object.assign(state.queryForm,{ids:selectObjs}), 'role.xlsx');
downBlobFile('/admin/role/export', Object.assign(state.queryForm, { ids: selectObjs }), 'role.xlsx');
};
// 是否可以多选(分组行不可选,管理员角色不可选)
@@ -470,9 +481,7 @@ async function loadAssignUserList() {
}
function handleAssignUserSelectionChange(rows: any[]) {
assignSelectedIds.value = (rows || [])
.filter((r: any) => !r._isDept && r.userId)
.map((r: any) => r.userId);
assignSelectedIds.value = (rows || []).filter((r: any) => !r._isDept && r.userId).map((r: any) => r.userId);
}
async function handleAssignUsersSubmit() {
@@ -520,9 +529,21 @@ const handleBatchGroup = async () => {
font-weight: 600;
color: var(--el-text-color-primary);
}
.mb12 { margin-bottom: 12px; }
.mb8 { margin-bottom: 8px; }
.mt8 { margin-top: 8px; }
.assign-user-tip { color: var(--el-text-color-secondary); font-size: 13px; }
.dept-row { font-weight: 600; color: var(--el-text-color-primary); }
.mb12 {
margin-bottom: 12px;
}
.mb8 {
margin-bottom: 8px;
}
.mt8 {
margin-top: 8px;
}
.assign-user-tip {
color: var(--el-text-color-secondary);
font-size: 13px;
}
.dept-row {
font-weight: 600;
color: var(--el-text-color-primary);
}
</style>

View File

@@ -15,12 +15,24 @@
<el-col :span="12" class="mb20">
<el-form-item :label="t('tenant.startTime')" prop="startTime">
<el-date-picker class="!w-full" v-model="form.startTime" type="date" :placeholder="t('tenant.inputstartTimeTip')" :value-format="dateTimeStr" />
<el-date-picker
class="!w-full"
v-model="form.startTime"
type="date"
:placeholder="t('tenant.inputstartTimeTip')"
:value-format="dateTimeStr"
/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('tenant.endTime')" prop="endTime">
<el-date-picker class="!w-full" v-model="form.endTime" type="date" :placeholder="t('tenant.inputendTimeTip')" :value-format="dateTimeStr" />
<el-date-picker
class="!w-full"
v-model="form.endTime"
type="date"
:placeholder="t('tenant.inputendTimeTip')"
:value-format="dateTimeStr"
/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
@@ -71,11 +83,11 @@
import { validateTenantCode, validateTenantName } from '/@/api/admin/tenant';
import { useDict } from '/@/hooks/dict';
import { useMessage } from '/@/hooks/message';
import { getObj, addObj, putObj, treemenu,fetchList } from '/@/api/admin/tenant';
import { getObj, addObj, putObj, treemenu, fetchList } from '/@/api/admin/tenant';
import { useI18n } from 'vue-i18n';
import other from '/@/utils/other';
import { CheckboxValueType } from 'element-plus';
import {rule} from "/@/utils/validate";
import { rule } from '/@/utils/validate';
// 定义子组件向父组件传值/事件
const emit = defineEmits(['refresh']);
@@ -120,7 +132,7 @@ const checkedMenu = ref<any[]>([]);
// 定义校验规则
const dataRules = ref({
name: [
{ validator: rule.overLength, trigger: 'blur' },
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '名称不能为空', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
@@ -130,7 +142,7 @@ const dataRules = ref({
},
],
code: [
{ validator: rule.overLength, trigger: 'blur' },
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '编码不能为空', trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any) => {
@@ -183,7 +195,7 @@ const onSubmit = async () => {
}
if (menuTreeRef.value?.getCheckedKeys()) {
let checkMenu = [...menuTreeRef.value.getCheckedKeys(), ...menuTreeRef.value.getHalfCheckedKeys()]
let checkMenu = [...menuTreeRef.value.getCheckedKeys(), ...menuTreeRef.value.getHalfCheckedKeys()];
if (!checkMenu.includes('1300')) {
useMessage().error('必须分配角色管理功能');
@@ -207,7 +219,7 @@ const onSubmit = async () => {
} catch (err: any) {
useMessage().error(err.msg);
} finally {
await fetchList()
await fetchList();
loading.value = false;
}
};

View File

@@ -1,53 +1,53 @@
export default {
tenant: {
index: '#',
importTenantTip: '导入租户',
id: '租户id',
name: '租户名称',
code: '编码',
tenantDomain: '域名',
startTime: '开始时间',
endTime: '结束时间',
status: '状态',
delFlag: 'delFlag',
createBy: '创建人',
updateBy: '修改人',
createTime: '创建',
updateTime: '更新时间',
menuId: '租户套餐',
individuationBtn: '个性化',
inputidTip: '请输入租户id',
inputnameTip: '请输入名称',
inputcodeTip: '请输入编码',
inputtenantDomainTip: '请输入域名',
inputstartTimeTip: '请输入开始时间',
inputendTimeTip: '请输入结束时间',
inputstatusTip: '请输入status',
inputdelFlagTip: '请输入delFlag',
inputcreateByTip: '请输入创建人',
inputupdateByTip: '请输入修改人',
inputcreateTimeTip: '请输入创建',
inputupdateTimeTip: '请输入更新时间',
inputmenuIdTip: '请选择租户套餐',
deleteDisabledTip: '基础租户不允许删除',
},
tenantmenu: {
name: '套餐',
index: '#',
status: '状态',
createTime: '创建',
},
tenant: {
index: '#',
importTenantTip: '导入租户',
id: '租户id',
name: '租户名称',
code: '编码',
tenantDomain: '域名',
startTime: '开始时间',
endTime: '结束时间',
status: '状态',
delFlag: 'delFlag',
createBy: '创建人',
updateBy: '修改人',
createTime: '创建',
updateTime: '更新时间',
menuId: '租户套餐',
individuationBtn: '个性化',
inputidTip: '请输入租户id',
inputnameTip: '请输入名称',
inputcodeTip: '请输入编码',
inputtenantDomainTip: '请输入域名',
inputstartTimeTip: '请输入开始时间',
inputendTimeTip: '请输入结束时间',
inputstatusTip: '请输入status',
inputdelFlagTip: '请输入delFlag',
inputcreateByTip: '请输入创建人',
inputupdateByTip: '请输入修改人',
inputcreateTimeTip: '请输入创建',
inputupdateTimeTip: '请输入更新时间',
inputmenuIdTip: '请选择租户套餐',
deleteDisabledTip: '基础租户不允许删除',
},
tenantmenu: {
name: '套餐',
index: '#',
status: '状态',
createTime: '创建',
},
individuation: {
websiteName: '网站名称',
miniQr: '移动端二维码',
logo: '网站图标',
footerAuthor: '页脚信息',
background: '登录页背景图',
inputIndividuationNameTip: '请输入网站名称',
inputMiniQrTip: '请输入网站图标',
inputLogoTip: '请输入网站Logo',
inputFooterAuthorTip: '请输入页脚信息',
inputBackgroundTip: '请输入登录页背景图',
}
individuation: {
websiteName: '网站名称',
miniQr: '移动端二维码',
logo: '网站图标',
footerAuthor: '页脚信息',
background: '登录页背景图',
inputIndividuationNameTip: '请输入网站名称',
inputMiniQrTip: '请输入网站图标',
inputLogoTip: '请输入网站Logo',
inputFooterAuthorTip: '请输入页脚信息',
inputBackgroundTip: '请输入登录页背景图',
},
};

View File

@@ -1,60 +1,60 @@
<template>
<el-drawer :title="$t('tenant.individuationBtn')" v-model="visible" :close-on-click-modal="false">
<el-form ref="dataFormRef" :model="form" :rules="dataRules" v-loading="loading">
<el-row>
<el-col :span="24" class="mt-4">
<el-form-item :label="t('individuation.websiteName')" prop="websiteName" label-width="120px" align="left">
<el-input v-model="form.websiteName" :placeholder="t('individuation.inputIndividuationNameTip')"/>
</el-form-item>
</el-col>
<el-col :span="24" class="mt-4">
<el-form-item prop="footerAuthor" label-width="120px" align="left">
<template #label>
{{ t('individuation.footerAuthor') }}
<tip content="浏览器底部版权信息、备案信息"/>
</template>
<el-input v-model="form.footer" :placeholder="t('individuation.inputFooterAuthorTip')"/>
</el-form-item>
</el-col>
<el-col :span="24" class="mt-4">
<el-form-item prop="icon" label-width="120px" align="left">
<template #label>
{{ t('individuation.miniQr') }}
<tip content="登录页右下角显示的移动端二维码"/>
</template>
<upload-img v-model:image-url="form.miniQr"/>
</el-form-item>
</el-col>
<el-col :span="24" class="mt-4">
<el-form-item :label="t('individuation.background')" prop="background" label-width="120px" align="left">
<upload-img v-model:image-url="form.background"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-drawer :title="$t('tenant.individuationBtn')" v-model="visible" :close-on-click-modal="false">
<el-form ref="dataFormRef" :model="form" :rules="dataRules" v-loading="loading">
<el-row>
<el-col :span="24" class="mt-4">
<el-form-item :label="t('individuation.websiteName')" prop="websiteName" label-width="120px" align="left">
<el-input v-model="form.websiteName" :placeholder="t('individuation.inputIndividuationNameTip')" />
</el-form-item>
</el-col>
<el-col :span="24" class="mt-4">
<el-form-item prop="footerAuthor" label-width="120px" align="left">
<template #label>
{{ t('individuation.footerAuthor') }}
<tip content="浏览器底部版权信息、备案信息" />
</template>
<el-input v-model="form.footer" :placeholder="t('individuation.inputFooterAuthorTip')" />
</el-form-item>
</el-col>
<el-col :span="24" class="mt-4">
<el-form-item prop="icon" label-width="120px" align="left">
<template #label>
{{ t('individuation.miniQr') }}
<tip content="登录页右下角显示的移动端二维码" />
</template>
<upload-img v-model:image-url="form.miniQr" />
</el-form-item>
</el-col>
<el-col :span="24" class="mt-4">
<el-form-item :label="t('individuation.background')" prop="background" label-width="120px" align="left">
<upload-img v-model:image-url="form.background" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
</span>
</template>
</el-drawer>
</template>
</el-drawer>
</template>
<script setup lang="ts" name="systemTenantDialog">
import {useDict} from '/@/hooks/dict';
import {useMessage} from '/@/hooks/message';
import {getObj, putObj} from '/@/api/admin/tenant';
import {useI18n} from 'vue-i18n';
import UploadImg from "/@/components/Upload/Image.vue";
import {useThemeConfig} from "/@/stores/themeConfig";
import pinia from "/@/stores";
import {storeToRefs} from "pinia";
import Tip from "/@/components/Tip/index.vue";
import { useDict } from '/@/hooks/dict';
import { useMessage } from '/@/hooks/message';
import { getObj, putObj } from '/@/api/admin/tenant';
import { useI18n } from 'vue-i18n';
import UploadImg from '/@/components/Upload/Image.vue';
import { useThemeConfig } from '/@/stores/themeConfig';
import pinia from '/@/stores';
import { storeToRefs } from 'pinia';
import Tip from '/@/components/Tip/index.vue';
// 定义子组件向父组件传值/事件
const emit = defineEmits(['refresh']);
const {t} = useI18n();
const { t } = useI18n();
// 定义变量内容
const dataFormRef = ref();
@@ -62,42 +62,38 @@ const visible = ref(false);
const loading = ref(false);
// 字典
const {status_type} = useDict('status_type');
const { status_type } = useDict('status_type');
// 导入配置文件
const stores = useThemeConfig(pinia);
const {themeConfig} = storeToRefs(stores);
const { themeConfig } = storeToRefs(stores);
// 提交表单数据
const form = reactive({
id: '',
websiteName: themeConfig.value.globalTitle,
background: '',
miniQr: '',
footer: themeConfig.value.footerAuthor,
id: '',
websiteName: themeConfig.value.globalTitle,
background: '',
miniQr: '',
footer: themeConfig.value.footerAuthor,
});
// 定义校验规则
const dataRules = ref({
});
const dataRules = ref({});
// 打开弹窗
const openDialog = (id: string): void => {
visible.value = true;
form.id = ''
visible.value = true;
form.id = '';
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
});
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
});
if (id) {
form.id = id;
getTenantData(id);
}
if (id) {
form.id = id;
getTenantData(id);
}
};
/**
@@ -105,33 +101,30 @@ const openDialog = (id: string): void => {
* @param {string} id - 部门 ID。
*/
const getTenantData = async (id: any) => {
const res = await getObj(id);
Object.assign(form, res.data);
const res = await getObj(id);
Object.assign(form, res.data);
};
// 提交
const onSubmit = async () => {
const valid = await dataFormRef.value.validate().catch(() => {
});
if (!valid) return false;
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) return false;
try {
loading.value = true;
await putObj(form);
useMessage().success(t('common.editSuccessText'));
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
try {
loading.value = true;
await putObj(form);
useMessage().success(t('common.editSuccessText'));
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// 暴露变量
defineExpose({
openDialog,
openDialog,
});
</script>

View File

@@ -3,41 +3,41 @@
<el-dialog :close-on-click-modal="false" :title="dataForm.userId ? $t('common.editBtn') : $t('common.addBtn')" draggable v-model="visible">
<el-form :model="dataForm" :rules="dataRules" label-width="90px" ref="dataFormRef" v-loading="loading">
<el-row :gutter="20">
<el-col :span="12" class="mb20">
<el-form-item :label="$t('sysuser.username')" prop="username">
<el-input
:disabled="dataForm.userId !== ''"
:placeholder="$t('sysuser.inputUsernameTip')"
v-model="dataForm.username"
autocomplete="off"
:readonly="formState.usernameReadonly"
@focus="formState.usernameReadonly = false"
></el-input>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="$t('sysuser.password')" prop="password">
<el-input
clearable
:placeholder="$t('sysuser.inputPasswordTip')"
type="password"
v-model="dataForm.password"
autocomplete="new-password"
:readonly="formState.passwordReadonly"
@focus="formState.passwordReadonly = false"
></el-input>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="$t('sysuser.username')" prop="username">
<el-input
:disabled="dataForm.userId !== ''"
:placeholder="$t('sysuser.inputUsernameTip')"
v-model="dataForm.username"
autocomplete="off"
:readonly="formState.usernameReadonly"
@focus="formState.usernameReadonly = false"
></el-input>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="$t('sysuser.password')" prop="password">
<el-input
clearable
:placeholder="$t('sysuser.inputPasswordTip')"
type="password"
v-model="dataForm.password"
autocomplete="new-password"
:readonly="formState.passwordReadonly"
@focus="formState.passwordReadonly = false"
></el-input>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="$t('sysuser.name')" prop="realName">
<el-input clearable :placeholder="$t('sysuser.inputNameTip')" v-model="dataForm.realName"></el-input>
</el-form-item>
</el-col>
<!-- <el-col :span="12" class="mb20">-->
<!-- <el-form-item :label="$t('sysuser.phone')" prop="phone">-->
<!-- <el-input clearable :placeholder="$t('sysuser.inputPhoneTip')" v-model="dataForm.phone"></el-input>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<!-- <el-col :span="12" class="mb20">-->
<!-- <el-form-item :label="$t('sysuser.phone')" prop="phone">-->
<!-- <el-input clearable :placeholder="$t('sysuser.inputPhoneTip')" v-model="dataForm.phone"></el-input>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<el-col :span="12" class="mb20">
<el-form-item :label="$t('sysuser.role')" prop="role">
<el-select clearable multiple :placeholder="$t('sysuser.selectRole')" v-model="dataForm.role" filterable>
@@ -45,13 +45,13 @@
</el-select>
</el-form-item>
</el-col>
<!-- <el-col :span="12" class="mb20">-->
<!-- <el-form-item :label="$t('sysuser.post')" prop="post">-->
<!-- <el-select clearable multiple :placeholder="$t('sysuser.selectPost')" v-model="dataForm.post">-->
<!-- <el-option :key="item.postId" :label="item.postName" :value="item.postId" v-for="item in postData" />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<!-- <el-col :span="12" class="mb20">-->
<!-- <el-form-item :label="$t('sysuser.post')" prop="post">-->
<!-- <el-select clearable multiple :placeholder="$t('sysuser.selectPost')" v-model="dataForm.post">-->
<!-- <el-option :key="item.postId" :label="item.postName" :value="item.postId" v-for="item in postData" />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<el-col :span="12" class="mb20">
<el-form-item :label="$t('sysuser.dept')" prop="deptId">
<el-tree-select
@@ -67,11 +67,11 @@
</el-form-item>
</el-col>
<!-- <el-col :span="12" class="mb20">-->
<!-- <el-form-item :label="$t('sysuser.email')" prop="email">-->
<!-- <el-input clearable :placeholder="$t('sysuser.inputEmailTip')" v-model="dataForm.email"></el-input>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<!-- <el-col :span="12" class="mb20">-->
<!-- <el-form-item :label="$t('sysuser.email')" prop="email">-->
<!-- <el-input clearable :placeholder="$t('sysuser.inputEmailTip')" v-model="dataForm.email"></el-input>-->
<!-- </el-form-item>-->
<!-- </el-col>-->
<el-col :span="12" class="mb20">
<el-form-item :label="$t('sysuser.lockFlag')" prop="lockFlag">
<el-radio-group v-model="dataForm.lockFlag">
@@ -135,7 +135,7 @@ const dataForm = reactive({
deptId: '',
roleList: [],
postList: [],
realName: '',
realName: '',
email: '',
post: [] as string[],
role: [] as string[],
@@ -227,7 +227,7 @@ const onSubmit = async () => {
// 立即设置 loading防止重复点击
if (loading.value) return;
loading.value = true;
try {
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) {

View File

@@ -79,12 +79,12 @@
<el-table-column :label="$t('sysuser.index')" type="index" width="60" fixed="left" />
<el-table-column :label="$t('sysuser.username')" prop="username" fixed="left" show-overflow-tooltip></el-table-column>
<el-table-column :label="$t('sysuser.name')" prop="realName" show-overflow-tooltip></el-table-column>
<!-- <el-table-column :label="$t('sysuser.phone')" prop="phone" show-overflow-tooltip></el-table-column>-->
<!-- <el-table-column :label="$t('sysuser.post')" show-overflow-tooltip>-->
<!-- <template #default="scope">-->
<!-- <el-tag v-for="(item, index) in scope.row.postList" :key="index">{{ item.postName }}</el-tag>-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- <el-table-column :label="$t('sysuser.phone')" prop="phone" show-overflow-tooltip></el-table-column>-->
<!-- <el-table-column :label="$t('sysuser.post')" show-overflow-tooltip>-->
<!-- <template #default="scope">-->
<!-- <el-tag v-for="(item, index) in scope.row.postList" :key="index">{{ item.postName }}</el-tag>-->
<!-- </template>-->
<!-- </el-table-column>-->
<el-table-column :label="$t('sysuser.role')" show-overflow-tooltip>
<template #default="scope">
<el-tag v-for="(item, index) in scope.row.roleList" :key="index">{{ item.roleName }}</el-tag>

View File

@@ -2,12 +2,16 @@
<el-drawer v-model="visible" :title="$t('personal.name')" size="40%">
<el-tabs style="height: 200px" class="demo-tabs">
<el-tab-pane label="基本信息" v-loading="loading">
<template #label>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
基本信息
</template>
<template #label>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
/>
</svg>
基本信息
</template>
<el-form :model="formData" :rules="ruleForm" label-width="100px" class="mt30" ref="formdataRef">
<el-row :gutter="20">
<el-col :span="24" class="mb20">
@@ -55,26 +59,36 @@
</el-form>
</el-tab-pane>
<el-tab-pane label="安全信息">
<template #label>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z" />
</svg>
安全信息
</template>
<template #label>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z"
/>
</svg>
安全信息
</template>
<el-form :model="passwordFormData" :rules="passwordRuleForm" label-width="100px" class="mt30" ref="passwordFormdataRef">
<el-row :gutter="20">
<el-col :span="24" class="mb20">
<el-form-item label="原密码" prop="password">
<el-input v-model="passwordFormData.password" :type="showPassword ? 'text' : 'password'" placeholder="请输入密码" clearable type="password">
<template #suffix>
<i
class="iconfont el-input__icon login-content-password"
:class="showPassword ? 'icon-yincangmima' : 'icon-xianshimima'"
@click="showPassword = !showPassword"
>
</i>
</template>
</el-input>
<el-input
v-model="passwordFormData.password"
:type="showPassword ? 'text' : 'password'"
placeholder="请输入密码"
clearable
type="password"
>
<template #suffix>
<i
class="iconfont el-input__icon login-content-password"
:class="showPassword ? 'icon-yincangmima' : 'icon-xianshimima'"
@click="showPassword = !showPassword"
>
</i>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
@@ -103,13 +117,17 @@
</el-form>
</el-tab-pane>
<el-tab-pane label="第三方账号">
<template #label>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M7.864 4.243A7.5 7.5 0 0 1 19.5 10.5c0 2.92-.556 5.709-1.568 8.268M5.742 6.364A7.465 7.465 0 0 0 4.5 10.5a7.464 7.464 0 0 1-1.15 3.993m1.989 3.559A11.209 11.209 0 0 0 8.25 10.5a3.75 3.75 0 1 1 7.5 0c0 .527-.021 1.049-.064 1.565M12 10.5a14.94 14.94 0 0 1-3.6 9.75m6.633-4.596a18.666 18.666 0 0 1-2.485 5.33" />
</svg>
<template #label>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M7.864 4.243A7.5 7.5 0 0 1 19.5 10.5c0 2.92-.556 5.709-1.568 8.268M5.742 6.364A7.465 7.465 0 0 0 4.5 10.5a7.464 7.464 0 0 1-1.15 3.993m1.989 3.559A11.209 11.209 0 0 0 8.25 10.5a3.75 3.75 0 1 1 7.5 0c0 .527-.021 1.049-.064 1.565M12 10.5a14.94 14.94 0 0 1-3.6 9.75m6.633-4.596a18.666 18.666 0 0 1-2.485 5.33"
/>
</svg>
社交登录
</template>
社交登录
</template>
<el-table :data="socialList" class="mt10">
<el-table-column type="index" label="序号" width="80"></el-table-column>
<el-table-column prop="name" label="平台"></el-table-column>
@@ -132,14 +150,14 @@
</template>
<script setup lang="ts" name="personal">
import {useUserInfo} from '/@/stores/userInfo';
import {editInfo, getObj, password, unbindingUser} from '/@/api/admin/user';
import {useMessage} from '/@/hooks/message';
import {rule, validateNull} from '/@/utils/validate';
import { useUserInfo } from '/@/stores/userInfo';
import { editInfo, getObj, password, unbindingUser } from '/@/api/admin/user';
import { useMessage } from '/@/hooks/message';
import { rule, validateNull } from '/@/utils/validate';
import other from '/@/utils/other';
import {Session} from '/@/utils/storage';
import {useI18n} from 'vue-i18n';
import {getLoginAppList} from "/@/api/admin/social";
import { Session } from '/@/utils/storage';
import { useI18n } from 'vue-i18n';
import { getLoginAppList } from '/@/api/admin/social';
import { SocialLoginEnum } from '/@/api/login';
const { t } = useI18n();
@@ -177,9 +195,18 @@ const ruleForm = reactive({
{ required: true, message: '手机号不能为空', trigger: 'blur' },
{ validator: rule.validatePhone, trigger: 'blur' },
],
nickname: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '昵称不能为空', trigger: 'blur' }],
email: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '邮箱不能为空', trigger: 'blur' }],
name: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '姓名不能为空', trigger: 'blur' }],
nickname: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '昵称不能为空', trigger: 'blur' },
],
email: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '邮箱不能为空', trigger: 'blur' },
],
name: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '姓名不能为空', trigger: 'blur' },
],
});
const validatorPassword2 = (rule: any, value: any, callback: any) => {
if (value !== passwordFormData.newpassword1) {