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

View File

@@ -1,71 +1,72 @@
<template>
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible"
width="40%"
:close-on-click-modal="false" draggable>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
<el-form-item label="联系人" prop="contactName" class="mb20">
<el-input v-model="form.contactName" placeholder="请输入联系人"/>
</el-form-item>
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible" width="40%" :close-on-click-modal="false" draggable>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
<el-form-item label="联系人" prop="contactName" class="mb20">
<el-input v-model="form.contactName" placeholder="请输入联系人" />
</el-form-item>
<el-form-item label="手机号" prop="contactPhone" class="mb20">
<el-input v-model="form.contactPhone" placeholder="请输入手机号"/>
</el-form-item>
<el-form-item label="手机号" prop="contactPhone" class="mb20">
<el-input v-model="form.contactPhone" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="备注" prop="remark" class="mb20">
<el-input v-model="form.remark" type="textarea" rows="3" placeholder="请输入备注"/>
</el-form-item>
</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-form-item label="备注" prop="remark" class="mb20">
<el-input v-model="form.remark" type="textarea" rows="3" placeholder="请输入备注" />
</el-form-item>
</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="AppContactsDialog">
import { useDict } from '/@/hooks/dict';
import { useMessage } from "/@/hooks/message";
import { getObj, addObj, putObj } from '/@/api/app/appContacts'
import { useMessage } from '/@/hooks/message';
import { getObj, addObj, putObj } from '/@/api/app/appContacts';
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 form = reactive({
id:'',
contactName: '',
contactPhone: '',
remark: '',
id: '',
contactName: '',
contactPhone: '',
remark: '',
});
// 定义校验规则
const dataRules = ref({
contactName: [{required: true, message: '联系人不能为空', trigger: 'blur'}],
contactPhone: [{required: true, message: '手机号不能为空', trigger: 'blur'}, { validator: rule.mobilePhone, trigger: 'blur' }],
})
contactName: [{ required: true, message: '联系人不能为空', trigger: 'blur' }],
contactPhone: [
{ required: true, message: '手机号不能为空', trigger: 'blur' },
{ validator: rule.mobilePhone, trigger: 'blur' },
],
});
// 打开弹窗
const openDialog = (id: string) => {
visible.value = true
form.id = ''
visible.value = true;
form.id = '';
// 重置表单数据
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
});
// 获取appContacts信息
if (id) {
form.id = id
getappContactsData(id)
}
// 获取appContacts信息
if (id) {
form.id = id;
getappContactsData(id);
}
};
// 提交
@@ -74,7 +75,7 @@ const onSubmit = async () => {
if (!valid) return false;
try {
loading.value = true;
loading.value = true;
form.id ? await putObj(form) : await addObj(form);
useMessage().success(form.id ? '修改成功' : '添加成功');
visible.value = false;
@@ -82,24 +83,25 @@ const onSubmit = async () => {
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
loading.value = false;
}
};
// 初始化表单数据
const getappContactsData = (id: string) => {
// 获取数据
loading.value = true
getObj(id).then((res: any) => {
Object.assign(form, res.data)
}).finally(() => {
loading.value = false
})
// 获取数据
loading.value = true;
getObj(id)
.then((res: any) => {
Object.assign(form, res.data);
})
.finally(() => {
loading.value = false;
});
};
// 暴露变量
defineExpose({
openDialog
openDialog,
});
</script>
</script>

View File

@@ -1,67 +1,73 @@
<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="contactName" >
<el-input placeholder="请输入联系人" v-model="state.queryForm.contactName" />
</el-form-item>
<el-form-item label="手机号" prop="contactPhone" >
<el-input placeholder="请输入手机号" v-model="state.queryForm.contactPhone" />
</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="'app_appContacts_add'">
</el-button>
<el-button plain :disabled="multiple" icon="Delete" type="primary"
v-auth="'app_appContacts_del'" @click="handleDelete(selectObjs)">
删除
</el-button>
<right-toolbar v-model:showSearch="showSearch" :export="'app_appContacts_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"
<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="contactName">
<el-input placeholder="请输入联系人" v-model="state.queryForm.contactName" />
</el-form-item>
<el-form-item label="手机号" prop="contactPhone">
<el-input placeholder="请输入手机号" v-model="state.queryForm.contactPhone" />
</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="'app_appContacts_add'">
</el-button>
<el-button plain :disabled="multiple" icon="Delete" type="primary" v-auth="'app_appContacts_del'" @click="handleDelete(selectObjs)">
删除
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
:export="'app_appContacts_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="contactName" label="联系人" show-overflow-tooltip/>
<el-table-column prop="contactPhone" label="手机号" show-overflow-tooltip/>
<el-table-column prop="remark" label="备注" show-overflow-tooltip/>
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button icon="edit-pen" text type="primary" v-auth="'app_appContacts_edit'"
@click="formDialogRef.openDialog(scope.row.id)">编辑</el-button>
<el-button icon="delete" text type="primary" v-auth="'app_appContacts_del'" @click="handleDelete([scope.row.id])">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
@sort-change="sortChangeHandle"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column type="index" label="#" width="40" />
<el-table-column prop="contactName" label="联系人" show-overflow-tooltip />
<el-table-column prop="contactPhone" label="手机号" show-overflow-tooltip />
<el-table-column prop="remark" label="备注" show-overflow-tooltip />
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button icon="edit-pen" text type="primary" v-auth="'app_appContacts_edit'" @click="formDialogRef.openDialog(scope.row.id)"
>编辑</el-button
>
<el-button icon="delete" text type="primary" v-auth="'app_appContacts_del'" @click="handleDelete([scope.row.id])">删除</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="systemAppContacts">
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObjs } from "/@/api/app/appContacts";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { BasicTableProps, useTable } from '/@/hooks/table';
import { fetchList, delObjs } from '/@/api/app/appContacts';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { useDict } from '/@/hooks/dict';
// 引入组件
@@ -69,63 +75,56 @@ const FormDialog = defineAsyncComponent(() => import('./form.vue'));
// 定义查询字典
// 定义变量内容
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 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('/app/appContacts/export', Object.assign(state.queryForm, { ids: selectObjs }), 'appContacts.xlsx')
}
downBlobFile('/app/appContacts/export', Object.assign(state.queryForm, { ids: selectObjs }), 'appContacts.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('此操作将永久删除');
} 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);
}
};
</script>
</script>

View File

@@ -1,292 +1,273 @@
<template>
<el-dialog
title="班级概况"
v-model="visible"
:close-on-click-modal="false"
draggable
width="1400px">
<div v-loading="loading">
<!-- 基本信息 -->
<el-descriptions :column="3" border v-if="detailData" class="mb20">
<el-descriptions-item label="班">{{ detailData.classNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="班级名称">{{ detailData.className || '-' }}</el-descriptions-item>
<el-descriptions-item label="班级规范名称">{{ detailData.classProName || '-' }}</el-descriptions-item>
<el-descriptions-item label="学院">{{ detailData.deptName || '-' }}</el-descriptions-item>
<el-descriptions-item label="班主任">{{ detailData.teacherRealName || '-' }}</el-descriptions-item>
<el-descriptions-item label="班主任电话">{{ detailData.teacherTel || '-' }}</el-descriptions-item>
<el-descriptions-item label="入学年份">{{ detailData.grade || '-' }}</el-descriptions-item>
<el-descriptions-item label="入学时间">{{ detailData.enterDate || '-' }}</el-descriptions-item>
<el-descriptions-item label="班级状态">
<span>{{ detailData.classStatus === '0' ? '正常' : detailData.classStatus === '1' ? '离校' : '-' }}</span>
</el-descriptions-item>
<el-descriptions-item label="班级人数/原始人数">
{{ detailData.stuNum || 0 }}/{{ detailData.preStuNum || 0 }}
</el-descriptions-item>
<el-descriptions-item label="流失率">
<span>{{ detailData.stuLoseRate ? `${detailData.stuLoseRate}%` : '-' }}</span>
</el-descriptions-item>
<el-descriptions-item label="联院">
<span>{{ detailData.isUnion === 1 ? '是' : detailData.isUnion === 0 ? '否' : '-' }}</span>
</el-descriptions-item>
<el-descriptions-item label="门禁规则" :span="3">{{ detailData.ruleName || '-' }}</el-descriptions-item>
</el-descriptions>
<el-dialog title="班级概况" v-model="visible" :close-on-click-modal="false" draggable width="1400px">
<div v-loading="loading">
<!-- 基本信息 -->
<el-descriptions :column="3" border v-if="detailData" class="mb20">
<el-descriptions-item label="班号">{{ detailData.classNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="班级名称">{{ detailData.className || '-' }}</el-descriptions-item>
<el-descriptions-item label="班级规范名称">{{ detailData.classProName || '-' }}</el-descriptions-item>
<el-descriptions-item label="学院">{{ detailData.deptName || '-' }}</el-descriptions-item>
<el-descriptions-item label="班主任">{{ detailData.teacherRealName || '-' }}</el-descriptions-item>
<el-descriptions-item label="班主任电话">{{ detailData.teacherTel || '-' }}</el-descriptions-item>
<el-descriptions-item label="入学年份">{{ detailData.grade || '-' }}</el-descriptions-item>
<el-descriptions-item label="入学时间">{{ detailData.enterDate || '-' }}</el-descriptions-item>
<el-descriptions-item label="班级状态">
<span>{{ detailData.classStatus === '0' ? '正常' : detailData.classStatus === '1' ? '离校' : '-' }}</span>
</el-descriptions-item>
<el-descriptions-item label="班级人数/原始人数"> {{ detailData.stuNum || 0 }}/{{ detailData.preStuNum || 0 }} </el-descriptions-item>
<el-descriptions-item label="流失率">
<span>{{ detailData.stuLoseRate ? `${detailData.stuLoseRate}%` : '-' }}</span>
</el-descriptions-item>
<el-descriptions-item label="联院">
<span>{{ detailData.isUnion === 1 ? '是' : detailData.isUnion === 0 ? '否' : '-' }}</span>
</el-descriptions-item>
<el-descriptions-item label="门禁规则" :span="3">{{ detailData.ruleName || '-' }}</el-descriptions-item>
</el-descriptions>
<!-- 详细信息 - 使用 tabs 展示六个接口的数据 -->
<el-tabs v-model="activeTab" type="border-card">
<el-tab-pane label="班级荣誉" name="honor">
<el-table :data="honorList" border style="width: 100%" v-loading="honorLoading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="标题" show-overflow-tooltip />
<el-table-column prop="author" label="作者" width="120" />
<el-table-column prop="createTime" label="更新时间" width="120" />
<el-table-column prop="belong" label="归档级别" width="120" />
</el-table>
<div v-if="honorList.length === 0 && !honorLoading" style="text-align: center; padding: 20px; color: #909399;">
暂无数据
</div>
</el-tab-pane>
<!-- 详细信息 - 使用 tabs 展示六个接口的数据 -->
<el-tabs v-model="activeTab" type="border-card">
<el-tab-pane label="班级荣誉" name="honor">
<el-table :data="honorList" border style="width: 100%" v-loading="honorLoading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="标题" show-overflow-tooltip />
<el-table-column prop="author" label="作者" width="120" />
<el-table-column prop="createTime" label="更新时间" width="120" />
<el-table-column prop="belong" label="归档级别" width="120" />
</el-table>
<div v-if="honorList.length === 0 && !honorLoading" style="text-align: center; padding: 20px; color: #909399">暂无数据</div>
</el-tab-pane>
<el-tab-pane label="班级宣传" name="publicity">
<el-table :data="publicityList" border style="width: 100%" v-loading="publicityLoading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="标题" show-overflow-tooltip />
<el-table-column prop="author" label="作者" width="120" />
<el-table-column prop="createTime" label="更新时间" width="120" />
<el-table-column prop="belong" label="归档级别" width="120" />
<el-table-column prop="website" label="网址" show-overflow-tooltip />
</el-table>
<div v-if="publicityList.length === 0 && !publicityLoading" style="text-align: center; padding: 20px; color: #909399;">
暂无数据
</div>
</el-tab-pane>
<el-tab-pane label="班级宣传" name="publicity">
<el-table :data="publicityList" border style="width: 100%" v-loading="publicityLoading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="标题" show-overflow-tooltip />
<el-table-column prop="author" label="作者" width="120" />
<el-table-column prop="createTime" label="更新时间" width="120" />
<el-table-column prop="belong" label="归档级别" width="120" />
<el-table-column prop="website" label="网址" show-overflow-tooltip />
</el-table>
<div v-if="publicityList.length === 0 && !publicityLoading" style="text-align: center; padding: 20px; color: #909399">暂无数据</div>
</el-tab-pane>
<el-tab-pane label="教室分配" name="classroom">
<el-table :data="classroomList" border style="width: 100%" v-loading="classroomLoading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="buildingNo" label="教学楼号" show-overflow-tooltip />
<el-table-column prop="roomNo" label="教室号" show-overflow-tooltip />
<el-table-column prop="roomName" label="教室名称" show-overflow-tooltip />
</el-table>
<div v-if="classroomList.length === 0 && !classroomLoading" style="text-align: center; padding: 20px; color: #909399;">
暂无数据
</div>
</el-tab-pane>
<el-tab-pane label="教室分配" name="classroom">
<el-table :data="classroomList" border style="width: 100%" v-loading="classroomLoading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="buildingNo" label="教学楼号" show-overflow-tooltip />
<el-table-column prop="roomNo" label="教室号" show-overflow-tooltip />
<el-table-column prop="roomName" label="教室名称" show-overflow-tooltip />
</el-table>
<div v-if="classroomList.length === 0 && !classroomLoading" style="text-align: center; padding: 20px; color: #909399">暂无数据</div>
</el-tab-pane>
<el-tab-pane label="学生数量" name="studentNum">
<el-table :data="studentNumList" border style="width: 100%" v-loading="studentNumLoading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip />
<el-table-column prop="sex" label="性别" width="80" align="center">
<template #default="scope">
<span>{{ scope.row.sex === '1' ? '男' : scope.row.sex === '0' ? '女' : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="phone" label="电话" show-overflow-tooltip />
</el-table>
<div v-if="studentNumList.length === 0 && !studentNumLoading" style="text-align: center; padding: 20px; color: #909399;">
暂无数据
</div>
</el-tab-pane>
<el-tab-pane label="学生数量" name="studentNum">
<el-table :data="studentNumList" border style="width: 100%" v-loading="studentNumLoading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip />
<el-table-column prop="sex" label="性别" width="80" align="center">
<template #default="scope">
<span>{{ scope.row.sex === '1' ? '男' : scope.row.sex === '0' ? '女' : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="phone" label="电话" show-overflow-tooltip />
</el-table>
<div v-if="studentNumList.length === 0 && !studentNumLoading" style="text-align: center; padding: 20px; color: #909399">暂无数据</div>
</el-tab-pane>
<el-tab-pane label="宿舍学生" name="dormStudent">
<el-table :data="dormStudentList" border style="width: 100%" v-loading="dormStudentLoading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="buildingNo" label="楼号" show-overflow-tooltip />
<el-table-column prop="roomNo" label="宿舍号" show-overflow-tooltip />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip />
</el-table>
<div v-if="dormStudentList.length === 0 && !dormStudentLoading" style="text-align: center; padding: 20px; color: #909399;">
暂无数据
</div>
</el-tab-pane>
<el-tab-pane label="宿舍学生" name="dormStudent">
<el-table :data="dormStudentList" border style="width: 100%" v-loading="dormStudentLoading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="buildingNo" label="楼号" show-overflow-tooltip />
<el-table-column prop="roomNo" label="宿舍号" show-overflow-tooltip />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip />
</el-table>
<div v-if="dormStudentList.length === 0 && !dormStudentLoading" style="text-align: center; padding: 20px; color: #909399">暂无数据</div>
</el-tab-pane>
<el-tab-pane label="惩罚记录" name="punish">
<el-table :data="punishList" border style="width: 100%" v-loading="punishLoading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip />
<el-table-column prop="punishType" label="惩罚类型" show-overflow-tooltip />
<el-table-column prop="punishReason" label="惩罚原因" show-overflow-tooltip />
<el-table-column prop="createTime" label="惩罚时间" width="120" />
</el-table>
<div v-if="punishList.length === 0 && !punishLoading" style="text-align: center; padding: 20px; color: #909399;">
暂无数据
</div>
</el-tab-pane>
</el-tabs>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
</span>
</template>
</el-dialog>
<el-tab-pane label="惩罚记录" name="punish">
<el-table :data="punishList" border style="width: 100%" v-loading="punishLoading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip />
<el-table-column prop="realName" label="姓名" show-overflow-tooltip />
<el-table-column prop="punishType" label="惩罚类型" show-overflow-tooltip />
<el-table-column prop="punishReason" label="惩罚原因" show-overflow-tooltip />
<el-table-column prop="createTime" label="惩罚时间" width="120" />
</el-table>
<div v-if="punishList.length === 0 && !punishLoading" style="text-align: center; padding: 20px; color: #909399">暂无数据</div>
</el-tab-pane>
</el-tabs>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="BasicClassDetail">
import { ref } from 'vue'
import { useMessage } from "/@/hooks/message";
import { queryClassHonorByClassCode } from '/@/api/stuwork/classhonor'
import { queryDataByClassCode } from '/@/api/stuwork/classpublicity'
import { getClassRoomByClassCode } from '/@/api/stuwork/teachclassroomassign'
import { queryStuNumByClassCode } from '/@/api/basic/basicstudent'
import { fearchStuNumByClassCode } from '/@/api/stuwork/dormroomstudent'
import { queryPunlishNumByClass } from '/@/api/stuwork/stupunlish'
import { ref } from 'vue';
import { useMessage } from '/@/hooks/message';
import { queryClassHonorByClassCode } from '/@/api/stuwork/classhonor';
import { queryDataByClassCode } from '/@/api/stuwork/classpublicity';
import { getClassRoomByClassCode } from '/@/api/stuwork/teachclassroomassign';
import { queryStuNumByClassCode } from '/@/api/basic/basicstudent';
import { fearchStuNumByClassCode } from '/@/api/stuwork/dormroomstudent';
import { queryPunlishNumByClass } from '/@/api/stuwork/stupunlish';
// 定义变量内容
const visible = ref(false)
const loading = ref(false)
const detailData = ref<any>(null)
const activeTab = ref('honor')
const visible = ref(false);
const loading = ref(false);
const detailData = ref<any>(null);
const activeTab = ref('honor');
// 六个接口的数据
const honorList = ref<any[]>([])
const honorLoading = ref(false)
const publicityList = ref<any[]>([])
const publicityLoading = ref(false)
const classroomList = ref<any[]>([])
const classroomLoading = ref(false)
const studentNumList = ref<any[]>([])
const studentNumLoading = ref(false)
const dormStudentList = ref<any[]>([])
const dormStudentLoading = ref(false)
const punishList = ref<any[]>([])
const punishLoading = ref(false)
const honorList = ref<any[]>([]);
const honorLoading = ref(false);
const publicityList = ref<any[]>([]);
const publicityLoading = ref(false);
const classroomList = ref<any[]>([]);
const classroomLoading = ref(false);
const studentNumList = ref<any[]>([]);
const studentNumLoading = ref(false);
const dormStudentList = ref<any[]>([]);
const dormStudentLoading = ref(false);
const punishList = ref<any[]>([]);
const punishLoading = ref(false);
// 打开弹窗
const openDialog = async (rowData: any) => {
visible.value = true
detailData.value = rowData || null
// 如果有班级代码,加载六个接口的数据
if (rowData?.classCode) {
await loadAllData(rowData.classCode)
}
}
visible.value = true;
detailData.value = rowData || null;
// 如果有班级代码,加载六个接口的数据
if (rowData?.classCode) {
await loadAllData(rowData.classCode);
}
};
// 加载所有六个接口的数据
const loadAllData = async (classCode: string) => {
loading.value = true
// 并行加载所有接口数据
await Promise.all([
loadHonorData(classCode),
loadPublicityData(classCode),
loadClassroomData(classCode),
loadStudentNumData(classCode),
loadDormStudentData(classCode),
loadPunishData(classCode),
])
loading.value = false
}
loading.value = true;
// 并行加载所有接口数据
await Promise.all([
loadHonorData(classCode),
loadPublicityData(classCode),
loadClassroomData(classCode),
loadStudentNumData(classCode),
loadDormStudentData(classCode),
loadPunishData(classCode),
]);
loading.value = false;
};
// 加载班级荣誉数据
const loadHonorData = async (classCode: string) => {
honorLoading.value = true
try {
const res = await queryClassHonorByClassCode(classCode)
if (res.data) {
honorList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err: any) {
console.error('获取班级荣誉失败', err)
honorList.value = []
} finally {
honorLoading.value = false
}
}
honorLoading.value = true;
try {
const res = await queryClassHonorByClassCode(classCode);
if (res.data) {
honorList.value = Array.isArray(res.data) ? res.data : [];
}
} catch (err: any) {
console.error('获取班级荣誉失败', err);
honorList.value = [];
} finally {
honorLoading.value = false;
}
};
// 加载班级宣传数据
const loadPublicityData = async (classCode: string) => {
publicityLoading.value = true
try {
const res = await queryDataByClassCode(classCode)
if (res.data) {
publicityList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err: any) {
console.error('获取班级宣传失败', err)
publicityList.value = []
} finally {
publicityLoading.value = false
}
}
publicityLoading.value = true;
try {
const res = await queryDataByClassCode(classCode);
if (res.data) {
publicityList.value = Array.isArray(res.data) ? res.data : [];
}
} catch (err: any) {
console.error('获取班级宣传失败', err);
publicityList.value = [];
} finally {
publicityLoading.value = false;
}
};
// 加载教室分配数据
const loadClassroomData = async (classCode: string) => {
classroomLoading.value = true
try {
const res = await getClassRoomByClassCode(classCode)
if (res.data) {
classroomList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err: any) {
console.error('获取教室分配失败', err)
classroomList.value = []
} finally {
classroomLoading.value = false
}
}
classroomLoading.value = true;
try {
const res = await getClassRoomByClassCode(classCode);
if (res.data) {
classroomList.value = Array.isArray(res.data) ? res.data : [];
}
} catch (err: any) {
console.error('获取教室分配失败', err);
classroomList.value = [];
} finally {
classroomLoading.value = false;
}
};
// 加载学生数量数据
const loadStudentNumData = async (classCode: string) => {
studentNumLoading.value = true
try {
const res = await queryStuNumByClassCode({ classCode })
if (res.data) {
studentNumList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err: any) {
console.error('获取学生数量失败', err)
studentNumList.value = []
} finally {
studentNumLoading.value = false
}
}
studentNumLoading.value = true;
try {
const res = await queryStuNumByClassCode({ classCode });
if (res.data) {
studentNumList.value = Array.isArray(res.data) ? res.data : [];
}
} catch (err: any) {
console.error('获取学生数量失败', err);
studentNumList.value = [];
} finally {
studentNumLoading.value = false;
}
};
// 加载宿舍学生数据
const loadDormStudentData = async (classCode: string) => {
dormStudentLoading.value = true
try {
const res = await fearchStuNumByClassCode(classCode)
if (res.data) {
dormStudentList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err: any) {
console.error('获取宿舍学生失败', err)
dormStudentList.value = []
} finally {
dormStudentLoading.value = false
}
}
dormStudentLoading.value = true;
try {
const res = await fearchStuNumByClassCode(classCode);
if (res.data) {
dormStudentList.value = Array.isArray(res.data) ? res.data : [];
}
} catch (err: any) {
console.error('获取宿舍学生失败', err);
dormStudentList.value = [];
} finally {
dormStudentLoading.value = false;
}
};
// 加载惩罚记录数据
const loadPunishData = async (classCode: string) => {
punishLoading.value = true
try {
const res = await queryPunlishNumByClass(classCode)
if (res.data) {
punishList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err: any) {
console.error('获取惩罚记录失败', err)
punishList.value = []
} finally {
punishLoading.value = false
}
}
punishLoading.value = true;
try {
const res = await queryPunlishNumByClass(classCode);
if (res.data) {
punishList.value = Array.isArray(res.data) ? res.data : [];
}
} catch (err: any) {
console.error('获取惩罚记录失败', err);
punishList.value = [];
} finally {
punishLoading.value = false;
}
};
// 暴露方法给父组件
defineExpose({
openDialog
openDialog,
});
</script>
<style lang="scss" scoped>
.mb20 {
margin-bottom: 20px;
margin-bottom: 20px;
}
</style>

View File

@@ -1,449 +1,373 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
:close-on-click-modal="false"
draggable
width="800px">
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
:validate-on-rule-change="false"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="入学日期" prop="enterDate">
<el-date-picker
v-model="form.enterDate"
type="date"
placeholder="选择日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%" />
</el-form-item>
</el-col>
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible" :close-on-click-modal="false" draggable width="800px">
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="120px" :validate-on-rule-change="false" v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="入学日期" prop="enterDate">
<el-date-picker
v-model="form.enterDate"
type="date"
placeholder="选择日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="专业" prop="majorCode">
<el-select
v-model="form.majorCode"
placeholder="请选择"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in majorList"
:key="item.majorCode"
:label="item.majorName"
:value="item.majorCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="专业" prop="majorCode">
<el-select v-model="form.majorCode" placeholder="请选择" clearable filterable style="width: 100%">
<el-option v-for="item in majorList" :key="item.majorCode" :label="item.majorName" :value="item.majorCode"> </el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="form.deptCode"
placeholder="请选择"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学院" prop="deptCode">
<el-select v-model="form.deptCode" placeholder="请选择" clearable filterable style="width: 100%">
<el-option v-for="item in deptList" :key="item.deptCode" :label="item.deptName" :value="item.deptCode"> </el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="班级代码" prop="classCode">
<el-input
v-model="form.classCode"
placeholder="请输入班级代码"
clearable />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="班级代码" prop="classCode">
<el-input v-model="form.classCode" placeholder="请输入班级代码" clearable />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="班号" prop="classNo">
<el-input
v-model="form.classNo"
placeholder="自动填充班号"
:disabled="true" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="班号" prop="classNo">
<el-input v-model="form.classNo" placeholder="自动填充班号" :disabled="true" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="班级名称" prop="className">
<el-input
v-model="form.className"
placeholder="请输入班级名称"
clearable />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="班级名称" prop="className">
<el-input v-model="form.className" placeholder="请输入班级名称" clearable />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="班级规范名称" prop="classProName">
<el-input
v-model="form.classProName"
placeholder="自动拼接班级规范名称"
:disabled="true" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="班级规范名称" prop="classProName">
<el-input v-model="form.classProName" placeholder="自动拼接班级规范名称" :disabled="true" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="入学年份" prop="grade">
<el-input
v-model="form.grade"
placeholder="自动填充入学年份"
:disabled="true" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="入学年份" prop="grade">
<el-input v-model="form.grade" placeholder="自动填充入学年份" :disabled="true" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="班主任" prop="teacherNos">
<el-select
v-model="form.teacherNos"
placeholder="请选择班主任"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in teacherList"
:key="item.teacherNo"
:label="item.realName"
:value="item.teacherNo">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="班主任" prop="teacherNos">
<el-select v-model="form.teacherNos" placeholder="请选择班主任" clearable filterable style="width: 100%">
<el-option v-for="item in teacherList" :key="item.teacherNo" :label="item.realName" :value="item.teacherNo"> </el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="预计招生人数" prop="preStuNum">
<el-input-number
v-model="form.preStuNum"
:precision="0"
:step="1"
:min="0"
placeholder="请输入预计招生人数"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="预计招生人数" prop="preStuNum">
<el-input-number v-model="form.preStuNum" :precision="0" :step="1" :min="0" placeholder="请输入预计招生人数" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="备注" prop="remark">
<el-input
v-model="form.remark"
type="textarea"
:rows="4"
placeholder="请输入备注"
maxlength="500"
show-word-limit />
</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" type="textarea" :rows="4" placeholder="请输入备注" maxlength="500" show-word-limit />
</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="BasicClassDialog">
import { ref, reactive, nextTick, onMounted, watch, computed } from 'vue'
import { useMessage } from "/@/hooks/message";
import { getDetail, addObj, putObj, getMajorNameList, getDeptList } from '/@/api/basic/basicclass'
import { getTeacherBaseList } from '/@/api/professional/professionaluser/teacherbase'
import { ref, reactive, nextTick, onMounted, watch, computed } from 'vue';
import { useMessage } from '/@/hooks/message';
import { getDetail, addObj, putObj, getMajorNameList, getDeptList } from '/@/api/basic/basicclass';
import { getTeacherBaseList } from '/@/api/professional/professionaluser/teacherbase';
const emit = defineEmits(['refresh']);
// 定义变量内容
const dataFormRef = ref();
const visible = ref(false)
const loading = ref(false)
const majorList = ref<any[]>([])
const deptList = ref<any[]>([])
const teacherList = ref<any[]>([])
const yearLast = ref('') // 年份后两位
const majorName = ref('') // 专业名称
const visible = ref(false);
const loading = ref(false);
const majorList = ref<any[]>([]);
const deptList = ref<any[]>([]);
const teacherList = ref<any[]>([]);
const yearLast = ref(''); // 年份后两位
const majorName = ref(''); // 专业名称
// 提交表单数据
const form = reactive({
id: '',
enterDate: '',
majorCode: '',
deptCode: '',
classCode: '',
classNo: '',
className: '',
classProName: '',
grade: '',
teacherNos: '',
preStuNum: 0,
remark: ''
id: '',
enterDate: '',
majorCode: '',
deptCode: '',
classCode: '',
classNo: '',
className: '',
classProName: '',
grade: '',
teacherNos: '',
preStuNum: 0,
remark: '',
});
// 定义校验规则
const dataRules = ref({
enterDate: [
{ required: true, message: '入学日期不能为空', trigger: 'change' }
],
majorCode: [
{ required: true, message: '专业不能为空', trigger: 'change' }
],
deptCode: [
{ required: true, message: '学院不能为空', trigger: 'change' }
],
classCode: [
{ required: true, message: '班级代码不能为空', trigger: 'blur' },
{ min: 4, message: '班级代码至少4位班号取后4位', trigger: 'blur' }
],
classNo: [
{ required: true, message: '班号不能为空', trigger: 'blur' }
],
className: [
{ required: true, message: '班级名称不能为空', trigger: 'blur' }
],
classProName: [
{ required: true, message: '班级规范名称不能为空', trigger: 'blur' }
],
grade: [
{ required: true, message: '入学年份不能为空', trigger: 'blur' }
],
teacherNos: [
{ required: true, message: '请选择班主任', trigger: 'change' }
],
preStuNum: [
{ required: true, message: '预计招生人数不能为空', trigger: 'blur' }
]
})
enterDate: [{ required: true, message: '入学日期不能为空', trigger: 'change' }],
majorCode: [{ required: true, message: '专业不能为空', trigger: 'change' }],
deptCode: [{ required: true, message: '学院不能为空', trigger: 'change' }],
classCode: [
{ required: true, message: '班级代码不能为空', trigger: 'blur' },
{ min: 4, message: '班级代码至少4位班号取后4位', trigger: 'blur' },
],
classNo: [{ required: true, message: '班号不能为空', trigger: 'blur' }],
className: [{ required: true, message: '班级名称不能为空', trigger: 'blur' }],
classProName: [{ required: true, message: '班级规范名称不能为空', trigger: 'blur' }],
grade: [{ required: true, message: '入学年份不能为空', trigger: 'blur' }],
teacherNos: [{ required: true, message: '请选择班主任', trigger: 'change' }],
preStuNum: [{ required: true, message: '预计招生人数不能为空', trigger: 'blur' }],
});
// 打开弹窗
const openDialog = (id?: string) => {
visible.value = true
// 重置表单数据
Object.assign(form, {
id: '',
enterDate: '',
majorCode: '',
deptCode: '',
classCode: '',
classNo: '',
className: '',
classProName: '',
grade: '',
teacherNos: '',
preStuNum: 0,
remark: ''
})
// 重置辅助变量
yearLast.value = ''
majorName.value = ''
visible.value = true;
// 清除表单验证状态,不触发验证
nextTick(() => {
dataFormRef.value?.clearValidate();
dataFormRef.value?.resetFields();
});
// 重置表单数据
Object.assign(form, {
id: '',
enterDate: '',
majorCode: '',
deptCode: '',
classCode: '',
classNo: '',
className: '',
classProName: '',
grade: '',
teacherNos: '',
preStuNum: 0,
remark: '',
});
// 获取详情
if (id) {
form.id = id
getBasicClassData(id)
} else {
// 新增时,确保验证状态已清除
nextTick(() => {
dataFormRef.value?.clearValidate();
});
}
// 重置辅助变量
yearLast.value = '';
majorName.value = '';
// 清除表单验证状态,不触发验证
nextTick(() => {
dataFormRef.value?.clearValidate();
dataFormRef.value?.resetFields();
});
// 获取详情
if (id) {
form.id = id;
getBasicClassData(id);
} else {
// 新增时,确保验证状态已清除
nextTick(() => {
dataFormRef.value?.clearValidate();
});
}
};
// 提交
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;
// 构建提交数据,将班主任工号放到 teacherNo 字段
const submitData = {
...form,
teacherNo: form.teacherNos // 将 teacherNos 的值赋值给 teacherNo
};
if (form.id) {
await putObj(submitData);
useMessage().success('编辑成功');
} else {
await addObj(submitData);
useMessage().success('新增成功');
}
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || (form.id ? '编辑失败' : '新增失败'));
} finally {
loading.value = false;
}
try {
loading.value = true;
// 构建提交数据,将班主任工号放到 teacherNo 字段
const submitData = {
...form,
teacherNo: form.teacherNos, // 将 teacherNos 的值赋值给 teacherNo
};
if (form.id) {
await putObj(submitData);
useMessage().success('编辑成功');
} else {
await addObj(submitData);
useMessage().success('新增成功');
}
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || (form.id ? '编辑失败' : '新增失败'));
} finally {
loading.value = false;
}
};
// 获取详情
const getBasicClassData = async (id: string) => {
loading.value = true
try {
const res = await getDetail(id)
if (res.data) {
Object.assign(form, {
id: res.data.id || '',
enterDate: res.data.enterDate || '',
majorCode: res.data.majorCode || '',
deptCode: res.data.deptCode || '',
classCode: res.data.classCode || '',
classNo: res.data.classNo || '',
className: res.data.className || '',
classProName: res.data.classProName || '',
grade: res.data.grade || '',
teacherNos: res.data.teacherNos || '',
preStuNum: res.data.preStuNum || 0,
remark: res.data.remark || ''
})
// 设置辅助变量(编辑时不自动填充,保持原有数据)
if (res.data.enterDate && res.data.enterDate.length >= 4) {
yearLast.value = res.data.enterDate.substring(2, 4)
}
// 确保专业列表已加载后再设置专业名称
if (res.data.majorCode && majorList.value.length > 0) {
const major = majorList.value.find(item => item.majorCode === res.data.majorCode)
if (major) {
majorName.value = major.majorName || ''
}
}
}
} catch (err: any) {
console.error('获取详情失败', err)
useMessage().error('获取详情失败')
} finally {
loading.value = false
// 数据加载完成后,清除验证状态,避免触发验证
nextTick(() => {
dataFormRef.value?.clearValidate();
});
}
}
loading.value = true;
try {
const res = await getDetail(id);
if (res.data) {
Object.assign(form, {
id: res.data.id || '',
enterDate: res.data.enterDate || '',
majorCode: res.data.majorCode || '',
deptCode: res.data.deptCode || '',
classCode: res.data.classCode || '',
classNo: res.data.classNo || '',
className: res.data.className || '',
classProName: res.data.classProName || '',
grade: res.data.grade || '',
teacherNos: res.data.teacherNos || '',
preStuNum: res.data.preStuNum || 0,
remark: res.data.remark || '',
});
// 设置辅助变量(编辑时不自动填充,保持原有数据)
if (res.data.enterDate && res.data.enterDate.length >= 4) {
yearLast.value = res.data.enterDate.substring(2, 4);
}
// 确保专业列表已加载后再设置专业名称
if (res.data.majorCode && majorList.value.length > 0) {
const major = majorList.value.find((item) => item.majorCode === res.data.majorCode);
if (major) {
majorName.value = major.majorName || '';
}
}
}
} catch (err: any) {
console.error('获取详情失败', err);
useMessage().error('获取详情失败');
} finally {
loading.value = false;
// 数据加载完成后,清除验证状态,避免触发验证
nextTick(() => {
dataFormRef.value?.clearValidate();
});
}
};
// 获取专业列表
const getMajorListData = async () => {
try {
const res = await getMajorNameList()
if (res.data) {
majorList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取专业列表失败', err)
majorList.value = []
}
}
try {
const res = await getMajorNameList();
if (res.data) {
majorList.value = Array.isArray(res.data) ? res.data : [];
}
} catch (err) {
console.error('获取专业列表失败', err);
majorList.value = [];
}
};
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptList()
if (res.data) {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
try {
const res = await getDeptList();
if (res.data) {
deptList.value = Array.isArray(res.data) ? res.data : [];
}
} catch (err) {
console.error('获取学院列表失败', err);
deptList.value = [];
}
};
// 获取班主任列表
const getTeacherListData = async () => {
try {
const res = await getTeacherBaseList()
if (res.data) {
teacherList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班主任列表失败', err)
teacherList.value = []
}
}
try {
const res = await getTeacherBaseList();
if (res.data) {
teacherList.value = Array.isArray(res.data) ? res.data : [];
}
} catch (err) {
console.error('获取班主任列表失败', err);
teacherList.value = [];
}
};
// 拼接班级规范名称
const montageClassProName = () => {
// 只在新增模式下自动填充,编辑模式下不覆盖已有数据
if (!form.id && yearLast.value && majorName.value) {
form.classProName = yearLast.value + majorName.value
if (form.classNo) {
form.classProName = yearLast.value + majorName.value + '(' + form.classNo + ')'
}
console.log('classProName', form.classProName)
}
}
// 只在新增模式下自动填充,编辑模式下不覆盖已有数据
if (!form.id && yearLast.value && majorName.value) {
form.classProName = yearLast.value + majorName.value;
if (form.classNo) {
form.classProName = yearLast.value + majorName.value + '(' + form.classNo + ')';
}
console.log('classProName', form.classProName);
}
};
// 监听入学日期,取年份后两位
watch(() => form.enterDate, (newVal) => {
if (newVal && newVal.length >= 4) {
console.log('newdate:', newVal)
yearLast.value = newVal.substring(2, 4)
// 只在新增模式下自动填充入学年份
if (!form.id) {
form.grade = newVal.substring(0, 4)
}
montageClassProName()
}
})
watch(
() => form.enterDate,
(newVal) => {
if (newVal && newVal.length >= 4) {
console.log('newdate:', newVal);
yearLast.value = newVal.substring(2, 4);
// 只在新增模式下自动填充入学年份
if (!form.id) {
form.grade = newVal.substring(0, 4);
}
montageClassProName();
}
}
);
// 监听班级代码,取后四位为班号班级代码至少4位
watch(() => form.classCode, (newVal) => {
if (newVal) {
const length = newVal.length
if (length >= 4) {
// 只在新增模式下自动填充班号,编辑模式下如果班号为空才填充
if (!form.id || !form.classNo) {
form.classNo = newVal.substring(length - 4, length)
montageClassProName()
}
}
}
})
watch(
() => form.classCode,
(newVal) => {
if (newVal) {
const length = newVal.length;
if (length >= 4) {
// 只在新增模式下自动填充班号,编辑模式下如果班号为空才填充
if (!form.id || !form.classNo) {
form.classNo = newVal.substring(length - 4, length);
montageClassProName();
}
}
}
}
);
// 监听专业代码变化,获取专业名称
watch(() => form.majorCode, (newVal) => {
if (newVal) {
const major = majorList.value.find(item => item.majorCode === newVal)
if (major) {
majorName.value = major.majorName || ''
montageClassProName()
}
} else {
majorName.value = ''
montageClassProName()
}
})
watch(
() => form.majorCode,
(newVal) => {
if (newVal) {
const major = majorList.value.find((item) => item.majorCode === newVal);
if (major) {
majorName.value = major.majorName || '';
montageClassProName();
}
} else {
majorName.value = '';
montageClassProName();
}
}
);
// 初始化
onMounted(() => {
getMajorListData()
getDeptListData()
getTeacherListData()
})
getMajorListData();
getDeptListData();
getTeacherListData();
});
// 暴露变量
defineExpose({
openDialog
openDialog,
});
</script>

View File

@@ -1,532 +1,438 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<!-- 搜索表单卡片 -->
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch" class="search-form">
<el-form-item label="学院" prop="deptCode">
<el-select
v-model="searchForm.deptCode"
placeholder="请选择学院"
clearable
filterable
style="width: 200px"
@change="handleDeptChange">
<el-option
v-for="item in deptList"
:key="item.deptCode"
:label="item.deptName"
:value="item.deptCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="班号" prop="classNo">
<el-input
v-model="searchForm.classNo"
placeholder="请输入班号"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="联院" prop="isUnion">
<el-select
v-model="searchForm.isUnion"
placeholder="请选择联院"
clearable
style="width: 200px">
<el-option label="是" value="1" />
<el-option label="否" value="0" />
</el-select>
</el-form-item>
<el-form-item label="流失率" prop="stuLoseRate">
<el-input
v-model="searchForm.stuLoseRate"
placeholder="请输入流失率"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="入学年份" prop="grade">
<el-input
v-model="searchForm.grade"
placeholder="请输入入学年份"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="班主任" prop="teacherRealName">
<el-input
v-model="searchForm.teacherRealName"
placeholder="请输入班主任姓名"
clearable
style="width: 200px" />
</el-form-item>
<el-form-item label="班级状态" prop="classStatus">
<el-select
v-model="searchForm.classStatus"
placeholder="请选择班级状态"
clearable
style="width: 200px">
<el-option label="正常" value="0" />
<el-option label="离校" value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="modern-page-container">
<div class="page-wrapper">
<!-- 搜索表单卡片 -->
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch" class="search-form">
<el-form-item label="学院" prop="deptCode">
<el-select v-model="searchForm.deptCode" placeholder="请选择学院" clearable filterable style="width: 200px" @change="handleDeptChange">
<el-option v-for="item in deptList" :key="item.deptCode" :label="item.deptName" :value="item.deptCode"> </el-option>
</el-select>
</el-form-item>
<el-form-item label="班号" prop="classNo">
<el-input v-model="searchForm.classNo" placeholder="请输入班号" clearable style="width: 200px" />
</el-form-item>
<el-form-item label="联院" prop="isUnion">
<el-select v-model="searchForm.isUnion" placeholder="请选择联院" clearable style="width: 200px">
<el-option label="是" value="1" />
<el-option label="否" value="0" />
</el-select>
</el-form-item>
<el-form-item label="流失率" prop="stuLoseRate">
<el-input v-model="searchForm.stuLoseRate" placeholder="请输入流失率" clearable style="width: 200px" />
</el-form-item>
<el-form-item label="入学年份" prop="grade">
<el-input v-model="searchForm.grade" placeholder="请输入入学年份" clearable style="width: 200px" />
</el-form-item>
<el-form-item label="班主任" prop="teacherRealName">
<el-input v-model="searchForm.teacherRealName" placeholder="请输入班主任姓名" clearable style="width: 200px" />
</el-form-item>
<el-form-item label="班级状态" prop="classStatus">
<el-select v-model="searchForm.classStatus" placeholder="请选择班级状态" clearable style="width: 200px">
<el-option label="正常" value="0" />
<el-option label="离校" value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 内容卡片 -->
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
班级列表
</span>
<div class="header-actions">
<el-button
icon="FolderAdd"
type="primary"
@click="formDialogRef.openDialog()">
新增
</el-button>
<el-button
icon="Link"
type="success"
class="ml10"
@click="handleLinkRule">
关联门禁规则
</el-button>
<el-button
icon="Download"
type="warning"
class="ml10"
@click="handleExport">
导出
</el-button>
<el-button
icon="DocumentAdd"
type="info"
class="ml10"
@click="handleGenerateAssessment">
生成考核班级
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</div>
</template>
<!-- 内容卡片 -->
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
班级列表
</span>
<div class="header-actions">
<el-button icon="FolderAdd" type="primary" @click="formDialogRef.openDialog()"> 新增 </el-button>
<el-button icon="Link" type="success" class="ml10" @click="handleLinkRule"> 关联门禁规则 </el-button>
<el-button icon="Download" type="warning" class="ml10" @click="handleExport"> 导出 </el-button>
<el-button icon="DocumentAdd" type="info" class="ml10" @click="handleGenerateAssessment"> 生成考核班级 </el-button>
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</div>
</template>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table"
row-key="id"
@selection-change="handleSelectionChange"
@sort-change="sortChangeHandle">
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="70" align="center">
<template #header>
<el-icon><List /></el-icon>
</template>
<template #default="{ $index }">
{{ $index + 1 + ((state.pagination?.current || 1) - 1) * (state.pagination?.size || 10) }}
</template>
</el-table-column>
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align">
<template #header>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
<template #default="scope" v-if="col.prop === 'stuNum'">
<el-tag size="small" type="primary" effect="plain">
{{ scope.row.stuNum || 0 }}/{{ scope.row.preStuNum || 0 }}
</el-tag>
</template>
<template #default="scope" v-else-if="col.prop === 'classStatus'">
<StatusTag
:value="scope.row.classStatus"
:options="[{ label: '正常', value: '0' }, { label: '离校', value: '1' }]"
:type-map="{ '0': { type: 'success', effect: 'light' }, '1': { type: 'warning', effect: 'light' } }"
/>
</template>
<template #default="scope" v-else-if="col.prop === 'stuLoseRate'">
<el-tag size="small" type="danger" effect="plain">
{{ scope.row.stuLoseRate ? `${scope.row.stuLoseRate}%` : '0%' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" width="260">
<template #default="scope">
<el-button
icon="View"
link
type="primary"
@click="handleViewDetail(scope.row)">
班级概况
</el-button>
<el-button
icon="Edit"
link
type="primary"
@click="formDialogRef.openDialog(scope.row.id)">
编辑
</el-button>
<el-button
icon="Delete"
link
type="danger"
@click="handleDelete([scope.row.id])">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table"
row-key="id"
@selection-change="handleSelectionChange"
@sort-change="sortChangeHandle"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="70" align="center">
<template #header>
<el-icon><List /></el-icon>
</template>
<template #default="{ $index }">
{{ $index + 1 + ((state.pagination?.current || 1) - 1) * (state.pagination?.size || 10) }}
</template>
</el-table-column>
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align"
>
<template #header>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
<template #default="scope" v-if="col.prop === 'stuNum'">
<el-tag size="small" type="primary" effect="plain"> {{ scope.row.stuNum || 0 }}/{{ scope.row.preStuNum || 0 }} </el-tag>
</template>
<template #default="scope" v-else-if="col.prop === 'classStatus'">
<StatusTag
:value="scope.row.classStatus"
:options="[
{ label: '正常', value: '0' },
{ label: '离校', value: '1' },
]"
:type-map="{ '0': { type: 'success', effect: 'light' }, '1': { type: 'warning', effect: 'light' } }"
/>
</template>
<template #default="scope" v-else-if="col.prop === 'stuLoseRate'">
<el-tag size="small" type="danger" effect="plain">
{{ scope.row.stuLoseRate ? `${scope.row.stuLoseRate}%` : '0%' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" width="260">
<template #default="scope">
<el-button icon="View" link type="primary" @click="handleViewDetail(scope.row)"> 班级概况 </el-button>
<el-button icon="Edit" link type="primary" @click="formDialogRef.openDialog(scope.row.id)"> 编辑 </el-button>
<el-button icon="Delete" link type="danger" @click="handleDelete([scope.row.id])"> 删除 </el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-wrapper">
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
</el-card>
</div>
<!-- 分页 -->
<div class="pagination-wrapper">
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
</el-card>
</div>
<!-- 编辑新增 -->
<FormDialog ref="formDialogRef" @refresh="getDataList(false)" />
<!-- 班级概况详情 -->
<DetailDialog ref="detailDialogRef" />
<!-- 关联门禁规则对话框 -->
<el-dialog
title="关联门禁规则"
v-model="linkRuleDialogVisible"
:close-on-click-modal="false"
draggable
width="600px">
<el-form :model="linkRuleForm" label-width="120px">
<el-form-item label="选择规则">
<el-select
v-model="linkRuleForm.ruleId"
placeholder="请选择门禁规则"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in ruleList"
:key="item.id"
:label="item.ruleName"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="linkRuleDialogVisible = false"> </el-button>
<el-button type="primary" @click="confirmLinkRule" :loading="linkRuleLoading"> </el-button>
</span>
</template>
</el-dialog>
</div>
<!-- 编辑新增 -->
<FormDialog ref="formDialogRef" @refresh="getDataList(false)" />
<!-- 班级概况详情 -->
<DetailDialog ref="detailDialogRef" />
<!-- 关联门禁规则对话框 -->
<el-dialog title="关联门禁规则" v-model="linkRuleDialogVisible" :close-on-click-modal="false" draggable width="600px">
<el-form :model="linkRuleForm" label-width="120px">
<el-form-item label="选择规则">
<el-select v-model="linkRuleForm.ruleId" placeholder="请选择门禁规则" clearable filterable style="width: 100%">
<el-option v-for="item in ruleList" :key="item.id" :label="item.ruleName" :value="item.id"> </el-option>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="linkRuleDialogVisible = false"> </el-button>
<el-button type="primary" @click="confirmLinkRule" :loading="linkRuleLoading"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="BasicClass">
import { ref, reactive, defineAsyncComponent, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList, delObj, putObjs, classExportData, getDeptList, getClassListByRole } from "/@/api/basic/basicclass";
import { makeExportClassOverviewTask } from "/@/api/stuwork/file";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { fetchList as getRuleList } from "/@/api/stuwork/entrancerule";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { List, OfficeBuilding, Grid, Document, UserFilled, Phone, User, Lock, CircleCheck, TrendCharts, Setting, Menu, Search } from '@element-plus/icons-vue'
import { useTableColumnControl } from '/@/hooks/tableColumn'
import { defineAsyncComponent as defineStatusTag } from 'vue'
const StatusTag = defineStatusTag(() => import('/@/components/StatusTag/index.vue'))
import { ref, reactive, defineAsyncComponent, computed, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { BasicTableProps, useTable } from '/@/hooks/table';
import { fetchList, delObj, putObjs, classExportData, getDeptList, getClassListByRole } from '/@/api/basic/basicclass';
import { makeExportClassOverviewTask } from '/@/api/stuwork/file';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { fetchList as getRuleList } from '/@/api/stuwork/entrancerule';
import TableColumnControl from '/@/components/TableColumnControl/index.vue';
import {
List,
OfficeBuilding,
Grid,
Document,
UserFilled,
Phone,
User,
Lock,
CircleCheck,
TrendCharts,
Setting,
Menu,
Search,
} from '@element-plus/icons-vue';
import { useTableColumnControl } from '/@/hooks/tableColumn';
import { defineAsyncComponent as defineStatusTag } from 'vue';
const StatusTag = defineStatusTag(() => import('/@/components/StatusTag/index.vue'));
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const DetailDialog = defineAsyncComponent(() => import('./detail.vue'));
// 定义变量内容
const route = useRoute()
const formDialogRef = ref()
const detailDialogRef = ref()
const searchFormRef = ref()
const columnControlRef = ref()
const route = useRoute();
const formDialogRef = ref();
const detailDialogRef = ref();
const searchFormRef = ref();
const columnControlRef = ref();
// 搜索变量
const showSearch = ref(true)
const deptList = ref<any[]>([])
const classList = ref<any[]>([])
const ruleList = ref<any[]>([])
const linkRuleDialogVisible = ref(false)
const linkRuleLoading = ref(false)
const selectedClassCodes = ref<string[]>([])
const showSearch = ref(true);
const deptList = ref<any[]>([]);
const classList = ref<any[]>([]);
const ruleList = ref<any[]>([]);
const linkRuleDialogVisible = ref(false);
const linkRuleLoading = ref(false);
const selectedClassCodes = ref<string[]>([]);
// 表格列配置
const tableColumns = [
{ prop: 'deptName', label: '学院', icon: OfficeBuilding },
{ prop: 'classNo', label: '班号', icon: Grid },
{ prop: 'classProName', label: '班级规范名称', icon: Document },
{ prop: 'teacherRealName', label: '班主任', icon: UserFilled },
{ prop: 'teacherTel', label: '班主任电话号码', icon: Phone },
{ prop: 'stuNum', label: '班级人数/原始人数', icon: User },
{ prop: 'ruleName', label: '门禁规则', icon: Lock },
{ prop: 'classStatus', label: '班级状态', icon: CircleCheck },
{ prop: 'stuLoseRate', label: '流失率', icon: TrendCharts }
]
{ prop: 'deptName', label: '学院', icon: OfficeBuilding },
{ prop: 'classNo', label: '班号', icon: Grid },
{ prop: 'classProName', label: '班级规范名称', icon: Document },
{ prop: 'teacherRealName', label: '班主任', icon: UserFilled },
{ prop: 'teacherTel', label: '班主任电话号码', icon: Phone },
{ prop: 'stuNum', label: '班级人数/原始人数', icon: User },
{ prop: 'ruleName', label: '门禁规则', icon: Lock },
{ prop: 'classStatus', label: '班级状态', icon: CircleCheck },
{ prop: 'stuLoseRate', label: '流失率', icon: TrendCharts },
];
// 使用表格列控制hook
const {
visibleColumns,
visibleColumnsSorted,
checkColumnVisible,
handleColumnChange,
handleColumnOrderChange
} = useTableColumnControl(tableColumns)
const { visibleColumns, visibleColumnsSorted, checkColumnVisible, handleColumnChange, handleColumnOrderChange } = useTableColumnControl(tableColumns);
// 搜索表单
const searchForm = reactive({
deptCode: '',
classNo: '',
isUnion: '',
stuLoseRate: '',
grade: '',
teacherRealName: '',
classStatus: ''
})
deptCode: '',
classNo: '',
isUnion: '',
stuLoseRate: '',
grade: '',
teacherRealName: '',
classStatus: '',
});
// 关联门禁规则表单
const linkRuleForm = reactive({
ruleId: ''
})
ruleId: '',
});
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: searchForm,
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
queryForm: searchForm,
pageList: fetchList,
props: {
item: 'records',
totalCount: 'total',
},
createdIsNeed: true,
});
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
tableStyle
} = useTable(state)
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle } = useTable(state);
// 表格多选
const handleSelectionChange = (rows: any[]) => {
selectedClassCodes.value = (rows || [])
.map((item) => item.classCode)
.filter((item) => !!item)
}
selectedClassCodes.value = (rows || []).map((item) => item.classCode).filter((item) => !!item);
};
// 学院选择变化
const handleDeptChange = () => {
// 可以根据需要清空班号选择
}
// 可以根据需要清空班号选择
};
// 查询
const handleSearch = () => {
getDataList()
}
getDataList();
};
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
searchForm.deptCode = ''
searchForm.classNo = ''
searchForm.isUnion = ''
searchForm.stuLoseRate = ''
searchForm.grade = ''
searchForm.teacherRealName = ''
searchForm.classStatus = ''
getDataList()
}
searchFormRef.value?.resetFields();
searchForm.deptCode = '';
searchForm.classNo = '';
searchForm.isUnion = '';
searchForm.stuLoseRate = '';
searchForm.grade = '';
searchForm.teacherRealName = '';
searchForm.classStatus = '';
getDataList();
};
// 删除
const handleDelete = async (ids: string[]) => {
try {
await useMessageBox().confirm('确定要删除选中的记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
} catch {
return
}
try {
await useMessageBox().confirm('确定要删除选中的记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
} catch {
return;
}
try {
// 根据API文件delObj接受单个id需要循环删除
for (const id of ids) {
await delObj(id)
}
useMessage().success('删除成功')
getDataList()
} catch (err: any) {
useMessage().error(err.msg || '删除失败')
}
}
try {
// 根据API文件delObj接受单个id需要循环删除
for (const id of ids) {
await delObj(id);
}
useMessage().success('删除成功');
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '删除失败');
}
};
// 查看班级概况 - 直接使用表格行数据
const handleViewDetail = (row: any) => {
if (detailDialogRef.value) {
detailDialogRef.value.openDialog(row)
}
}
if (detailDialogRef.value) {
detailDialogRef.value.openDialog(row);
}
};
// 关联门禁规则
const handleLinkRule = () => {
if (!selectedClassCodes.value.length) {
useMessage().warning('请先勾选要关联的班级')
return
}
linkRuleDialogVisible.value = true
linkRuleForm.ruleId = ''
// 加载规则列表
getRuleListData()
}
if (!selectedClassCodes.value.length) {
useMessage().warning('请先勾选要关联的班级');
return;
}
linkRuleDialogVisible.value = true;
linkRuleForm.ruleId = '';
// 加载规则列表
getRuleListData();
};
// 确认关联门禁规则
const confirmLinkRule = async () => {
if (!selectedClassCodes.value.length) {
useMessage().warning('请先勾选要关联的班级')
return
}
if (!linkRuleForm.ruleId) {
useMessage().warning('请选择门禁规则')
return
}
if (!selectedClassCodes.value.length) {
useMessage().warning('请先勾选要关联的班级');
return;
}
if (!linkRuleForm.ruleId) {
useMessage().warning('请选择门禁规则');
return;
}
try {
linkRuleLoading.value = true
await putObjs({
ruleId: linkRuleForm.ruleId,
classCodes: selectedClassCodes.value
})
useMessage().success('关联成功')
linkRuleDialogVisible.value = false
getDataList()
} catch (err: any) {
useMessage().error(err.msg || '关联失败')
} finally {
linkRuleLoading.value = false
}
}
try {
linkRuleLoading.value = true;
await putObjs({
ruleId: linkRuleForm.ruleId,
classCodes: selectedClassCodes.value,
});
useMessage().success('关联成功');
linkRuleDialogVisible.value = false;
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '关联失败');
} finally {
linkRuleLoading.value = false;
}
};
// 导出
const handleExport = async () => {
try {
await makeExportClassOverviewTask(searchForm)
useMessage().success('导出任务已创建,请在文件管理中下载')
} catch (err: any) {
useMessage().error(err.msg || '导出失败')
}
}
try {
await makeExportClassOverviewTask(searchForm);
useMessage().success('导出任务已创建,请在文件管理中下载');
} catch (err: any) {
useMessage().error(err.msg || '导出失败');
}
};
// 生成考核班级(占位,待确认接口)
const handleGenerateAssessment = () => {
useMessage().warning('生成考核班级功能待确认接口后启用')
}
useMessage().warning('生成考核班级功能待确认接口后启用');
};
// 获取学院列表
const getDeptListData = async () => {
try {
const res = await getDeptList()
if (res.data) {
deptList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取学院列表失败', err)
deptList.value = []
}
}
try {
const res = await getDeptList();
if (res.data) {
deptList.value = Array.isArray(res.data) ? res.data : [];
}
} catch (err) {
console.error('获取学院列表失败', err);
deptList.value = [];
}
};
// 获取班号列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班号列表失败', err)
classList.value = []
}
}
try {
const res = await getClassListByRole();
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : [];
}
} catch (err) {
console.error('获取班号列表失败', err);
classList.value = [];
}
};
// 获取门禁规则列表
const getRuleListData = async () => {
try {
const res = await getRuleList({ current: 1, size: 9999 })
if (res.data && res.data.records) {
ruleList.value = Array.isArray(res.data.records) ? res.data.records : []
} else if (Array.isArray(res.data)) {
ruleList.value = res.data
} else {
ruleList.value = []
}
} catch (err) {
console.error('获取门禁规则列表失败', err)
ruleList.value = []
}
}
try {
const res = await getRuleList({ current: 1, size: 9999 });
if (res.data && res.data.records) {
ruleList.value = Array.isArray(res.data.records) ? res.data.records : [];
} else if (Array.isArray(res.data)) {
ruleList.value = res.data;
} else {
ruleList.value = [];
}
} catch (err) {
console.error('获取门禁规则列表失败', err);
ruleList.value = [];
}
};
// 初始化
onMounted(() => {
getDeptListData()
getClassListData()
})
getDeptListData();
getClassListData();
});
</script>
<style scoped lang="scss">

View File

@@ -1,117 +1,94 @@
<template>
<el-dialog
title="简单信息维护"
v-model="visible"
:close-on-click-modal="false"
draggable
width="600px">
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
:validate-on-rule-change="false"
v-loading="loading">
<el-form-item label="姓名" prop="realName">
<el-input
v-model="form.realName"
placeholder="请输入姓名"
clearable />
</el-form-item>
<el-form-item label="曾用名" prop="oldName">
<el-input
v-model="form.oldName"
placeholder="请输入曾用名"
clearable />
</el-form-item>
<el-form-item label="身份证号" prop="idCard">
<el-input
v-model="form.idCard"
placeholder="请输入身份证号"
clearable />
</el-form-item>
</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 width="600px">
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="120px" :validate-on-rule-change="false" v-loading="loading">
<el-form-item label="姓名" prop="realName">
<el-input v-model="form.realName" placeholder="请输入姓名" clearable />
</el-form-item>
<el-form-item label="曾用名" prop="oldName">
<el-input v-model="form.oldName" placeholder="请输入曾用名" clearable />
</el-form-item>
<el-form-item label="身份证号" prop="idCard">
<el-input v-model="form.idCard" placeholder="请输入身份证号" clearable />
</el-form-item>
</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="SimpleEditDialog">
import { ref, reactive, nextTick } from 'vue'
import { useMessage } from "/@/hooks/message";
import { updateStuSimpleInfo } from '/@/api/basic/basicstudent'
import { ref, reactive, nextTick } from 'vue';
import { useMessage } from '/@/hooks/message';
import { updateStuSimpleInfo } from '/@/api/basic/basicstudent';
const emit = defineEmits(['refresh']);
// 定义变量内容
const dataFormRef = ref();
const visible = ref(false)
const loading = ref(false)
const visible = ref(false);
const loading = ref(false);
// 提交表单数据
const form = reactive({
id: '',
realName: '',
oldName: '',
idCard: ''
id: '',
realName: '',
oldName: '',
idCard: '',
});
// 定义校验规则 - 只有姓名和身份证号是必填的,曾用名不是必填
const dataRules = ref({
realName: [
{ required: true, message: '姓名不能为空', trigger: 'blur' }
],
idCard: [
{ required: true, message: '身份证号不能为空', trigger: 'blur' },
{ pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/, message: '请输入正确的身份证号', trigger: 'blur' }
]
// oldName 不是必填,不需要验证规则
})
realName: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
idCard: [
{ required: true, message: '身份证号不能为空', trigger: 'blur' },
{ pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/, message: '请输入正确的身份证号', trigger: 'blur' },
],
// oldName 不是必填,不需要验证规则
});
// 打开弹窗
const openDialog = (rowData: any) => {
visible.value = true
// 重置表单数据
Object.assign(form, {
id: rowData.id || '',
realName: rowData.realName || '',
oldName: rowData.oldName || '',
idCard: rowData.idCard || ''
})
visible.value = true;
// 清除表单验证状态
nextTick(() => {
dataFormRef.value?.clearValidate();
dataFormRef.value?.resetFields();
});
// 重置表单数据
Object.assign(form, {
id: rowData.id || '',
realName: rowData.realName || '',
oldName: rowData.oldName || '',
idCard: rowData.idCard || '',
});
// 清除表单验证状态
nextTick(() => {
dataFormRef.value?.clearValidate();
dataFormRef.value?.resetFields();
});
};
// 提交
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 updateStuSimpleInfo(form);
useMessage().success('更新成功');
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || '更新失败');
} finally {
loading.value = false;
}
try {
loading.value = true;
await updateStuSimpleInfo(form);
useMessage().success('更新成功');
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || '更新失败');
} finally {
loading.value = false;
}
};
// 暴露方法给父组件
defineExpose({
openDialog
openDialog,
});
</script>

View File

@@ -1,213 +1,194 @@
<template>
<el-dialog
title="班级概况"
v-model="visible"
:close-on-click-modal="false"
draggable
width="1400px">
<div v-loading="loading">
<!-- 基本信息 -->
<el-descriptions :column="3" border v-if="detailData" class="mb20">
<el-descriptions-item label="班">{{ detailData.classNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="班级名称">{{ detailData.className || '-' }}</el-descriptions-item>
<el-descriptions-item label="班级规范名称">{{ detailData.classProName || '-' }}</el-descriptions-item>
<el-descriptions-item label="学院">{{ detailData.deptName || '-' }}</el-descriptions-item>
<el-descriptions-item label="班主任">{{ detailData.teacherRealName || '-' }}</el-descriptions-item>
<el-descriptions-item label="班主任电话">{{ detailData.teacherTel || '-' }}</el-descriptions-item>
<el-descriptions-item label="入学年份">{{ detailData.grade || '-' }}</el-descriptions-item>
<el-descriptions-item label="入学时间">{{ detailData.enterDate || '-' }}</el-descriptions-item>
<el-descriptions-item label="班级状态">
<span>{{ detailData.classStatus === '0' ? '正常' : detailData.classStatus === '1' ? '离校' : '-' }}</span>
</el-descriptions-item>
<el-descriptions-item label="班级人数/原始人数">
{{ detailData.stuNum || 0 }}/{{ detailData.preStuNum || 0 }}
</el-descriptions-item>
<el-descriptions-item label="流失率">
<span>{{ detailData.stuLoseRate ? `${detailData.stuLoseRate}%` : '-' }}</span>
</el-descriptions-item>
<el-descriptions-item label="联院">
<span>{{ detailData.isUnion === 1 ? '是' : detailData.isUnion === 0 ? '否' : '-' }}</span>
</el-descriptions-item>
<el-descriptions-item label="门禁规则" :span="3">{{ detailData.ruleName || '-' }}</el-descriptions-item>
</el-descriptions>
<el-dialog title="班级概况" v-model="visible" :close-on-click-modal="false" draggable width="1400px">
<div v-loading="loading">
<!-- 基本信息 -->
<el-descriptions :column="3" border v-if="detailData" class="mb20">
<el-descriptions-item label="班号">{{ detailData.classNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="班级名称">{{ detailData.className || '-' }}</el-descriptions-item>
<el-descriptions-item label="班级规范名称">{{ detailData.classProName || '-' }}</el-descriptions-item>
<el-descriptions-item label="学院">{{ detailData.deptName || '-' }}</el-descriptions-item>
<el-descriptions-item label="班主任">{{ detailData.teacherRealName || '-' }}</el-descriptions-item>
<el-descriptions-item label="班主任电话">{{ detailData.teacherTel || '-' }}</el-descriptions-item>
<el-descriptions-item label="入学年份">{{ detailData.grade || '-' }}</el-descriptions-item>
<el-descriptions-item label="入学时间">{{ detailData.enterDate || '-' }}</el-descriptions-item>
<el-descriptions-item label="班级状态">
<span>{{ detailData.classStatus === '0' ? '正常' : detailData.classStatus === '1' ? '离校' : '-' }}</span>
</el-descriptions-item>
<el-descriptions-item label="班级人数/原始人数"> {{ detailData.stuNum || 0 }}/{{ detailData.preStuNum || 0 }} </el-descriptions-item>
<el-descriptions-item label="流失率">
<span>{{ detailData.stuLoseRate ? `${detailData.stuLoseRate}%` : '-' }}</span>
</el-descriptions-item>
<el-descriptions-item label="联院">
<span>{{ detailData.isUnion === 1 ? '是' : detailData.isUnion === 0 ? '否' : '-' }}</span>
</el-descriptions-item>
<el-descriptions-item label="门禁规则" :span="3">{{ detailData.ruleName || '-' }}</el-descriptions-item>
</el-descriptions>
<!-- 详细信息 - 使用 tabs 展示六个接口的数据 -->
<el-tabs v-model="activeTab" type="border-card">
<el-tab-pane label="班级荣誉" name="honor">
<el-table :data="honorList" border style="width: 100%" v-loading="honorLoading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="标题" show-overflow-tooltip />
<el-table-column prop="author" label="作者" width="120" />
<el-table-column prop="createTime" label="更新时间" width="120" />
<el-table-column prop="belong" label="归档级别" width="120" />
</el-table>
<div v-if="honorList.length === 0 && !honorLoading" style="text-align: center; padding: 20px; color: #909399;">
暂无数据
</div>
</el-tab-pane>
<!-- 详细信息 - 使用 tabs 展示六个接口的数据 -->
<el-tabs v-model="activeTab" type="border-card">
<el-tab-pane label="班级荣誉" name="honor">
<el-table :data="honorList" border style="width: 100%" v-loading="honorLoading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="标题" show-overflow-tooltip />
<el-table-column prop="author" label="作者" width="120" />
<el-table-column prop="createTime" label="更新时间" width="120" />
<el-table-column prop="belong" label="归档级别" width="120" />
</el-table>
<div v-if="honorList.length === 0 && !honorLoading" style="text-align: center; padding: 20px; color: #909399">暂无数据</div>
</el-tab-pane>
<el-tab-pane label="班级宣传" name="publicity">
<el-table :data="publicityList" border style="width: 100%" v-loading="publicityLoading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="标题" show-overflow-tooltip />
<el-table-column prop="author" label="作者" width="120" />
<el-table-column prop="createTime" label="更新时间" width="120" />
<el-table-column prop="belong" label="归档级别" width="120" />
<el-table-column prop="website" label="网址" show-overflow-tooltip />
</el-table>
<div v-if="publicityList.length === 0 && !publicityLoading" style="text-align: center; padding: 20px; color: #909399;">
暂无数据
</div>
</el-tab-pane>
<el-tab-pane label="班级宣传" name="publicity">
<el-table :data="publicityList" border style="width: 100%" v-loading="publicityLoading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="标题" show-overflow-tooltip />
<el-table-column prop="author" label="作者" width="120" />
<el-table-column prop="createTime" label="更新时间" width="120" />
<el-table-column prop="belong" label="归档级别" width="120" />
<el-table-column prop="website" label="网址" show-overflow-tooltip />
</el-table>
<div v-if="publicityList.length === 0 && !publicityLoading" style="text-align: center; padding: 20px; color: #909399">暂无数据</div>
</el-tab-pane>
<el-tab-pane label="数据3" name="data3">
<el-table :data="data3List" border style="width: 100%" v-loading="data3Loading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="标题" show-overflow-tooltip />
<el-table-column prop="createTime" label="更新时间" width="120" />
</el-table>
<div v-if="data3List.length === 0 && !data3Loading" style="text-align: center; padding: 20px; color: #909399;">
暂无数据
</div>
</el-tab-pane>
<el-tab-pane label="数据3" name="data3">
<el-table :data="data3List" border style="width: 100%" v-loading="data3Loading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="标题" show-overflow-tooltip />
<el-table-column prop="createTime" label="更新时间" width="120" />
</el-table>
<div v-if="data3List.length === 0 && !data3Loading" style="text-align: center; padding: 20px; color: #909399">暂无数据</div>
</el-tab-pane>
<el-tab-pane label="数据4" name="data4">
<el-table :data="data4List" border style="width: 100%" v-loading="data4Loading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="标题" show-overflow-tooltip />
<el-table-column prop="createTime" label="更新时间" width="120" />
</el-table>
<div v-if="data4List.length === 0 && !data4Loading" style="text-align: center; padding: 20px; color: #909399;">
暂无数据
</div>
</el-tab-pane>
<el-tab-pane label="数据4" name="data4">
<el-table :data="data4List" border style="width: 100%" v-loading="data4Loading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="标题" show-overflow-tooltip />
<el-table-column prop="createTime" label="更新时间" width="120" />
</el-table>
<div v-if="data4List.length === 0 && !data4Loading" style="text-align: center; padding: 20px; color: #909399">暂无数据</div>
</el-tab-pane>
<el-tab-pane label="数据5" name="data5">
<el-table :data="data5List" border style="width: 100%" v-loading="data5Loading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="标题" show-overflow-tooltip />
<el-table-column prop="createTime" label="更新时间" width="120" />
</el-table>
<div v-if="data5List.length === 0 && !data5Loading" style="text-align: center; padding: 20px; color: #909399;">
暂无数据
</div>
</el-tab-pane>
<el-tab-pane label="数据5" name="data5">
<el-table :data="data5List" border style="width: 100%" v-loading="data5Loading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="标题" show-overflow-tooltip />
<el-table-column prop="createTime" label="更新时间" width="120" />
</el-table>
<div v-if="data5List.length === 0 && !data5Loading" style="text-align: center; padding: 20px; color: #909399">暂无数据</div>
</el-tab-pane>
<el-tab-pane label="数据6" name="data6">
<el-table :data="data6List" border style="width: 100%" v-loading="data6Loading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="标题" show-overflow-tooltip />
<el-table-column prop="createTime" label="更新时间" width="120" />
</el-table>
<div v-if="data6List.length === 0 && !data6Loading" style="text-align: center; padding: 20px; color: #909399;">
暂无数据
</div>
</el-tab-pane>
</el-tabs>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
</span>
</template>
</el-dialog>
<el-tab-pane label="数据6" name="data6">
<el-table :data="data6List" border style="width: 100%" v-loading="data6Loading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="title" label="标题" show-overflow-tooltip />
<el-table-column prop="createTime" label="更新时间" width="120" />
</el-table>
<div v-if="data6List.length === 0 && !data6Loading" style="text-align: center; padding: 20px; color: #909399">暂无数据</div>
</el-tab-pane>
</el-tabs>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="BasicClassDetail">
import { ref, watch } from 'vue'
import { useMessage } from "/@/hooks/message";
import { queryClassHonorByClassCode } from '/@/api/stuwork/classhonor'
import { queryDataByClassCode } from '/@/api/stuwork/classpublicity'
import { ref, watch } from 'vue';
import { useMessage } from '/@/hooks/message';
import { queryClassHonorByClassCode } from '/@/api/stuwork/classhonor';
import { queryDataByClassCode } from '/@/api/stuwork/classpublicity';
// 定义变量内容
const visible = ref(false)
const loading = ref(false)
const detailData = ref<any>(null)
const activeTab = ref('honor')
const visible = ref(false);
const loading = ref(false);
const detailData = ref<any>(null);
const activeTab = ref('honor');
// 六个接口的数据
const honorList = ref<any[]>([])
const honorLoading = ref(false)
const publicityList = ref<any[]>([])
const publicityLoading = ref(false)
const data3List = ref<any[]>([])
const data3Loading = ref(false)
const data4List = ref<any[]>([])
const data4Loading = ref(false)
const data5List = ref<any[]>([])
const data5Loading = ref(false)
const data6List = ref<any[]>([])
const data6Loading = ref(false)
const honorList = ref<any[]>([]);
const honorLoading = ref(false);
const publicityList = ref<any[]>([]);
const publicityLoading = ref(false);
const data3List = ref<any[]>([]);
const data3Loading = ref(false);
const data4List = ref<any[]>([]);
const data4Loading = ref(false);
const data5List = ref<any[]>([]);
const data5Loading = ref(false);
const data6List = ref<any[]>([]);
const data6Loading = ref(false);
// 打开弹窗
const openDialog = async (rowData: any) => {
visible.value = true
detailData.value = rowData || null
// 如果有班级代码,加载六个接口的数据
if (rowData?.classCode) {
await loadAllData(rowData.classCode)
}
}
visible.value = true;
detailData.value = rowData || null;
// 如果有班级代码,加载六个接口的数据
if (rowData?.classCode) {
await loadAllData(rowData.classCode);
}
};
// 加载所有六个接口的数据
const loadAllData = async (classCode: string) => {
loading.value = true
// 并行加载所有接口数据
await Promise.all([
loadHonorData(classCode),
loadPublicityData(classCode),
// TODO: 添加其他四个接口的加载
// loadData3(classCode),
// loadData4(classCode),
// loadData5(classCode),
// loadData6(classCode),
])
loading.value = false
}
loading.value = true;
// 并行加载所有接口数据
await Promise.all([
loadHonorData(classCode),
loadPublicityData(classCode),
// TODO: 添加其他四个接口的加载
// loadData3(classCode),
// loadData4(classCode),
// loadData5(classCode),
// loadData6(classCode),
]);
loading.value = false;
};
// 加载班级荣誉数据
const loadHonorData = async (classCode: string) => {
honorLoading.value = true
try {
const res = await queryClassHonorByClassCode(classCode)
if (res.data) {
honorList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err: any) {
console.error('获取班级荣誉失败', err)
honorList.value = []
} finally {
honorLoading.value = false
}
}
honorLoading.value = true;
try {
const res = await queryClassHonorByClassCode(classCode);
if (res.data) {
honorList.value = Array.isArray(res.data) ? res.data : [];
}
} catch (err: any) {
console.error('获取班级荣誉失败', err);
honorList.value = [];
} finally {
honorLoading.value = false;
}
};
// 加载班级宣传数据
const loadPublicityData = async (classCode: string) => {
publicityLoading.value = true
try {
const res = await queryDataByClassCode(classCode)
if (res.data) {
publicityList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err: any) {
console.error('获取班级宣传失败', err)
publicityList.value = []
} finally {
publicityLoading.value = false
}
}
publicityLoading.value = true;
try {
const res = await queryDataByClassCode(classCode);
if (res.data) {
publicityList.value = Array.isArray(res.data) ? res.data : [];
}
} catch (err: any) {
console.error('获取班级宣传失败', err);
publicityList.value = [];
} finally {
publicityLoading.value = false;
}
};
// 暴露方法给父组件
defineExpose({
openDialog
openDialog,
});
</script>
<style lang="scss" scoped>
.mb20 {
margin-bottom: 20px;
margin-bottom: 20px;
}
</style>

View File

@@ -1,536 +1,407 @@
<template>
<el-dialog
:title="form.id ? '编辑' : '新增'"
v-model="visible"
:close-on-click-modal="false"
draggable
width="900px">
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="140px"
:validate-on-rule-change="false"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="姓名" prop="realName">
<el-input
v-model="form.realName"
placeholder="请输入姓名"
clearable />
</el-form-item>
</el-col>
<el-dialog :title="form.id ? '编辑' : '新增'" v-model="visible" :close-on-click-modal="false" draggable width="900px">
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="140px" :validate-on-rule-change="false" v-loading="loading">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="姓名" prop="realName">
<el-input v-model="form.realName" placeholder="请输入姓名" clearable />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="身份证" prop="idCard">
<el-input
v-model="form.idCard"
placeholder="请输入身份证号"
clearable />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="身份证" prop="idCard">
<el-input v-model="form.idCard" placeholder="请输入身份证号" clearable />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="性别" prop="sex">
<el-select
v-model="form.sex"
placeholder="请选择"
clearable
style="width: 100%">
<el-option label="男" value="1" />
<el-option label="女" value="0" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="性别" prop="sex">
<el-select v-model="form.sex" placeholder="请选择" clearable style="width: 100%">
<el-option label="男" value="1" />
<el-option label="女" value="0" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="出生日期" prop="birthday">
<el-date-picker
v-model="form.birthday"
type="date"
placeholder="选择日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="出生日期" prop="birthday">
<el-date-picker
v-model="form.birthday"
type="date"
placeholder="选择日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="户口所在地" prop="householdAddress">
<el-input
v-model="form.householdAddress"
placeholder="请输入户口所在地"
clearable />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="户口所在地" prop="householdAddress">
<el-input v-model="form.householdAddress" placeholder="请输入户口所在地" clearable />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="班级" prop="classCode">
<el-select
v-model="form.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="班级" prop="classCode">
<el-select v-model="form.classCode" placeholder="请选择班级" clearable filterable style="width: 100%">
<el-option v-for="item in classList" :key="item.classCode" :label="item.classNo" :value="item.classCode"> </el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学号" prop="stuNo">
<el-input
v-model="form.stuNo"
placeholder="请输入学号"
clearable />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="学号" prop="stuNo">
<el-input v-model="form.stuNo" placeholder="请输入学号" clearable />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="辩色力" prop="colourSense">
<el-select
v-model="form.colourSense"
placeholder="请选择辩色力"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in eyeStatusList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="辩色力" prop="colourSense">
<el-select v-model="form.colourSense" placeholder="请选择辩色力" clearable filterable style="width: 100%">
<el-option v-for="item in eyeStatusList" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="退伍军人" prop="veteran">
<el-select
v-model="form.veteran"
placeholder="请选择"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in veteranStatusList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="退伍军人" prop="veteran">
<el-select v-model="form.veteran" placeholder="请选择" clearable filterable style="width: 100%">
<el-option v-for="item in veteranStatusList" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="政治面貌" prop="politicsStatus">
<el-select
v-model="form.politicsStatus"
placeholder="请选择政治面貌"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in politicsStatusList"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="政治面貌" prop="politicsStatus">
<el-select v-model="form.politicsStatus" placeholder="请选择政治面貌" clearable filterable style="width: 100%">
<el-option v-for="item in politicsStatusList" :key="item.id" :label="item.name" :value="item.id"> </el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="民族" prop="national">
<el-select
v-model="form.national"
placeholder="请选择民族"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in nationalList"
:key="item.nationCode"
:label="item.nationName"
:value="item.nationCode">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="民族" prop="national">
<el-select v-model="form.national" placeholder="请选择民族" clearable filterable style="width: 100%">
<el-option v-for="item in nationalList" :key="item.nationCode" :label="item.nationName" :value="item.nationCode"> </el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="是否10万以下民族" prop="isLowPopulation">
<el-select
v-model="form.isLowPopulation"
placeholder="请选择"
clearable
style="width: 100%">
<el-option label="是" value="1" />
<el-option label="否" value="0" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="是否10万以下民族" prop="isLowPopulation">
<el-select v-model="form.isLowPopulation" placeholder="请选择" clearable style="width: 100%">
<el-option label="是" value="1" />
<el-option label="否" value="0" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="既往病史" prop="medicalHistory">
<el-input
v-model="form.medicalHistory"
placeholder="请输入既往病史"
clearable />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="既往病史" prop="medicalHistory">
<el-input v-model="form.medicalHistory" placeholder="请输入既往病史" clearable />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="入学前文化程度" prop="beforeEducation">
<el-select
v-model="form.beforeEducation"
placeholder="请选择入学前文化程度"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in preSchoolEducationList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="入学前文化程度" prop="beforeEducation">
<el-select v-model="form.beforeEducation" placeholder="请选择入学前文化程度" clearable filterable style="width: 100%">
<el-option v-for="item in preSchoolEducationList" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="入学前毕业学校" prop="beforeSchool">
<el-input
v-model="form.beforeSchool"
placeholder="请输入入学前毕业学校"
clearable />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="入学前毕业学校" prop="beforeSchool">
<el-input v-model="form.beforeSchool" placeholder="请输入入学前毕业学校" clearable />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="毕业学校省市" prop="schoolProvince">
<el-select
v-model="form.schoolProvince"
placeholder="请选择毕业学校省市"
clearable
filterable
style="width: 100%">
<el-option
v-for="item in schoolProvinceList"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</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="12" class="mb20">
<el-form-item label="毕业学校省市" prop="schoolProvince">
<el-select v-model="form.schoolProvince" placeholder="请选择毕业学校省市" clearable filterable style="width: 100%">
<el-option v-for="item in schoolProvinceList" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</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="BasicStudentDialog">
import { ref, reactive, nextTick, onMounted } from 'vue'
import { useMessage } from "/@/hooks/message";
import { getObj, addObj, putObj } from '/@/api/basic/basicstudent'
import { getClassListByRole } from '/@/api/basic/basicclass'
import { getNationalList } from '/@/api/basic/basicnation'
import { getPoliticsStatusDict } from '/@/api/basic/basicpoliticsstatusbase'
import { queryDictByTypeList } from '/@/api/admin/dict'
import { ref, reactive, nextTick, onMounted } from 'vue';
import { useMessage } from '/@/hooks/message';
import { getObj, addObj, putObj } from '/@/api/basic/basicstudent';
import { getClassListByRole } from '/@/api/basic/basicclass';
import { getNationalList } from '/@/api/basic/basicnation';
import { getPoliticsStatusDict } from '/@/api/basic/basicpoliticsstatusbase';
import { queryDictByTypeList } from '/@/api/admin/dict';
const emit = defineEmits(['refresh']);
// 定义变量内容
const dataFormRef = ref();
const visible = ref(false)
const loading = ref(false)
const classList = ref<any[]>([])
const nationalList = ref<any[]>([])
const politicsStatusList = ref<any[]>([])
const eyeStatusList = ref<any[]>([])
const veteranStatusList = ref<any[]>([])
const preSchoolEducationList = ref<any[]>([])
const schoolProvinceList = ref<any[]>([])
const visible = ref(false);
const loading = ref(false);
const classList = ref<any[]>([]);
const nationalList = ref<any[]>([]);
const politicsStatusList = ref<any[]>([]);
const eyeStatusList = ref<any[]>([]);
const veteranStatusList = ref<any[]>([]);
const preSchoolEducationList = ref<any[]>([]);
const schoolProvinceList = ref<any[]>([]);
// 提交表单数据
const form = reactive({
id: '',
realName: '',
idCard: '',
sex: '',
birthday: '',
householdAddress: '',
classCode: '',
stuNo: '',
colourSense: '',
veteran: '',
politicsStatus: '',
national: '',
isLowPopulation: '',
medicalHistory: '',
beforeEducation: '',
beforeSchool: '',
schoolProvince: ''
id: '',
realName: '',
idCard: '',
sex: '',
birthday: '',
householdAddress: '',
classCode: '',
stuNo: '',
colourSense: '',
veteran: '',
politicsStatus: '',
national: '',
isLowPopulation: '',
medicalHistory: '',
beforeEducation: '',
beforeSchool: '',
schoolProvince: '',
});
// 定义校验规则 - 除了"是否10万以下民族"外,其他字段都是必填
const dataRules = ref({
realName: [
{ required: true, message: '姓名不能为空', trigger: 'blur' }
],
idCard: [
{ required: true, message: '身份证不能为空', trigger: 'blur' },
{ pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/, message: '请输入正确的身份证号', trigger: 'blur' }
],
sex: [
{ required: true, message: '性别不能为空', trigger: 'change' }
],
birthday: [
{ required: true, message: '出生日期不能为空', trigger: 'change' }
],
householdAddress: [
{ required: true, message: '户口所在地不能为空', trigger: 'blur' }
],
classCode: [
{ required: true, message: '班级不能为空', trigger: 'change' }
],
stuNo: [
{ required: true, message: '学号不能为空', trigger: 'blur' }
],
colourSense: [
{ required: true, message: '辩色力不能为空', trigger: 'blur' }
],
veteran: [
{ required: true, message: '退伍军人不能为空', trigger: 'change' }
],
politicsStatus: [
{ required: true, message: '政治面貌不能为空', trigger: 'change' }
],
national: [
{ required: true, message: '民族不能为空', trigger: 'change' }
],
medicalHistory: [
{ required: true, message: '既往病史不能为空', trigger: 'blur' }
],
beforeEducation: [
{ required: true, message: '入学前文化程度不能为空', trigger: 'blur' }
],
beforeSchool: [
{ required: true, message: '入学前毕业学校不能为空', trigger: 'blur' }
],
schoolProvince: [
{ required: true, message: '毕业学校省市不能为空', trigger: 'blur' }
]
// isLowPopulation 不需要必填验证
})
realName: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
idCard: [
{ required: true, message: '身份证不能为空', trigger: 'blur' },
{ pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/, message: '请输入正确的身份证号', trigger: 'blur' },
],
sex: [{ required: true, message: '性别不能为空', trigger: 'change' }],
birthday: [{ required: true, message: '出生日期不能为空', trigger: 'change' }],
householdAddress: [{ required: true, message: '户口所在地不能为空', trigger: 'blur' }],
classCode: [{ required: true, message: '班级不能为空', trigger: 'change' }],
stuNo: [{ required: true, message: '学号不能为空', trigger: 'blur' }],
colourSense: [{ required: true, message: '辩色力不能为空', trigger: 'blur' }],
veteran: [{ required: true, message: '退伍军人不能为空', trigger: 'change' }],
politicsStatus: [{ required: true, message: '政治面貌不能为空', trigger: 'change' }],
national: [{ required: true, message: '民族不能为空', trigger: 'change' }],
medicalHistory: [{ required: true, message: '既往病史不能为空', trigger: 'blur' }],
beforeEducation: [{ required: true, message: '入学前文化程度不能为空', trigger: 'blur' }],
beforeSchool: [{ required: true, message: '入学前毕业学校不能为空', trigger: 'blur' }],
schoolProvince: [{ required: true, message: '毕业学校省市不能为空', trigger: 'blur' }],
// isLowPopulation 不需要必填验证
});
// 打开弹窗
const openDialog = (id?: string) => {
visible.value = true
// 重置表单数据
Object.assign(form, {
id: '',
realName: '',
idCard: '',
sex: '',
birthday: '',
householdAddress: '',
classCode: '',
stuNo: '',
colourSense: '',
veteran: '',
politicsStatus: '',
national: '',
isLowPopulation: '',
medicalHistory: '',
beforeEducation: '',
beforeSchool: '',
schoolProvince: ''
})
visible.value = true;
// 清除表单验证状态,不触发验证
nextTick(() => {
dataFormRef.value?.clearValidate();
dataFormRef.value?.resetFields();
});
// 重置表单数据
Object.assign(form, {
id: '',
realName: '',
idCard: '',
sex: '',
birthday: '',
householdAddress: '',
classCode: '',
stuNo: '',
colourSense: '',
veteran: '',
politicsStatus: '',
national: '',
isLowPopulation: '',
medicalHistory: '',
beforeEducation: '',
beforeSchool: '',
schoolProvince: '',
});
// 获取详情
if (id) {
form.id = id
getStudentData(id)
} else {
// 新增时,确保验证状态已清除
nextTick(() => {
dataFormRef.value?.clearValidate();
});
}
// 清除表单验证状态,不触发验证
nextTick(() => {
dataFormRef.value?.clearValidate();
dataFormRef.value?.resetFields();
});
// 获取详情
if (id) {
form.id = id;
getStudentData(id);
} else {
// 新增时,确保验证状态已清除
nextTick(() => {
dataFormRef.value?.clearValidate();
});
}
};
// 提交
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;
if (form.id) {
await putObj(form);
useMessage().success('编辑成功');
} else {
await addObj(form);
useMessage().success('新增成功');
}
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || (form.id ? '编辑失败' : '新增失败'));
} finally {
loading.value = false;
}
try {
loading.value = true;
if (form.id) {
await putObj(form);
useMessage().success('编辑成功');
} else {
await addObj(form);
useMessage().success('新增成功');
}
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || (form.id ? '编辑失败' : '新增失败'));
} finally {
loading.value = false;
}
};
// 获取详情
const getStudentData = (id: string) => {
loading.value = true
getObj(id).then((res: any) => {
if (res.data) {
Object.assign(form, {
id: res.data.id || '',
realName: res.data.realName || '',
idCard: res.data.idCard || '',
sex: res.data.sex || '',
birthday: res.data.birthday || '',
householdAddress: res.data.householdAddress || '',
classCode: res.data.classCode || '',
stuNo: res.data.stuNo || '',
colourSense: res.data.colourSense || '',
veteran: res.data.veteran || '',
politicsStatus: res.data.politicsStatus || '',
national: res.data.national || '',
isLowPopulation: res.data.isLowPopulation || '',
medicalHistory: res.data.medicalHistory || '',
beforeEducation: res.data.beforeEducation || '',
beforeSchool: res.data.beforeSchool || '',
schoolProvince: res.data.schoolProvince || ''
})
}
}).catch((err: any) => {
console.error('获取详情失败', err)
useMessage().error('获取详情失败')
}).finally(() => {
loading.value = false
// 数据加载完成后,清除验证状态,避免触发验证
nextTick(() => {
dataFormRef.value?.clearValidate();
});
})
}
loading.value = true;
getObj(id)
.then((res: any) => {
if (res.data) {
Object.assign(form, {
id: res.data.id || '',
realName: res.data.realName || '',
idCard: res.data.idCard || '',
sex: res.data.sex || '',
birthday: res.data.birthday || '',
householdAddress: res.data.householdAddress || '',
classCode: res.data.classCode || '',
stuNo: res.data.stuNo || '',
colourSense: res.data.colourSense || '',
veteran: res.data.veteran || '',
politicsStatus: res.data.politicsStatus || '',
national: res.data.national || '',
isLowPopulation: res.data.isLowPopulation || '',
medicalHistory: res.data.medicalHistory || '',
beforeEducation: res.data.beforeEducation || '',
beforeSchool: res.data.beforeSchool || '',
schoolProvince: res.data.schoolProvince || '',
});
}
})
.catch((err: any) => {
console.error('获取详情失败', err);
useMessage().error('获取详情失败');
})
.finally(() => {
loading.value = false;
// 数据加载完成后,清除验证状态,避免触发验证
nextTick(() => {
dataFormRef.value?.clearValidate();
});
});
};
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
try {
const res = await getClassListByRole();
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : [];
}
} catch (err) {
console.error('获取班级列表失败', err);
classList.value = [];
}
};
// 获取民族列表
const getNationalListData = async () => {
try {
const res = await getNationalList()
if (res.data) {
nationalList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取民族列表失败', err)
nationalList.value = []
}
}
try {
const res = await getNationalList();
if (res.data) {
nationalList.value = Array.isArray(res.data) ? res.data : [];
}
} catch (err) {
console.error('获取民族列表失败', err);
nationalList.value = [];
}
};
// 获取政治面貌列表
const getPoliticsStatusListData = async () => {
try {
const res = await getPoliticsStatusDict()
if (res.data) {
politicsStatusList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取政治面貌列表失败', err)
politicsStatusList.value = []
}
}
try {
const res = await getPoliticsStatusDict();
if (res.data) {
politicsStatusList.value = Array.isArray(res.data) ? res.data : [];
}
} catch (err) {
console.error('获取政治面貌列表失败', err);
politicsStatusList.value = [];
}
};
// 获取字典数据
const getDictData = async () => {
try {
const dictTypes = ['eye_status', 'veteran_status', 'pre_school_education', 'school_province']
const res = await queryDictByTypeList(dictTypes)
if (res.data && typeof res.data === 'object') {
// 解析返回的字典数据,统一转换为 {label, value} 格式
const parseDictList = (dictData: any) => {
if (!Array.isArray(dictData)) return []
return dictData.map((item: any) => ({
label: item.label || item.name || item.dictLabel || '',
value: item.value || item.code || item.dictValue || ''
})).filter((item: any) => item.label && item.value !== undefined && item.value !== null && item.value !== '')
}
if (res.data.eye_status) {
eyeStatusList.value = parseDictList(res.data.eye_status)
}
if (res.data.veteran_status) {
veteranStatusList.value = parseDictList(res.data.veteran_status)
}
if (res.data.pre_school_education) {
preSchoolEducationList.value = parseDictList(res.data.pre_school_education)
}
if (res.data.school_province) {
schoolProvinceList.value = parseDictList(res.data.school_province)
}
}
} catch (err) {
console.error('获取字典数据失败', err)
eyeStatusList.value = []
veteranStatusList.value = []
preSchoolEducationList.value = []
schoolProvinceList.value = []
}
}
try {
const dictTypes = ['eye_status', 'veteran_status', 'pre_school_education', 'school_province'];
const res = await queryDictByTypeList(dictTypes);
if (res.data && typeof res.data === 'object') {
// 解析返回的字典数据,统一转换为 {label, value} 格式
const parseDictList = (dictData: any) => {
if (!Array.isArray(dictData)) return [];
return dictData
.map((item: any) => ({
label: item.label || item.name || item.dictLabel || '',
value: item.value || item.code || item.dictValue || '',
}))
.filter((item: any) => item.label && item.value !== undefined && item.value !== null && item.value !== '');
};
if (res.data.eye_status) {
eyeStatusList.value = parseDictList(res.data.eye_status);
}
if (res.data.veteran_status) {
veteranStatusList.value = parseDictList(res.data.veteran_status);
}
if (res.data.pre_school_education) {
preSchoolEducationList.value = parseDictList(res.data.pre_school_education);
}
if (res.data.school_province) {
schoolProvinceList.value = parseDictList(res.data.school_province);
}
}
} catch (err) {
console.error('获取字典数据失败', err);
eyeStatusList.value = [];
veteranStatusList.value = [];
preSchoolEducationList.value = [];
schoolProvinceList.value = [];
}
};
// 初始化
onMounted(() => {
getClassListData()
getNationalListData()
getPoliticsStatusListData()
getDictData()
})
getClassListData();
getNationalListData();
getPoliticsStatusListData();
getDictData();
});
// 暴露变量
defineExpose({
openDialog
openDialog,
});
</script>
<style lang="scss" scoped>
.mb20 {
margin-bottom: 20px;
margin-bottom: 20px;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,246 +1,255 @@
<template>
<div class="app-container">
<!-- 搜索 -->
<el-card shadow="never" class="mb12">
<el-form :inline="true" :model="searchForm" ref="searchFormRef" label-width="90px">
<el-form-item label="学号" prop="stuNo">
<el-input v-model="searchForm.stuNo" placeholder="请输入学号" clearable />
</el-form-item>
<el-form-item label="姓名" prop="stuName">
<el-input v-model="searchForm.stuName" placeholder="请输入姓名" clearable />
</el-form-item>
<el-form-item label="身份证" prop="idCard">
<el-input v-model="searchForm.idCard" placeholder="请输入身份证" clearable />
</el-form-item>
<el-form-item label="电话" prop="phone">
<el-input v-model="searchForm.phone" placeholder="请输入电话" clearable />
</el-form-item>
<el-form-item label="家长电话" prop="parentPhone">
<el-input v-model="searchForm.parentPhone" placeholder="请输入家长电话" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">
<el-icon><Search /></el-icon>
查询
</el-button>
<el-button @click="handleReset">
<el-icon><Refresh /></el-icon>
重置
</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="app-container">
<!-- 搜索 -->
<el-card shadow="never" class="mb12">
<el-form :inline="true" :model="searchForm" ref="searchFormRef" label-width="90px">
<el-form-item label="学号" prop="stuNo">
<el-input v-model="searchForm.stuNo" placeholder="请输入学号" clearable />
</el-form-item>
<el-form-item label="姓名" prop="stuName">
<el-input v-model="searchForm.stuName" placeholder="请输入姓名" clearable />
</el-form-item>
<el-form-item label="身份证" prop="idCard">
<el-input v-model="searchForm.idCard" placeholder="请输入身份证" clearable />
</el-form-item>
<el-form-item label="电话" prop="phone">
<el-input v-model="searchForm.phone" placeholder="请输入电话" clearable />
</el-form-item>
<el-form-item label="家长电话" prop="parentPhone">
<el-input v-model="searchForm.parentPhone" placeholder="请输入家长电话" clearable />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">
<el-icon><Search /></el-icon>
查询
</el-button>
<el-button @click="handleReset">
<el-icon><Refresh /></el-icon>
重置
</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 表格 -->
<el-card shadow="never">
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@sort-change="sortChangeHandle"
style="width: 100%;">
<el-table-column type="index" label="序号" width="70" align="center">
<template #header>
<el-icon><List /></el-icon>
<span style="margin-left: 4px;">序号</span>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip>
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px;">学院</span>
</template>
</el-table-column>
<el-table-column prop="majorName" label="专业" show-overflow-tooltip>
<template #header>
<el-icon><Briefcase /></el-icon>
<span style="margin-left: 4px;">专业</span>
</template>
</el-table-column>
<el-table-column prop="className" label="班级" show-overflow-tooltip>
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px;">班级</span>
</template>
</el-table-column>
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip width="130">
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px;">学号</span>
</template>
</el-table-column>
<el-table-column prop="stuName" label="姓名" show-overflow-tooltip>
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px;">姓名</span>
</template>
</el-table-column>
<el-table-column prop="idCard" label="身份证" show-overflow-tooltip width="180">
<template #header>
<el-icon><CreditCard /></el-icon>
<span style="margin-left: 4px;">身份证</span>
</template>
</el-table-column>
<el-table-column prop="phone" label="电话" show-overflow-tooltip width="130">
<template #header>
<el-icon><Phone /></el-icon>
<span style="margin-left: 4px;">电话</span>
</template>
</el-table-column>
<el-table-column prop="parentPhone" label="家长电话" show-overflow-tooltip width="140">
<template #header>
<el-icon><Phone /></el-icon>
<span style="margin-left: 4px;">家长电话</span>
</template>
</el-table-column>
<el-table-column prop="gender" label="性别" width="90" align="center">
<template #header>
<el-icon><User /></el-icon>
<span style="margin-left: 4px;">性别</span>
</template>
<template #default="scope">
<GenderTag :gender="scope.row.gender" />
</template>
</el-table-column>
<el-table-column prop="dormNo" label="宿舍号" show-overflow-tooltip width="120">
<template #header>
<el-icon><HomeFilled /></el-icon>
<span style="margin-left: 4px;">宿舍号</span>
</template>
</el-table-column>
<el-table-column prop="teacherName" label="班主任" show-overflow-tooltip>
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px;">班主任</span>
</template>
</el-table-column>
<el-table-column prop="teacherPhone" label="班主任电话" show-overflow-tooltip width="140">
<template #header>
<el-icon><Phone /></el-icon>
<span style="margin-left: 4px;">班主任电话</span>
</template>
</el-table-column>
<el-table-column prop="schoolRollStatus" label="学籍状态" show-overflow-tooltip width="110" align="center">
<template #header>
<el-icon><CircleCheck /></el-icon>
<span style="margin-left: 4px;">学籍状态</span>
</template>
<template #default="scope">
<StatusTag :status="scope.row.schoolRollStatus" :options="rollStatusOptions" />
</template>
</el-table-column>
<el-table-column prop="stuStatus" label="学生状态" show-overflow-tooltip width="110" align="center">
<template #header>
<el-icon><Tickets /></el-icon>
<span style="margin-left: 4px;">学生状态</span>
</template>
<template #default="scope">
<StatusTag :status="scope.row.stuStatus" :options="stuStatusOptions" />
</template>
</el-table-column>
<el-table-column prop="avatarUrl" label="头像" width="120" align="center">
<template #header>
<el-icon><Picture /></el-icon>
<span style="margin-left: 4px;">头像</span>
</template>
<template #default="scope">
<el-image
v-if="scope.row.avatarUrl"
:src="scope.row.avatarUrl"
:preview-src-list="[scope.row.avatarUrl]"
fit="cover"
style="width: 60px; height: 60px; border-radius: 6px;" />
<el-tag v-else type="info" effect="plain"></el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right" align="center">
<template #header>
<el-icon><Setting /></el-icon>
<span style="margin-left: 4px;">操作</span>
</template>
<template #default>
<span style="color: #999;"></span>
</template>
</el-table-column>
</el-table>
<!-- 表格 -->
<el-card shadow="never">
<el-table
:data="state.dataList"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@sort-change="sortChangeHandle"
style="width: 100%"
>
<el-table-column type="index" label="序号" width="70" align="center">
<template #header>
<el-icon><List /></el-icon>
<span style="margin-left: 4px">序号</span>
</template>
</el-table-column>
<el-table-column prop="deptName" label="学院" show-overflow-tooltip>
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">学院</span>
</template>
</el-table-column>
<el-table-column prop="majorName" label="专业" show-overflow-tooltip>
<template #header>
<el-icon><Briefcase /></el-icon>
<span style="margin-left: 4px">专业</span>
</template>
</el-table-column>
<el-table-column prop="className" label="班级" show-overflow-tooltip>
<template #header>
<el-icon><Grid /></el-icon>
<span style="margin-left: 4px">班级</span>
</template>
</el-table-column>
<el-table-column prop="stuNo" label="学号" show-overflow-tooltip width="130">
<template #header>
<el-icon><Document /></el-icon>
<span style="margin-left: 4px">学号</span>
</template>
</el-table-column>
<el-table-column prop="stuName" label="姓名" show-overflow-tooltip>
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">姓名</span>
</template>
</el-table-column>
<el-table-column prop="idCard" label="身份证" show-overflow-tooltip width="180">
<template #header>
<el-icon><CreditCard /></el-icon>
<span style="margin-left: 4px">身份证</span>
</template>
</el-table-column>
<el-table-column prop="phone" label="电话" show-overflow-tooltip width="130">
<template #header>
<el-icon><Phone /></el-icon>
<span style="margin-left: 4px">电话</span>
</template>
</el-table-column>
<el-table-column prop="parentPhone" label="家长电话" show-overflow-tooltip width="140">
<template #header>
<el-icon><Phone /></el-icon>
<span style="margin-left: 4px">家长电话</span>
</template>
</el-table-column>
<el-table-column prop="gender" label="性别" width="90" align="center">
<template #header>
<el-icon><User /></el-icon>
<span style="margin-left: 4px">性别</span>
</template>
<template #default="scope">
<GenderTag :gender="scope.row.gender" />
</template>
</el-table-column>
<el-table-column prop="dormNo" label="宿舍号" show-overflow-tooltip width="120">
<template #header>
<el-icon><HomeFilled /></el-icon>
<span style="margin-left: 4px">宿舍号</span>
</template>
</el-table-column>
<el-table-column prop="teacherName" label="班主任" show-overflow-tooltip>
<template #header>
<el-icon><UserFilled /></el-icon>
<span style="margin-left: 4px">班主任</span>
</template>
</el-table-column>
<el-table-column prop="teacherPhone" label="班主任电话" show-overflow-tooltip width="140">
<template #header>
<el-icon><Phone /></el-icon>
<span style="margin-left: 4px">班主任电话</span>
</template>
</el-table-column>
<el-table-column prop="schoolRollStatus" label="学籍状态" show-overflow-tooltip width="110" align="center">
<template #header>
<el-icon><CircleCheck /></el-icon>
<span style="margin-left: 4px">学籍状态</span>
</template>
<template #default="scope">
<StatusTag :status="scope.row.schoolRollStatus" :options="rollStatusOptions" />
</template>
</el-table-column>
<el-table-column prop="stuStatus" label="学生状态" show-overflow-tooltip width="110" align="center">
<template #header>
<el-icon><Tickets /></el-icon>
<span style="margin-left: 4px">学生状态</span>
</template>
<template #default="scope">
<StatusTag :status="scope.row.stuStatus" :options="stuStatusOptions" />
</template>
</el-table-column>
<el-table-column prop="avatarUrl" label="头像" width="120" align="center">
<template #header>
<el-icon><Picture /></el-icon>
<span style="margin-left: 4px">头像</span>
</template>
<template #default="scope">
<el-image
v-if="scope.row.avatarUrl"
:src="scope.row.avatarUrl"
:preview-src-list="[scope.row.avatarUrl]"
fit="cover"
style="width: 60px; height: 60px; border-radius: 6px"
/>
<el-tag v-else type="info" effect="plain"></el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right" align="center">
<template #header>
<el-icon><Setting /></el-icon>
<span style="margin-left: 4px">操作</span>
</template>
<template #default>
<span style="color: #999"></span>
</template>
</el-table-column>
</el-table>
<pagination
v-show="state.total > 0"
:total="state.total"
v-model:page="state.page"
v-model:limit="state.limit"
@pagination="getDataList" />
</el-card>
</div>
<pagination v-show="state.total > 0" :total="state.total" v-model:page="state.page" v-model:limit="state.limit" @pagination="getDataList" />
</el-card>
</div>
</template>
<script setup lang="ts" name="QueryStuindex">
import { ref, reactive } from 'vue'
import { Search, Refresh, List, OfficeBuilding, Grid, Document, UserFilled, CreditCard, Phone, User, HomeFilled, CircleCheck, Tickets, Setting, Picture, Briefcase } from '@element-plus/icons-vue'
import { BasicTableProps, useTable } from '/@/hooks/table'
import { queryStuindex } from '/@/api/basic/basicstudent'
import GenderTag from '/@/components/GenderTag/index.vue'
import StatusTag from '/@/components/StatusTag/index.vue'
import { ref, reactive } from 'vue';
import {
Search,
Refresh,
List,
OfficeBuilding,
Grid,
Document,
UserFilled,
CreditCard,
Phone,
User,
HomeFilled,
CircleCheck,
Tickets,
Setting,
Picture,
Briefcase,
} from '@element-plus/icons-vue';
import { BasicTableProps, useTable } from '/@/hooks/table';
import { queryStuindex } from '/@/api/basic/basicstudent';
import GenderTag from '/@/components/GenderTag/index.vue';
import StatusTag from '/@/components/StatusTag/index.vue';
// 搜索表单
const searchFormRef = ref()
const searchFormRef = ref();
const searchForm = reactive({
stuNo: '',
stuName: '',
idCard: '',
phone: '',
parentPhone: ''
})
stuNo: '',
stuName: '',
idCard: '',
phone: '',
parentPhone: '',
});
// 状态映射(可根据实际字典调整)
const rollStatusOptions = [
{ value: '0', label: '在籍', type: 'success' },
{ value: '1', label: '离校', type: 'warning' },
{ value: '2', label: '休学', type: 'info' }
]
{ value: '0', label: '在籍', type: 'success' },
{ value: '1', label: '离校', type: 'warning' },
{ value: '2', label: '休学', type: 'info' },
];
const stuStatusOptions = [
{ value: '0', label: '正常', type: 'success' },
{ value: '1', label: '预警', type: 'warning' },
{ value: '2', label: '异常', type: 'danger' }
]
{ value: '0', label: '正常', type: 'success' },
{ value: '1', label: '预警', type: 'warning' },
{ value: '2', label: '异常', type: 'danger' },
];
// 表格配置
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: searchForm,
pageList: queryStuindex,
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
queryForm: searchForm,
pageList: queryStuindex,
props: {
item: 'records',
totalCount: 'total',
},
createdIsNeed: true,
});
const {
getDataList,
sortChangeHandle,
tableStyle
} = useTable(state)
const { getDataList, sortChangeHandle, tableStyle } = useTable(state);
const handleSearch = () => {
state.page = 1
getDataList()
}
state.page = 1;
getDataList();
};
const handleReset = () => {
searchFormRef.value?.resetFields()
state.page = 1
getDataList()
}
searchFormRef.value?.resetFields();
state.page = 1;
getDataList();
};
</script>
<style scoped>
.app-container {
padding: 16px;
padding: 16px;
}
.mb12 {
margin-bottom: 12px;
margin-bottom: 12px;
}
</style>

View File

@@ -1,270 +1,245 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<!-- 搜索表单卡片 -->
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch" class="search-form">
<el-form-item label="班级" prop="classCode">
<el-select
v-model="searchForm.classCode"
placeholder="请选择班级"
clearable
filterable
style="width: 200px">
<el-option
v-for="item in classList"
:key="item.classCode"
:label="item.classNo"
:value="item.classCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="modern-page-container">
<div class="page-wrapper">
<!-- 搜索表单卡片 -->
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch" class="search-form">
<el-form-item label="班级" prop="classCode">
<el-select v-model="searchForm.classCode" placeholder="请选择班级" clearable filterable style="width: 200px">
<el-option v-for="item in classList" :key="item.classCode" :label="item.classNo" :value="item.classCode"> </el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 内容卡片 -->
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
学生头像列表
</span>
<div class="header-actions">
<right-toolbar
v-model:showSearch="showSearch"
class="ml10"
@queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0;">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</div>
</template>
<!-- 内容卡片 -->
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
学生头像列表
</span>
<div class="header-actions">
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList">
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip class="item" effect="dark" content="列设置" placement="top">
<el-button circle style="margin-left: 0">
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
</div>
</div>
</template>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table"
@sort-change="sortChangeHandle">
<el-table-column type="index" label="序号" width="70" align="center">
<template #header>
<el-icon><List /></el-icon>
</template>
<template #default="{ $index }">
{{ $index + 1 + ((state.pagination?.current || 1) - 1) * (state.pagination?.size || 10) }}
</template>
</el-table-column>
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align">
<template #header>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
<template #default="scope" v-if="col.prop === 'headImg'">
<el-image
v-if="scope.row.headImg || scope.row.imageUrl || scope.row.qrCode"
:src="scope.row.headImg || scope.row.imageUrl || scope.row.qrCode"
:preview-src-list="[scope.row.headImg || scope.row.imageUrl || scope.row.qrCode]"
fit="cover"
style="width: 80px; height: 100px; cursor: pointer;"
lazy>
<template #error>
<div class="image-slot">
<el-icon><Picture /></el-icon>
</div>
</template>
</el-image>
<span v-else>-</span>
</template>
</el-table-column>
</el-table>
<!-- 表格 -->
<el-table
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table"
@sort-change="sortChangeHandle"
>
<el-table-column type="index" label="序号" width="70" align="center">
<template #header>
<el-icon><List /></el-icon>
</template>
<template #default="{ $index }">
{{ $index + 1 + ((state.pagination?.current || 1) - 1) * (state.pagination?.size || 10) }}
</template>
</el-table-column>
<el-table-column
v-for="col in visibleColumnsSorted"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
:show-overflow-tooltip="col.showOverflowTooltip !== false"
:align="col.align"
>
<template #header>
<el-icon v-if="col.icon"><component :is="col.icon" /></el-icon>
<span :style="{ marginLeft: col.icon ? '4px' : '0' }">{{ col.label }}</span>
</template>
<template #default="scope" v-if="col.prop === 'headImg'">
<el-image
v-if="scope.row.headImg || scope.row.imageUrl || scope.row.qrCode"
:src="scope.row.headImg || scope.row.imageUrl || scope.row.qrCode"
:preview-src-list="[scope.row.headImg || scope.row.imageUrl || scope.row.qrCode]"
fit="cover"
style="width: 80px; height: 100px; cursor: pointer"
lazy
>
<template #error>
<div class="image-slot">
<el-icon><Picture /></el-icon>
</div>
</template>
</el-image>
<span v-else>-</span>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-wrapper">
<pagination
@size-change="sizeChangeHandle"
@current-change="currentChangeHandle"
v-bind="state.pagination" />
</div>
</el-card>
</div>
</div>
<!-- 分页 -->
<div class="pagination-wrapper">
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
</div>
</el-card>
</div>
</div>
</template>
<script setup lang="ts" name="BasicStudentAvatar">
import { ref, reactive, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { fetchList } from "/@/api/basic/basicstudentavatar";
import { getClassListByRole } from "/@/api/basic/basicclass";
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
import { Picture, List, Document, UserFilled, Grid, Menu, Search } from '@element-plus/icons-vue'
import { useTableColumnControl } from '/@/hooks/tableColumn'
import { ref, reactive, computed, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { BasicTableProps, useTable } from '/@/hooks/table';
import { fetchList } from '/@/api/basic/basicstudentavatar';
import { getClassListByRole } from '/@/api/basic/basicclass';
import TableColumnControl from '/@/components/TableColumnControl/index.vue';
import { Picture, List, Document, UserFilled, Grid, Menu, Search } from '@element-plus/icons-vue';
import { useTableColumnControl } from '/@/hooks/tableColumn';
// 定义变量内容
const route = useRoute()
const searchFormRef = ref()
const columnControlRef = ref()
const showSearch = ref(true)
const classList = ref<any[]>([])
const route = useRoute();
const searchFormRef = ref();
const columnControlRef = ref();
const showSearch = ref(true);
const classList = ref<any[]>([]);
// 表格列配置
const tableColumns = [
{ prop: 'stuNo', label: '学号', icon: Document },
{ prop: 'realName', label: '姓名', icon: UserFilled },
{ prop: 'className', label: '班级', icon: Grid },
{ prop: 'headImg', label: '头像', icon: Picture, width: 120 }
]
{ prop: 'stuNo', label: '学号', icon: Document },
{ prop: 'realName', label: '姓名', icon: UserFilled },
{ prop: 'className', label: '班级', icon: Grid },
{ prop: 'headImg', label: '头像', icon: Picture, width: 120 },
];
// 使用表格列控制hook
const {
visibleColumns,
visibleColumnsSorted,
checkColumnVisible,
handleColumnChange,
handleColumnOrderChange
} = useTableColumnControl(tableColumns)
const { visibleColumns, visibleColumnsSorted, checkColumnVisible, handleColumnChange, handleColumnOrderChange } = useTableColumnControl(tableColumns);
// 搜索表单
const searchForm = reactive({
classCode: ''
})
classCode: '',
});
// 配置 useTable - 接口返回的数据结构是 { classes: [], students: [] }
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: searchForm,
pageList: async (queryParams: any) => {
const res = await fetchList(queryParams)
// 接口返回的数据结构是 { classes: [], students: [] }
// 需要将 students 数组转换为表格数据,并关联班级信息
if (res.data && res.data.students) {
const students = res.data.students || []
const classes = res.data.classes || []
const classMap = new Map()
classes.forEach((cls: any) => {
classMap.set(cls.classCode, cls)
})
// 将学生数据与班级信息合并
const dataList = students.map((stu: any) => {
const classInfo = classMap.get(stu.classCode)
return {
...stu,
className: classInfo ? classInfo.classNo : stu.className || '',
classNo: classInfo ? classInfo.classNo : ''
}
})
return {
...res,
data: {
records: dataList,
total: dataList.length,
current: 1,
size: dataList.length,
pages: 1
}
}
}
return res
},
props: {
item: 'records',
totalCount: 'total'
},
createdIsNeed: true
})
queryForm: searchForm,
pageList: async (queryParams: any) => {
const res = await fetchList(queryParams);
// 接口返回的数据结构是 { classes: [], students: [] }
// 需要将 students 数组转换为表格数据,并关联班级信息
if (res.data && res.data.students) {
const students = res.data.students || [];
const classes = res.data.classes || [];
const classMap = new Map();
classes.forEach((cls: any) => {
classMap.set(cls.classCode, cls);
});
// 将学生数据与班级信息合并
const dataList = students.map((stu: any) => {
const classInfo = classMap.get(stu.classCode);
return {
...stu,
className: classInfo ? classInfo.classNo : stu.className || '',
classNo: classInfo ? classInfo.classNo : '',
};
});
return {
...res,
data: {
records: dataList,
total: dataList.length,
current: 1,
size: dataList.length,
pages: 1,
},
};
}
return res;
},
props: {
item: 'records',
totalCount: 'total',
},
createdIsNeed: true,
});
// table hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
sortChangeHandle,
tableStyle
} = useTable(state)
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle } = useTable(state);
// 查询
const handleSearch = () => {
getDataList()
}
getDataList();
};
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
searchForm.classCode = ''
getDataList()
}
searchFormRef.value?.resetFields();
searchForm.classCode = '';
getDataList();
};
// 获取班级列表
const getClassListData = async () => {
try {
const res = await getClassListByRole()
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : []
}
} catch (err) {
console.error('获取班级列表失败', err)
classList.value = []
}
}
try {
const res = await getClassListByRole();
if (res.data) {
classList.value = Array.isArray(res.data) ? res.data : [];
}
} catch (err) {
console.error('获取班级列表失败', err);
classList.value = [];
}
};
// 初始化
onMounted(() => {
getClassListData()
})
getClassListData();
});
</script>
<style lang="scss" scoped>
@import '/@/assets/styles/modern-page.scss';
.image-slot {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background: #f5f7fa;
color: #909399;
font-size: 30px;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background: #f5f7fa;
color: #909399;
font-size: 30px;
}
</style>

View File

@@ -1,502 +1,462 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<!-- 搜索表单卡片 -->
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch" class="search-form">
<el-form-item label="批次号" prop="batchNo">
<el-input
v-model="searchForm.batchNo"
placeholder="请输入批次号"
clearable
style="width: 180px" />
</el-form-item>
<el-form-item label="学号" prop="stuNo">
<el-input
v-model="searchForm.stuNo"
placeholder="请输入学号"
clearable
style="width: 150px" />
</el-form-item>
<el-form-item label="姓名" prop="systemRealName">
<el-input
v-model="searchForm.systemRealName"
placeholder="请输入姓名"
clearable
style="width: 120px" />
</el-form-item>
<el-form-item label="异常类型" prop="exceptionType">
<el-select
v-model="searchForm.exceptionType"
placeholder="请选择异常类型"
clearable
style="width: 150px">
<el-option label="姓名不一致" value="1" />
<el-option label="身份证不一致" value="2" />
<el-option label="学籍号不一致" value="3" />
<el-option label="毕业院校不一致" value="4" />
<el-option label="系统有学籍无" value="5" />
<el-option label="学籍有系统无" value="6" />
</el-select>
</el-form-item>
<el-form-item label="复核状态" prop="reviewStatus">
<el-select
v-model="searchForm.reviewStatus"
placeholder="请选择复核状态"
clearable
style="width: 120px">
<el-option label="待复核" value="0" />
<el-option label="已复核" value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="modern-page-container">
<div class="page-wrapper">
<!-- 搜索表单卡片 -->
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch" class="search-form">
<el-form-item label="批次号" prop="batchNo">
<el-input v-model="searchForm.batchNo" placeholder="请输入批次号" clearable style="width: 180px" />
</el-form-item>
<el-form-item label="学号" prop="stuNo">
<el-input v-model="searchForm.stuNo" placeholder="请输入学号" clearable style="width: 150px" />
</el-form-item>
<el-form-item label="姓名" prop="systemRealName">
<el-input v-model="searchForm.systemRealName" placeholder="请输入姓名" clearable style="width: 120px" />
</el-form-item>
<el-form-item label="异常类型" prop="exceptionType">
<el-select v-model="searchForm.exceptionType" placeholder="请选择异常类型" clearable style="width: 150px">
<el-option label="姓名不一致" value="1" />
<el-option label="身份证不一致" value="2" />
<el-option label="学籍号不一致" value="3" />
<el-option label="毕业院校不一致" value="4" />
<el-option label="系统有学籍无" value="5" />
<el-option label="学籍有系统无" value="6" />
</el-select>
</el-form-item>
<el-form-item label="复核状态" prop="reviewStatus">
<el-select v-model="searchForm.reviewStatus" placeholder="请选择复核状态" clearable style="width: 120px">
<el-option label="待复核" value="0" />
<el-option label="已复核" value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 内容卡片 -->
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><DocumentChecked /></el-icon>
学籍比对异常数据
</span>
<div class="header-actions">
<el-upload
:show-file-list="false"
:before-upload="beforeUpload"
:http-request="handleImport"
accept=".xlsx,.xls"
class="upload-btn">
<el-button type="primary" icon="Upload" :loading="importLoading">导入比对</el-button>
</el-upload>
<el-button type="success" icon="Download" @click="handleDownloadTemplate">下载模板</el-button>
<el-button type="warning" icon="Download" @click="handleExport">导出异常</el-button>
<el-button type="info" icon="Bell" @click="handleSend" :disabled="!searchForm.batchNo">下发班主任</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10"
@queryTable="getDataList" />
</div>
</div>
</template>
<!-- 内容卡片 -->
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><DocumentChecked /></el-icon>
学籍比对异常数据
</span>
<div class="header-actions">
<el-upload :show-file-list="false" :before-upload="beforeUpload" :http-request="handleImport" accept=".xlsx,.xls" class="upload-btn">
<el-button type="primary" icon="Upload" :loading="importLoading">导入比对</el-button>
</el-upload>
<el-button type="success" icon="Download" @click="handleDownloadTemplate">下载模板</el-button>
<el-button type="warning" icon="Download" @click="handleExport">导出异常</el-button>
<el-button type="info" icon="Bell" @click="handleSend" :disabled="!searchForm.batchNo">下发班主任</el-button>
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList" />
</div>
</div>
</template>
<!-- 统计卡片 -->
<el-row :gutter="16" class="stat-row">
<el-col :span="4">
<el-statistic title="总异常数" :value="statistics.total" />
</el-col>
<el-col :span="4">
<el-statistic title="待复核" :value="statistics.pending" />
</el-col>
<el-col :span="4">
<el-statistic title="已复核" :value="statistics.reviewed" />
</el-col>
</el-row>
<!-- 统计卡片 -->
<el-row :gutter="16" class="stat-row">
<el-col :span="4">
<el-statistic title="总异常数" :value="statistics.total" />
</el-col>
<el-col :span="4">
<el-statistic title="待复核" :value="statistics.pending" />
</el-col>
<el-col :span="4">
<el-statistic title="已复核" :value="statistics.reviewed" />
</el-col>
</el-row>
<!-- 表格 -->
<el-table
:data="dataList"
v-loading="loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table">
<el-table-column type="index" label="序号" width="70" align="center">
<template #default="{ $index }">
{{ (page.currentPage - 1) * page.pageSize + $index + 1 }}
</template>
</el-table-column>
<el-table-column prop="batchNo" label="批次号" width="160" align="center" />
<el-table-column prop="stuNo" label="号" width="120" align="center" />
<el-table-column label="姓名" width="160" align="center">
<template #default="{ row }">
<div>
<span v-if="row.systemRealName">{{ row.systemRealName }}</span>
<span v-if="row.systemRealName && row.enrollRealName && row.systemRealName !== row.enrollRealName"
class="text-danger">
/ {{ row.enrollRealName }}
</span>
<span v-else-if="row.enrollRealName && !row.systemRealName">{{ row.enrollRealName }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="身份证号" width="200" align="center">
<template #default="{ row }">
<div>
<div v-if="row.systemIdCard">系统{{ row.systemIdCard }}</div>
<div v-if="row.enrollIdCard && row.enrollIdCard !== row.systemIdCard" class="text-warning">
学籍{{ row.enrollIdCard }}
</div>
</div>
</template>
</el-table-column>
<el-table-column label="学籍号" width="200" align="center">
<template #default="{ row }">
<div>
<div v-if="row.systemEnrollNo">系统{{ row.systemEnrollNo }}</div>
<div v-if="row.nationalEnrollNo && row.nationalEnrollNo !== row.systemEnrollNo" class="text-warning">
全国{{ row.nationalEnrollNo }}
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="className" label="班级" width="150" />
<el-table-column prop="classMasterName" label="班主任" width="100" align="center" />
<el-table-column prop="exceptionType" label="异常类型" width="140" align="center">
<template #default="{ row }">
<el-tag :type="getExceptionTagType(row.exceptionType)" size="small">
{{ getExceptionTypeName(row.exceptionType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="exceptionDesc" label="异常说明" min-width="250" show-overflow-tooltip />
<el-table-column prop="reviewStatus" label="复核状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.reviewStatus === '1' ? 'success' : 'warning'" size="small">
{{ row.reviewStatus === '1' ? '已复核' : '待复核' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="reviewResult" label="复核结果" width="150" show-overflow-tooltip />
<el-table-column label="操作" width="120" align="center" fixed="right">
<template #default="{ row }">
<el-button
v-if="row.reviewStatus === '0'"
icon="Edit"
link
type="primary"
@click="handleReview(row)">
复核
</el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="暂无数据" :image-size="120" />
</template>
</el-table>
<!-- 表格 -->
<el-table
:data="dataList"
v-loading="loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table"
>
<el-table-column type="index" label="序号" width="70" align="center">
<template #default="{ $index }">
{{ (page.currentPage - 1) * page.pageSize + $index + 1 }}
</template>
</el-table-column>
<el-table-column prop="batchNo" label="批次号" width="160" align="center" />
<el-table-column prop="stuNo" label="学号" width="120" align="center" />
<el-table-column label="姓名" width="160" align="center">
<template #default="{ row }">
<div>
<span v-if="row.systemRealName">{{ row.systemRealName }}</span>
<span v-if="row.systemRealName && row.enrollRealName && row.systemRealName !== row.enrollRealName" class="text-danger">
/ {{ row.enrollRealName }}
</span>
<span v-else-if="row.enrollRealName && !row.systemRealName">{{ row.enrollRealName }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="身份证号" width="200" align="center">
<template #default="{ row }">
<div>
<div v-if="row.systemIdCard">系统{{ row.systemIdCard }}</div>
<div v-if="row.enrollIdCard && row.enrollIdCard !== row.systemIdCard" class="text-warning">学籍{{ row.enrollIdCard }}</div>
</div>
</template>
</el-table-column>
<el-table-column label="学籍号" width="200" align="center">
<template #default="{ row }">
<div>
<div v-if="row.systemEnrollNo">系统{{ row.systemEnrollNo }}</div>
<div v-if="row.nationalEnrollNo && row.nationalEnrollNo !== row.systemEnrollNo" class="text-warning">
全国{{ row.nationalEnrollNo }}
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="className" label="班级" width="150" />
<el-table-column prop="classMasterName" label="班主任" width="100" align="center" />
<el-table-column prop="exceptionType" label="异常类型" width="140" align="center">
<template #default="{ row }">
<el-tag :type="getExceptionTagType(row.exceptionType)" size="small">
{{ getExceptionTypeName(row.exceptionType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="exceptionDesc" label="异常说明" min-width="250" show-overflow-tooltip />
<el-table-column prop="reviewStatus" label="复核状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.reviewStatus === '1' ? 'success' : 'warning'" size="small">
{{ row.reviewStatus === '1' ? '已复核' : '待复核' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="reviewResult" label="复核结果" width="150" show-overflow-tooltip />
<el-table-column label="操作" width="120" align="center" fixed="right">
<template #default="{ row }">
<el-button v-if="row.reviewStatus === '0'" icon="Edit" link type="primary" @click="handleReview(row)"> 复核 </el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="暂无数据" :image-size="120" />
</template>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="page.currentPage"
v-model:page-size="page.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="page.total"
layout="total, sizes, prev, pager, next, jumper"
class="pagination"
@size-change="handleSizeChange"
@current-change="handleCurrentChange" />
</el-card>
</div>
<!-- 分页 -->
<el-pagination
v-model:current-page="page.currentPage"
v-model:page-size="page.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="page.total"
layout="total, sizes, prev, pager, next, jumper"
class="pagination"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
</div>
<!-- 复核弹窗 -->
<el-dialog
v-model="reviewDialogVisible"
title="学籍信息复核"
width="600px"
:close-on-click-modal="false">
<el-descriptions :column="2" border>
<el-descriptions-item label="学号">{{ reviewData.stuNo }}</el-descriptions-item>
<el-descriptions-item label="系统姓名">{{ reviewData.systemRealName }}</el-descriptions-item>
<el-descriptions-item label="学籍姓名">{{ reviewData.enrollRealName }}</el-descriptions-item>
<el-descriptions-item label="班级">{{ reviewData.className }}</el-descriptions-item>
<el-descriptions-item label="系统身份证">{{ reviewData.systemIdCard }}</el-descriptions-item>
<el-descriptions-item label="学籍身份证">{{ reviewData.enrollIdCard }}</el-descriptions-item>
<el-descriptions-item label="系统学籍号">{{ reviewData.systemEnrollNo }}</el-descriptions-item>
<el-descriptions-item label="全国学籍号">{{ reviewData.nationalEnrollNo }}</el-descriptions-item>
<el-descriptions-item label="异常类型" :span="2">
<el-tag :type="getExceptionTagType(reviewData.exceptionType)">
{{ getExceptionTypeName(reviewData.exceptionType) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="异常说明" :span="2">{{ reviewData.exceptionDesc }}</el-descriptions-item>
</el-descriptions>
<el-form :model="reviewForm" label-width="80px" class="review-form">
<el-form-item label="复核结果">
<el-radio-group v-model="reviewForm.reviewResult">
<el-radio label="信息无误">信息无误</el-radio>
<el-radio label="需修改系统">需修改系统</el-radio>
<el-radio label="需修改学籍">需修改学籍</el-radio>
<el-radio label="其他问题">其他问题</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="reviewDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitReview" :loading="reviewLoading">确定</el-button>
</template>
</el-dialog>
</div>
<!-- 复核弹窗 -->
<el-dialog v-model="reviewDialogVisible" title="学籍信息复核" width="600px" :close-on-click-modal="false">
<el-descriptions :column="2" border>
<el-descriptions-item label="学号">{{ reviewData.stuNo }}</el-descriptions-item>
<el-descriptions-item label="系统姓名">{{ reviewData.systemRealName }}</el-descriptions-item>
<el-descriptions-item label="学籍姓名">{{ reviewData.enrollRealName }}</el-descriptions-item>
<el-descriptions-item label="班级">{{ reviewData.className }}</el-descriptions-item>
<el-descriptions-item label="系统身份证">{{ reviewData.systemIdCard }}</el-descriptions-item>
<el-descriptions-item label="学籍身份证">{{ reviewData.enrollIdCard }}</el-descriptions-item>
<el-descriptions-item label="系统学籍号">{{ reviewData.systemEnrollNo }}</el-descriptions-item>
<el-descriptions-item label="全国学籍号">{{ reviewData.nationalEnrollNo }}</el-descriptions-item>
<el-descriptions-item label="异常类型" :span="2">
<el-tag :type="getExceptionTagType(reviewData.exceptionType)">
{{ getExceptionTypeName(reviewData.exceptionType) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="异常说明" :span="2">{{ reviewData.exceptionDesc }}</el-descriptions-item>
</el-descriptions>
<el-form :model="reviewForm" label-width="80px" class="review-form">
<el-form-item label="复核结果">
<el-radio-group v-model="reviewForm.reviewResult">
<el-radio label="信息无误">信息无误</el-radio>
<el-radio label="需修改系统">需修改系统</el-radio>
<el-radio label="需修改学籍">需修改学籍</el-radio>
<el-radio label="其他问题">其他问题</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="reviewDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitReview" :loading="reviewLoading">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="EnrollCompare">
import { reactive, ref, onMounted, computed } from 'vue'
import { fetchList, importData, exportData, sendToClassMaster, submitReview as submitReviewApi, downloadTemplate } from '/@/api/basic/enrollcompare'
import { useMessage, useMessageBox } from '/@/hooks/message'
import { Search, DocumentChecked, Upload, Download, Bell, Edit } from '@element-plus/icons-vue'
import { reactive, ref, onMounted, computed } from 'vue';
import { fetchList, importData, exportData, sendToClassMaster, submitReview as submitReviewApi, downloadTemplate } from '/@/api/basic/enrollcompare';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { Search, DocumentChecked, Upload, Download, Bell, Edit } from '@element-plus/icons-vue';
// 定义变量内容
const searchFormRef = ref()
const showSearch = ref(true)
const loading = ref(false)
const importLoading = ref(false)
const reviewLoading = ref(false)
const dataList = ref<any[]>([])
const reviewDialogVisible = ref(false)
const reviewData = ref<any>({})
const searchFormRef = ref();
const showSearch = ref(true);
const loading = ref(false);
const importLoading = ref(false);
const reviewLoading = ref(false);
const dataList = ref<any[]>([]);
const reviewDialogVisible = ref(false);
const reviewData = ref<any>({});
const reviewForm = reactive({
id: '',
reviewResult: '信息无误'
})
id: '',
reviewResult: '信息无误',
});
// 统计数据
const statistics = reactive({
total: 0,
pending: 0,
reviewed: 0
})
total: 0,
pending: 0,
reviewed: 0,
});
// 分页
const page = reactive({
currentPage: 1,
pageSize: 20,
total: 0
})
currentPage: 1,
pageSize: 20,
total: 0,
});
// 搜索表单
const searchForm = reactive({
batchNo: '',
stuNo: '',
systemRealName: '',
exceptionType: '',
reviewStatus: ''
})
batchNo: '',
stuNo: '',
systemRealName: '',
exceptionType: '',
reviewStatus: '',
});
// 表格样式
const tableStyle = {
cellStyle: { textAlign: 'center' },
headerCellStyle: {
textAlign: 'center',
background: 'var(--el-table-row-hover-bg-color)',
color: 'var(--el-text-color-primary)'
}
}
cellStyle: { textAlign: 'center' },
headerCellStyle: {
textAlign: 'center',
background: 'var(--el-table-row-hover-bg-color)',
color: 'var(--el-text-color-primary)',
},
};
// 获取异常类型名称
const getExceptionTypeName = (type: string) => {
const map: Record<string, string> = {
'1': '姓名不一致',
'2': '身份证不一致',
'3': '学籍号不一致',
'4': '毕业院校不一致',
'5': '系统有学籍无',
'6': '学籍有系统无'
}
return map[type] || type
}
const map: Record<string, string> = {
'1': '姓名不一致',
'2': '身份证不一致',
'3': '学籍号不一致',
'4': '毕业院校不一致',
'5': '系统有学籍无',
'6': '学籍有系统无',
};
return map[type] || type;
};
// 获取异常类型标签颜色
const getExceptionTagType = (type: string) => {
const map: Record<string, string> = {
'1': 'danger',
'2': 'danger',
'3': 'warning',
'4': 'info',
'5': 'warning',
'6': 'warning'
}
return map[type] || 'info'
}
const map: Record<string, string> = {
'1': 'danger',
'2': 'danger',
'3': 'warning',
'4': 'info',
'5': 'warning',
'6': 'warning',
};
return map[type] || 'info';
};
// 查询
const handleSearch = () => {
page.currentPage = 1
getDataList()
}
page.currentPage = 1;
getDataList();
};
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
getDataList()
}
searchFormRef.value?.resetFields();
getDataList();
};
// 分页
const handleSizeChange = (val: number) => {
page.pageSize = val
getDataList()
}
page.pageSize = val;
getDataList();
};
const handleCurrentChange = (val: number) => {
page.currentPage = val
getDataList()
}
page.currentPage = val;
getDataList();
};
// 获取数据列表
const getDataList = async () => {
loading.value = true
try {
const res = await fetchList({
current: page.currentPage,
size: page.pageSize,
...searchForm
})
if (res.data && res.data.records) {
dataList.value = res.data.records
page.total = res.data.total
loading.value = true;
try {
const res = await fetchList({
current: page.currentPage,
size: page.pageSize,
...searchForm,
});
if (res.data && res.data.records) {
dataList.value = res.data.records;
page.total = res.data.total;
// 计算统计数据
statistics.total = res.data.total
statistics.pending = dataList.value.filter((item: any) => item.reviewStatus === '0').length
statistics.reviewed = dataList.value.filter((item: any) => item.reviewStatus === '1').length
} else {
dataList.value = []
page.total = 0
}
} catch (err: any) {
useMessage().error(err.msg || '获取数据失败')
} finally {
loading.value = false
}
}
// 计算统计数据
statistics.total = res.data.total;
statistics.pending = dataList.value.filter((item: any) => item.reviewStatus === '0').length;
statistics.reviewed = dataList.value.filter((item: any) => item.reviewStatus === '1').length;
} else {
dataList.value = [];
page.total = 0;
}
} catch (err: any) {
useMessage().error(err.msg || '获取数据失败');
} finally {
loading.value = false;
}
};
// 上传前验证
const beforeUpload = (file: File) => {
const isExcel = file.name.endsWith('.xlsx') || file.name.endsWith('.xls')
if (!isExcel) {
useMessage().error('只能上传Excel文件')
return false
}
return true
}
const isExcel = file.name.endsWith('.xlsx') || file.name.endsWith('.xls');
if (!isExcel) {
useMessage().error('只能上传Excel文件');
return false;
}
return true;
};
// 导入比对
const handleImport = async (options: any) => {
importLoading.value = true
try {
const res = await importData(options.file)
useMessage().success(res.msg || '导入比对成功')
// 自动填充批次号
if (res.data) {
searchForm.batchNo = ''
}
getDataList()
} catch (err: any) {
useMessage().error(err.msg || '导入失败')
} finally {
importLoading.value = false
}
}
importLoading.value = true;
try {
const res = await importData(options.file);
useMessage().success(res.msg || '导入比对成功');
// 自动填充批次号
if (res.data) {
searchForm.batchNo = '';
}
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '导入失败');
} finally {
importLoading.value = false;
}
};
// 下载模板
const handleDownloadTemplate = async () => {
try {
const res = await downloadTemplate()
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '全国学籍导入模板.xlsx'
link.click()
window.URL.revokeObjectURL(url)
} catch (err: any) {
useMessage().error(err.msg || '下载失败')
}
}
try {
const res = await downloadTemplate();
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = '全国学籍导入模板.xlsx';
link.click();
window.URL.revokeObjectURL(url);
} catch (err: any) {
useMessage().error(err.msg || '下载失败');
}
};
// 导出
const handleExport = async () => {
try {
const res = await exportData(searchForm)
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '学籍比对异常数据.xlsx'
link.click()
window.URL.revokeObjectURL(url)
} catch (err: any) {
useMessage().error(err.msg || '导出失败')
}
}
try {
const res = await exportData(searchForm);
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = '学籍比对异常数据.xlsx';
link.click();
window.URL.revokeObjectURL(url);
} catch (err: any) {
useMessage().error(err.msg || '导出失败');
}
};
// 下发班主任
const handleSend = async () => {
if (!searchForm.batchNo) {
useMessage().warning('请先选择批次号')
return
}
try {
await useMessageBox().confirm('确定要将该批次异常数据下发至班主任复核吗?')
await sendToClassMaster(searchForm.batchNo)
useMessage().success('下发成功')
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '下发失败')
}
}
}
if (!searchForm.batchNo) {
useMessage().warning('请先选择批次号');
return;
}
try {
await useMessageBox().confirm('确定要将该批次异常数据下发至班主任复核吗?');
await sendToClassMaster(searchForm.batchNo);
useMessage().success('下发成功');
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '下发失败');
}
}
};
// 复核
const handleReview = (row: any) => {
reviewData.value = row
reviewForm.id = row.id
reviewForm.reviewResult = '信息无误'
reviewDialogVisible.value = true
}
reviewData.value = row;
reviewForm.id = row.id;
reviewForm.reviewResult = '信息无误';
reviewDialogVisible.value = true;
};
// 提交复核
const submitReview = async () => {
reviewLoading.value = true
try {
await submitReviewApi(reviewForm.id, reviewForm.reviewResult)
useMessage().success('复核成功')
reviewDialogVisible.value = false
getDataList()
} catch (err: any) {
useMessage().error(err.msg || '复核失败')
} finally {
reviewLoading.value = false
}
}
reviewLoading.value = true;
try {
await submitReviewApi(reviewForm.id, reviewForm.reviewResult);
useMessage().success('复核成功');
reviewDialogVisible.value = false;
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '复核失败');
} finally {
reviewLoading.value = false;
}
};
// 初始化
onMounted(() => {
getDataList()
})
getDataList();
});
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
.stat-row {
margin-bottom: 16px;
margin-bottom: 16px;
}
.upload-btn {
display: inline-block;
margin-right: 10px;
display: inline-block;
margin-right: 10px;
}
.text-danger {
color: var(--el-color-danger);
color: var(--el-color-danger);
}
.text-warning {
color: var(--el-color-warning);
color: var(--el-color-warning);
}
.review-form {
margin-top: 16px;
margin-top: 16px;
}
</style>
</style>

View File

@@ -1,495 +1,450 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<!-- 搜索表单卡片 -->
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch" class="search-form">
<el-form-item label="批次号" prop="batchNo">
<el-input
v-model="searchForm.batchNo"
placeholder="请输入批次号"
clearable
style="width: 180px" />
</el-form-item>
<el-form-item label="学号" prop="stuNo">
<el-input
v-model="searchForm.stuNo"
placeholder="请输入学号"
clearable
style="width: 150px" />
</el-form-item>
<el-form-item label="姓名" prop="systemRealName">
<el-input
v-model="searchForm.systemRealName"
placeholder="请输入姓名"
clearable
style="width: 120px" />
</el-form-item>
<el-form-item label="入学年份" prop="enterYear">
<el-date-picker
v-model="searchForm.enterYear"
type="year"
placeholder="选择年份"
value-format="YYYY"
style="width: 120px" />
</el-form-item>
<el-form-item label="是否一致" prop="isMatch">
<el-select
v-model="searchForm.isMatch"
placeholder="请选择"
clearable
style="width: 120px">
<el-option label="一致" value="1" />
<el-option label="不一致" value="0" />
</el-select>
</el-form-item>
<el-form-item label="核对状态" prop="verifyStatus">
<el-select
v-model="searchForm.verifyStatus"
placeholder="请选择核对状态"
clearable
style="width: 120px">
<el-option label="待核对" value="0" />
<el-option label="已核对" value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="modern-page-container">
<div class="page-wrapper">
<!-- 搜索表单卡片 -->
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="searchForm" ref="searchFormRef" :inline="true" @keyup.enter="handleSearch" class="search-form">
<el-form-item label="批次号" prop="batchNo">
<el-input v-model="searchForm.batchNo" placeholder="请输入批次号" clearable style="width: 180px" />
</el-form-item>
<el-form-item label="学号" prop="stuNo">
<el-input v-model="searchForm.stuNo" placeholder="请输入学号" clearable style="width: 150px" />
</el-form-item>
<el-form-item label="姓名" prop="systemRealName">
<el-input v-model="searchForm.systemRealName" placeholder="请输入姓名" clearable style="width: 120px" />
</el-form-item>
<el-form-item label="入学年份" prop="enterYear">
<el-date-picker v-model="searchForm.enterYear" type="year" placeholder="选择年份" value-format="YYYY" style="width: 120px" />
</el-form-item>
<el-form-item label="是否一致" prop="isMatch">
<el-select v-model="searchForm.isMatch" placeholder="请选择" clearable style="width: 120px">
<el-option label="一致" value="1" />
<el-option label="不一致" value="0" />
</el-select>
</el-form-item>
<el-form-item label="核对状态" prop="verifyStatus">
<el-select v-model="searchForm.verifyStatus" placeholder="请选择核对状态" clearable style="width: 120px">
<el-option label="待核对" value="0" />
<el-option label="已核对" value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleSearch">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 内容卡片 -->
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><User /></el-icon>
毕业学生信息核对
</span>
<div class="header-actions">
<el-upload
:show-file-list="false"
:before-upload="beforeUpload"
:http-request="handleImport"
accept=".xlsx,.xls"
class="upload-btn">
<el-button type="primary" icon="Upload" :loading="importLoading">导入核对</el-button>
</el-upload>
<el-button type="success" icon="Download" @click="handleDownloadTemplate">下载模板</el-button>
<el-button type="warning" icon="Download" @click="handleExport">导出数据</el-button>
<el-button type="info" icon="Bell" @click="handleSend" :disabled="!searchForm.batchNo">下发班主任</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10"
@queryTable="getDataList" />
</div>
</div>
</template>
<!-- 内容卡片 -->
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><User /></el-icon>
毕业学生信息核对
</span>
<div class="header-actions">
<el-upload :show-file-list="false" :before-upload="beforeUpload" :http-request="handleImport" accept=".xlsx,.xls" class="upload-btn">
<el-button type="primary" icon="Upload" :loading="importLoading">导入核对</el-button>
</el-upload>
<el-button type="success" icon="Download" @click="handleDownloadTemplate">下载模板</el-button>
<el-button type="warning" icon="Download" @click="handleExport">导出数据</el-button>
<el-button type="info" icon="Bell" @click="handleSend" :disabled="!searchForm.batchNo">下发班主任</el-button>
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList" />
</div>
</div>
</template>
<!-- 统计卡片 -->
<el-row :gutter="16" class="stat-row">
<el-col :span="4">
<el-statistic title="总核对数" :value="statistics.total" />
</el-col>
<el-col :span="4">
<el-statistic title="信息一致" :value="statistics.matched">
<template #suffix>
<span class="text-success"></span>
</template>
</el-statistic>
</el-col>
<el-col :span="4">
<el-statistic title="信息不一致" :value="statistics.mismatched">
<template #suffix>
<span class="text-danger"></span>
</template>
</el-statistic>
</el-col>
<el-col :span="4">
<el-statistic title="待核对" :value="statistics.pending" />
</el-col>
<el-col :span="4">
<el-statistic title="已核对" :value="statistics.verified" />
</el-col>
</el-row>
<!-- 统计卡片 -->
<el-row :gutter="16" class="stat-row">
<el-col :span="4">
<el-statistic title="总核对数" :value="statistics.total" />
</el-col>
<el-col :span="4">
<el-statistic title="信息一致" :value="statistics.matched">
<template #suffix>
<span class="text-success"></span>
</template>
</el-statistic>
</el-col>
<el-col :span="4">
<el-statistic title="信息不一致" :value="statistics.mismatched">
<template #suffix>
<span class="text-danger"></span>
</template>
</el-statistic>
</el-col>
<el-col :span="4">
<el-statistic title="待核对" :value="statistics.pending" />
</el-col>
<el-col :span="4">
<el-statistic title="已核对" :value="statistics.verified" />
</el-col>
</el-row>
<!-- 表格 -->
<el-table
:data="dataList"
v-loading="loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table">
<el-table-column type="index" label="序号" width="70" align="center">
<template #default="{ $index }">
{{ (page.currentPage - 1) * page.pageSize + $index + 1 }}
</template>
</el-table-column>
<el-table-column prop="batchNo" label="批次号" width="160" align="center" />
<el-table-column prop="stuNo" label="号" width="120" align="center" />
<el-table-column label="姓名" width="180" align="center">
<template #default="{ row }">
<div>
<span v-if="row.systemRealName">系统{{ row.systemRealName }}</span>
<span v-if="row.systemRealName && row.enrollRealName && row.systemRealName !== row.enrollRealName"
class="text-danger">
<br />回流{{ row.enrollRealName }}
</span>
<span v-else-if="row.enrollRealName && !row.systemRealName">回流{{ row.enrollRealName }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="身份证号" width="220" align="center">
<template #default="{ row }">
<div v-if="row.systemIdCard || row.enrollIdCard">
<div v-if="row.systemIdCard">系统{{ row.systemIdCard }}</div>
<div v-if="row.enrollIdCard && row.enrollIdCard !== row.systemIdCard" class="text-warning">
回流{{ row.enrollIdCard }}
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="enrollNo" label="学籍号" width="180" align="center" />
<el-table-column prop="className" label="班级" width="150" />
<el-table-column prop="classMasterName" label="班主任" width="100" align="center" />
<el-table-column prop="enterYear" label="入学年份" width="100" align="center" />
<el-table-column prop="isMatch" label="是否一致" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.isMatch === '1' ? 'success' : 'danger'" size="small">
{{ row.isMatch === '1' ? '一致' : '不一致' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="verifyStatus" label="核对状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.verifyStatus === '1' ? 'success' : 'warning'" size="small">
{{ row.verifyStatus === '1' ? '已核对' : '待核对' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="verifyResult" label="核对结果" width="150" show-overflow-tooltip />
<el-table-column prop="remarks" label="备注" width="150" show-overflow-tooltip />
<el-table-column label="操作" width="120" align="center" fixed="right">
<template #default="{ row }">
<el-button
v-if="row.verifyStatus === '0'"
icon="Edit"
link
type="primary"
@click="handleVerify(row)">
核对
</el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="暂无数据" :image-size="120" />
</template>
</el-table>
<!-- 表格 -->
<el-table
:data="dataList"
v-loading="loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table"
>
<el-table-column type="index" label="序号" width="70" align="center">
<template #default="{ $index }">
{{ (page.currentPage - 1) * page.pageSize + $index + 1 }}
</template>
</el-table-column>
<el-table-column prop="batchNo" label="批次号" width="160" align="center" />
<el-table-column prop="stuNo" label="学号" width="120" align="center" />
<el-table-column label="姓名" width="180" align="center">
<template #default="{ row }">
<div>
<span v-if="row.systemRealName">系统{{ row.systemRealName }}</span>
<span v-if="row.systemRealName && row.enrollRealName && row.systemRealName !== row.enrollRealName" class="text-danger">
<br />回流{{ row.enrollRealName }}
</span>
<span v-else-if="row.enrollRealName && !row.systemRealName">回流{{ row.enrollRealName }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="身份证号" width="220" align="center">
<template #default="{ row }">
<div v-if="row.systemIdCard || row.enrollIdCard">
<div v-if="row.systemIdCard">系统{{ row.systemIdCard }}</div>
<div v-if="row.enrollIdCard && row.enrollIdCard !== row.systemIdCard" class="text-warning">回流{{ row.enrollIdCard }}</div>
</div>
</template>
</el-table-column>
<el-table-column prop="enrollNo" label="学籍号" width="180" align="center" />
<el-table-column prop="className" label="班级" width="150" />
<el-table-column prop="classMasterName" label="班主任" width="100" align="center" />
<el-table-column prop="enterYear" label="入学年份" width="100" align="center" />
<el-table-column prop="isMatch" label="是否一致" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.isMatch === '1' ? 'success' : 'danger'" size="small">
{{ row.isMatch === '1' ? '一致' : '不一致' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="verifyStatus" label="核对状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.verifyStatus === '1' ? 'success' : 'warning'" size="small">
{{ row.verifyStatus === '1' ? '已核对' : '待核对' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="verifyResult" label="核对结果" width="150" show-overflow-tooltip />
<el-table-column prop="remarks" label="备注" width="150" show-overflow-tooltip />
<el-table-column label="操作" width="120" align="center" fixed="right">
<template #default="{ row }">
<el-button v-if="row.verifyStatus === '0'" icon="Edit" link type="primary" @click="handleVerify(row)"> 核对 </el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="暂无数据" :image-size="120" />
</template>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="page.currentPage"
v-model:page-size="page.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="page.total"
layout="total, sizes, prev, pager, next, jumper"
class="pagination"
@size-change="handleSizeChange"
@current-change="handleCurrentChange" />
</el-card>
</div>
<!-- 分页 -->
<el-pagination
v-model:current-page="page.currentPage"
v-model:page-size="page.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="page.total"
layout="total, sizes, prev, pager, next, jumper"
class="pagination"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</el-card>
</div>
<!-- 核对弹窗 -->
<el-dialog
v-model="verifyDialogVisible"
title="毕业学生信息核对"
width="600px"
:close-on-click-modal="false">
<el-descriptions :column="2" border>
<el-descriptions-item label="学号">{{ verifyData.stuNo }}</el-descriptions-item>
<el-descriptions-item label="入学年份">{{ verifyData.enterYear }}</el-descriptions-item>
<el-descriptions-item label="系统姓名">{{ verifyData.systemRealName }}</el-descriptions-item>
<el-descriptions-item label="回流姓名">{{ verifyData.enrollRealName }}</el-descriptions-item>
<el-descriptions-item label="系统身份证">{{ verifyData.systemIdCard }}</el-descriptions-item>
<el-descriptions-item label="回流身份证">{{ verifyData.enrollIdCard }}</el-descriptions-item>
<el-descriptions-item label="学籍号">{{ verifyData.enrollNo }}</el-descriptions-item>
<el-descriptions-item label="班级">{{ verifyData.className }}</el-descriptions-item>
<el-descriptions-item label="信息是否一致" :span="2">
<el-tag :type="verifyData.isMatch === '1' ? 'success' : 'danger'">
{{ verifyData.isMatch === '1' ? '一致' : '不一致' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item v-if="verifyData.remarks" label="备注" :span="2">{{ verifyData.remarks }}</el-descriptions-item>
</el-descriptions>
<el-form :model="verifyForm" label-width="80px" class="verify-form">
<el-form-item label="核对结果">
<el-radio-group v-model="verifyForm.verifyResult">
<el-radio label="信息无误">信息无误</el-radio>
<el-radio label="需修改系统">需修改系统</el-radio>
<el-radio label="需修改回流">需修改回流</el-radio>
<el-radio label="其他问题">其他问题</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="verifyDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitVerify" :loading="verifyLoading">确定</el-button>
</template>
</el-dialog>
</div>
<!-- 核对弹窗 -->
<el-dialog v-model="verifyDialogVisible" title="毕业学生信息核对" width="600px" :close-on-click-modal="false">
<el-descriptions :column="2" border>
<el-descriptions-item label="学号">{{ verifyData.stuNo }}</el-descriptions-item>
<el-descriptions-item label="入学年份">{{ verifyData.enterYear }}</el-descriptions-item>
<el-descriptions-item label="系统姓名">{{ verifyData.systemRealName }}</el-descriptions-item>
<el-descriptions-item label="回流姓名">{{ verifyData.enrollRealName }}</el-descriptions-item>
<el-descriptions-item label="系统身份证">{{ verifyData.systemIdCard }}</el-descriptions-item>
<el-descriptions-item label="回流身份证">{{ verifyData.enrollIdCard }}</el-descriptions-item>
<el-descriptions-item label="学籍号">{{ verifyData.enrollNo }}</el-descriptions-item>
<el-descriptions-item label="班级">{{ verifyData.className }}</el-descriptions-item>
<el-descriptions-item label="信息是否一致" :span="2">
<el-tag :type="verifyData.isMatch === '1' ? 'success' : 'danger'">
{{ verifyData.isMatch === '1' ? '一致' : '不一致' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item v-if="verifyData.remarks" label="备注" :span="2">{{ verifyData.remarks }}</el-descriptions-item>
</el-descriptions>
<el-form :model="verifyForm" label-width="80px" class="verify-form">
<el-form-item label="核对结果">
<el-radio-group v-model="verifyForm.verifyResult">
<el-radio label="信息无误">信息无误</el-radio>
<el-radio label="需修改系统">需修改系统</el-radio>
<el-radio label="需修改回流">需修改回流</el-radio>
<el-radio label="其他问题">其他问题</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="verifyDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitVerify" :loading="verifyLoading">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="GraduVerify">
import { reactive, ref, onMounted } from 'vue'
import { fetchList, importData, exportData, sendToClassMaster, submitVerify as submitVerifyApi, downloadTemplate } from '/@/api/basic/graduverify'
import { useMessage, useMessageBox } from '/@/hooks/message'
import { Search, User, Upload, Download, Bell, Edit } from '@element-plus/icons-vue'
import { reactive, ref, onMounted } from 'vue';
import { fetchList, importData, exportData, sendToClassMaster, submitVerify as submitVerifyApi, downloadTemplate } from '/@/api/basic/graduverify';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { Search, User, Upload, Download, Bell, Edit } from '@element-plus/icons-vue';
// 定义变量内容
const searchFormRef = ref()
const showSearch = ref(true)
const loading = ref(false)
const importLoading = ref(false)
const verifyLoading = ref(false)
const dataList = ref<any[]>([])
const verifyDialogVisible = ref(false)
const verifyData = ref<any>({})
const searchFormRef = ref();
const showSearch = ref(true);
const loading = ref(false);
const importLoading = ref(false);
const verifyLoading = ref(false);
const dataList = ref<any[]>([]);
const verifyDialogVisible = ref(false);
const verifyData = ref<any>({});
const verifyForm = reactive({
id: '',
verifyResult: '信息无误'
})
id: '',
verifyResult: '信息无误',
});
// 统计数据
const statistics = reactive({
total: 0,
matched: 0,
mismatched: 0,
pending: 0,
verified: 0
})
total: 0,
matched: 0,
mismatched: 0,
pending: 0,
verified: 0,
});
// 分页
const page = reactive({
currentPage: 1,
pageSize: 20,
total: 0
})
currentPage: 1,
pageSize: 20,
total: 0,
});
// 搜索表单
const searchForm = reactive({
batchNo: '',
stuNo: '',
systemRealName: '',
enterYear: undefined as number | undefined,
isMatch: '',
verifyStatus: ''
})
batchNo: '',
stuNo: '',
systemRealName: '',
enterYear: undefined as number | undefined,
isMatch: '',
verifyStatus: '',
});
// 表格样式
const tableStyle = {
cellStyle: { textAlign: 'center' },
headerCellStyle: {
textAlign: 'center',
background: 'var(--el-table-row-hover-bg-color)',
color: 'var(--el-text-color-primary)'
}
}
cellStyle: { textAlign: 'center' },
headerCellStyle: {
textAlign: 'center',
background: 'var(--el-table-row-hover-bg-color)',
color: 'var(--el-text-color-primary)',
},
};
// 查询
const handleSearch = () => {
page.currentPage = 1
getDataList()
}
page.currentPage = 1;
getDataList();
};
// 重置
const handleReset = () => {
searchFormRef.value?.resetFields()
getDataList()
}
searchFormRef.value?.resetFields();
getDataList();
};
// 分页
const handleSizeChange = (val: number) => {
page.pageSize = val
getDataList()
}
page.pageSize = val;
getDataList();
};
const handleCurrentChange = (val: number) => {
page.currentPage = val
getDataList()
}
page.currentPage = val;
getDataList();
};
// 获取数据列表
const getDataList = async () => {
loading.value = true
try {
const res = await fetchList({
current: page.currentPage,
size: page.pageSize,
...searchForm,
enterYear: searchForm.enterYear ? parseInt(searchForm.enterYear as any) : undefined
})
if (res.data && res.data.records) {
dataList.value = res.data.records
page.total = res.data.total
loading.value = true;
try {
const res = await fetchList({
current: page.currentPage,
size: page.pageSize,
...searchForm,
enterYear: searchForm.enterYear ? parseInt(searchForm.enterYear as any) : undefined,
});
if (res.data && res.data.records) {
dataList.value = res.data.records;
page.total = res.data.total;
// 计算统计数据
statistics.total = res.data.total
statistics.matched = dataList.value.filter((item: any) => item.isMatch === '1').length
statistics.mismatched = dataList.value.filter((item: any) => item.isMatch === '0').length
statistics.pending = dataList.value.filter((item: any) => item.verifyStatus === '0').length
statistics.verified = dataList.value.filter((item: any) => item.verifyStatus === '1').length
} else {
dataList.value = []
page.total = 0
}
} catch (err: any) {
useMessage().error(err.msg || '获取数据失败')
} finally {
loading.value = false
}
}
// 计算统计数据
statistics.total = res.data.total;
statistics.matched = dataList.value.filter((item: any) => item.isMatch === '1').length;
statistics.mismatched = dataList.value.filter((item: any) => item.isMatch === '0').length;
statistics.pending = dataList.value.filter((item: any) => item.verifyStatus === '0').length;
statistics.verified = dataList.value.filter((item: any) => item.verifyStatus === '1').length;
} else {
dataList.value = [];
page.total = 0;
}
} catch (err: any) {
useMessage().error(err.msg || '获取数据失败');
} finally {
loading.value = false;
}
};
// 上传前验证
const beforeUpload = (file: File) => {
const isExcel = file.name.endsWith('.xlsx') || file.name.endsWith('.xls')
if (!isExcel) {
useMessage().error('只能上传Excel文件')
return false
}
return true
}
const isExcel = file.name.endsWith('.xlsx') || file.name.endsWith('.xls');
if (!isExcel) {
useMessage().error('只能上传Excel文件');
return false;
}
return true;
};
// 导入核对
const handleImport = async (options: any) => {
importLoading.value = true
try {
const res = await importData(options.file)
useMessage().success(res.msg || '导入核对成功')
if (res.data) {
searchForm.batchNo = ''
}
getDataList()
} catch (err: any) {
useMessage().error(err.msg || '导入失败')
} finally {
importLoading.value = false
}
}
importLoading.value = true;
try {
const res = await importData(options.file);
useMessage().success(res.msg || '导入核对成功');
if (res.data) {
searchForm.batchNo = '';
}
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '导入失败');
} finally {
importLoading.value = false;
}
};
// 下载模板
const handleDownloadTemplate = async () => {
try {
const res = await downloadTemplate()
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '毕业学籍回流导入模板.xlsx'
link.click()
window.URL.revokeObjectURL(url)
} catch (err: any) {
useMessage().error(err.msg || '下载失败')
}
}
try {
const res = await downloadTemplate();
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = '毕业学籍回流导入模板.xlsx';
link.click();
window.URL.revokeObjectURL(url);
} catch (err: any) {
useMessage().error(err.msg || '下载失败');
}
};
// 导出
const handleExport = async () => {
try {
const res = await exportData(searchForm)
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '毕业学生核对数据.xlsx'
link.click()
window.URL.revokeObjectURL(url)
} catch (err: any) {
useMessage().error(err.msg || '导出失败')
}
}
try {
const res = await exportData(searchForm);
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = '毕业学生核对数据.xlsx';
link.click();
window.URL.revokeObjectURL(url);
} catch (err: any) {
useMessage().error(err.msg || '导出失败');
}
};
// 下发班主任
const handleSend = async () => {
if (!searchForm.batchNo) {
useMessage().warning('请先选择批次号')
return
}
try {
await useMessageBox().confirm('确定要将该批次数据下发至班主任核对吗?')
await sendToClassMaster(searchForm.batchNo)
useMessage().success('下发成功')
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '下发失败')
}
}
}
if (!searchForm.batchNo) {
useMessage().warning('请先选择批次号');
return;
}
try {
await useMessageBox().confirm('确定要将该批次数据下发至班主任核对吗?');
await sendToClassMaster(searchForm.batchNo);
useMessage().success('下发成功');
} catch (err: any) {
if (err !== 'cancel') {
useMessage().error(err.msg || '下发失败');
}
}
};
// 核对
const handleVerify = (row: any) => {
verifyData.value = row
verifyForm.id = row.id
verifyForm.verifyResult = '信息无误'
verifyDialogVisible.value = true
}
verifyData.value = row;
verifyForm.id = row.id;
verifyForm.verifyResult = '信息无误';
verifyDialogVisible.value = true;
};
// 提交核对
const submitVerify = async () => {
verifyLoading.value = true
try {
await submitVerifyApi(verifyForm.id, verifyForm.verifyResult)
useMessage().success('核对成功')
verifyDialogVisible.value = false
getDataList()
} catch (err: any) {
useMessage().error(err.msg || '核对失败')
} finally {
verifyLoading.value = false
}
}
verifyLoading.value = true;
try {
await submitVerifyApi(verifyForm.id, verifyForm.verifyResult);
useMessage().success('核对成功');
verifyDialogVisible.value = false;
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '核对失败');
} finally {
verifyLoading.value = false;
}
};
// 初始化
onMounted(() => {
getDataList()
})
getDataList();
});
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
.stat-row {
margin-bottom: 16px;
margin-bottom: 16px;
}
.upload-btn {
display: inline-block;
margin-right: 10px;
display: inline-block;
margin-right: 10px;
}
.text-danger {
color: var(--el-color-danger);
color: var(--el-color-danger);
}
.text-warning {
color: var(--el-color-warning);
color: var(--el-color-warning);
}
.text-success {
color: var(--el-color-success);
color: var(--el-color-success);
}
.verify-form {
margin-top: 16px;
margin-top: 16px;
}
</style>
</style>

View File

@@ -1,104 +1,105 @@
<template>
<div class="layout-padding-auto layout-padding-view">
<el-card shadow="never">
<el-form ref="dataFormRef" class="form" :model="form" label-width="85px" :rules="dataRules">
<div class="flex">
<div>
<el-form-item label="文章标题" prop="title">
<div class="w-80">
<el-input
v-model="form.title"
placeholder="请输入文章标题"
type="textarea"
:autosize="{ minRows: 3, maxRows: 3 }"
maxlength="64"
show-word-limit
clearable
/>
</div>
</el-form-item>
<el-form-item label="文章简介" prop="intro">
<div class="w-80">
<el-input
v-model="form.intro"
placeholder="请输入文章简介"
type="textarea"
:autosize="{ minRows: 3, maxRows: 6 }"
:maxlength="200"
show-word-limit
clearable
/>
</div>
</el-form-item>
<el-form-item label="摘要" prop="summary">
<div class="w-80">
<el-input type="textarea" :autosize="{ minRows: 6, maxRows: 6 }" v-model="form.summary" maxlength="200"
show-word-limit clearable/>
</div>
</el-form-item>
<el-form-item label="文章封面" prop="image">
<div>
<div>
<upload-img v-model:imageUrl="form.image"/>
</div>
<div class="form-tips">建议尺寸240*180px</div>
</div>
</el-form-item>
</div>
<div class="xl:ml-20">
<el-row class="xl:mb-8">
<el-col :span="12">
<el-form-item label="文章栏目" prop="cid">
<el-select class="w-80" v-model="form.cid" placeholder="请选择文章栏目" clearable>
<el-option v-for="item in articleCateList" :key="item.id" :label="item.name" :value="item.id"/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="作者" prop="author">
<div class="w-80">
<el-input v-model="form.author" placeholder="请输入作者名称"/>
</div>
</el-form-item>
</el-col>
</el-row>
<div class="layout-padding-auto layout-padding-view">
<el-card shadow="never">
<el-form ref="dataFormRef" class="form" :model="form" label-width="85px" :rules="dataRules">
<div class="flex">
<div>
<el-form-item label="文章标题" prop="title">
<div class="w-80">
<el-input
v-model="form.title"
placeholder="请输入文章标题"
type="textarea"
:autosize="{ minRows: 3, maxRows: 3 }"
maxlength="64"
show-word-limit
clearable
/>
</div>
</el-form-item>
<el-form-item label="文章简介" prop="intro">
<div class="w-80">
<el-input
v-model="form.intro"
placeholder="请输入文章简介"
type="textarea"
:autosize="{ minRows: 3, maxRows: 6 }"
:maxlength="200"
show-word-limit
clearable
/>
</div>
</el-form-item>
<el-form-item label="摘要" prop="summary">
<div class="w-80">
<el-input type="textarea" :autosize="{ minRows: 6, maxRows: 6 }" v-model="form.summary" maxlength="200" show-word-limit clearable />
</div>
</el-form-item>
<el-form-item label="文章封面" prop="image">
<div>
<div>
<upload-img v-model:imageUrl="form.image" />
</div>
<div class="form-tips">建议尺寸240*180px</div>
</div>
</el-form-item>
</div>
<div class="xl:ml-20">
<el-row class="xl:mb-8">
<el-col :span="12">
<el-form-item label="文章栏目" prop="cid">
<el-select class="w-80" v-model="form.cid" placeholder="请选择文章栏目" clearable>
<el-option v-for="item in articleCateList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="作者" prop="author">
<div class="w-80">
<el-input v-model="form.author" placeholder="请输入作者名称" />
</div>
</el-form-item>
</el-col>
</el-row>
<el-row class="xl:mb-8">
<el-col :span="12">
<el-form-item label="初始浏览量" prop="visit">
<template #label> 浏览量
<tip content="初始值"/>
</template>
<el-input-number class="!w-80" v-model="form.visit" :min="0" :max="9999"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="sort">
<template #label> 排序
<tip content="默认为0 数值越大越排前"/>
</template>
<el-input-number class="!w-80" v-model="form.sort" :min="0" :max="9999"/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="文章内容" required prop="content">
<editor v-model:get-html="form.content" height="500" width="600" :disable="form.id !== ''"/>
</el-form-item>
<div style="text-align: center">
<el-button type="primary" @click="onSubmit">保存</el-button>
</div>
</div>
</div>
</el-form>
</el-card>
</div>
<el-row class="xl:mb-8">
<el-col :span="12">
<el-form-item label="初始浏览量" prop="visit">
<template #label>
浏览量
<tip content="初始值" />
</template>
<el-input-number class="!w-80" v-model="form.visit" :min="0" :max="9999" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="sort">
<template #label>
排序
<tip content="默认为0 数值越大越排前" />
</template>
<el-input-number class="!w-80" v-model="form.sort" :min="0" :max="9999" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="文章内容" required prop="content">
<editor v-model:get-html="form.content" height="500" width="600" :disable="form.id !== ''" />
</el-form-item>
<div style="text-align: center">
<el-button type="primary" @click="onSubmit">保存</el-button>
</div>
</div>
</div>
</el-form>
</el-card>
</div>
</template>
<script setup lang="ts" name="AppArticleDialog">
import mittBus from "/@/utils/mitt";
import {useMessage} from '/@/hooks/message';
import {getObj, addObj, putObj} from '/@/api/app/appArticle';
import {getObjList} from '/@/api/app/appArticleCategory';
import mittBus from '/@/utils/mitt';
import { useMessage } from '/@/hooks/message';
import { getObj, addObj, putObj } from '/@/api/app/appArticle';
import { getObjList } from '/@/api/app/appArticleCategory';
const emit = defineEmits(['refresh']);
const route = useRoute();
@@ -112,93 +113,89 @@ const loading = ref(false);
const articleCateList = ref([]);
// 提交表单数据
const form = reactive({
id: '',
cid: '',
title: '',
intro: '',
summary: '',
image: '',
content: '',
author: '',
visit: 0,
sort: 0,
id: '',
cid: '',
title: '',
intro: '',
summary: '',
image: '',
content: '',
author: '',
visit: 0,
sort: 0,
});
// 定义校验规则
const dataRules = ref({
cid: [{required: true, message: '分类不能为空', trigger: 'blur'}],
title: [{required: true, message: '标题不能为空', trigger: 'blur'}],
intro: [{required: true, message: '简介不能为空', trigger: 'blur'}],
summary: [{required: true, message: '摘要不能为空', trigger: 'blur'}],
image: [{required: true, message: '封面不能为空', trigger: 'blur'}],
content: [{required: true, message: '内容不能为空', trigger: 'blur'}],
author: [{required: true, message: '作者不能为空', trigger: 'blur'}],
visit: [{required: true, message: '浏览不能为空', trigger: 'blur'}],
cid: [{ required: true, message: '分类不能为空', trigger: 'blur' }],
title: [{ required: true, message: '标题不能为空', trigger: 'blur' }],
intro: [{ required: true, message: '简介不能为空', trigger: 'blur' }],
summary: [{ required: true, message: '摘要不能为空', trigger: 'blur' }],
image: [{ required: true, message: '封面不能为空', trigger: 'blur' }],
content: [{ required: true, message: '内容不能为空', trigger: 'blur' }],
author: [{ required: true, message: '作者不能为空', trigger: 'blur' }],
visit: [{ required: true, message: '浏览不能为空', trigger: 'blur' }],
});
// 提交
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;
form.id ? await putObj(form) : await addObj(form);
useMessage().success(form.id ? '修改成功' : '添加成功');
visible.value = false;
emit('refresh');
// 关闭当前tab
mittBus.emit(
"onCurrentContextmenuClick",
Object.assign({}, {contextMenuClickId: 1, ...route})
);
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
try {
loading.value = true;
form.id ? await putObj(form) : await addObj(form);
useMessage().success(form.id ? '修改成功' : '添加成功');
visible.value = false;
emit('refresh');
// 关闭当前tab
mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 1, ...route }));
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// 初始化表单数据
const getAppArticleData = (id: string) => {
// 获取数据
loading.value = true;
getObj(id)
.then((res: any) => {
Object.assign(form, res.data);
})
.finally(() => {
loading.value = false;
});
// 获取数据
loading.value = true;
getObj(id)
.then((res: any) => {
Object.assign(form, res.data);
})
.finally(() => {
loading.value = false;
});
};
// 查询全部的分类
const getAppCateList = () => {
getObjList().then((res: any) => {
articleCateList.value = res.data;
});
getObjList().then((res: any) => {
articleCateList.value = res.data;
});
};
onMounted(() => {
getAppCateList();
if (route.query?.id) {
getAppArticleData(route.query?.id);
}
getAppCateList();
if (route.query?.id) {
getAppArticleData(route.query?.id);
}
});
</script>
<style scoped lang="scss">
.footer-btns {
height: 60px;
height: 60px;
&__content {
bottom: 0;
height: 60px;
right: 0;
left: 0;
z-index: 99;
@apply flex justify-center items-center shadow;
}
&__content {
bottom: 0;
height: 60px;
right: 0;
left: 0;
z-index: 99;
@apply flex justify-center items-center shadow;
}
}
</style>

View File

@@ -126,13 +126,13 @@ const onSubmit = async () => {
try {
// 批量插入用户角色关联
const userRoleList = selectedUsers.value.map(user => ({
const userRoleList = selectedUsers.value.map((user) => ({
userId: user.userId,
roleId: props.roleId
roleId: props.roleId,
}));
await batchAddObj(userRoleList);
useMessage().success(`成功为 ${selectedUsers.value.length} 个用户分配角色`);
emit('refresh');
closeDialog();
@@ -156,4 +156,4 @@ defineExpose({
align-items: center;
}
}
</style>
</style>

View File

@@ -99,13 +99,11 @@
</span>
</div>
<div class="card-subtitle" v-if="selectedRoleId">
<el-tag type="info" size="small">
{{ userState.pagination?.total || 0 }} 个授权用户
</el-tag>
<el-tag type="info" size="small"> {{ userState.pagination?.total || 0 }} 个授权用户 </el-tag>
</div>
</div>
</template>
<div v-if="!selectedRoleId" class="empty-state">
<el-empty description="请在左侧选择角色以查看对应的用户列表">
<template #image>
@@ -138,9 +136,7 @@
<!-- 操作按钮 -->
<el-row>
<div class="mb8" style="width: 100%">
<el-button @click="addUserDialogRef.openDialog()" icon="folder-add" type="primary">
新增用户
</el-button>
<el-button @click="addUserDialogRef.openDialog()" icon="folder-add" type="primary"> 新增用户 </el-button>
<right-toolbar
@queryTable="getUserDataList"
class="ml10"
@@ -164,9 +160,7 @@
<el-table-column label="姓名" prop="name" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="120">
<template #default="scope">
<el-button icon="delete" @click="handleRemoveUser(scope.row.userId)" text type="danger">
取消授权
</el-button>
<el-button icon="delete" @click="handleRemoveUser(scope.row.userId)" text type="danger"> 取消授权 </el-button>
</template>
</el-table-column>
<template #empty>
@@ -253,18 +247,14 @@ const userState: BasicTableProps = reactive<BasicTableProps>({
roleId: '',
username: '',
},
pageList: fetchUserRoleList
pageList: fetchUserRoleList,
});
// 角色 table hook
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state);
// 用户角色关联 table hook
const {
getDataList: getUserDataList,
currentChangeHandle: userCurrentChangeHandle,
sizeChangeHandle: userSizeChangeHandle
} = useTable(userState);
const { getDataList: getUserDataList, currentChangeHandle: userCurrentChangeHandle, sizeChangeHandle: userSizeChangeHandle } = useTable(userState);
// 清空角色搜索条件
const resetQuery = () => {
@@ -382,7 +372,7 @@ const handleRemoveUser = async (userId: string) => {
flex-direction: column;
align-items: flex-start;
}
.card-subtitle {
width: 100%;
justify-content: flex-start;

View File

@@ -1,30 +1,15 @@
<template>
<el-dialog
:close-on-click-modal="false"
title="分配工作台菜单"
width="800px"
draggable
v-model="visible"
>
<el-dialog :close-on-click-modal="false" title="分配工作台菜单" width="800px" draggable v-model="visible">
<div v-loading="loading">
<div class="mb-4">
<el-alert
title="请选择该角色可以访问的工作台菜单项"
type="info"
:closable="false"
show-icon
/>
<el-alert title="请选择该角色可以访问的工作台菜单项" type="info" :closable="false" show-icon />
</div>
<div v-if="workbenchData.length > 0" class="workbench-menu-list">
<div
v-for="item in workbenchData"
:key="item.id"
class="menu-item"
>
<div v-for="item in workbenchData" :key="item.id" class="menu-item">
<div class="menu-header">
<div class="menu-info">
<el-checkbox
<el-checkbox
:model-value="isModuleAllSelected(item)"
:indeterminate="isModuleIndeterminate(item)"
@change="toggleModuleSelection(item)"
@@ -34,17 +19,17 @@
<div class="menu-name">{{ item.name }}</div>
</div>
</div>
<div v-if="item.content && item.content.data" class="menu-content">
<div class="content-grid">
<div
v-for="(dataItem, index) in item.content.data"
<div
v-for="(dataItem, index) in item.content.data"
:key="dataItem.id || index"
class="content-item"
:class="{ 'selected': selectedMenuIds.includes(dataItem.id) }"
:class="{ selected: selectedMenuIds.includes(dataItem.id) }"
@click="toggleMenuItem(dataItem.id)"
>
<el-checkbox
<el-checkbox
:model-value="selectedMenuIds.includes(dataItem.id)"
@change="toggleMenuItem(dataItem.id)"
class="item-checkbox"
@@ -65,12 +50,12 @@
</div>
</div>
</div>
<div v-else class="empty-state">
<el-empty description="暂无工作台菜单数据" />
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
@@ -105,13 +90,13 @@ const openDialog = async (roleId: string) => {
visible.value = true;
currentRoleId.value = roleId;
selectedMenuIds.value = [];
try {
loading.value = true;
// 获取工作台装饰数据
await getWorkbenchData();
// 获取角色当前的菜单配置
if (roleId) {
await getRoleMenuData(roleId);
@@ -128,10 +113,10 @@ const getWorkbenchData = async () => {
try {
const response = await getWorkbenchDecorate();
const { data } = response;
if (data && data.pageData) {
const pageData = JSON.parse(data.pageData);
workbenchData.value = pageData
workbenchData.value = pageData;
}
} catch (err: any) {
console.error('获取工作台数据失败:', err);
@@ -143,7 +128,7 @@ const getWorkbenchData = async () => {
const getRoleMenuData = async (roleId: string) => {
try {
const { data } = await getObj(roleId);
if (data && data.menuId) {
selectedMenuIds.value = data.menuId.split(',').filter((id: string) => id.trim());
}
@@ -185,9 +170,9 @@ const toggleModuleSelection = (module: any) => {
if (!module.content || !module.content.data) {
return;
}
const isAllSelected = isModuleAllSelected(module);
if (isAllSelected) {
// 取消选择该模块下的所有项
module.content.data.forEach((item: any) => {
@@ -211,9 +196,9 @@ const onSubmit = async () => {
try {
loading.value = true;
const menuId = selectedMenuIds.value.join(',');
await updateRoleWorkbenchMenus(currentRoleId.value, menuId);
useMessage().success('工作台菜单分配成功');
visible.value = false;
emit('refresh');
@@ -297,12 +282,12 @@ defineExpose({
cursor: pointer;
transition: all 0.3s ease;
border: 1px solid transparent;
&:hover {
border-color: #409eff;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
}
&.selected {
border-color: #409eff;
background-color: #f0f9ff;
@@ -358,4 +343,4 @@ defineExpose({
justify-content: flex-end;
gap: 12px;
}
</style>
</style>

View File

@@ -23,7 +23,15 @@
<el-button plain @click="excelUploadRef.show()" class="ml10" icon="upload-filled" type="primary" v-auth="'app_appuser_export'">
{{ $t('common.importBtn') }}
</el-button>
<el-button plain :disabled="multiple" @click="handleDelete(selectObjs)" class="ml10" icon="Delete" type="primary" v-auth="'app_appuser_del'">
<el-button
plain
:disabled="multiple"
@click="handleDelete(selectObjs)"
class="ml10"
icon="Delete"
type="primary"
v-auth="'app_appuser_del'"
>
{{ $t('common.delBtn') }}
</el-button>
<right-toolbar
@@ -40,7 +48,7 @@
:data="state.dataList"
@selection-change="handleSelectionChange"
style="width: 100%"
row-key="userId"
row-key="userId"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
@@ -140,7 +148,7 @@ const resetQuery = () => {
// 导出excel
const exportExcel = () => {
downBlobFile('/app/appuser/export',Object.assign( state.queryForm,{ids:selectObjs}), 'users.xlsx');
downBlobFile('/app/appuser/export', Object.assign(state.queryForm, { ids: selectObjs }), 'users.xlsx');
};
// 多选事件

View File

@@ -86,15 +86,15 @@ const handleAdd = () => {
name: '功能名称',
link: {},
};
if (props.showColor) {
newItem.color = '#007aff';
}
if (props.showBadge) {
newItem.badge = '';
}
workbenchLists.value.push(newItem);
} else {
useMessage().error(`最多添加${props.max}`);
@@ -109,4 +109,4 @@ const handleDelete = (index: number) => {
};
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped></style>

View File

@@ -6,7 +6,7 @@
<div class="user-info flex items-center mb-4">
<div class="avatar w-12 h-12 bg-white/20 rounded-full flex items-center justify-center mr-3">
<svg width="24" height="24" viewBox="0 0 24 24" fill="white">
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" />
</svg>
</div>
<div>
@@ -14,13 +14,13 @@
<div class="font-medium">管理员</div>
</div>
</div>
<!-- 任务统计 -->
<div class="task-stats grid grid-cols-3 gap-4">
<div class="stat-item text-center">
<div class="stat-icon w-8 h-8 bg-orange-400 rounded-full mx-auto mb-2 flex items-center justify-center">
<svg width="16" height="16" viewBox="0 0 24 24" fill="white">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
</svg>
</div>
<div class="text-xl font-bold">0</div>
@@ -29,7 +29,7 @@
<div class="stat-item text-center">
<div class="stat-icon w-8 h-8 bg-blue-400 rounded-full mx-auto mb-2 flex items-center justify-center">
<svg width="16" height="16" viewBox="0 0 24 24" fill="white">
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/>
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z" />
</svg>
</div>
<div class="text-xl font-bold">0</div>
@@ -38,7 +38,7 @@
<div class="stat-item text-center">
<div class="stat-icon w-8 h-8 bg-green-400 rounded-full mx-auto mb-2 flex items-center justify-center">
<svg width="16" height="16" viewBox="0 0 24 24" fill="white">
<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/>
<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" />
</svg>
</div>
<div class="text-xl font-bold">0</div>
@@ -111,15 +111,15 @@ const handleClick = (widget: any, index: number) => {
height: 680px;
color: #333;
overflow-y: auto;
&.workbench {
height: 800px;
}
.widget-hoverable {
@apply border-2 border-dashed border-[#dcdfe6] z-[100];
}
.widget-selected {
@apply border-2 border-solid border-primary z-[101];
box-shadow: 0 0 0 2px rgba(var(--color-primary), 0.1);

View File

@@ -37,4 +37,4 @@ defineProps({
});
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped></style>

View File

@@ -6,7 +6,7 @@
<decoration-img v-if="item.icon" width="24px" height="24px" :src="item.icon" alt="" />
<div v-else class="w-6 h-6 flex items-center justify-center">
<svg width="16" height="16" viewBox="0 0 16 16" fill="white">
<path d="M8 1L8 15M1 8L15 8" stroke="white" stroke-width="2" stroke-linecap="round"/>
<path d="M8 1L8 15M1 8L15 8" stroke="white" stroke-width="2" stroke-linecap="round" />
</svg>
</div>
</div>
@@ -50,4 +50,4 @@ defineProps({
.icon-wrapper:hover {
transform: scale(1.05);
}
</style>
</style>

View File

@@ -5,4 +5,4 @@ export default {
attr,
content,
options,
};
};

View File

@@ -19,4 +19,4 @@ export default () => ({
],
},
styles: {},
});
});

View File

@@ -43,4 +43,4 @@ defineProps({
});
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped></style>

View File

@@ -7,7 +7,7 @@
</div>
<div class="expand-icon">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M6 12L10 8L6 4" stroke="#999" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 12L10 8L6 4" stroke="#999" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</div>
</div>
@@ -17,7 +17,7 @@
<decoration-img v-if="item.icon" width="24px" height="24px" :src="item.icon" alt="" />
<div v-else class="w-6 h-6 bg-blue-500 rounded flex items-center justify-center">
<svg width="16" height="16" viewBox="0 0 16 16" fill="white">
<path d="M8 1L8 15M1 8L15 8" stroke="white" stroke-width="2" stroke-linecap="round"/>
<path d="M8 1L8 15M1 8L15 8" stroke="white" stroke-width="2" stroke-linecap="round" />
</svg>
</div>
</div>
@@ -59,4 +59,4 @@ defineProps({
align-items: center;
justify-content: center;
}
</style>
</style>

View File

@@ -5,4 +5,4 @@ export default {
attr,
content,
options,
};
};

View File

@@ -29,4 +29,4 @@ export default () => ({
],
},
styles: {},
});
});

View File

@@ -43,4 +43,4 @@ defineProps({
});
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped></style>

View File

@@ -7,20 +7,27 @@
</div>
<div class="expand-icon">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M6 12L10 8L6 4" stroke="#999" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 12L10 8L6 4" stroke="#999" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</div>
</div>
<div class="grid grid-cols-3 gap-4">
<div v-for="(item, index) in content.data" :key="index" class="relative flex flex-col items-center p-4 hover:bg-gray-50 rounded-lg cursor-pointer">
<div
v-for="(item, index) in content.data"
:key="index"
class="relative flex flex-col items-center p-4 hover:bg-gray-50 rounded-lg cursor-pointer"
>
<div class="icon-wrapper mb-2 relative">
<decoration-img v-if="item.icon" width="32px" height="32px" :src="item.icon" alt="" />
<div v-else class="w-8 h-8 bg-green-500 rounded flex items-center justify-center">
<svg width="20" height="20" viewBox="0 0 16 16" fill="white">
<path d="M8 1L8 15M1 8L15 8" stroke="white" stroke-width="2" stroke-linecap="round"/>
<path d="M8 1L8 15M1 8L15 8" stroke="white" stroke-width="2" stroke-linecap="round" />
</svg>
</div>
<div v-if="item.badge" class="absolute -top-2 -right-2 bg-red-500 text-white text-xs rounded-full min-w-[16px] h-4 flex items-center justify-center px-1">
<div
v-if="item.badge"
class="absolute -top-2 -right-2 bg-red-500 text-white text-xs rounded-full min-w-[16px] h-4 flex items-center justify-center px-1"
>
{{ item.badge }}
</div>
</div>
@@ -62,4 +69,4 @@ defineProps({
align-items: center;
justify-content: center;
}
</style>
</style>

View File

@@ -5,4 +5,4 @@ export default {
attr,
content,
options,
};
};

View File

@@ -25,4 +25,4 @@ export default () => ({
],
},
styles: {},
});
});

View File

@@ -43,7 +43,7 @@ const generatePageData = (widgetNames: string[]) => {
const menus: Record<
string,
{
pageType: number;
pageType: number;
name: string;
pageData: any[];
}
@@ -66,7 +66,7 @@ const menus: Record<
[pagesTypeEnum.WORKBENCH]: {
pageType: 4,
name: '工作台装修',
pageData: generatePageData(['workbench-shortcuts','workbench-system', 'workbench-tools']),
pageData: generatePageData(['workbench-shortcuts', 'workbench-system', 'workbench-tools']),
},
});

View File

@@ -5,7 +5,7 @@
<div class="pages-preview mx-[30px]">
<div class="flex tabbar dark:bg-gray-700">
<div class="flex flex-col flex-1 justify-center items-center tabbar-item" v-for="(item, index) in tabbar.list" :key="index">
<img class="w-[22px] h-[22px]" :src="item.unselected.includes('http') ? item.unselected: baseURL + item.unselected" alt="" />
<img class="w-[22px] h-[22px]" :src="item.unselected.includes('http') ? item.unselected : baseURL + item.unselected" alt="" />
<div class="leading-3 text-[12px] mt-[4px] dark:text-gray-300">{{ item.name }}</div>
</div>
</div>
@@ -112,7 +112,7 @@ const setData = async () => {
selected: item.selected,
unselected: item.unselected,
link: JSON.stringify(item.link), // 将link转为字符串
sortOrder: index,
sortOrder: index,
};
});
await putObj(data);

View File

@@ -141,10 +141,10 @@
</template>
<script setup lang="ts" name="wx-auto-reply">
import {fetchAccountList} from '/@/api/mp/wx-account';
import {BasicTableProps, useTable} from '/@/hooks/table';
import {useMessage, useMessageBox} from '/@/hooks/message';
import {addObj, delObj, getPage, putObj} from '/@/api/mp/wx-auto-reply';
import { fetchAccountList } from '/@/api/mp/wx-account';
import { BasicTableProps, useTable } from '/@/hooks/table';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { addObj, delObj, getPage, putObj } from '/@/api/mp/wx-auto-reply';
const QueryTree = defineAsyncComponent(() => import('/@/components/QueryTree/index.vue'));
@@ -343,10 +343,10 @@ const handleAdd = () => {
// 默认选择第一个公众号
onMounted(async () => {
const { data } = await deptData.queryList();
if (data?.length > 0) {
handleNodeClick(data[0]);
}
const { data } = await deptData.queryList();
if (data?.length > 0) {
handleNodeClick(data[0]);
}
});
</script>

View File

@@ -4,7 +4,7 @@
<el-row v-show="showSearch">
<el-form ref="queryRef" :inline="true" :model="state.queryForm" @keyup.enter="getDataList">
<el-form-item :label="$t('wxFansMsg.appName')" prop="wxAccountAppid">
<el-select v-model="state.queryForm.wxAccountAppid" :placeholder="$t('wxFansMsg.appName')" clearable >
<el-select v-model="state.queryForm.wxAccountAppid" :placeholder="$t('wxFansMsg.appName')" clearable>
<el-option v-for="item in accountList" :key="item.appid" :label="item.name" :value="item.appid" />
</el-select>
</el-form-item>

View File

@@ -1,154 +1,144 @@
<template>
<div class="layout-padding">
<splitpanes>
<pane size="20">
<div class="layout-padding-auto layout-padding-view">
<el-scrollbar>
<query-tree class="mt10" :query="deptData.queryList" @node-click="handleNodeClick" placeholder="请输入微信公众号名称" />
</el-scrollbar>
</div>
</pane>
<pane>
<div class="layout-padding-auto layout-padding-view">
<el-scrollbar>
<div v-loading="loading" class="clearfix public-account-management">
<div class="left">
<div class="weixin-hd">
<div class="weixin-title">{{ name }}</div>
</div>
<div class="clearfix weixin-menu menu_main">
<div v-for="(item, i) of menuList" :key="i" class="menu_bottom">
<div :class="{ active: isActive === i }" class="menu_item el-icon-s-fold" @click="menuClick(i, item)">
{{ item.name }}
</div>
<!-- 以下为二级菜单-->
<div v-if="isSubMenuFlag === i" class="submenu">
<template v-for="(subItem, k) in item.sub_button">
<div v-if="item.sub_button" :key="k" class="subtitle menu_bottom">
<div :class="{ active: isSubMenuActive === i + '' + k }" class="menu_subItem" @click="subMenuClick(subItem, i, k)">
{{ subItem.name }}
</div>
</div>
</template>
<!-- 二级菜单加号 当长度 小于 5 才显示二级菜单的加号 -->
<div v-if="!item.sub_button || item.sub_button.length < 5" class="menu_bottom menu_addicon" @click="addSubMenu(i, item)">
<el-icon>
<el-icon><Plus /></el-icon>
</el-icon>
</div>
</div>
</div>
<!-- 一级菜单加号 -->
<div v-if="menuList.length < 3" class="menu_bottom menu_addicon" @click="addMenu">
<el-icon>
<el-icon><Plus /></el-icon>
</el-icon>
</div>
</div>
<div class="flex items-center justify-center gap-4 mt-4 mb-6 save_div">
<el-button
class="save_btn !px-6 !h-9 hover:scale-105 transition-transform"
type="primary"
size="small"
@click="handleSave"
>
<el-icon class="mr-1"><Check /></el-icon>
保存发布
</el-button>
<el-button
class="save_btn !px-6 !h-9 hover:scale-105 transition-transform"
type="warning"
size="small"
@click="handleDelete"
>
<el-icon class="mr-1"><Delete /></el-icon>
清空菜单
</el-button>
</div>
</div>
<div class="layout-padding">
<splitpanes>
<pane size="20">
<div class="layout-padding-auto layout-padding-view">
<el-scrollbar>
<query-tree class="mt10" :query="deptData.queryList" @node-click="handleNodeClick" placeholder="请输入微信公众号名称" />
</el-scrollbar>
</div>
</pane>
<pane>
<div class="layout-padding-auto layout-padding-view">
<el-scrollbar>
<div v-loading="loading" class="clearfix public-account-management">
<div class="left">
<div class="weixin-hd">
<div class="weixin-title">{{ name }}</div>
</div>
<div class="clearfix weixin-menu menu_main">
<div v-for="(item, i) of menuList" :key="i" class="menu_bottom">
<div :class="{ active: isActive === i }" class="menu_item el-icon-s-fold" @click="menuClick(i, item)">
{{ item.name }}
</div>
<!-- 以下为二级菜单-->
<div v-if="isSubMenuFlag === i" class="submenu">
<template v-for="(subItem, k) in item.sub_button">
<div v-if="item.sub_button" :key="k" class="subtitle menu_bottom">
<div :class="{ active: isSubMenuActive === i + '' + k }" class="menu_subItem" @click="subMenuClick(subItem, i, k)">
{{ subItem.name }}
</div>
</div>
</template>
<!-- 二级菜单加号 当长度 小于 5 才显示二级菜单的加号 -->
<div v-if="!item.sub_button || item.sub_button.length < 5" class="menu_bottom menu_addicon" @click="addSubMenu(i, item)">
<el-icon>
<el-icon><Plus /></el-icon>
</el-icon>
</div>
</div>
</div>
<!-- 一级菜单加号 -->
<div v-if="menuList.length < 3" class="menu_bottom menu_addicon" @click="addMenu">
<el-icon>
<el-icon><Plus /></el-icon>
</el-icon>
</div>
</div>
<div class="flex items-center justify-center gap-4 mt-4 mb-6 save_div">
<el-button class="save_btn !px-6 !h-9 hover:scale-105 transition-transform" type="primary" size="small" @click="handleSave">
<el-icon class="mr-1"><Check /></el-icon>
保存发布
</el-button>
<el-button class="save_btn !px-6 !h-9 hover:scale-105 transition-transform" type="warning" size="small" @click="handleDelete">
<el-icon class="mr-1"><Delete /></el-icon>
清空菜单
</el-button>
</div>
</div>
<div v-if="showRightFlag" class="right">
<div class="configure_page">
<div class="delete_btn">
<el-button icon="Delete" size="mini" type="danger" @click="deleteMenu(tempObj)">删除当前菜单 </el-button>
</div>
<div>
<span>菜单名称</span>
<el-input v-model="tempObj.name" class="input_width" clearable placeholder="请输入菜单名称" />
</div>
<div v-if="showConfigureContent">
<div class="menu_content">
<span>菜单标识</span>
<el-input v-model="tempObj.key" class="input_width" clearable placeholder="请输入菜单 KEY" />
</div>
<div class="menu_content">
<span>菜单内容</span>
<el-select v-model="tempObj.type" class="menu_option" clearable placeholder="请选择">
<el-option v-for="item in menuOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="configur_content" v-if="tempObj.type === 'view'">
<span>跳转链接</span>
<el-input class="input_width" v-model="tempObj.url" placeholder="请输入链接" clearable />
</div>
<div class="configur_content" v-if="tempObj.type === 'miniprogram'">
<div class="applet">
<span>小程序的 appid </span>
<el-input class="input_width" v-model="tempObj.miniProgramAppId" placeholder="请输入小程序的appid" clearable />
</div>
<div class="applet">
<span>小程序的页面路径</span>
<el-input
class="input_width"
v-model="tempObj.miniProgramPagePath"
placeholder="请输入小程序的页面路径pages/index"
clearable
/>
</div>
<div class="applet">
<span>小程序的备用网页</span>
<el-input class="input_width" v-model="tempObj.url" placeholder="不支持小程序的老版本客户端将打开本网页" clearable />
</div>
<p class="blue">tips:需要和公众号进行关联才可以把小程序绑定带微信菜单上哟</p>
</div>
<div class="configur_content" v-if="tempObj.type === 'article_view_limited'">
<el-row>
<div class="select-item" v-if="tempObj && tempObj.replyArticles">
<wx-news :objData="tempObj.replyArticles" />
<el-row class="ope-row">
<el-button type="danger" icon="delete" circle @click="deleteMaterial" />
</el-row>
</div>
<div v-else>
<el-row>
<el-col :span="24" style="text-align: center">
<el-button type="success" @click="openMaterial"> 素材库选择<i class="fansel-icon--right"></i> </el-button>
</el-col>
</el-row>
</div>
<wx-material-select ref="dialogNewsRef" @selectMaterial="selectMaterial" />
</el-row>
</div>
<div class="configur_content" v-if="tempObj.type === 'click' || tempObj.type === 'scancode_waitmsg'">
<wx-reply :objData="tempObj" v-if="hackResetWxReplySelect" />
</div>
</div>
</div>
</div>
</div>
</el-scrollbar>
</div>
</pane>
</splitpanes>
</div>
<div v-if="showRightFlag" class="right">
<div class="configure_page">
<div class="delete_btn">
<el-button icon="Delete" size="mini" type="danger" @click="deleteMenu(tempObj)">删除当前菜单 </el-button>
</div>
<div>
<span>菜单名称</span>
<el-input v-model="tempObj.name" class="input_width" clearable placeholder="请输入菜单名称" />
</div>
<div v-if="showConfigureContent">
<div class="menu_content">
<span>菜单标识</span>
<el-input v-model="tempObj.key" class="input_width" clearable placeholder="请输入菜单 KEY" />
</div>
<div class="menu_content">
<span>菜单内容</span>
<el-select v-model="tempObj.type" class="menu_option" clearable placeholder="请选择">
<el-option v-for="item in menuOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="configur_content" v-if="tempObj.type === 'view'">
<span>跳转链接</span>
<el-input class="input_width" v-model="tempObj.url" placeholder="请输入链接" clearable />
</div>
<div class="configur_content" v-if="tempObj.type === 'miniprogram'">
<div class="applet">
<span>小程序的 appid </span>
<el-input class="input_width" v-model="tempObj.miniProgramAppId" placeholder="请输入小程序的appid" clearable />
</div>
<div class="applet">
<span>小程序的页面路径</span>
<el-input
class="input_width"
v-model="tempObj.miniProgramPagePath"
placeholder="请输入小程序的页面路径pages/index"
clearable
/>
</div>
<div class="applet">
<span>小程序的备用网页</span>
<el-input class="input_width" v-model="tempObj.url" placeholder="不支持小程序的老版本客户端将打开本网页" clearable />
</div>
<p class="blue">tips:需要和公众号进行关联才可以把小程序绑定带微信菜单上哟</p>
</div>
<div class="configur_content" v-if="tempObj.type === 'article_view_limited'">
<el-row>
<div class="select-item" v-if="tempObj && tempObj.replyArticles">
<wx-news :objData="tempObj.replyArticles" />
<el-row class="ope-row">
<el-button type="danger" icon="delete" circle @click="deleteMaterial" />
</el-row>
</div>
<div v-else>
<el-row>
<el-col :span="24" style="text-align: center">
<el-button type="success" @click="openMaterial"> 素材库选择<i class="fansel-icon--right"></i> </el-button>
</el-col>
</el-row>
</div>
<wx-material-select ref="dialogNewsRef" @selectMaterial="selectMaterial" />
</el-row>
</div>
<div class="configur_content" v-if="tempObj.type === 'click' || tempObj.type === 'scancode_waitmsg'">
<wx-reply :objData="tempObj" v-if="hackResetWxReplySelect" />
</div>
</div>
</div>
</div>
</div>
</el-scrollbar>
</div>
</pane>
</splitpanes>
</div>
</template>
<script lang="ts" name="wx-menu" setup>
import {getObj, publishObj, saveObj} from '/@/api/mp/wx-menu';
import { getObj, publishObj, saveObj } from '/@/api/mp/wx-menu';
// 部门树使用的数据
import {fetchAccountList} from '/@/api/mp/wx-account';
import {useMessage, useMessageBox} from '/@/hooks/message';
import { fetchAccountList } from '/@/api/mp/wx-account';
import { useMessage, useMessageBox } from '/@/hooks/message';
const WxMaterialSelect = defineAsyncComponent(() => import('/@/components/Wechat/wx-material-select/main.vue'));
@@ -160,17 +150,17 @@ const WxNews = defineAsyncComponent(() => import('/@/components/Wechat/wx-news/i
// 点击树
const handleNodeClick = (node: any) => {
accountId.value = node.appid;
name.value = node.name;
getMenuFun();
accountId.value = node.appid;
name.value = node.name;
getMenuFun();
};
const deptData = reactive({
queryList: (name?: string) => {
return fetchAccountList({
name: name,
});
},
queryList: (name?: string) => {
return fetchAccountList({
name: name,
});
},
});
const loading = ref(false);
@@ -186,228 +176,228 @@ const isSubMenuActive = ref('-1');
const isSubMenuFlag = ref(-1);
const menuList = reactive([
{
name: '菜单名称',
sub_button: [],
},
{
name: '菜单名称',
sub_button: [],
},
] as any);
const hackResetWxReplySelect = ref(false);
const menuOptions = ref([
{
value: 'view',
label: '跳转网页',
},
{
value: 'miniprogram',
label: '跳转小程序',
},
{
value: 'click',
label: '点击回复',
},
{
value: 'article_view_limited',
label: '跳转图文消息',
},
{
value: 'scancode_push',
label: '扫码直接返回结果',
},
{
value: 'scancode_waitmsg',
label: '扫码回复',
},
{
value: 'pic_sysphoto',
label: '系统拍照发图',
},
{
value: 'pic_photo_or_album',
label: '拍照或者相册',
},
{
value: 'pic_weixin',
label: '微信相册',
},
{
value: 'location_select',
label: '选择地理位置',
},
{
value: 'view',
label: '跳转网页',
},
{
value: 'miniprogram',
label: '跳转小程序',
},
{
value: 'click',
label: '点击回复',
},
{
value: 'article_view_limited',
label: '跳转图文消息',
},
{
value: 'scancode_push',
label: '扫码直接返回结果',
},
{
value: 'scancode_waitmsg',
label: '扫码回复',
},
{
value: 'pic_sysphoto',
label: '系统拍照发图',
},
{
value: 'pic_photo_or_album',
label: '拍照或者相册',
},
{
value: 'pic_weixin',
label: '微信相册',
},
{
value: 'location_select',
label: '选择地理位置',
},
]);
const showRightFlag = ref(false);
let tempObj = ref({
replyArticles: [] as any,
articleId: '',
appId: '',
replyArticles: [] as any,
articleId: '',
appId: '',
});
const tempSelfObj = reactive({
grand: '', // 表示二级菜单
index: '', // 表示一级菜单索引
secondIndex: '', // 表示二级菜单索引
grand: '', // 表示二级菜单
index: '', // 表示一级菜单索引
secondIndex: '', // 表示二级菜单索引
});
const getMenuFun = () => {
getObj(accountId.value).then((res) => {
if (res.data) {
const data = JSON.parse(res.data);
if (data && data.button) {
Object.assign(menuList, data.button);
}
} else {
menuList.length = 0;
Object.assign(menuList, {
name: '菜单名称',
sub_button: [],
});
}
});
getObj(accountId.value).then((res) => {
if (res.data) {
const data = JSON.parse(res.data);
if (data && data.button) {
Object.assign(menuList, data.button);
}
} else {
menuList.length = 0;
Object.assign(menuList, {
name: '菜单名称',
sub_button: [],
});
}
});
};
const showConfigureContent = ref(true);
// 一级菜单点击事件
const menuClick = (i, item) => {
hackResetWxReplySelect.value = false;
nextTick(() => {
hackResetWxReplySelect.value = true;
});
showRightFlag.value = true; // 右边菜单
tempObj.value = item;
tempObj.value.appId = accountId.value;
showConfigureContent.value = !(item.sub_button && item.sub_button.length > 0); // 有子菜单,就不显示配置内容
isActive.value = i;
isSubMenuFlag.value = i;
isSubMenuActive.value = '-1';
tempSelfObj.grand = '1'; //表示一级菜单
tempSelfObj.index = i; //表示一级菜单索引
hackResetWxReplySelect.value = false;
nextTick(() => {
hackResetWxReplySelect.value = true;
});
showRightFlag.value = true; // 右边菜单
tempObj.value = item;
tempObj.value.appId = accountId.value;
showConfigureContent.value = !(item.sub_button && item.sub_button.length > 0); // 有子菜单,就不显示配置内容
isActive.value = i;
isSubMenuFlag.value = i;
isSubMenuActive.value = '-1';
tempSelfObj.grand = '1'; //表示一级菜单
tempSelfObj.index = i; //表示一级菜单索引
};
// 点击二级菜单
const subMenuClick = (subItem, index, k) => {
hackResetWxReplySelect.value = false;
nextTick(() => {
hackResetWxReplySelect.value = true;
});
showRightFlag.value = true; // 右边菜单
// Object.assign(tempObj, subItem) // 这个如果放在顶部flag 会没有。因为重新赋值了。
tempObj.value = subItem;
tempObj.value.appId = accountId.value;
showConfigureContent.value = true;
isActive.value = -1; // 一级菜单去除样式
isSubMenuActive.value = index + '' + k; // 二级菜单选中样式
tempSelfObj.grand = '2'; //表示二级菜单
tempSelfObj.index = index; //表示一级菜单索引
tempSelfObj.secondIndex = k; //表示二级菜单索引
hackResetWxReplySelect.value = false;
nextTick(() => {
hackResetWxReplySelect.value = true;
});
showRightFlag.value = true; // 右边菜单
// Object.assign(tempObj, subItem) // 这个如果放在顶部flag 会没有。因为重新赋值了。
tempObj.value = subItem;
tempObj.value.appId = accountId.value;
showConfigureContent.value = true;
isActive.value = -1; // 一级菜单去除样式
isSubMenuActive.value = index + '' + k; // 二级菜单选中样式
tempSelfObj.grand = '2'; //表示二级菜单
tempSelfObj.index = index; //表示一级菜单索引
tempSelfObj.secondIndex = k; //表示二级菜单索引
};
// 添加横向二级菜单item 表示要操作的父菜单
const addSubMenu = (i, item) => {
if (!item.sub_button || item.sub_button.length <= 0) {
item['sub_button'] = [];
showConfigureContent.value = false;
}
let addButton = {
name: '子菜单名称',
reply: {
// 用于存储回复内容
type: 'text',
accountId: accountId.value, // 保证组件里,可以使用到对应的公众号
},
};
item.sub_button.push(addButton);
if (!item.sub_button || item.sub_button.length <= 0) {
item['sub_button'] = [];
showConfigureContent.value = false;
}
let addButton = {
name: '子菜单名称',
reply: {
// 用于存储回复内容
type: 'text',
accountId: accountId.value, // 保证组件里,可以使用到对应的公众号
},
};
item.sub_button.push(addButton);
};
// 添加横向一级菜单
const addMenu = () => {
const addButton = {
name: '菜单名称',
sub_button: [],
reply: {
// 用于存储回复内容
type: 'text',
accountId: accountId.value, // 保证组件里,可以使用到对应的公众号
},
};
menuList.push(addButton);
const addButton = {
name: '菜单名称',
sub_button: [],
reply: {
// 用于存储回复内容
type: 'text',
accountId: accountId.value, // 保证组件里,可以使用到对应的公众号
},
};
menuList.push(addButton);
};
const deleteMenu = () => {
useMessageBox()
.confirm('确定要删除吗?')
.then(() => {
if (tempSelfObj.grand === '1') {
menuList.splice(tempSelfObj.index, 1);
} else if (tempSelfObj.grand === '2') {
menuList[tempSelfObj.index].sub_button.splice(tempSelfObj.secondIndex, 1);
}
useMessage().success('删除成功');
Object.assign(tempObj, {});
showRightFlag.value = false;
isActive.value = -1;
isSubMenuActive.value = '-1';
})
.catch((err) => {
useMessage().error(err.msg);
});
useMessageBox()
.confirm('确定要删除吗?')
.then(() => {
if (tempSelfObj.grand === '1') {
menuList.splice(tempSelfObj.index, 1);
} else if (tempSelfObj.grand === '2') {
menuList[tempSelfObj.index].sub_button.splice(tempSelfObj.secondIndex, 1);
}
useMessage().success('删除成功');
Object.assign(tempObj, {});
showRightFlag.value = false;
isActive.value = -1;
isSubMenuActive.value = '-1';
})
.catch((err) => {
useMessage().error(err.msg);
});
};
const handleSave = async () => {
try {
await useMessageBox().confirm('确定要保存该菜单吗?');
await saveObj(accountId.value, { button: menuList });
await publishObj(accountId.value);
useMessage().success('发布成功');
} catch (err: any) {
useMessage().error(err.msg);
}
try {
await useMessageBox().confirm('确定要保存该菜单吗?');
await saveObj(accountId.value, { button: menuList });
await publishObj(accountId.value);
useMessage().success('发布成功');
} catch (err: any) {
useMessage().error(err.msg);
}
};
const deleteMaterial = () => {
tempObj.value.replyArticles = [];
tempObj.value.articleId = '';
tempObj.value.replyArticles = [];
tempObj.value.articleId = '';
};
const dialogNewsRef = ref();
const openMaterial = () => {
dialogNewsRef.value.openDialog({ type: 'news', accountId: accountId.value });
dialogNewsRef.value.openDialog({ type: 'news', accountId: accountId.value });
};
const selectMaterial = (item) => {
const articleId = item.articleId;
const articles = item.content.newsItem;
// 提示,针对多图文
if (articles.length > 1) {
// this.$alert('您选择的是多图文,将默认跳转第一篇', '提示', {
// confirmButtonText: '确定'
// })
}
const articleId = item.articleId;
const articles = item.content.newsItem;
// 提示,针对多图文
if (articles.length > 1) {
// this.$alert('您选择的是多图文,将默认跳转第一篇', '提示', {
// confirmButtonText: '确定'
// })
}
// 设置菜单的回复
tempObj.value.articleId = articleId;
tempObj.value.replyArticles = [];
articles.forEach((article) => {
tempObj.value.replyArticles.push({
title: article.title,
description: article.digest,
picUrl: article.picUrl,
url: article.url,
});
});
// 设置菜单的回复
tempObj.value.articleId = articleId;
tempObj.value.replyArticles = [];
articles.forEach((article) => {
tempObj.value.replyArticles.push({
title: article.title,
description: article.digest,
picUrl: article.picUrl,
url: article.url,
});
});
};
const handleDelete = () => {};
// 默认选择第一个公众号
onMounted(async () => {
const { data } = await deptData.queryList();
if (data?.length > 0) {
handleNodeClick(data[0]);
}
const { data } = await deptData.queryList();
if (data?.length > 0) {
handleNodeClick(data[0]);
}
});
</script>

View File

@@ -49,9 +49,9 @@
</template>
<script setup lang="ts" name="wx-statistics">
import {useMessage} from '/@/hooks/message';
import {fetchAccountList, fetchStatistics} from '/@/api/mp/wx-account';
import {markRaw} from 'vue';
import { useMessage } from '/@/hooks/message';
import { fetchAccountList, fetchStatistics } from '/@/api/mp/wx-account';
import { markRaw } from 'vue';
import * as echarts from 'echarts';
const QueryTree = defineAsyncComponent(() => import('/@/components/QueryTree/index.vue'));
@@ -216,10 +216,10 @@ const initdata = () => {
// 默认选择第一个公众号
onMounted(async () => {
const { data } = await deptData.queryList();
if (data?.length > 0) {
handleNodeClick(data[0]);
}
const { data } = await deptData.queryList();
if (data?.length > 0) {
handleNodeClick(data[0]);
}
});
</script>

View File

@@ -118,10 +118,10 @@ const dictType = ref([
label: '处理完成',
value: '2',
},
{
label: '退款成功',
value: '5',
},
{
label: '退款成功',
value: '5',
},
]);
// table hook

View File

@@ -136,7 +136,7 @@ const onSubmit = async () => {
refundAmount: form.amount,
remark: form.remark,
channelId: form.channelId,
channelMchId: form.channelMchId,
channelMchId: form.channelMchId,
});
useMessage().success(t('common.optSuccessText')); // 如果退款成功,则显示成功提示信息

View File

@@ -1,178 +1,156 @@
<template>
<el-dialog
title="编辑评语"
v-model="visible"
:width="700"
:close-on-click-modal="false"
draggable>
<el-form
ref="dataFormRef"
:model="form"
:rules="dataRules"
label-width="120px"
v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="学">
<el-input v-model="form.stuNo" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="姓名">
<el-input v-model="form.realName" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="学年">
<el-input v-model="form.schoolYear" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="学期">
<el-input v-model="form.schoolTerm" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="评语" prop="comment">
<el-input
v-model="form.comment"
type="textarea"
:rows="6"
placeholder="请输入评语"
style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="家长寄语" prop="parentalMsg">
<el-input
v-model="form.parentalMsg"
type="textarea"
:rows="6"
placeholder="请输入家长寄语"
style="width: 100%" />
</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" :width="700" :close-on-click-modal="false" draggable>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="120px" v-loading="loading">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item label="学号">
<el-input v-model="form.stuNo" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="姓名">
<el-input v-model="form.realName" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="学">
<el-input v-model="form.schoolYear" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="学期">
<el-input v-model="form.schoolTerm" disabled style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="评语" prop="comment">
<el-input v-model="form.comment" type="textarea" :rows="6" placeholder="请输入评语" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item label="家长寄语" prop="parentalMsg">
<el-input v-model="form.parentalMsg" type="textarea" :rows="6" placeholder="请输入家长寄语" style="width: 100%" />
</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="QualityReportEditDialog">
import { ref, reactive, nextTick } from 'vue'
import { useMessage } from '/@/hooks/message'
import { updatePY } from '/@/api/ems/qualityReport'
import { getDicts } from '/@/api/admin/dict'
import { ref, reactive, nextTick } from 'vue';
import { useMessage } from '/@/hooks/message';
import { updatePY } from '/@/api/ems/qualityReport';
import { getDicts } from '/@/api/admin/dict';
const emit = defineEmits(['refresh'])
const emit = defineEmits(['refresh']);
// 定义变量内容
const dataFormRef = ref()
const visible = ref(false)
const loading = ref(false)
const schoolTermList = ref<any[]>([])
const dataFormRef = ref();
const visible = ref(false);
const loading = ref(false);
const schoolTermList = ref<any[]>([]);
// 提交表单数据
const form = reactive({
stuNo: '',
realName: '',
schoolYear: '',
schoolTerm: '',
comment: '',
parentalMsg: ''
})
stuNo: '',
realName: '',
schoolYear: '',
schoolTerm: '',
comment: '',
parentalMsg: '',
});
// 定义校验规则
const dataRules = {
// comment 和 parentalMsg 非必填,不需要验证规则
}
// comment 和 parentalMsg 非必填,不需要验证规则
};
// 格式化学期
const formatSchoolTerm = (value: string | number) => {
if (value === null || value === undefined || value === '') {
return '-'
}
const dictItem = schoolTermList.value.find(item => item.value == value)
return dictItem ? dictItem.label : value
}
if (value === null || value === undefined || value === '') {
return '-';
}
const dictItem = schoolTermList.value.find((item) => item.value == value);
return dictItem ? dictItem.label : value;
};
// 打开弹窗
const openDialog = async (row: any) => {
visible.value = true
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields()
form.stuNo = row.stuNo || ''
form.realName = row.realName || ''
form.schoolYear = row.schoolYear || ''
form.schoolTerm = row.schoolTerm || ''
form.comment = row.comment || ''
form.parentalMsg = row.parentalMsg || ''
})
}
visible.value = true;
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
form.stuNo = row.stuNo || '';
form.realName = row.realName || '';
form.schoolYear = row.schoolYear || '';
form.schoolTerm = row.schoolTerm || '';
form.comment = row.comment || '';
form.parentalMsg = row.parentalMsg || '';
});
};
// 提交表单
const onSubmit = async () => {
if (!dataFormRef.value) return
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return
loading.value = true
try {
const submitData = {
stuNo: form.stuNo,
comment: form.comment || '',
parentalMsg: form.parentalMsg || '',
schoolYear: form.schoolYear,
schoolTerm: form.schoolTerm
}
await updatePY(submitData)
useMessage().success('保存成功')
visible.value = false
emit('refresh')
} catch (err: any) {
useMessage().error(err.msg || '保存失败')
} finally {
loading.value = false
}
})
}
if (!dataFormRef.value) return;
await dataFormRef.value.validate(async (valid: boolean) => {
if (!valid) return;
loading.value = true;
try {
const submitData = {
stuNo: form.stuNo,
comment: form.comment || '',
parentalMsg: form.parentalMsg || '',
schoolYear: form.schoolYear,
schoolTerm: form.schoolTerm,
};
await updatePY(submitData);
useMessage().success('保存成功');
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || '保存失败');
} finally {
loading.value = false;
}
});
};
// 获取学期字典
const getSchoolTermDict = async () => {
try {
const res = await getDicts('school_term')
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}))
} else {
schoolTermList.value = []
}
} catch (err) {
console.error('获取学期字典失败', err)
schoolTermList.value = []
}
}
try {
const res = await getDicts('school_term');
if (res.data && Array.isArray(res.data)) {
schoolTermList.value = res.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code,
}));
} else {
schoolTermList.value = [];
}
} catch (err) {
console.error('获取学期字典失败', err);
schoolTermList.value = [];
}
};
// 初始化
getSchoolTermDict()
getSchoolTermDict();
// 暴露方法
defineExpose({
openDialog
})
openDialog,
});
</script>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

File diff suppressed because it is too large Load Diff

View File

@@ -13,9 +13,7 @@
</div>
</div>
<div class="right">
<img
:src="errorPng"
/>
<img :src="errorPng" />
</div>
</div>
</div>

View File

@@ -1,33 +1,33 @@
<template>
<el-dialog :title="dataForm.id ? '编辑' : '新增'" v-model="visible" width="600px" :close-on-click-modal="false" draggable>
<el-form ref="formRef" :model="dataForm" :rules="dataRules" label-width="100px" v-loading="loading">
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="dataForm.projectName" placeholder="请输入项目名称" clearable />
</el-form-item>
<el-form-item label="项目代码" prop="projectCode">
<el-input v-model="dataForm.projectCode" placeholder="请输入项目代码" clearable />
</el-form-item>
<el-form-item label="年份" prop="year">
<el-date-picker v-model="dataForm.year" type="year" placeholder="选择年份" value-format="YYYY" style="width: 100%" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="dataForm.sort" :min="0" placeholder="请输入排序" style="width: 100%" />
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input v-model="dataForm.remarks" type="textarea" :rows="3" placeholder="请输入备注" clearable />
</el-form-item>
</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="dataForm.id ? '编辑' : '新增'" v-model="visible" width="600px" :close-on-click-modal="false" draggable>
<el-form ref="formRef" :model="dataForm" :rules="dataRules" label-width="100px" v-loading="loading">
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="dataForm.projectName" placeholder="请输入项目名称" clearable />
</el-form-item>
<el-form-item label="项目代码" prop="projectCode">
<el-input v-model="dataForm.projectCode" placeholder="请输入项目代码" clearable />
</el-form-item>
<el-form-item label="年份" prop="year">
<el-date-picker v-model="dataForm.year" type="year" placeholder="选择年份" value-format="YYYY" style="width: 100%" />
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number v-model="dataForm.sort" :min="0" placeholder="请输入排序" style="width: 100%" />
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input v-model="dataForm.remarks" type="textarea" :rows="3" placeholder="请输入备注" clearable />
</el-form-item>
</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="FinanceRecruitProjectForm">
import { reactive, ref, nextTick } from 'vue'
import { reactive, ref, nextTick } from 'vue';
import { getObj, addObj, editObj } from '/@/api/finance/recruitProject';
import { useMessage } from '/@/hooks/message';
@@ -35,86 +35,88 @@ const emit = defineEmits(['refresh']);
const formRef = ref();
const dataForm = reactive({
id: '',
projectName: '',
projectCode: '',
year: '',
sort: 0,
remarks: '',
id: '',
projectName: '',
projectCode: '',
year: '',
sort: 0,
remarks: '',
});
const visible = ref(false);
const loading = ref(false);
const dataRules = ref({
projectName: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
projectCode: [{ required: true, message: '请输入项目代码', trigger: 'blur' }],
year: [{ required: true, message: '请选择年份', trigger: 'change' }],
projectName: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
projectCode: [{ required: true, message: '请输入项目代码', trigger: 'blur' }],
year: [{ required: true, message: '请选择年份', trigger: 'change' }],
});
const openDialog = async (type: string, rowData?: any) => {
visible.value = true;
dataForm.id = '';
dataForm.projectName = '';
dataForm.projectCode = '';
dataForm.year = '';
dataForm.sort = 0;
dataForm.remarks = '';
visible.value = true;
dataForm.id = '';
dataForm.projectName = '';
dataForm.projectCode = '';
dataForm.year = '';
dataForm.sort = 0;
dataForm.remarks = '';
nextTick(() => {
formRef.value?.resetFields();
if (type === 'edit' && rowData?.id) {
loading.value = true;
getObj(rowData.id).then((res: any) => {
if (res.data) {
Object.assign(dataForm, {
id: res.data.id || '',
projectName: res.data.projectName || '',
projectCode: res.data.projectCode || '',
year: res.data.year ? String(res.data.year) : '',
sort: res.data.sort || 0,
remarks: res.data.remarks || '',
});
}
loading.value = false;
}).catch((err: any) => {
useMessage().error(err.msg || '获取详情失败');
loading.value = false;
});
}
});
nextTick(() => {
formRef.value?.resetFields();
if (type === 'edit' && rowData?.id) {
loading.value = true;
getObj(rowData.id)
.then((res: any) => {
if (res.data) {
Object.assign(dataForm, {
id: res.data.id || '',
projectName: res.data.projectName || '',
projectCode: res.data.projectCode || '',
year: res.data.year ? String(res.data.year) : '',
sort: res.data.sort || 0,
remarks: res.data.remarks || '',
});
}
loading.value = false;
})
.catch((err: any) => {
useMessage().error(err.msg || '获取详情失败');
loading.value = false;
});
}
});
};
const onSubmit = async () => {
if (loading.value) return;
loading.value = true;
if (loading.value) return;
loading.value = true;
try {
const valid = await formRef.value.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
try {
const valid = await formRef.value.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
const submitData = {
...dataForm,
year: dataForm.year ? parseInt(dataForm.year) : null,
};
const submitData = {
...dataForm,
year: dataForm.year ? parseInt(dataForm.year) : null,
};
if (dataForm.id) {
await editObj(submitData);
useMessage().success('编辑成功');
} else {
await addObj(submitData);
useMessage().success('新增成功');
}
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || (dataForm.id ? '编辑失败' : '新增失败'));
} finally {
loading.value = false;
}
if (dataForm.id) {
await editObj(submitData);
useMessage().success('编辑成功');
} else {
await addObj(submitData);
useMessage().success('新增成功');
}
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || (dataForm.id ? '编辑失败' : '新增失败'));
} finally {
loading.value = false;
}
};
defineExpose({ openDialog });
</script>
</script>

View File

@@ -1,121 +1,133 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="state.queryForm.projectName" placeholder="请输入项目名称" clearable style="width: 200px" />
</el-form-item>
<el-form-item label="项目代码" prop="projectCode">
<el-input v-model="state.queryForm.projectCode" placeholder="请输入项目代码" clearable style="width: 150px" />
</el-form-item>
<el-form-item label="年份" prop="year">
<el-date-picker v-model="state.queryForm.year" type="year" placeholder="选择年份" value-format="YYYY" style="width: 150px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="modern-page-container">
<div class="page-wrapper">
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="state.queryForm.projectName" placeholder="请输入项目名称" clearable style="width: 200px" />
</el-form-item>
<el-form-item label="项目代码" prop="projectCode">
<el-input v-model="state.queryForm.projectCode" placeholder="请输入项目代码" clearable style="width: 150px" />
</el-form-item>
<el-form-item label="年份" prop="year">
<el-date-picker v-model="state.queryForm.year" type="year" placeholder="选择年份" value-format="YYYY" style="width: 150px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
收费项目管理
</span>
<div class="header-actions">
<el-button icon="FolderAdd" type="primary" @click="formDialogRef.openDialog('add')" v-auth="'finance_recruit_project_add'">新增</el-button>
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList" />
</div>
</div>
</template>
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
收费项目管理
</span>
<div class="header-actions">
<el-button icon="FolderAdd" type="primary" @click="formDialogRef.openDialog('add')" v-auth="'finance_recruit_project_add'"
>新增</el-button
>
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList" />
</div>
</div>
</template>
<el-table ref="tableRef" :data="state.dataList" v-loading="state.loading" stripe :cell-style="tableStyle.cellStyle" :header-cell-style="tableStyle.headerCellStyle" class="modern-table">
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="projectName" label="项目名称" min-width="200" show-overflow-tooltip />
<el-table-column prop="projectCode" label="项目代码" width="150" align="center" />
<el-table-column prop="year" label="年份" width="100" align="center" />
<el-table-column prop="sort" label="排序" width="80" align="center" />
<el-table-column prop="createTime" label="创建时间" width="180" align="center" />
<el-table-column label="操作" align="center" fixed="right" width="150">
<template #default="scope">
<el-button icon="Edit" link type="primary" v-auth="'finance_recruit_project_edit'" @click="formDialogRef.openDialog('edit', scope.row)">编辑</el-button>
<el-button icon="Delete" link type="danger" v-auth="'finance_recruit_project_del'" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table"
>
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="projectName" label="项目名称" min-width="200" show-overflow-tooltip />
<el-table-column prop="projectCode" label="项目代码" width="150" align="center" />
<el-table-column prop="year" label="年份" width="100" align="center" />
<el-table-column prop="sort" label="排序" width="80" align="center" />
<el-table-column prop="createTime" label="创建时间" width="180" align="center" />
<el-table-column label="操作" align="center" fixed="right" width="150">
<template #default="scope">
<el-button icon="Edit" link type="primary" v-auth="'finance_recruit_project_edit'" @click="formDialogRef.openDialog('edit', scope.row)"
>编辑</el-button
>
<el-button icon="Delete" link type="danger" v-auth="'finance_recruit_project_del'" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="state.total > 0" :total="state.total" v-model:page="state.page" v-model:limit="state.limit" @pagination="getDataList" />
</el-card>
</div>
<pagination v-show="state.total > 0" :total="state.total" v-model:page="state.page" v-model:limit="state.limit" @pagination="getDataList" />
</el-card>
</div>
<FormDialog ref="formDialogRef" @refresh="getDataList" />
</div>
<FormDialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="FinanceRecruitProject">
import { ref, reactive, defineAsyncComponent } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { getPage, delObj } from "/@/api/finance/recruitProject";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { Search, Document } from '@element-plus/icons-vue'
import { ref, reactive, defineAsyncComponent } from 'vue';
import { BasicTableProps, useTable } from '/@/hooks/table';
import { getPage, delObj } from '/@/api/finance/recruitProject';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { Search, Document } from '@element-plus/icons-vue';
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const tableRef = ref()
const formDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const tableRef = ref();
const formDialogRef = ref();
const searchFormRef = ref();
const showSearch = ref(true);
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: getPage,
queryForm: {
projectName: '',
projectCode: '',
year: '',
},
createdIsNeed: true
pageList: getPage,
queryForm: {
projectName: '',
projectCode: '',
year: '',
},
createdIsNeed: true,
});
const { getDataList, tableStyle } = useTable(state);
const handleReset = () => {
searchFormRef.value?.resetFields();
getDataList();
searchFormRef.value?.resetFields();
getDataList();
};
const handleDelete = async (row: any) => {
try {
await useMessageBox().confirm('确定要删除该记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
} catch {
return;
}
try {
await useMessageBox().confirm('确定要删除该记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
} catch {
return;
}
try {
await delObj(row.id);
useMessage().success('删除成功');
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '删除失败');
}
try {
await delObj(row.id);
useMessage().success('删除成功');
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '删除失败');
}
};
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
</style>
</style>

View File

@@ -1,43 +1,49 @@
<template>
<el-dialog :title="dataForm.id ? '编辑' : '新增'" v-model="visible" width="600px" :close-on-click-modal="false" draggable>
<el-form ref="formRef" :model="dataForm" :rules="dataRules" label-width="100px" v-loading="loading">
<el-form-item label="批次名称" prop="settingName">
<el-input v-model="dataForm.settingName" placeholder="请输入批次名称" clearable />
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="dataForm.type" placeholder="请选择类型" style="width: 100%">
<el-option label="普通类型" value="0" />
<el-option label="新生收费" value="6" />
</el-select>
</el-form-item>
<el-form-item label="年份" prop="year">
<el-date-picker v-model="dataForm.year" type="year" placeholder="选择年份" value-format="YYYY" style="width: 100%" />
</el-form-item>
<el-form-item label="截止时间" prop="lastTime">
<el-date-picker v-model="dataForm.lastTime" type="datetime" placeholder="选择截止时间" value-format="YYYY-MM-DD HH:mm:ss" style="width: 100%" />
</el-form-item>
<el-form-item label="状态" prop="state">
<el-select v-model="dataForm.state" placeholder="请选择状态" style="width: 100%">
<el-option label="未开始" value="0" />
<el-option label="进行中" value="30" />
<el-option label="已结束" value="100" />
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input v-model="dataForm.remarks" type="textarea" :rows="3" placeholder="请输入备注" clearable />
</el-form-item>
</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="dataForm.id ? '编辑' : '新增'" v-model="visible" width="600px" :close-on-click-modal="false" draggable>
<el-form ref="formRef" :model="dataForm" :rules="dataRules" label-width="100px" v-loading="loading">
<el-form-item label="批次名称" prop="settingName">
<el-input v-model="dataForm.settingName" placeholder="请输入批次名称" clearable />
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="dataForm.type" placeholder="请选择类型" style="width: 100%">
<el-option label="普通类型" value="0" />
<el-option label="新生收费" value="6" />
</el-select>
</el-form-item>
<el-form-item label="年份" prop="year">
<el-date-picker v-model="dataForm.year" type="year" placeholder="选择年份" value-format="YYYY" style="width: 100%" />
</el-form-item>
<el-form-item label="截止时间" prop="lastTime">
<el-date-picker
v-model="dataForm.lastTime"
type="datetime"
placeholder="选择截止时间"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="状态" prop="state">
<el-select v-model="dataForm.state" placeholder="请选择状态" style="width: 100%">
<el-option label="未开始" value="0" />
<el-option label="进行中" value="30" />
<el-option label="已结束" value="100" />
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input v-model="dataForm.remarks" type="textarea" :rows="3" placeholder="请输入备注" clearable />
</el-form-item>
</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="FinanceRecruitSettingForm">
import { reactive, ref, nextTick } from 'vue'
import { reactive, ref, nextTick } from 'vue';
import { getObj, addObj, editObj } from '/@/api/finance/recruitSetting';
import { useMessage } from '/@/hooks/message';
@@ -45,91 +51,93 @@ const emit = defineEmits(['refresh']);
const formRef = ref();
const dataForm = reactive({
id: '',
settingName: '',
type: '6',
year: '',
lastTime: '',
state: '0',
remarks: '',
id: '',
settingName: '',
type: '6',
year: '',
lastTime: '',
state: '0',
remarks: '',
});
const visible = ref(false);
const loading = ref(false);
const dataRules = ref({
settingName: [{ required: true, message: '请输入批次名称', trigger: 'blur' }],
type: [{ required: true, message: '请选择类型', trigger: 'change' }],
year: [{ required: true, message: '请选择年份', trigger: 'change' }],
lastTime: [{ required: true, message: '请选择截止时间', trigger: 'change' }],
state: [{ required: true, message: '请选择状态', trigger: 'change' }],
settingName: [{ required: true, message: '请输入批次名称', trigger: 'blur' }],
type: [{ required: true, message: '请选择类型', trigger: 'change' }],
year: [{ required: true, message: '请选择年份', trigger: 'change' }],
lastTime: [{ required: true, message: '请选择截止时间', trigger: 'change' }],
state: [{ required: true, message: '请选择状态', trigger: 'change' }],
});
const openDialog = async (type: string, rowData?: any) => {
visible.value = true;
dataForm.id = '';
dataForm.settingName = '';
dataForm.type = '6';
dataForm.year = '';
dataForm.lastTime = '';
dataForm.state = '0';
dataForm.remarks = '';
visible.value = true;
dataForm.id = '';
dataForm.settingName = '';
dataForm.type = '6';
dataForm.year = '';
dataForm.lastTime = '';
dataForm.state = '0';
dataForm.remarks = '';
nextTick(() => {
formRef.value?.resetFields();
if (type === 'edit' && rowData?.id) {
loading.value = true;
getObj(rowData.id).then((res: any) => {
if (res.data) {
Object.assign(dataForm, {
id: res.data.id || '',
settingName: res.data.settingName || '',
type: res.data.type || '6',
year: res.data.year ? String(res.data.year) : '',
lastTime: res.data.lastTime || '',
state: res.data.state || '0',
remarks: res.data.remarks || '',
});
}
loading.value = false;
}).catch((err: any) => {
useMessage().error(err.msg || '获取详情失败');
loading.value = false;
});
}
});
nextTick(() => {
formRef.value?.resetFields();
if (type === 'edit' && rowData?.id) {
loading.value = true;
getObj(rowData.id)
.then((res: any) => {
if (res.data) {
Object.assign(dataForm, {
id: res.data.id || '',
settingName: res.data.settingName || '',
type: res.data.type || '6',
year: res.data.year ? String(res.data.year) : '',
lastTime: res.data.lastTime || '',
state: res.data.state || '0',
remarks: res.data.remarks || '',
});
}
loading.value = false;
})
.catch((err: any) => {
useMessage().error(err.msg || '获取详情失败');
loading.value = false;
});
}
});
};
const onSubmit = async () => {
if (loading.value) return;
loading.value = true;
if (loading.value) return;
loading.value = true;
try {
const valid = await formRef.value.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
try {
const valid = await formRef.value.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
const submitData = {
...dataForm,
year: dataForm.year ? parseInt(dataForm.year) : null,
};
const submitData = {
...dataForm,
year: dataForm.year ? parseInt(dataForm.year) : null,
};
if (dataForm.id) {
await editObj(submitData);
useMessage().success('编辑成功');
} else {
await addObj(submitData);
useMessage().success('新增成功');
}
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || (dataForm.id ? '编辑失败' : '新增失败'));
} finally {
loading.value = false;
}
if (dataForm.id) {
await editObj(submitData);
useMessage().success('编辑成功');
} else {
await addObj(submitData);
useMessage().success('新增成功');
}
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || (dataForm.id ? '编辑失败' : '新增失败'));
} finally {
loading.value = false;
}
};
defineExpose({ openDialog });
</script>
</script>

View File

@@ -1,139 +1,151 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
<el-form-item label="批次名称" prop="settingName">
<el-input v-model="state.queryForm.settingName" placeholder="请输入批次名称" clearable style="width: 200px" />
</el-form-item>
<el-form-item label="年份" prop="year">
<el-date-picker v-model="state.queryForm.year" type="year" placeholder="选择年份" value-format="YYYY" style="width: 150px" />
</el-form-item>
<el-form-item label="状态" prop="state">
<el-select v-model="state.queryForm.state" placeholder="请选择状态" clearable style="width: 150px">
<el-option label="未开始" value="0" />
<el-option label="进行中" value="30" />
<el-option label="已结束" value="100" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="modern-page-container">
<div class="page-wrapper">
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
<el-form-item label="批次名称" prop="settingName">
<el-input v-model="state.queryForm.settingName" placeholder="请输入批次名称" clearable style="width: 200px" />
</el-form-item>
<el-form-item label="年份" prop="year">
<el-date-picker v-model="state.queryForm.year" type="year" placeholder="选择年份" value-format="YYYY" style="width: 150px" />
</el-form-item>
<el-form-item label="状态" prop="state">
<el-select v-model="state.queryForm.state" placeholder="请选择状态" clearable style="width: 150px">
<el-option label="未开始" value="0" />
<el-option label="进行中" value="30" />
<el-option label="已结束" value="100" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
收费批次设置
</span>
<div class="header-actions">
<el-button icon="FolderAdd" type="primary" @click="formDialogRef.openDialog('add')" v-auth="'finance_recruit_setting_add'">新增</el-button>
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList" />
</div>
</div>
</template>
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
收费批次设置
</span>
<div class="header-actions">
<el-button icon="FolderAdd" type="primary" @click="formDialogRef.openDialog('add')" v-auth="'finance_recruit_setting_add'"
>新增</el-button
>
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList" />
</div>
</div>
</template>
<el-table ref="tableRef" :data="state.dataList" v-loading="state.loading" stripe :cell-style="tableStyle.cellStyle" :header-cell-style="tableStyle.headerCellStyle" class="modern-table">
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="settingName" label="批次名称" min-width="200" show-overflow-tooltip />
<el-table-column prop="type" label="类型" width="120" align="center">
<template #default="{ row }">
<el-tag v-if="row.type === '0'" type="info">普通类型</el-tag>
<el-tag v-else-if="row.type === '6'" type="success">新生收费</el-tag>
<span v-else>{{ row.type }}</span>
</template>
</el-table-column>
<el-table-column prop="year" label="年份" width="100" align="center" />
<el-table-column prop="lastTime" label="截止时间" width="180" align="center" />
<el-table-column prop="state" label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag v-if="row.state === '0'" type="info">未开始</el-tag>
<el-tag v-else-if="row.state === '30'" type="warning">进行中</el-tag>
<el-tag v-else-if="row.state === '100'" type="success">已结束</el-tag>
<span v-else>{{ row.state }}</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180" align="center" />
<el-table-column label="操作" align="center" fixed="right" width="150">
<template #default="scope">
<el-button icon="Edit" link type="primary" v-auth="'finance_recruit_setting_edit'" @click="formDialogRef.openDialog('edit', scope.row)">编辑</el-button>
<el-button icon="Delete" link type="danger" v-auth="'finance_recruit_setting_del'" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table"
>
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="settingName" label="批次名称" min-width="200" show-overflow-tooltip />
<el-table-column prop="type" label="类型" width="120" align="center">
<template #default="{ row }">
<el-tag v-if="row.type === '0'" type="info">普通类型</el-tag>
<el-tag v-else-if="row.type === '6'" type="success">新生收费</el-tag>
<span v-else>{{ row.type }}</span>
</template>
</el-table-column>
<el-table-column prop="year" label="年份" width="100" align="center" />
<el-table-column prop="lastTime" label="截止时间" width="180" align="center" />
<el-table-column prop="state" label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag v-if="row.state === '0'" type="info">未开始</el-tag>
<el-tag v-else-if="row.state === '30'" type="warning">进行中</el-tag>
<el-tag v-else-if="row.state === '100'" type="success">已结束</el-tag>
<span v-else>{{ row.state }}</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180" align="center" />
<el-table-column label="操作" align="center" fixed="right" width="150">
<template #default="scope">
<el-button icon="Edit" link type="primary" v-auth="'finance_recruit_setting_edit'" @click="formDialogRef.openDialog('edit', scope.row)"
>编辑</el-button
>
<el-button icon="Delete" link type="danger" v-auth="'finance_recruit_setting_del'" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="state.total > 0" :total="state.total" v-model:page="state.page" v-model:limit="state.limit" @pagination="getDataList" />
</el-card>
</div>
<pagination v-show="state.total > 0" :total="state.total" v-model:page="state.page" v-model:limit="state.limit" @pagination="getDataList" />
</el-card>
</div>
<FormDialog ref="formDialogRef" @refresh="getDataList" />
</div>
<FormDialog ref="formDialogRef" @refresh="getDataList" />
</div>
</template>
<script setup lang="ts" name="FinanceRecruitSetting">
import { ref, reactive, defineAsyncComponent } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { getPage, delObj } from "/@/api/finance/recruitSetting";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { Search, Document } from '@element-plus/icons-vue'
import { ref, reactive, defineAsyncComponent } from 'vue';
import { BasicTableProps, useTable } from '/@/hooks/table';
import { getPage, delObj } from '/@/api/finance/recruitSetting';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { Search, Document } from '@element-plus/icons-vue';
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const tableRef = ref()
const formDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const tableRef = ref();
const formDialogRef = ref();
const searchFormRef = ref();
const showSearch = ref(true);
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: getPage,
queryForm: {
settingName: '',
year: '',
state: '',
},
createdIsNeed: true
pageList: getPage,
queryForm: {
settingName: '',
year: '',
state: '',
},
createdIsNeed: true,
});
const { getDataList, tableStyle } = useTable(state);
const handleReset = () => {
searchFormRef.value?.resetFields();
getDataList();
searchFormRef.value?.resetFields();
getDataList();
};
const handleDelete = async (row: any) => {
try {
await useMessageBox().confirm('确定要删除该记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
} catch {
return;
}
try {
await useMessageBox().confirm('确定要删除该记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
} catch {
return;
}
try {
await delObj(row.id);
useMessage().success('删除成功');
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '删除失败');
}
try {
await delObj(row.id);
useMessage().success('删除成功');
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '删除失败');
}
};
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
</style>
</style>

View File

@@ -1,93 +1,93 @@
<template>
<el-dialog title="收费明细" v-model="visible" width="900px" :close-on-click-modal="false" draggable>
<div class="fee-header">
<span>学号: {{ serialNumber }}</span>
<span style="margin-left: 20px;">姓名: {{ realName }}</span>
<el-button type="primary" size="small" style="margin-left: 20px;" @click="handleBindProject">绑定收费项目</el-button>
</div>
<el-table :data="feeList" v-loading="loading" stripe border style="margin-top: 15px;">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="projectCode" label="项目代码" width="120" align="center" />
<el-table-column prop="money" label="应缴金额" width="120" align="right">
<template #default="{ row }">
{{ row.money ? Number(row.money).toFixed(2) : '0.00' }}
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag v-if="row.status === '0'" type="info">未缴费</el-tag>
<el-tag v-else-if="row.status === '10'" type="success">已缴费</el-tag>
</template>
</el-table-column>
<el-table-column prop="payType" label="缴费方式" width="120" align="center">
<template #default="{ row }">
<span v-if="row.payType === '0'">-</span>
<span v-else>{{ row.payType }}</span>
</template>
</el-table-column>
<el-table-column prop="paiedMoney" label="实缴金额" width="120" align="right">
<template #default="{ row }">
{{ row.paiedMoney ? Number(row.paiedMoney).toFixed(2) : '-' }}
</template>
</el-table-column>
<el-table-column prop="payTime" label="缴费时间" width="180" align="center" />
<el-table-column label="操作" align="center" fixed="right" width="180">
<template #default="scope">
<template v-if="scope.row.status === '0'">
<el-button link type="primary" size="small" @click="handlePay(scope.row)">缴费</el-button>
<el-button link type="danger" size="small" @click="handleDeleteFee(scope.row)">删除</el-button>
</template>
<template v-else>
<el-button link type="warning" size="small" @click="handleCancelPay(scope.row)">取消缴费</el-button>
</template>
</template>
</el-table-column>
</el-table>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
</span>
</template>
</el-dialog>
<el-dialog title="收费明细" v-model="visible" width="900px" :close-on-click-modal="false" draggable>
<div class="fee-header">
<span>学号: {{ serialNumber }}</span>
<span style="margin-left: 20px">姓名: {{ realName }}</span>
<el-button type="primary" size="small" style="margin-left: 20px" @click="handleBindProject">绑定收费项目</el-button>
</div>
<el-table :data="feeList" v-loading="loading" stripe border style="margin-top: 15px">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="projectCode" label="项目代码" width="120" align="center" />
<el-table-column prop="money" label="应缴金额" width="120" align="right">
<template #default="{ row }">
{{ row.money ? Number(row.money).toFixed(2) : '0.00' }}
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag v-if="row.status === '0'" type="info">未缴费</el-tag>
<el-tag v-else-if="row.status === '10'" type="success">已缴费</el-tag>
</template>
</el-table-column>
<el-table-column prop="payType" label="缴费方式" width="120" align="center">
<template #default="{ row }">
<span v-if="row.payType === '0'">-</span>
<span v-else>{{ row.payType }}</span>
</template>
</el-table-column>
<el-table-column prop="paiedMoney" label="实缴金额" width="120" align="right">
<template #default="{ row }">
{{ row.paiedMoney ? Number(row.paiedMoney).toFixed(2) : '-' }}
</template>
</el-table-column>
<el-table-column prop="payTime" label="缴费时间" width="180" align="center" />
<el-table-column label="操作" align="center" fixed="right" width="180">
<template #default="scope">
<template v-if="scope.row.status === '0'">
<el-button link type="primary" size="small" @click="handlePay(scope.row)">缴费</el-button>
<el-button link type="danger" size="small" @click="handleDeleteFee(scope.row)">删除</el-button>
</template>
<template v-else>
<el-button link type="warning" size="small" @click="handleCancelPay(scope.row)">取消缴费</el-button>
</template>
</template>
</el-table-column>
</el-table>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false"> </el-button>
</span>
</template>
</el-dialog>
<el-dialog title="绑定收费项目" v-model="bindDialogVisible" width="500px">
<el-form label-width="100px">
<el-form-item label="选择项目">
<el-select v-model="selectedProjectCodes" multiple placeholder="请选择收费项目" style="width: 100%">
<el-option v-for="item in projectList" :key="item.projectCode" :label="item.projectName" :value="item.projectCode" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="bindDialogVisible = false"> </el-button>
<el-button type="primary" @click="submitBind"> </el-button>
</template>
</el-dialog>
<el-dialog title="绑定收费项目" v-model="bindDialogVisible" width="500px">
<el-form label-width="100px">
<el-form-item label="选择项目">
<el-select v-model="selectedProjectCodes" multiple placeholder="请选择收费项目" style="width: 100%">
<el-option v-for="item in projectList" :key="item.projectCode" :label="item.projectName" :value="item.projectCode" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="bindDialogVisible = false"> </el-button>
<el-button type="primary" @click="submitBind"> </el-button>
</template>
</el-dialog>
<el-dialog title="缴费登记" v-model="payDialogVisible" width="400px">
<el-form label-width="100px">
<el-form-item label="缴费金额">
<el-input-number v-model="payForm.money" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
<el-form-item label="缴费方式">
<el-select v-model="payForm.payType" placeholder="请选择缴费方式" style="width: 100%">
<el-option label="现金" value="20" />
<el-option label="银行转账" value="30" />
<el-option label="微信" value="40" />
<el-option label="支付宝" value="21" />
<el-option label="其他" value="22" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="payDialogVisible = false"> </el-button>
<el-button type="primary" @click="submitPay"> </el-button>
</template>
</el-dialog>
<el-dialog title="缴费登记" v-model="payDialogVisible" width="400px">
<el-form label-width="100px">
<el-form-item label="缴费金额">
<el-input-number v-model="payForm.money" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
<el-form-item label="缴费方式">
<el-select v-model="payForm.payType" placeholder="请选择缴费方式" style="width: 100%">
<el-option label="现金" value="20" />
<el-option label="银行转账" value="30" />
<el-option label="微信" value="40" />
<el-option label="支付宝" value="21" />
<el-option label="其他" value="22" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="payDialogVisible = false"> </el-button>
<el-button type="primary" @click="submitPay"> </el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="FinanceRecruitStuFeeDetail">
import { ref, reactive } from 'vue'
import { ref, reactive } from 'vue';
import { getBySerialNumber, bindProject, payRegister, cancelPay, delObj } from '/@/api/finance/recruitStuProject';
import { getList } from '/@/api/finance/recruitProject';
import { useMessage, useMessageBox } from '/@/hooks/message';
@@ -104,99 +104,99 @@ const selectedProjectCodes = ref<string[]>([]);
const payDialogVisible = ref(false);
const payForm = reactive({
id: '',
money: 0,
payType: '',
id: '',
money: 0,
payType: '',
});
const openDialog = async (stuSerialNumber: string, stuRealName: string) => {
serialNumber.value = stuSerialNumber;
realName.value = stuRealName;
visible.value = true;
await loadFeeList();
serialNumber.value = stuSerialNumber;
realName.value = stuRealName;
visible.value = true;
await loadFeeList();
};
const loadFeeList = async () => {
loading.value = true;
try {
const res = await getBySerialNumber(serialNumber.value);
feeList.value = res?.data || [];
} catch (err: any) {
useMessage().error(err.msg || '获取收费明细失败');
feeList.value = [];
} finally {
loading.value = false;
}
loading.value = true;
try {
const res = await getBySerialNumber(serialNumber.value);
feeList.value = res?.data || [];
} catch (err: any) {
useMessage().error(err.msg || '获取收费明细失败');
feeList.value = [];
} finally {
loading.value = false;
}
};
const handleBindProject = async () => {
try {
const res = await getList({});
projectList.value = res?.data || [];
selectedProjectCodes.value = [];
bindDialogVisible.value = true;
} catch (err: any) {
useMessage().error(err.msg || '获取收费项目失败');
}
try {
const res = await getList({});
projectList.value = res?.data || [];
selectedProjectCodes.value = [];
bindDialogVisible.value = true;
} catch (err: any) {
useMessage().error(err.msg || '获取收费项目失败');
}
};
const submitBind = async () => {
if (selectedProjectCodes.value.length === 0) {
useMessage().warning('请选择收费项目');
return;
}
try {
await bindProject(serialNumber.value, selectedProjectCodes.value);
useMessage().success('绑定成功');
bindDialogVisible.value = false;
await loadFeeList();
} catch (err: any) {
useMessage().error(err.msg || '绑定失败');
}
if (selectedProjectCodes.value.length === 0) {
useMessage().warning('请选择收费项目');
return;
}
try {
await bindProject(serialNumber.value, selectedProjectCodes.value);
useMessage().success('绑定成功');
bindDialogVisible.value = false;
await loadFeeList();
} catch (err: any) {
useMessage().error(err.msg || '绑定失败');
}
};
const handlePay = (row: any) => {
payForm.id = row.id;
payForm.money = Number(row.money) || 0;
payForm.payType = '';
payDialogVisible.value = true;
payForm.id = row.id;
payForm.money = Number(row.money) || 0;
payForm.payType = '';
payDialogVisible.value = true;
};
const submitPay = async () => {
if (!payForm.payType) {
useMessage().warning('请选择缴费方式');
return;
}
try {
await payRegister(payForm.id, payForm.payType);
useMessage().success('缴费成功');
payDialogVisible.value = false;
await loadFeeList();
} catch (err: any) {
useMessage().error(err.msg || '缴费失败');
}
if (!payForm.payType) {
useMessage().warning('请选择缴费方式');
return;
}
try {
await payRegister(payForm.id, payForm.payType);
useMessage().success('缴费成功');
payDialogVisible.value = false;
await loadFeeList();
} catch (err: any) {
useMessage().error(err.msg || '缴费失败');
}
};
const handleCancelPay = async (row: any) => {
try {
await useMessageBox().confirm('确定要取消该缴费记录吗?', '提示', { type: 'warning' });
await cancelPay(row.id);
useMessage().success('取消成功');
await loadFeeList();
} catch {
// 用户取消操作
}
try {
await useMessageBox().confirm('确定要取消该缴费记录吗?', '提示', { type: 'warning' });
await cancelPay(row.id);
useMessage().success('取消成功');
await loadFeeList();
} catch {
// 用户取消操作
}
};
const handleDeleteFee = async (row: any) => {
try {
await useMessageBox().confirm('确定要删除该收费项目吗?', '提示', { type: 'warning' });
await delObj(row.id);
useMessage().success('删除成功');
await loadFeeList();
} catch {
// 用户取消操作
}
try {
await useMessageBox().confirm('确定要删除该收费项目吗?', '提示', { type: 'warning' });
await delObj(row.id);
useMessage().success('删除成功');
await loadFeeList();
} catch {
// 用户取消操作
}
};
defineExpose({ openDialog });
@@ -204,8 +204,8 @@ defineExpose({ openDialog });
<style scoped>
.fee-header {
padding: 10px 0;
font-size: 14px;
color: #606266;
padding: 10px 0;
font-size: 14px;
color: #606266;
}
</style>
</style>

View File

@@ -1,49 +1,49 @@
<template>
<el-dialog :title="dataForm.id ? '编辑' : '新增'" v-model="visible" width="600px" :close-on-click-modal="false" draggable>
<el-form ref="formRef" :model="dataForm" :rules="dataRules" label-width="100px" v-loading="loading">
<el-form-item label="学号" prop="serialNumber">
<el-input v-model="dataForm.serialNumber" placeholder="请输入学号" clearable />
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input v-model="dataForm.realName" placeholder="请输入姓名" clearable />
</el-form-item>
<el-form-item label="学生来源" prop="stuSource">
<el-select v-model="dataForm.stuSource" placeholder="请选择学生来源" style="width: 100%">
<el-option label="本地" value="1" />
<el-option label="外地" value="2" />
<el-option label="其他" value="3" />
</el-select>
</el-form-item>
<el-form-item label="年份" prop="year">
<el-date-picker v-model="dataForm.year" type="year" placeholder="选择年份" value-format="YYYY" style="width: 100%" />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="dataForm.phone" placeholder="请输入手机号" clearable />
</el-form-item>
<el-form-item label="缴费码" prop="payCode">
<el-input v-model="dataForm.payCode" placeholder="请输入缴费码" clearable />
</el-form-item>
<el-form-item label="是否取消" prop="isCancal">
<el-radio-group v-model="dataForm.isCancal">
<el-radio label="0">正常</el-radio>
<el-radio label="1">已取消</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input v-model="dataForm.remarks" type="textarea" :rows="3" placeholder="请输入备注" clearable />
</el-form-item>
</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="dataForm.id ? '编辑' : '新增'" v-model="visible" width="600px" :close-on-click-modal="false" draggable>
<el-form ref="formRef" :model="dataForm" :rules="dataRules" label-width="100px" v-loading="loading">
<el-form-item label="学号" prop="serialNumber">
<el-input v-model="dataForm.serialNumber" placeholder="请输入学号" clearable />
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input v-model="dataForm.realName" placeholder="请输入姓名" clearable />
</el-form-item>
<el-form-item label="学生来源" prop="stuSource">
<el-select v-model="dataForm.stuSource" placeholder="请选择学生来源" style="width: 100%">
<el-option label="本地" value="1" />
<el-option label="外地" value="2" />
<el-option label="其他" value="3" />
</el-select>
</el-form-item>
<el-form-item label="年份" prop="year">
<el-date-picker v-model="dataForm.year" type="year" placeholder="选择年份" value-format="YYYY" style="width: 100%" />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="dataForm.phone" placeholder="请输入手机号" clearable />
</el-form-item>
<el-form-item label="缴费码" prop="payCode">
<el-input v-model="dataForm.payCode" placeholder="请输入缴费码" clearable />
</el-form-item>
<el-form-item label="是否取消" prop="isCancal">
<el-radio-group v-model="dataForm.isCancal">
<el-radio label="0">正常</el-radio>
<el-radio label="1">已取消</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input v-model="dataForm.remarks" type="textarea" :rows="3" placeholder="请输入备注" clearable />
</el-form-item>
</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="FinanceRecruitStuForm">
import { reactive, ref, nextTick } from 'vue'
import { reactive, ref, nextTick } from 'vue';
import { getObj, addObj, editObj } from '/@/api/finance/recruitStu';
import { useMessage } from '/@/hooks/message';
@@ -51,97 +51,99 @@ const emit = defineEmits(['refresh']);
const formRef = ref();
const dataForm = reactive({
id: '',
serialNumber: '',
realName: '',
stuSource: '1',
year: '',
phone: '',
payCode: '',
isCancal: '0',
remarks: '',
id: '',
serialNumber: '',
realName: '',
stuSource: '1',
year: '',
phone: '',
payCode: '',
isCancal: '0',
remarks: '',
});
const visible = ref(false);
const loading = ref(false);
const dataRules = ref({
serialNumber: [{ required: true, message: '请输入学号', trigger: 'blur' }],
realName: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
stuSource: [{ required: true, message: '请选择学生来源', trigger: 'change' }],
year: [{ required: true, message: '请选择年份', trigger: 'change' }],
phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
serialNumber: [{ required: true, message: '请输入学号', trigger: 'blur' }],
realName: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
stuSource: [{ required: true, message: '请选择学生来源', trigger: 'change' }],
year: [{ required: true, message: '请选择年份', trigger: 'change' }],
phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
});
const openDialog = async (type: string, rowData?: any) => {
visible.value = true;
dataForm.id = '';
dataForm.serialNumber = '';
dataForm.realName = '';
dataForm.stuSource = '1';
dataForm.year = '';
dataForm.phone = '';
dataForm.payCode = '';
dataForm.isCancal = '0';
dataForm.remarks = '';
visible.value = true;
dataForm.id = '';
dataForm.serialNumber = '';
dataForm.realName = '';
dataForm.stuSource = '1';
dataForm.year = '';
dataForm.phone = '';
dataForm.payCode = '';
dataForm.isCancal = '0';
dataForm.remarks = '';
nextTick(() => {
formRef.value?.resetFields();
if (type === 'edit' && rowData?.id) {
loading.value = true;
getObj(rowData.id).then((res: any) => {
if (res.data) {
Object.assign(dataForm, {
id: res.data.id || '',
serialNumber: res.data.serialNumber || '',
realName: res.data.realName || '',
stuSource: res.data.stuSource || '1',
year: res.data.year ? String(res.data.year) : '',
phone: res.data.phone || '',
payCode: res.data.payCode || '',
isCancal: res.data.isCancal || '0',
remarks: res.data.remarks || '',
});
}
loading.value = false;
}).catch((err: any) => {
useMessage().error(err.msg || '获取详情失败');
loading.value = false;
});
}
});
nextTick(() => {
formRef.value?.resetFields();
if (type === 'edit' && rowData?.id) {
loading.value = true;
getObj(rowData.id)
.then((res: any) => {
if (res.data) {
Object.assign(dataForm, {
id: res.data.id || '',
serialNumber: res.data.serialNumber || '',
realName: res.data.realName || '',
stuSource: res.data.stuSource || '1',
year: res.data.year ? String(res.data.year) : '',
phone: res.data.phone || '',
payCode: res.data.payCode || '',
isCancal: res.data.isCancal || '0',
remarks: res.data.remarks || '',
});
}
loading.value = false;
})
.catch((err: any) => {
useMessage().error(err.msg || '获取详情失败');
loading.value = false;
});
}
});
};
const onSubmit = async () => {
if (loading.value) return;
loading.value = true;
if (loading.value) return;
loading.value = true;
try {
const valid = await formRef.value.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
try {
const valid = await formRef.value.validate().catch(() => {});
if (!valid) {
loading.value = false;
return false;
}
const submitData = {
...dataForm,
year: dataForm.year ? parseInt(dataForm.year) : null,
};
const submitData = {
...dataForm,
year: dataForm.year ? parseInt(dataForm.year) : null,
};
if (dataForm.id) {
await editObj(submitData);
useMessage().success('编辑成功');
} else {
await addObj(submitData);
useMessage().success('新增成功');
}
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || (dataForm.id ? '编辑失败' : '新增失败'));
} finally {
loading.value = false;
}
if (dataForm.id) {
await editObj(submitData);
useMessage().success('编辑成功');
} else {
await addObj(submitData);
useMessage().success('新增成功');
}
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg || (dataForm.id ? '编辑失败' : '新增失败'));
} finally {
loading.value = false;
}
};
defineExpose({ openDialog });
</script>
</script>

View File

@@ -1,148 +1,160 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
<el-form-item label="学号" prop="serialNumber">
<el-input v-model="state.queryForm.serialNumber" placeholder="请输入学号" clearable style="width: 150px" />
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input v-model="state.queryForm.realName" placeholder="请输入姓名" clearable style="width: 150px" />
</el-form-item>
<el-form-item label="年份" prop="year">
<el-date-picker v-model="state.queryForm.year" type="year" placeholder="选择年份" value-format="YYYY" style="width: 150px" />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="state.queryForm.phone" placeholder="请输入手机号" clearable style="width: 150px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="modern-page-container">
<div class="page-wrapper">
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
<el-form-item label="学号" prop="serialNumber">
<el-input v-model="state.queryForm.serialNumber" placeholder="请输入学号" clearable style="width: 150px" />
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input v-model="state.queryForm.realName" placeholder="请输入姓名" clearable style="width: 150px" />
</el-form-item>
<el-form-item label="年份" prop="year">
<el-date-picker v-model="state.queryForm.year" type="year" placeholder="选择年份" value-format="YYYY" style="width: 150px" />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="state.queryForm.phone" placeholder="请输入手机号" clearable style="width: 150px" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><User /></el-icon>
新生信息管理
</span>
<div class="header-actions">
<el-button icon="FolderAdd" type="primary" @click="formDialogRef.openDialog('add')" v-auth="'finance_recruit_stu_add'">新增</el-button>
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList" />
</div>
</div>
</template>
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><User /></el-icon>
新生信息管理
</span>
<div class="header-actions">
<el-button icon="FolderAdd" type="primary" @click="formDialogRef.openDialog('add')" v-auth="'finance_recruit_stu_add'">新增</el-button>
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList" />
</div>
</div>
</template>
<el-table ref="tableRef" :data="state.dataList" v-loading="state.loading" stripe :cell-style="tableStyle.cellStyle" :header-cell-style="tableStyle.headerCellStyle" class="modern-table">
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="serialNumber" label="学号" width="120" align="center" />
<el-table-column prop="realName" label="姓名" width="100" align="center" />
<el-table-column prop="stuSource" label="学生来源" width="100" align="center">
<template #default="{ row }">
<span v-if="row.stuSource === '1'">本地</span>
<span v-else-if="row.stuSource === '2'">外地</span>
<span v-else-if="row.stuSource === '3'">其他</span>
<span v-else>{{ row.stuSource }}</span>
</template>
</el-table-column>
<el-table-column prop="year" label="年份" width="100" align="center" />
<el-table-column prop="phone" label="手机号" width="140" align="center" />
<el-table-column prop="payCode" label="缴费码" width="150" show-overflow-tooltip />
<el-table-column prop="isCancal" label="是否取消" width="100" align="center">
<template #default="{ row }">
<el-tag v-if="row.isCancal === '1'" type="danger">已取消</el-tag>
<el-tag v-else type="success">正常</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180" align="center" />
<el-table-column label="操作" align="center" fixed="right" width="200">
<template #default="scope">
<el-button icon="View" link type="primary" v-auth="'finance_recruit_stu_project_look'" @click="viewFeeDetail(scope.row)">收费明细</el-button>
<el-button icon="Edit" link type="primary" v-auth="'finance_recruit_stu_edit'" @click="formDialogRef.openDialog('edit', scope.row)">编辑</el-button>
<el-button icon="Delete" link type="danger" v-auth="'finance_recruit_stu_del'" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table"
>
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="serialNumber" label="学号" width="120" align="center" />
<el-table-column prop="realName" label="姓名" width="100" align="center" />
<el-table-column prop="stuSource" label="学生来源" width="100" align="center">
<template #default="{ row }">
<span v-if="row.stuSource === '1'">本地</span>
<span v-else-if="row.stuSource === '2'">外地</span>
<span v-else-if="row.stuSource === '3'">其他</span>
<span v-else>{{ row.stuSource }}</span>
</template>
</el-table-column>
<el-table-column prop="year" label="年份" width="100" align="center" />
<el-table-column prop="phone" label="手机号" width="140" align="center" />
<el-table-column prop="payCode" label="缴费码" width="150" show-overflow-tooltip />
<el-table-column prop="isCancal" label="是否取消" width="100" align="center">
<template #default="{ row }">
<el-tag v-if="row.isCancal === '1'" type="danger">已取消</el-tag>
<el-tag v-else type="success">正常</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180" align="center" />
<el-table-column label="操作" align="center" fixed="right" width="200">
<template #default="scope">
<el-button icon="View" link type="primary" v-auth="'finance_recruit_stu_project_look'" @click="viewFeeDetail(scope.row)"
>收费明细</el-button
>
<el-button icon="Edit" link type="primary" v-auth="'finance_recruit_stu_edit'" @click="formDialogRef.openDialog('edit', scope.row)"
>编辑</el-button
>
<el-button icon="Delete" link type="danger" v-auth="'finance_recruit_stu_del'" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="state.total > 0" :total="state.total" v-model:page="state.page" v-model:limit="state.limit" @pagination="getDataList" />
</el-card>
</div>
<pagination v-show="state.total > 0" :total="state.total" v-model:page="state.page" v-model:limit="state.limit" @pagination="getDataList" />
</el-card>
</div>
<FormDialog ref="formDialogRef" @refresh="getDataList" />
<FeeDetailDialog ref="feeDetailDialogRef" />
</div>
<FormDialog ref="formDialogRef" @refresh="getDataList" />
<FeeDetailDialog ref="feeDetailDialogRef" />
</div>
</template>
<script setup lang="ts" name="FinanceRecruitStu">
import { ref, reactive, defineAsyncComponent } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { getPage, delObj } from "/@/api/finance/recruitStu";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { Search, User } from '@element-plus/icons-vue'
import { ref, reactive, defineAsyncComponent } from 'vue';
import { BasicTableProps, useTable } from '/@/hooks/table';
import { getPage, delObj } from '/@/api/finance/recruitStu';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { Search, User } from '@element-plus/icons-vue';
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const FeeDetailDialog = defineAsyncComponent(() => import('./feeDetail.vue'));
const tableRef = ref()
const formDialogRef = ref()
const feeDetailDialogRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const tableRef = ref();
const formDialogRef = ref();
const feeDetailDialogRef = ref();
const searchFormRef = ref();
const showSearch = ref(true);
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: getPage,
queryForm: {
serialNumber: '',
realName: '',
year: '',
phone: '',
},
createdIsNeed: true
pageList: getPage,
queryForm: {
serialNumber: '',
realName: '',
year: '',
phone: '',
},
createdIsNeed: true,
});
const { getDataList, tableStyle } = useTable(state);
const handleReset = () => {
searchFormRef.value?.resetFields();
getDataList();
searchFormRef.value?.resetFields();
getDataList();
};
const handleDelete = async (row: any) => {
try {
await useMessageBox().confirm('确定要删除该记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
} catch {
return;
}
try {
await useMessageBox().confirm('确定要删除该记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
} catch {
return;
}
try {
await delObj(row.id);
useMessage().success('删除成功');
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '删除失败');
}
try {
await delObj(row.id);
useMessage().success('删除成功');
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '删除失败');
}
};
const viewFeeDetail = (row: any) => {
feeDetailDialogRef.value.openDialog(row.serialNumber, row.realName);
feeDetailDialogRef.value.openDialog(row.serialNumber, row.realName);
};
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
</style>
</style>

View File

@@ -1,205 +1,213 @@
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
<el-form-item label="学号" prop="serialNumber">
<el-input v-model="state.queryForm.serialNumber" placeholder="请输入学号" clearable style="width: 150px" />
</el-form-item>
<el-form-item label="项目代码" prop="projectCode">
<el-input v-model="state.queryForm.projectCode" placeholder="请输入项目代码" clearable style="width: 150px" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="state.queryForm.status" placeholder="请选择状态" clearable style="width: 150px">
<el-option label="未缴费" value="0" />
<el-option label="已缴费" value="10" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="modern-page-container">
<div class="page-wrapper">
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
<el-form-item label="学号" prop="serialNumber">
<el-input v-model="state.queryForm.serialNumber" placeholder="请输入学号" clearable style="width: 150px" />
</el-form-item>
<el-form-item label="项目代码" prop="projectCode">
<el-input v-model="state.queryForm.projectCode" placeholder="请输入项目代码" clearable style="width: 150px" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="state.queryForm.status" placeholder="请选择状态" clearable style="width: 150px">
<el-option label="未缴费" value="0" />
<el-option label="已缴费" value="10" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Money /></el-icon>
学生收费管理
</span>
<div class="header-actions">
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList" />
</div>
</div>
</template>
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Money /></el-icon>
学生收费管理
</span>
<div class="header-actions">
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList" />
</div>
</div>
</template>
<el-table ref="tableRef" :data="state.dataList" v-loading="state.loading" stripe :cell-style="tableStyle.cellStyle" :header-cell-style="tableStyle.headerCellStyle" class="modern-table">
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="serialNumber" label="学号" width="120" align="center" />
<el-table-column prop="projectCode" label="项目代码" width="150" align="center" />
<el-table-column prop="money" label="应缴金额" width="120" align="right">
<template #default="{ row }">
{{ row.money ? Number(row.money).toFixed(2) : '0.00' }}
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag v-if="row.status === '0'" type="info">未缴费</el-tag>
<el-tag v-else-if="row.status === '10'" type="success">已缴费</el-tag>
</template>
</el-table-column>
<el-table-column prop="payType" label="缴费方式" width="120" align="center">
<template #default="{ row }">
<span v-if="row.payType === '0'">-</span>
<span v-else>{{ row.payType }}</span>
</template>
</el-table-column>
<el-table-column prop="paiedMoney" label="实缴金额" width="120" align="right">
<template #default="{ row }">
{{ row.paiedMoney ? Number(row.paiedMoney).toFixed(2) : '-' }}
</template>
</el-table-column>
<el-table-column prop="payTime" label="缴费时间" width="180" align="center" />
<el-table-column prop="createTime" label="创建时间" width="180" align="center" />
<el-table-column label="操作" align="center" fixed="right" width="200">
<template #default="scope">
<template v-if="scope.row.status === '0'">
<el-button link type="primary" v-auth="'finance_recruit_stu_project_edit'" @click="handlePay(scope.row)">缴费</el-button>
<el-button link type="danger" v-auth="'finance_recruit_stu_project_del'" @click="handleDelete(scope.row)">删除</el-button>
</template>
<template v-else>
<el-button link type="warning" v-auth="'finance_recruit_stu_project_edit'" @click="handleCancelPay(scope.row)">取消缴费</el-button>
</template>
</template>
</el-table-column>
</el-table>
<el-table
ref="tableRef"
:data="state.dataList"
v-loading="state.loading"
stripe
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
class="modern-table"
>
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column prop="serialNumber" label="学号" width="120" align="center" />
<el-table-column prop="projectCode" label="项目代码" width="150" align="center" />
<el-table-column prop="money" label="应缴金额" width="120" align="right">
<template #default="{ row }">
{{ row.money ? Number(row.money).toFixed(2) : '0.00' }}
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag v-if="row.status === '0'" type="info">未缴费</el-tag>
<el-tag v-else-if="row.status === '10'" type="success">已缴费</el-tag>
</template>
</el-table-column>
<el-table-column prop="payType" label="缴费方式" width="120" align="center">
<template #default="{ row }">
<span v-if="row.payType === '0'">-</span>
<span v-else>{{ row.payType }}</span>
</template>
</el-table-column>
<el-table-column prop="paiedMoney" label="实缴金额" width="120" align="right">
<template #default="{ row }">
{{ row.paiedMoney ? Number(row.paiedMoney).toFixed(2) : '-' }}
</template>
</el-table-column>
<el-table-column prop="payTime" label="缴费时间" width="180" align="center" />
<el-table-column prop="createTime" label="创建时间" width="180" align="center" />
<el-table-column label="操作" align="center" fixed="right" width="200">
<template #default="scope">
<template v-if="scope.row.status === '0'">
<el-button link type="primary" v-auth="'finance_recruit_stu_project_edit'" @click="handlePay(scope.row)">缴费</el-button>
<el-button link type="danger" v-auth="'finance_recruit_stu_project_del'" @click="handleDelete(scope.row)">删除</el-button>
</template>
<template v-else>
<el-button link type="warning" v-auth="'finance_recruit_stu_project_edit'" @click="handleCancelPay(scope.row)">取消缴费</el-button>
</template>
</template>
</el-table-column>
</el-table>
<pagination v-show="state.total > 0" :total="state.total" v-model:page="state.page" v-model:limit="state.limit" @pagination="getDataList" />
</el-card>
</div>
<pagination v-show="state.total > 0" :total="state.total" v-model:page="state.page" v-model:limit="state.limit" @pagination="getDataList" />
</el-card>
</div>
<el-dialog title="缴费登记" v-model="payDialogVisible" width="400px">
<el-form label-width="100px">
<el-form-item label="缴费金额">
<el-input-number v-model="payForm.money" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
<el-form-item label="缴费方式">
<el-select v-model="payForm.payType" placeholder="请选择缴费方式" style="width: 100%">
<el-option label="现金" value="20" />
<el-option label="银行转账" value="30" />
<el-option label="微信" value="40" />
<el-option label="支付宝" value="21" />
<el-option label="其他" value="22" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="payDialogVisible = false"> </el-button>
<el-button type="primary" @click="submitPay"> </el-button>
</template>
</el-dialog>
</div>
<el-dialog title="缴费登记" v-model="payDialogVisible" width="400px">
<el-form label-width="100px">
<el-form-item label="缴费金额">
<el-input-number v-model="payForm.money" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
<el-form-item label="缴费方式">
<el-select v-model="payForm.payType" placeholder="请选择缴费方式" style="width: 100%">
<el-option label="现金" value="20" />
<el-option label="银行转账" value="30" />
<el-option label="微信" value="40" />
<el-option label="支付宝" value="21" />
<el-option label="其他" value="22" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="payDialogVisible = false"> </el-button>
<el-button type="primary" @click="submitPay"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="FinanceRecruitStuProject">
import { ref, reactive } from 'vue'
import { BasicTableProps, useTable } from "/@/hooks/table";
import { getPage, delObj, payRegister, cancelPay } from "/@/api/finance/recruitStuProject";
import { useMessage, useMessageBox } from "/@/hooks/message";
import { Search, Money } from '@element-plus/icons-vue'
import { ref, reactive } from 'vue';
import { BasicTableProps, useTable } from '/@/hooks/table';
import { getPage, delObj, payRegister, cancelPay } from '/@/api/finance/recruitStuProject';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { Search, Money } from '@element-plus/icons-vue';
const tableRef = ref()
const searchFormRef = ref()
const showSearch = ref(true)
const tableRef = ref();
const searchFormRef = ref();
const showSearch = ref(true);
const state: BasicTableProps = reactive<BasicTableProps>({
pageList: getPage,
queryForm: {
serialNumber: '',
projectCode: '',
status: '',
},
createdIsNeed: true
pageList: getPage,
queryForm: {
serialNumber: '',
projectCode: '',
status: '',
},
createdIsNeed: true,
});
const { getDataList, tableStyle } = useTable(state);
const handleReset = () => {
searchFormRef.value?.resetFields();
getDataList();
searchFormRef.value?.resetFields();
getDataList();
};
const handleDelete = async (row: any) => {
try {
await useMessageBox().confirm('确定要删除该记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
} catch {
return;
}
try {
await useMessageBox().confirm('确定要删除该记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
} catch {
return;
}
try {
await delObj(row.id);
useMessage().success('删除成功');
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '删除失败');
}
try {
await delObj(row.id);
useMessage().success('删除成功');
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '删除失败');
}
};
const payDialogVisible = ref(false);
const payForm = reactive({
id: '',
money: 0,
payType: '',
id: '',
money: 0,
payType: '',
});
const handlePay = (row: any) => {
payForm.id = row.id;
payForm.money = Number(row.money) || 0;
payForm.payType = '';
payDialogVisible.value = true;
payForm.id = row.id;
payForm.money = Number(row.money) || 0;
payForm.payType = '';
payDialogVisible.value = true;
};
const submitPay = async () => {
if (!payForm.payType) {
useMessage().warning('请选择缴费方式');
return;
}
try {
await payRegister(payForm.id, payForm.payType);
useMessage().success('缴费成功');
payDialogVisible.value = false;
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '缴费失败');
}
if (!payForm.payType) {
useMessage().warning('请选择缴费方式');
return;
}
try {
await payRegister(payForm.id, payForm.payType);
useMessage().success('缴费成功');
payDialogVisible.value = false;
getDataList();
} catch (err: any) {
useMessage().error(err.msg || '缴费失败');
}
};
const handleCancelPay = async (row: any) => {
try {
await useMessageBox().confirm('确定要取消该缴费记录吗?', '提示', { type: 'warning' });
await cancelPay(row.id);
useMessage().success('取消成功');
getDataList();
} catch {
// 用户取消操作
}
try {
await useMessageBox().confirm('确定要取消该缴费记录吗?', '提示', { type: 'warning' });
await cancelPay(row.id);
useMessage().success('取消成功');
getDataList();
} catch {
// 用户取消操作
}
};
</script>
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
</style>
</style>

View File

@@ -1,118 +1,112 @@
<template>
<el-dialog :title="title" v-model="visible" width="90%"
:close-on-click-modal="false" draggable>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="150px" v-loading="loading"
:disabled="operType === 'view'">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item :label="t('createTable.tableName')" prop="tableName">
<el-input v-model="form.tableName" :placeholder="t('createTable.inputTableNameTip')"/>
</el-form-item>
</el-col>
<el-dialog :title="title" v-model="visible" width="90%" :close-on-click-modal="false" draggable>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="150px" v-loading="loading" :disabled="operType === 'view'">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item :label="t('createTable.tableName')" prop="tableName">
<el-input v-model="form.tableName" :placeholder="t('createTable.inputTableNameTip')" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('createTable.comments')" prop="comments">
<el-input v-model="form.comments" :placeholder="t('createTable.inputCommentsTip')"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('createTable.comments')" prop="comments">
<el-input v-model="form.comments" :placeholder="t('createTable.inputCommentsTip')" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item :label="t('createTable.columnInfo')" prop="columns">
<el-table :data="form.columns" border style="width: 100%" max-height="500">
<el-table-column type="index" :label="t('createTable.index')" width="80">
<template #header>
<el-button icon="Plus" size="small" type="primary" circle @click="onAddItem"></el-button>
</template>
<template #default="scope">
<el-button icon="Minus" size="small" type="danger" circle
@click="handleDelete(scope.$index, scope.row)"></el-button>
</template>
</el-table-column>
<el-table-column prop="name" :label="t('createTable.name')" show-overflow-tooltip>
<template #default="scope">
<el-input v-model="scope.row.name" :placeholder="t('createTable.name')"
@blur="suggestedFieldType(scope.row)"/>
</template>
</el-table-column>
<el-table-column prop="comment" :label="t('createTable.comment')" show-overflow-tooltip>
<template #default="scope">
<el-input v-model="scope.row.comment" :placeholder="t('createTable.comment')"/>
</template>
</el-table-column>
<el-table-column prop="typeName" :label="t('createTable.typeName')" show-overflow-tooltip>
<template #default="scope">
<el-select v-model="scope.row.typeName" :placeholder="t('createTable.typeName')" clearable filterable>
<el-option v-for="item in typeList" :key="item.value" :label="item.label" :value="item.value"/>
</el-select>
</template>
</el-table-column>
<el-table-column prop="precision" :label="t('createTable.precision')" show-overflow-tooltip>
<template #default="scope">
<el-input-number :min="0" :max="10000" v-model="scope.row.precision"
:placeholder="t('createTable.precision')"></el-input-number>
</template>
</el-table-column>
<el-table-column prop="scale" :label="t('createTable.scale')" show-overflow-tooltip>
<template #default="scope">
<el-input-number :min="0" :max="10000" v-model="scope.row.scale"
:placeholder="t('createTable.scale')"></el-input-number>
</template>
</el-table-column>
<el-table-column prop="defaultValue" :label="t('createTable.defaultValue')" show-overflow-tooltip>
<template #default="scope">
<el-input v-model="scope.row.defaultValue" :placeholder="t('createTable.defaultValue')"/>
</template>
</el-table-column>
<el-table-column prop="primary" :label="t('createTable.primary')" show-overflow-tooltip>
<template #default="scope">
<el-radio-group v-model="scope.row.primary">
<el-radio v-for="(item, index) in tableDict" :key="index" :label="item.value" class="w-5">
{{ item.label }}
</el-radio>
</el-radio-group>
</template>
</el-table-column>
<el-table-column prop="nullable" :label="t('createTable.nullable')" show-overflow-tooltip>
<template #default="scope">
<el-radio-group v-model="scope.row.nullable">
<el-radio v-for="(item, index) in tableDict" :key="index" :label="item.value" class="w-5">
{{ item.label }}
</el-radio>
</el-radio-group>
</template>
</el-table-column>
</el-table>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer v-if="operType !== 'view'">
<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>
<el-col :span="24" class="mb20">
<el-form-item :label="t('createTable.columnInfo')" prop="columns">
<el-table :data="form.columns" border style="width: 100%" max-height="500">
<el-table-column type="index" :label="t('createTable.index')" width="80">
<template #header>
<el-button icon="Plus" size="small" type="primary" circle @click="onAddItem"></el-button>
</template>
<template #default="scope">
<el-button icon="Minus" size="small" type="danger" circle @click="handleDelete(scope.$index, scope.row)"></el-button>
</template>
</el-table-column>
<el-table-column prop="name" :label="t('createTable.name')" show-overflow-tooltip>
<template #default="scope">
<el-input v-model="scope.row.name" :placeholder="t('createTable.name')" @blur="suggestedFieldType(scope.row)" />
</template>
</el-table-column>
<el-table-column prop="comment" :label="t('createTable.comment')" show-overflow-tooltip>
<template #default="scope">
<el-input v-model="scope.row.comment" :placeholder="t('createTable.comment')" />
</template>
</el-table-column>
<el-table-column prop="typeName" :label="t('createTable.typeName')" show-overflow-tooltip>
<template #default="scope">
<el-select v-model="scope.row.typeName" :placeholder="t('createTable.typeName')" clearable filterable>
<el-option v-for="item in typeList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
</el-table-column>
<el-table-column prop="precision" :label="t('createTable.precision')" show-overflow-tooltip>
<template #default="scope">
<el-input-number :min="0" :max="10000" v-model="scope.row.precision" :placeholder="t('createTable.precision')"></el-input-number>
</template>
</el-table-column>
<el-table-column prop="scale" :label="t('createTable.scale')" show-overflow-tooltip>
<template #default="scope">
<el-input-number :min="0" :max="10000" v-model="scope.row.scale" :placeholder="t('createTable.scale')"></el-input-number>
</template>
</el-table-column>
<el-table-column prop="defaultValue" :label="t('createTable.defaultValue')" show-overflow-tooltip>
<template #default="scope">
<el-input v-model="scope.row.defaultValue" :placeholder="t('createTable.defaultValue')" />
</template>
</el-table-column>
<el-table-column prop="primary" :label="t('createTable.primary')" show-overflow-tooltip>
<template #default="scope">
<el-radio-group v-model="scope.row.primary">
<el-radio v-for="(item, index) in tableDict" :key="index" :label="item.value" class="w-5">
{{ item.label }}
</el-radio>
</el-radio-group>
</template>
</el-table-column>
<el-table-column prop="nullable" :label="t('createTable.nullable')" show-overflow-tooltip>
<template #default="scope">
<el-radio-group v-model="scope.row.nullable">
<el-radio v-for="(item, index) in tableDict" :key="index" :label="item.value" class="w-5">
{{ item.label }}
</el-radio>
</el-radio-group>
</template>
</el-table-column>
</el-table>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer v-if="operType !== 'view'">
<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>
<script setup lang="ts" name="CreateTableDialog">
import {useMessage, useMessageBox} from "/@/hooks/message";
import {getObj, addObj, putObj} from '/@/api/gen/create-table'
import {useI18n} from "vue-i18n"
import {rule, validateNull} from '/@/utils/validate';
import {useDict} from "/@/hooks/dict";
import {list} from "/@/api/gen/fieldtype";
import {fetchList} from '/@/api/gen/table';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { getObj, addObj, putObj } from '/@/api/gen/create-table';
import { useI18n } from 'vue-i18n';
import { rule, validateNull } from '/@/utils/validate';
import { useDict } from '/@/hooks/dict';
import { list } from '/@/api/gen/fieldtype';
import { fetchList } from '/@/api/gen/table';
const emit = defineEmits(['refresh']);
const {t} = useI18n();
const {yes_no_type} = useDict('yes_no_type');
const { t } = useI18n();
const { yes_no_type } = useDict('yes_no_type');
const tableDict = [{value: -1, label: '否'}, {value: 1, label: '是'}]
const tableDict = [
{ value: -1, label: '否' },
{ value: 1, label: '是' },
];
// 定义变量内容
const dataFormRef = ref();
@@ -124,16 +118,16 @@ const typeList = ref([]) as any;
// 提交表单数据
const form = reactive({
id: '',
tableName: '',
dsName: '',
comments: '',
databaseType: '',
pkPolicy: '',
primary: '',
columnsInfo: '',
columnInfo: '',
columns: [] as any
id: '',
tableName: '',
dsName: '',
comments: '',
databaseType: '',
pkPolicy: '',
primary: '',
columnsInfo: '',
columnInfo: '',
columns: [] as any,
});
/**
@@ -143,249 +137,253 @@ const form = reactive({
* @param {*} callback
*/
let validateTableName = async (rule, value, callback) => {
const {data} = await fetchList({tableName: value})
if (data.total === 0) {
callback()
} else {
callback(new Error('表名已存在'))
}
}
const { data } = await fetchList({ tableName: value });
if (data.total === 0) {
callback();
} else {
callback(new Error('表名已存在'));
}
};
// 定义校验规则
const dataRules = ref({
tableName: [
{required: true, message: '表名称不能为空', trigger: 'blur'},
{max: 32, message: '长度在 32 个字符', trigger: 'blur'},
{validator: rule.validatorLowercase, trigger: 'blur'},
{validator: validateTableName, trigger: 'blur'}
],
comments: [{validator: rule.overLength, trigger: 'blur'}, {
required: true,
message: '表注释不能为空',
trigger: 'blur'
}],
databaseType: [{required: true, message: '数据库类型不能为空', trigger: 'blur'}],
pkPolicy: [{required: true, message: '主键策略不能为空', trigger: 'blur'}],
columns: [{required: true, message: '字段信息不能为空', trigger: 'blur'}],
})
tableName: [
{ required: true, message: '表名称不能为空', trigger: 'blur' },
{ max: 32, message: '长度在 32 个字符', trigger: 'blur' },
{ validator: rule.validatorLowercase, trigger: 'blur' },
{ validator: validateTableName, trigger: 'blur' },
],
comments: [
{ validator: rule.overLength, trigger: 'blur' },
{
required: true,
message: '表注释不能为空',
trigger: 'blur',
},
],
databaseType: [{ required: true, message: '数据库类型不能为空', trigger: 'blur' }],
pkPolicy: [{ required: true, message: '主键策略不能为空', trigger: 'blur' }],
columns: [{ required: true, message: '字段信息不能为空', trigger: 'blur' }],
});
// 打开弹窗
const openDialog = (type: string, id: string, dsName: string) => {
visible.value = true
operType.value = type;
form.id = ''
form.dsName = dsName
index = 1
if (type === 'add') {
title.value = t('common.addBtn');
} else if (type === 'edit') {
title.value = t('common.editBtn');
} else if (type === 'view') {
title.value = t('common.viewBtn');
}
// 获取字典值
getFieldTypeList()
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
if (!id) onAddItem()
});
visible.value = true;
operType.value = type;
form.id = '';
form.dsName = dsName;
index = 1;
if (type === 'add') {
title.value = t('common.addBtn');
} else if (type === 'edit') {
title.value = t('common.editBtn');
} else if (type === 'view') {
title.value = t('common.viewBtn');
}
// 获取字典值
getFieldTypeList();
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
if (!id) onAddItem();
});
// 获取CreateTable信息
if (id) {
form.id = id
getCreateTableData(id)
}
// 获取CreateTable信息
if (id) {
form.id = id;
getCreateTableData(id);
}
};
// 提交
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 {
if (form.id) {
await useMessageBox().confirm("注意:目前修改会重新建表" + form.tableName);
}
} catch {
return;
}
try {
if (form.id) {
await useMessageBox().confirm('注意:目前修改会重新建表' + form.tableName);
}
} catch {
return;
}
try {
loading.value = true;
form.columnInfo = JSON.stringify(form.columns)
let columns = {} as any
form.columns.forEach(each => {
if (validateNull(each.defaultValue)) each.defaultValue = null
columns[each.name] = each
})
form.columnsInfo = JSON.stringify(columns)
await addObj(form);
useMessage().success(t(form.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;
form.columnInfo = JSON.stringify(form.columns);
let columns = {} as any;
form.columns.forEach((each) => {
if (validateNull(each.defaultValue)) each.defaultValue = null;
columns[each.name] = each;
});
form.columnsInfo = JSON.stringify(columns);
await addObj(form);
useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText'));
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// 初始化表单数据
const getCreateTableData = (id: string) => {
// 获取数据
loading.value = true
getObj(id).then((res: any) => {
let columnInfo = res.data.columnInfo;
res.data.columns = validateNull(columnInfo) ? [] : JSON.parse(columnInfo);
Object.assign(form, res.data)
}).finally(() => {
loading.value = false
})
// 获取数据
loading.value = true;
getObj(id)
.then((res: any) => {
let columnInfo = res.data.columnInfo;
res.data.columns = validateNull(columnInfo) ? [] : JSON.parse(columnInfo);
Object.assign(form, res.data);
})
.finally(() => {
loading.value = false;
});
};
let index = 1;
const onAddItem = () => {
let find = form.columns.find(f => f.name === 'id');
if (find) {
let obj = {
name: '',
comment: '',
typeName: 'varchar',
precision: 255,
scale: 0,
defaultValue: null,
primary: -1,
nullable: 1
};
let find = form.columns.find((f) => f.name === 'id');
if (find) {
let obj = {
name: '',
comment: '',
typeName: 'varchar',
precision: 255,
scale: 0,
defaultValue: null,
primary: -1,
nullable: 1,
};
// 从 index为 1 的位置开始添加, 并且新增的字段要依次向后
form.columns.splice(index++, 0, obj)
return
}
let id = {
name: 'id',
comment: '主键',
typeName: 'bigint',
precision: 20,
scale: 0,
defaultValue: null,
primary: 1,
nullable: -1
};
let create_user = {
name: 'create_by',
comment: '创建人',
typeName: 'varchar',
precision: 64,
scale: 0,
defaultValue: null,
primary: -1,
nullable: 1
};
let create_time = {
name: 'create_time',
comment: '创建时间',
typeName: 'datetime',
precision: 0,
scale: 0,
defaultValue: null,
primary: -1,
nullable: 1
};
// 从 index为 1 的位置开始添加, 并且新增的字段要依次向后
form.columns.splice(index++, 0, obj);
return;
}
let id = {
name: 'id',
comment: '主键',
typeName: 'bigint',
precision: 20,
scale: 0,
defaultValue: null,
primary: 1,
nullable: -1,
};
let create_user = {
name: 'create_by',
comment: '创建人',
typeName: 'varchar',
precision: 64,
scale: 0,
defaultValue: null,
primary: -1,
nullable: 1,
};
let create_time = {
name: 'create_time',
comment: '创建时间',
typeName: 'datetime',
precision: 0,
scale: 0,
defaultValue: null,
primary: -1,
nullable: 1,
};
let update_user = {
name: 'update_by',
comment: '修改人',
typeName: 'varchar',
precision: 64,
scale: 0,
defaultValue: null,
primary: -1,
nullable: 1
};
let update_user = {
name: 'update_by',
comment: '修改人',
typeName: 'varchar',
precision: 64,
scale: 0,
defaultValue: null,
primary: -1,
nullable: 1,
};
let update_time = {
name: 'update_time',
comment: '修改时间',
typeName: 'datetime',
precision: 0,
scale: 0,
defaultValue: null,
primary: -1,
nullable: 1
};
let update_time = {
name: 'update_time',
comment: '修改时间',
typeName: 'datetime',
precision: 0,
scale: 0,
defaultValue: null,
primary: -1,
nullable: 1,
};
let del_flag = {
name: 'del_flag',
comment: '删除标记',
typeName: 'char',
precision: 1,
scale: 0,
defaultValue: '0',
primary: -1,
nullable: -1
};
let del_flag = {
name: 'del_flag',
comment: '删除标记',
typeName: 'char',
precision: 1,
scale: 0,
defaultValue: '0',
primary: -1,
nullable: -1,
};
let tenant_id = {
name: 'tenant_id',
comment: '租户ID',
typeName: 'bigint',
precision: 0,
scale: 0,
primary: -1,
nullable: -1
};
let tenant_id = {
name: 'tenant_id',
comment: '租户ID',
typeName: 'bigint',
precision: 0,
scale: 0,
primary: -1,
nullable: -1,
};
form.columns.push(id);
form.columns.push(create_user);
form.columns.push(create_time);
form.columns.push(update_user);
form.columns.push(update_time);
form.columns.push(del_flag);
form.columns.push(tenant_id);
}
form.columns.push(id);
form.columns.push(create_user);
form.columns.push(create_time);
form.columns.push(update_user);
form.columns.push(update_time);
form.columns.push(del_flag);
form.columns.push(tenant_id);
};
const getFieldTypeList = async () => {
typeList.value = [];
// 获取数据
const {data} = await list();
// 设置属性类型值
const typeMap = new Map();
data.forEach((item: any) => {
const {attrType, columnType} = item;
if (!typeMap.has(attrType)) {
typeMap.set(columnType, attrType);
typeList.value.push({label: columnType, value: columnType});
}
});
typeList.value = [];
// 获取数据
const { data } = await list();
// 设置属性类型值
const typeMap = new Map();
data.forEach((item: any) => {
const { attrType, columnType } = item;
if (!typeMap.has(attrType)) {
typeMap.set(columnType, attrType);
typeList.value.push({ label: columnType, value: columnType });
}
});
};
const handleDelete = (index: number, row: any) => {
form.columns.splice(index, 1)
}
form.columns.splice(index, 1);
};
// 字段建议
const suggestedFieldType = (row: { name: string, typeName: string, precision: number }) => {
// 如果fieldName 包含 time,date ,则默认为时间类型, 如果包含id ,则默认是 bigint
if (row.name.includes('time') || row.name.includes('date')) {
row.typeName = 'datetime'
row.precision = 0
} else if (row.name.includes('id')) {
row.typeName = 'bigint'
row.precision = 20
} else if(row.name.includes('flag') || row.name.includes('status')){
row.typeName = 'char'
row.precision = 1
} else {
row.typeName = 'varchar'
row.precision = 255
}
}
const suggestedFieldType = (row: { name: string; typeName: string; precision: number }) => {
// 如果fieldName 包含 time,date ,则默认为时间类型, 如果包含id ,则默认是 bigint
if (row.name.includes('time') || row.name.includes('date')) {
row.typeName = 'datetime';
row.precision = 0;
} else if (row.name.includes('id')) {
row.typeName = 'bigint';
row.precision = 20;
} else if (row.name.includes('flag') || row.name.includes('status')) {
row.typeName = 'char';
row.precision = 1;
} else {
row.typeName = 'varchar';
row.precision = 255;
}
};
// 暴露变量
defineExpose({
openDialog
openDialog,
});
</script>

View File

@@ -1,33 +1,33 @@
export default {
createTable: {
index: '#',
importcreateTableTip: 'import CreateTable',
id: 'id',
tableName: 'tableName',
comments: 'comments',
comment: 'comment',
databaseType: 'databaseType',
pkPolicy: 'pkPolicy',
createUser: 'createUser',
createTime: 'createTime',
columnInfo: 'columnInfo',
tenantId: 'tenantId',
inputIdTip: 'input id',
inputTableNameTip: 'input tableName',
inputCommentsTip: 'input comments',
inputCommentTip: 'input comment',
inputDatabaseTypeTip: 'input databaseType',
inputPkPolicyTip: 'input pkPolicy',
inputCreateUserTip: 'input createUser',
inputCreateTimeTip: 'input createTime',
inputColumnInfoTip: 'input columnInfo',
inputTenantIdTip: 'input tenantId',
name: 'name',
typeName: 'typeName',
precision: 'precision',
scale: 'scale',
defaultValue: 'defaultValue',
primary: 'primary',
nullable: 'nullable',
}
}
createTable: {
index: '#',
importcreateTableTip: 'import CreateTable',
id: 'id',
tableName: 'tableName',
comments: 'comments',
comment: 'comment',
databaseType: 'databaseType',
pkPolicy: 'pkPolicy',
createUser: 'createUser',
createTime: 'createTime',
columnInfo: 'columnInfo',
tenantId: 'tenantId',
inputIdTip: 'input id',
inputTableNameTip: 'input tableName',
inputCommentsTip: 'input comments',
inputCommentTip: 'input comment',
inputDatabaseTypeTip: 'input databaseType',
inputPkPolicyTip: 'input pkPolicy',
inputCreateUserTip: 'input createUser',
inputCreateTimeTip: 'input createTime',
inputColumnInfoTip: 'input columnInfo',
inputTenantIdTip: 'input tenantId',
name: 'name',
typeName: 'typeName',
precision: 'precision',
scale: 'scale',
defaultValue: 'defaultValue',
primary: 'primary',
nullable: 'nullable',
},
};

View File

@@ -1,33 +1,33 @@
export default {
createTable: {
index: '#',
importcreateTableTip: '导入自动创建表管理',
id: '主键ID',
tableName: '表名称',
comments: '表注释',
comment: '字段注释',
databaseType: '数据库类型',
pkPolicy: '主键策略',
createUser: '创建人',
createTime: '创建时间',
columnInfo: '字段信息',
tenantId: '租户ID',
inputIdTip: '请输入主键ID',
inputTableNameTip: '请输入表名称',
inputCommentsTip: '请输入表注释',
inputCommentTip: '请输入字段注释',
inputDatabaseTypeTip: '请输入数据库引擎',
inputPkPolicyTip: '请输入主键策略',
inputCreateUserTip: '请输入创建人',
inputCreateTimeTip: '请输入创建时间',
inputColumnInfoTip: '请输入字段信息',
inputTenantIdTip: '请输入租户ID',
name: '字段名称',
typeName: '字段类型',
precision: '字段长度',
scale: '小数位数',
defaultValue: '默认值',
primary: '主键',
nullable: 'NULL',
}
}
createTable: {
index: '#',
importcreateTableTip: '导入自动创建表管理',
id: '主键ID',
tableName: '表名称',
comments: '表注释',
comment: '字段注释',
databaseType: '数据库类型',
pkPolicy: '主键策略',
createUser: '创建人',
createTime: '创建时间',
columnInfo: '字段信息',
tenantId: '租户ID',
inputIdTip: '请输入主键ID',
inputTableNameTip: '请输入表名称',
inputCommentsTip: '请输入表注释',
inputCommentTip: '请输入字段注释',
inputDatabaseTypeTip: '请输入数据库引擎',
inputPkPolicyTip: '请输入主键策略',
inputCreateUserTip: '请输入创建人',
inputCreateTimeTip: '请输入创建时间',
inputColumnInfoTip: '请输入字段信息',
inputTenantIdTip: '请输入租户ID',
name: '字段名称',
typeName: '字段类型',
precision: '字段长度',
scale: '小数位数',
defaultValue: '默认值',
primary: '主键',
nullable: 'NULL',
},
};

View File

@@ -1,160 +1,168 @@
<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="name">
<el-select @change="getDataList" placeholder="请选择数据源" v-model="state.queryForm.dsName">
<el-option label="默认数据源" value="master"></el-option>
<el-option :key="ds.id" :label="ds.name" :value="ds.name" v-for="ds in datasourceList"></el-option>
</el-select>
</el-form-item>
<el-form-item :label="$t('createTable.tableName')" prop="tableName">
<el-input :placeholder="t('createTable.inputTableNameTip')" v-model="state.queryForm.tableName"/>
</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" v-auth="'codegen_table_add'"
@click="formDialogRef.openDialog('add',null,state.queryForm.dsName)">
{{ $t('common.addBtn') }}
</el-button>
<el-button plain :disabled="multiple" v-auth="'codegen_table_add'" icon="Delete" type="primary" class="ml10"
@click="handleDelete(selectObjs)">
{{ $t('common.delBtn') }}
</el-button>
<right-toolbar v-model:showSearch="showSearch" :export="'order_createtable_export'"
@exportExcel="exportExcel" class="ml10" style="float: right;margin-right: 20px"
@queryTable="getDataList"></right-toolbar>
</div>
</el-row>
<el-table :data="state.dataList" v-loading="state.loading" style="width: 100%"
:cell-style="tableStyle.cellStyle"
border
:header-cell-style="tableStyle.headerCellStyle"
@selection-change="handleSelectionChange" @sort-change="sortChangeHandle">
<el-table-column type="selection" width="40" align="center"/>
<el-table-column type="index" fixed="left" :label="t('createTable.index')" width="40"/>
<el-table-column prop="tableName" :label="t('createTable.tableName')" show-overflow-tooltip/>
<el-table-column prop="comments" :label="t('createTable.comments')" show-overflow-tooltip/>
<el-table-column prop="primary" :label="t('createTable.primary')" show-overflow-tooltip>
<template #default="scope">
<!-- 获取主键列名称-->
{{ JSON.parse(scope.row.columnInfo).find(col => col.primary === 1)?.name }}
</template>
</el-table-column>
<el-table-column prop="createTime" :label="t('createTable.createTime')" show-overflow-tooltip/>
<el-table-column :label="$t('common.action')" fixed="right" width="200">
<template #default="scope">
<el-button text type="primary" icon="view" @click="formDialogRef.openDialog('view', scope.row.id)">
{{ $t('common.viewBtn') }}
</el-button>
<el-button icon="FolderOpened" @click="openGen(scope.row)" text type="primary">{{
$t('gen.genBtn')
}}
</el-button>
<el-button icon="edit-pen" text type="primary" v-auth="'order_createtable_edit'"
@click="formDialogRef.openDialog('edit', scope.row.id)">{{ $t('common.editBtn') }}
</el-button>
<el-button icon="delete" text type="primary" @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="数据源" prop="name">
<el-select @change="getDataList" placeholder="请选择数据源" v-model="state.queryForm.dsName">
<el-option label="默认数据源" value="master"></el-option>
<el-option :key="ds.id" :label="ds.name" :value="ds.name" v-for="ds in datasourceList"></el-option>
</el-select>
</el-form-item>
<el-form-item :label="$t('createTable.tableName')" prop="tableName">
<el-input :placeholder="t('createTable.inputTableNameTip')" v-model="state.queryForm.tableName" />
</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"
v-auth="'codegen_table_add'"
@click="formDialogRef.openDialog('add', null, state.queryForm.dsName)"
>
{{ $t('common.addBtn') }}
</el-button>
<el-button
plain
:disabled="multiple"
v-auth="'codegen_table_add'"
icon="Delete"
type="primary"
class="ml10"
@click="handleDelete(selectObjs)"
>
{{ $t('common.delBtn') }}
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
:export="'order_createtable_export'"
@exportExcel="exportExcel"
class="ml10"
style="float: right; margin-right: 20px"
@queryTable="getDataList"
></right-toolbar>
</div>
</el-row>
<el-table
:data="state.dataList"
v-loading="state.loading"
style="width: 100%"
:cell-style="tableStyle.cellStyle"
border
:header-cell-style="tableStyle.headerCellStyle"
@selection-change="handleSelectionChange"
@sort-change="sortChangeHandle"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column type="index" fixed="left" :label="t('createTable.index')" width="40" />
<el-table-column prop="tableName" :label="t('createTable.tableName')" show-overflow-tooltip />
<el-table-column prop="comments" :label="t('createTable.comments')" show-overflow-tooltip />
<el-table-column prop="primary" :label="t('createTable.primary')" show-overflow-tooltip>
<template #default="scope">
<!-- 获取主键列名称-->
{{ JSON.parse(scope.row.columnInfo).find((col) => col.primary === 1)?.name }}
</template>
</el-table-column>
<el-table-column prop="createTime" :label="t('createTable.createTime')" show-overflow-tooltip />
<el-table-column :label="$t('common.action')" fixed="right" width="200">
<template #default="scope">
<el-button text type="primary" icon="view" @click="formDialogRef.openDialog('view', scope.row.id)">
{{ $t('common.viewBtn') }}
</el-button>
<el-button icon="FolderOpened" @click="openGen(scope.row)" text type="primary">{{ $t('gen.genBtn') }} </el-button>
<el-button icon="edit-pen" text type="primary" v-auth="'order_createtable_edit'" @click="formDialogRef.openDialog('edit', scope.row.id)"
>{{ $t('common.editBtn') }}
</el-button>
<el-button icon="delete" text type="primary" @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="systemCreateTable">
import {BasicTableProps, useTable} from "/@/hooks/table";
import {fetchList, delObjs} from "/@/api/gen/create-table";
import {useMessage, useMessageBox} from "/@/hooks/message";
import { BasicTableProps, useTable } from '/@/hooks/table';
import { fetchList, delObjs } from '/@/api/gen/create-table';
import { useMessage, useMessageBox } from '/@/hooks/message';
import {useI18n} from "vue-i18n";
import {list} from "/@/api/gen/datasource";
import {useSyncTableApi, useTableApi} from "/@/api/gen/table";
import {validateNull} from "/@/utils/validate";
import { useI18n } from 'vue-i18n';
import { list } from '/@/api/gen/datasource';
import { useSyncTableApi, useTableApi } from '/@/api/gen/table';
import { validateNull } from '/@/utils/validate';
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const {t} = useI18n()
const { t } = useI18n();
// 定义变量内容
const formDialogRef = ref()
const formDialogRef = ref();
const datasourceList = ref();
const router = useRouter();
// 搜索变量
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,
descs: ["create_time"]
})
queryForm: {},
pageList: fetchList,
descs: ['create_time'],
});
// 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('/order/create-table/export', state.queryForm, 'create-table.xlsx')
}
downBlobFile('/order/create-table/export', state.queryForm, 'create-table.xlsx');
};
// 多选事件
const handleSelectionChange = (objs: any) => {
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);
}
};
/**
@@ -162,30 +170,30 @@ const handleDelete = async (ids: string[]) => {
* @param row
*/
const openGen = (row: { tableName: string }) => {
useTableApi(state.queryForm.dsName, row.tableName)
.then((res) => {
if (validateNull(res.data.fieldList)) {
useSyncTableApi(state.queryForm.dsName, row.tableName);
}
})
.finally(() => {
router.push({
path: '/gen/gener/index',
query: {
tableName: row.tableName,
dsName: state.queryForm.dsName,
},
});
});
useTableApi(state.queryForm.dsName, row.tableName)
.then((res) => {
if (validateNull(res.data.fieldList)) {
useSyncTableApi(state.queryForm.dsName, row.tableName);
}
})
.finally(() => {
router.push({
path: '/gen/gener/index',
query: {
tableName: row.tableName,
dsName: state.queryForm.dsName,
},
});
});
};
// 初始化数据
onMounted(() => {
list().then((res) => {
datasourceList.value = res.data;
// 默认去第一个数据源
state.queryForm.dsName = datasourceList.value[0]?.name || 'master';
getDataList();
});
list().then((res) => {
datasourceList.value = res.data;
// 默认去第一个数据源
state.queryForm.dsName = datasourceList.value[0]?.name || 'master';
getDataList();
});
});
</script>

View File

@@ -1,85 +1,84 @@
<template>
<el-dialog :title="form.id ? $t('common.editBtn') : $t('common.addBtn')" 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="20">
<el-col :span="12" class="mb20">
<el-form-item :label="t('datasourceconf.dsType')" prop="dsType">
<el-select v-model="form.dsType" :placeholder="t('datasourceconf.inputdsTypeTip')">
<el-option :key="item.value" :label="item.label" :value="item.value" v-for="item in ds_type"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('datasourceconf.name')" prop="name">
<el-input v-model="form.name" :placeholder="t('datasourceconf.inputnameTip')"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('datasourceconf.username')" prop="username">
<el-input v-model="form.username" :placeholder="t('datasourceconf.inputusernameTip')"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('datasourceconf.password')" prop="password">
<el-input v-model="form.password" :placeholder="t('datasourceconf.inputpasswordTip')"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('datasourceconf.confType')" prop="confType">
<el-radio-group v-model="form.confType">
<el-radio :label="Number(item.value)" v-for="(item, index) in ds_config_type" border :key="index">
{{ item.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20" v-if="form.confType === 0 && form.dsType === 'mssql'">
<el-form-item :label="t('datasourceconf.instance')" prop="instance">
<el-input v-model="form.instance" :placeholder="t('datasourceconf.inputinstanceTip')"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20" v-if="form.confType === 0">
<el-form-item :label="t('datasourceconf.port')" prop="port">
<el-input-number v-model="form.port" :placeholder="t('datasourceconf.inputportTip')"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20" v-if="form.confType === 0">
<el-form-item :label="t('datasourceconf.host')" prop="host">
<el-input v-model="form.host" :placeholder="t('datasourceconf.inputhostTip')"/>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20" v-if="form.confType === 0">
<el-form-item :label="t('datasourceconf.dsName')" prop="dsName">
<el-input v-model="form.dsName" :placeholder="t('datasourceconf.inputdsNameTip')"/>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20" v-if="form.confType === 1">
<el-form-item :label="t('datasourceconf.url')" prop="url">
<el-input v-model="form.url" type="textarea" :placeholder="t('datasourceconf.inputurlTip')"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-dialog :title="form.id ? $t('common.editBtn') : $t('common.addBtn')" 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="20">
<el-col :span="12" class="mb20">
<el-form-item :label="t('datasourceconf.dsType')" prop="dsType">
<el-select v-model="form.dsType" :placeholder="t('datasourceconf.inputdsTypeTip')">
<el-option :key="item.value" :label="item.label" :value="item.value" v-for="item in ds_type"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('datasourceconf.name')" prop="name">
<el-input v-model="form.name" :placeholder="t('datasourceconf.inputnameTip')" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('datasourceconf.username')" prop="username">
<el-input v-model="form.username" :placeholder="t('datasourceconf.inputusernameTip')" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('datasourceconf.password')" prop="password">
<el-input v-model="form.password" :placeholder="t('datasourceconf.inputpasswordTip')" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('datasourceconf.confType')" prop="confType">
<el-radio-group v-model="form.confType">
<el-radio :label="Number(item.value)" v-for="(item, index) in ds_config_type" border :key="index">
{{ item.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20" v-if="form.confType === 0 && form.dsType === 'mssql'">
<el-form-item :label="t('datasourceconf.instance')" prop="instance">
<el-input v-model="form.instance" :placeholder="t('datasourceconf.inputinstanceTip')" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20" v-if="form.confType === 0">
<el-form-item :label="t('datasourceconf.port')" prop="port">
<el-input-number v-model="form.port" :placeholder="t('datasourceconf.inputportTip')" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20" v-if="form.confType === 0">
<el-form-item :label="t('datasourceconf.host')" prop="host">
<el-input v-model="form.host" :placeholder="t('datasourceconf.inputhostTip')" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20" v-if="form.confType === 0">
<el-form-item :label="t('datasourceconf.dsName')" prop="dsName">
<el-input v-model="form.dsName" :placeholder="t('datasourceconf.inputdsNameTip')" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20" v-if="form.confType === 1">
<el-form-item :label="t('datasourceconf.url')" prop="url">
<el-input v-model="form.url" type="textarea" :placeholder="t('datasourceconf.inputurlTip')" />
</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-dialog>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="systemDatasourceConfDialog">
import {useMessage} from '/@/hooks/message';
import {getObj, addObj, putObj} from '/@/api/gen/datasource';
import {useI18n} from 'vue-i18n';
import {useDict} from '/@/hooks/dict';
import {rule} from "/@/utils/validate";
import { useMessage } from '/@/hooks/message';
import { getObj, addObj, putObj } from '/@/api/gen/datasource';
import { useI18n } from 'vue-i18n';
import { useDict } from '/@/hooks/dict';
import { rule } from '/@/utils/validate';
// 定义子组件向父组件传值/事件
const emit = defineEmits(['refresh']);
const {t} = useI18n();
const { t } = useI18n();
// 定义变量内容
const dataFormRef = ref();
@@ -87,23 +86,23 @@ const visible = ref(false);
const loading = ref(false);
// 定义字典处理
const {ds_config_type, ds_type} = useDict('ds_config_type', 'ds_type');
const { ds_config_type, ds_type } = useDict('ds_config_type', 'ds_type');
// 提交表单数据
const form = reactive({
id: '',
name: '',
url: '',
username: '',
password: ('' as string) || undefined,
createTime: '',
updateTime: '',
dsType: '',
confType: 0,
dsName: '',
instance: '',
port: 3306,
host: '',
id: '',
name: '',
url: '',
username: '',
password: ('' as string) || undefined,
createTime: '',
updateTime: '',
dsType: '',
confType: 0,
dsName: '',
instance: '',
port: 3306,
host: '',
});
/**
@@ -113,86 +112,103 @@ const form = reactive({
* @param {*} callback
*/
const validateDsName = (_rule, value, callback) => {
var re = /(?=.*[a-z])(?=.*_)/;
if (value && !re.test(value)) {
callback(new Error('数据源名称不合法, 组名_数据源名形式'));
} else {
callback();
}
var re = /(?=.*[a-z])(?=.*_)/;
if (value && !re.test(value)) {
callback(new Error('数据源名称不合法, 组名_数据源名形式'));
} else {
callback();
}
};
// 定义校验规则
const dataRules = ref({
name: [
{required: true, message: '别名不能为空', trigger: 'blur'},
{validator: validateDsName, trigger: 'blur'},
],
url: [{required: true, message: 'jdbcurl不能为空', trigger: 'blur'}, {
min: 10,
max: 500,
message: 'URL长度必须介于 10 和 500 字符之间',
trigger: 'blur'
},],
username: [{ validator: rule.overLength, trigger: 'blur' },{required: true, message: '用户名不能为空', trigger: 'blur'}],
password: [{ validator: rule.overLength, trigger: 'blur' },{required: true, message: '密码不能为空', trigger: 'blur'}],
dsType: [{required: true, message: '数据库类型不能为空', trigger: 'blur'}],
confType: [{required: true, message: '配置类型不能为空', trigger: 'blur'}],
dsName: [{ validator: rule.overLength, trigger: 'blur' },{required: true, message: '数据库名称不能为空', trigger: 'blur'}],
instance: [{ validator: rule.overLength, trigger: 'blur' },{required: true, message: '实例不能为空', trigger: 'blur'}],
port: [{required: true, message: '端口不能为空', trigger: 'blur'}],
host: [{ validator: rule.overLength, trigger: 'blur' },{required: true, message: '主机不能为空', trigger: 'blur'}],
name: [
{ required: true, message: '别名不能为空', trigger: 'blur' },
{ validator: validateDsName, trigger: 'blur' },
],
url: [
{ required: true, message: 'jdbcurl不能为空', trigger: 'blur' },
{
min: 10,
max: 500,
message: 'URL长度必须介于 10 和 500 字符之间',
trigger: 'blur',
},
],
username: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '用户名不能为空', trigger: 'blur' },
],
password: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '密码不能为空', trigger: 'blur' },
],
dsType: [{ required: true, message: '数据库类型不能为空', trigger: 'blur' }],
confType: [{ required: true, message: '配置类型不能为空', trigger: 'blur' }],
dsName: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '数据库名称不能为空', trigger: 'blur' },
],
instance: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '实例不能为空', trigger: 'blur' },
],
port: [{ required: true, message: '端口不能为空', trigger: 'blur' }],
host: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '主机不能为空', trigger: 'blur' },
],
});
// 打开弹窗
const openDialog = async (id: string) => {
visible.value = true;
form.id = '';
visible.value = true;
form.id = '';
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
});
// 重置表单数据
nextTick(() => {
dataFormRef.value?.resetFields();
});
// 获取DatasourceConf信息
if (id) {
form.id = id;
await getDatasourceConfData(id);
// 修改密码时候,原密码打码
form.password = '********';
}
// 获取DatasourceConf信息
if (id) {
form.id = id;
await getDatasourceConfData(id);
// 修改密码时候,原密码打码
form.password = '********';
}
};
// 提交
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;
form.password = form.password?.includes('******') ? undefined : form.password;
form.password = form.password?.includes('******') ? undefined : form.password;
try {
loading.value = true;
form.id ? await putObj(form) : await addObj(form);
useMessage().success(t(form.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;
form.id ? await putObj(form) : await addObj(form);
useMessage().success(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText'));
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// 初始化表单数据
const getDatasourceConfData = (id: string) => {
// 获取数据
return getObj(id).then((res: any) => {
Object.assign(form, res.data);
});
// 获取数据
return getObj(id).then((res: any) => {
Object.assign(form, res.data);
});
};
// 暴露变量
defineExpose({
openDialog,
openDialog,
});
</script>

View File

@@ -1,15 +1,15 @@
<template>
<el-dialog :title="form.id ? $t('common.editBtn') : $t('common.addBtn')" v-model="visible" width="600" :close-on-click-modal="false" draggable>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px" v-loading="loading">
<el-form-item :label="t('fieldtype.columnType')" prop="columnType">
<el-input v-model="form.columnType" :placeholder="t('fieldtype.inputcolumnTypeTip')" />
</el-form-item>
<el-form-item :label="t('fieldtype.attrType')" prop="attrType">
<el-input v-model="form.attrType" :placeholder="t('fieldtype.inputattrTypeTip')" />
</el-form-item>
<el-form-item :label="t('fieldtype.packageName')" prop="packageName">
<el-input v-model="form.packageName" :placeholder="t('fieldtype.inputpackageNameTip')" />
</el-form-item>
<el-form-item :label="t('fieldtype.columnType')" prop="columnType">
<el-input v-model="form.columnType" :placeholder="t('fieldtype.inputcolumnTypeTip')" />
</el-form-item>
<el-form-item :label="t('fieldtype.attrType')" prop="attrType">
<el-input v-model="form.attrType" :placeholder="t('fieldtype.inputattrTypeTip')" />
</el-form-item>
<el-form-item :label="t('fieldtype.packageName')" prop="packageName">
<el-input v-model="form.packageName" :placeholder="t('fieldtype.inputpackageNameTip')" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">

View File

@@ -37,8 +37,8 @@
</el-dialog>
</template>
<script setup lang="ts" name="child">
import {useListTableApi, useListTableColumnApi, useSyncTableApi} from '/@/api/gen/table';
import {useMessage} from "/@/hooks/message";
import { useListTableApi, useListTableColumnApi, useSyncTableApi } from '/@/api/gen/table';
import { useMessage } from '/@/hooks/message';
const emit = defineEmits(['update:modelValue']);
@@ -109,10 +109,10 @@ const getChildTableColumnList = (val: string) => {
const onSubmit = async () => {
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) return false;
// 同步子表数据
useSyncTableApi(currentDsName.value,form.childTableName).then(() => {
useMessage().success('子表信息同步成功')
});
// 同步子表数据
useSyncTableApi(currentDsName.value, form.childTableName).then(() => {
useMessage().success('子表信息同步成功');
});
visible.value = false;
};

View File

@@ -35,7 +35,7 @@
import { useMessage } from '/@/hooks/message';
import { addObj, getObj, putObj } from '/@/api/gen/template';
import { useI18n } from 'vue-i18n';
import {rule} from "/@/utils/validate";
import { rule } from '/@/utils/validate';
const CodeEditor = defineAsyncComponent(() => import('/@/components/CodeEditor/index.vue'));
const emit = defineEmits(['refresh']);
@@ -60,9 +60,18 @@ const form = reactive({
// 定义校验规则
const dataRules = ref({
templateName: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '模板名称不能为空', trigger: 'blur' }],
generatorPath: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '模板路径不能为空', trigger: 'blur' }],
templateDesc: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '模板描述不能为空', trigger: 'blur' }],
templateName: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '模板名称不能为空', trigger: 'blur' },
],
generatorPath: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '模板路径不能为空', trigger: 'blur' },
],
templateDesc: [
{ validator: rule.overLength, trigger: 'blur' },
{ required: true, message: '模板描述不能为空', trigger: 'blur' },
],
});
// 打开弹窗

Some files were not shown because too many files have changed in this diff Show More