first commit
This commit is contained in:
24
src/views/admin/audit/i18n/en.ts
Normal file
24
src/views/admin/audit/i18n/en.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export default {
|
||||
audit: {
|
||||
index: '#',
|
||||
importsysAuditLogTip: 'import SysAuditLog',
|
||||
id: 'id',
|
||||
auditName: 'auditName',
|
||||
auditField: 'auditField',
|
||||
beforeVal: 'beforeVal',
|
||||
afterVal: 'afterVal',
|
||||
createBy: 'createBy',
|
||||
createTime: 'createTime',
|
||||
delFlag: 'delFlagx',
|
||||
tenantId: 'tenantId',
|
||||
inputIdTip: 'input id',
|
||||
inputAuditNameTip: 'input auditName',
|
||||
inputAuditFieldTip: 'input auditField',
|
||||
inputBeforeValTip: 'input beforeVal',
|
||||
inputAfterValTip: 'input afterVal',
|
||||
inputCreateByTip: 'input createBy',
|
||||
inputCreateTimeTip: 'input createTime',
|
||||
inputDelFlagTip: 'input delFlagx',
|
||||
inputTenantIdTip: 'input tenantId',
|
||||
},
|
||||
};
|
||||
24
src/views/admin/audit/i18n/zh-cn.ts
Normal file
24
src/views/admin/audit/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export default {
|
||||
audit: {
|
||||
index: '#',
|
||||
importsysAuditLogTip: '导入审计记录表',
|
||||
id: '主键',
|
||||
auditName: '审计名称',
|
||||
auditField: '字段名称',
|
||||
beforeVal: '变更前值',
|
||||
afterVal: '变更后值',
|
||||
createBy: '操作人',
|
||||
createTime: '操作时间',
|
||||
delFlag: '删除标记',
|
||||
tenantId: '租户ID',
|
||||
inputIdTip: '请输入主键',
|
||||
inputAuditNameTip: '请输入审计名称',
|
||||
inputAuditFieldTip: '请输入字段名称',
|
||||
inputBeforeValTip: '请输入变更前值',
|
||||
inputAfterValTip: '请输入变更后值',
|
||||
inputCreateByTip: '请输入操作人',
|
||||
inputCreateTimeTip: '请输入操作时间',
|
||||
inputDelFlagTip: '请输入删除标记',
|
||||
inputTenantIdTip: '请输入租户ID',
|
||||
},
|
||||
};
|
||||
137
src/views/admin/audit/index.vue
Normal file
137
src/views/admin/audit/index.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<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('audit.auditName')" prop="auditName">
|
||||
<el-input :placeholder="t('audit.inputAuditNameTip')" v-model="state.queryForm.auditName" style="max-width: 180px" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('audit.auditField')" prop="auditField">
|
||||
<el-input :placeholder="t('audit.inputAuditFieldTip')" v-model="state.queryForm.auditField" style="max-width: 180px" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('audit.createBy')" prop="createBy">
|
||||
<el-input :placeholder="t('audit.inputCreateByTip')" v-model="state.queryForm.createBy" style="max-width: 180px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button formDialogRef icon="search" type="primary" @click="getDataList">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="Refresh" formDialogRef @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button
|
||||
plain
|
||||
formDialogRef
|
||||
:disabled="multiple"
|
||||
icon="Delete"
|
||||
type="primary"
|
||||
class="ml10"
|
||||
v-auth="'sys_audit_del'"
|
||||
@click="handleDelete(selectObjs)"
|
||||
>
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
:export="'sys_audit_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%"
|
||||
row-key="id"
|
||||
@selection-change="handleSelectionChange"
|
||||
@sort-change="sortChangeHandle"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column type="selection" width="40" align="center" />
|
||||
<el-table-column type="index" :label="$t('audit.index')" width="80" />
|
||||
<el-table-column prop="auditName" :label="$t('audit.auditName')" show-overflow-tooltip />
|
||||
<el-table-column prop="auditField" :label="$t('audit.auditField')" show-overflow-tooltip />
|
||||
<el-table-column prop="beforeVal" :label="$t('audit.beforeVal')" show-overflow-tooltip />
|
||||
<el-table-column prop="afterVal" :label="$t('audit.afterVal')" show-overflow-tooltip />
|
||||
<el-table-column prop="createBy" :label="$t('audit.createBy')" show-overflow-tooltip />
|
||||
<el-table-column prop="createTime" :label="$t('audit.createTime')" show-overflow-tooltip />
|
||||
<el-table-column :label="$t('common.action')" width="150">
|
||||
<template #default="scope">
|
||||
<el-button icon="delete" text type="primary" v-auth="'sys_audit_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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemSysAuditLog">
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { fetchList, delObjs } from '/@/api/admin/audit';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
// 搜索变量
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
// table hook
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
pageList: fetchList,
|
||||
descs: ['create_time'],
|
||||
});
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
// 清空搜索条件
|
||||
queryRef.value?.resetFields();
|
||||
// 清空多选
|
||||
selectObjs.value = [];
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/audit/export', Object.assign(state.queryForm,{ids:selectObjs}), 'audit.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { id: string }[]) => {
|
||||
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 delObjs(ids);
|
||||
getDataList();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
245
src/views/admin/client/form.vue
Normal file
245
src/views/admin/client/form.vue
Normal file
@@ -0,0 +1,245 @@
|
||||
<template>
|
||||
<el-dialog :close-on-click-modal="false" :title="form.id ? $t('common.editBtn') : $t('common.addBtn')" draggable v-model="visible">
|
||||
<el-form :model="form" :rules="dataRules" formDialogRef label-width="120px" ref="dataFormRef" v-loading="loading">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('client.clientId')" prop="clientId">
|
||||
<el-input :placeholder="t('client.inputClientIdTip')" v-model="form.clientId" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('client.clientSecret')" prop="clientSecret">
|
||||
<el-input :placeholder="t('client.inputClientSecretTip')" v-model="form.clientSecret" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('client.scope')" prop="scope">
|
||||
<el-input :placeholder="t('client.inputScopeTip')" v-model="form.scope" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('client.authorizedGrantTypes')" prop="authorizedGrantTypes">
|
||||
<el-select collapse-tags collapse-tags-tooltip multiple v-model="form.authorizedGrantTypes">
|
||||
<el-option :key="index" :label="item.label" :value="item.value" v-for="(item, index) in grant_types"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('client.accessTokenValidity')" prop="accessTokenValidity">
|
||||
<el-input-number :placeholder="t('client.inputAccessTokenValidityTip')" v-model="form.accessTokenValidity" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('client.refreshTokenValidity')" prop="refreshTokenValidity">
|
||||
<el-input-number :placeholder="t('client.inputRefreshTokenValidityTip')" v-model="form.refreshTokenValidity" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20" v-if="form.authorizedGrantTypes.includes('authorization_code')">
|
||||
<el-form-item :label="t('client.autoapprove')" prop="autoapprove">
|
||||
<el-radio-group v-model="form.autoapprove">
|
||||
<el-radio :key="index" :label="item.value" border v-for="(item, index) in common_status">{{ item.label }} </el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20" v-if="form.authorizedGrantTypes.includes('authorization_code')">
|
||||
<el-form-item :label="t('client.authorities')" prop="authorities">
|
||||
<el-input :placeholder="t('client.inputAuthoritiesTip')" v-model="form.authorities" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24" class="mb20" v-if="form.authorizedGrantTypes.includes('authorization_code')">
|
||||
<el-form-item :label="t('client.webServerRedirectUri')" prop="webServerRedirectUri">
|
||||
<el-input :placeholder="t('client.inputWebServerRedirectUriTip')" v-model="form.webServerRedirectUri" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-collapse v-model="collapseActive">
|
||||
<el-collapse-item name="1" title="安全属性">
|
||||
<template #title>
|
||||
<el-icon class="header-icon">
|
||||
<info-filled />
|
||||
</el-icon>
|
||||
安全属性
|
||||
</template>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('client.captchaFlag')" prop="captchaFlag">
|
||||
<el-radio-group v-model="form.captchaFlag">
|
||||
<el-radio :key="index" :label="item.value" border v-for="(item, index) in captcha_flag_types">
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('client.encFlag')" prop="encFlag">
|
||||
<el-radio-group v-model="form.encFlag">
|
||||
<el-radio :key="index" :label="item.value" border v-for="(item, index) in enc_flag_types">
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('client.onlineQuantity')" prop="onlineQuantity">
|
||||
<el-radio-group v-model="form.onlineQuantity">
|
||||
<el-radio :key="index" :label="item.value" border v-for="(item, index) in enc_flag_types">
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</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 lang="ts" name="SysOauthClientDetailsDialog" setup>
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { addObj, getObj, putObj, validateclientId } from '/@/api/admin/client';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { rule } from '/@/utils/validate';
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 定义字典
|
||||
const { grant_types, common_status, captcha_flag_types, enc_flag_types } = useDict(
|
||||
'grant_types',
|
||||
'common_status',
|
||||
'captcha_flag_types',
|
||||
'enc_flag_types'
|
||||
);
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
clientId: '',
|
||||
clientSecret: '',
|
||||
scope: 'server',
|
||||
authorizedGrantTypes: [] as string[],
|
||||
webServerRedirectUri: '',
|
||||
authorities: '',
|
||||
accessTokenValidity: 43200,
|
||||
refreshTokenValidity: 2592001,
|
||||
autoapprove: 'true',
|
||||
delFlag: '',
|
||||
createBy: '',
|
||||
updateBy: '',
|
||||
createTime: '',
|
||||
updateTime: '',
|
||||
tenantId: '',
|
||||
onlineQuantity: '1',
|
||||
captchaFlag: '1',
|
||||
encFlag: '1',
|
||||
});
|
||||
|
||||
const collapseActive = ref('1');
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
clientId: [
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
{ required: true, message: '编号不能为空', trigger: 'blur' },
|
||||
{ validator: rule.validatorLowercase, trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateclientId(rule, value, callback, form.id !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
clientSecret: [
|
||||
{ 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' }],
|
||||
authorizedGrantTypes: [{ required: true, message: '授权模式不能为空', trigger: 'blur' }],
|
||||
accessTokenValidity: [
|
||||
{ required: true, message: '令牌时效不能为空', trigger: 'blur' },
|
||||
{ type: 'number', min: 1, message: '令牌时效不能小于一小时', trigger: 'blur' },
|
||||
],
|
||||
refreshTokenValidity: [
|
||||
{ required: true, message: '刷新时效不能为空', trigger: 'blur' },
|
||||
{ type: 'number', min: 1, message: '刷新时效不能小于两小时', trigger: 'blur' },
|
||||
],
|
||||
captchaFlag: [{ required: true, message: '是否开启验证码校验', trigger: 'blur' }],
|
||||
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' }],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true;
|
||||
form.id = '';
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
// 获取sysOauthClientDetails信息
|
||||
if (id) {
|
||||
form.id = id;
|
||||
getsysOauthClientDetailsData(id);
|
||||
}
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
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(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText'));
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化表单数据
|
||||
const getsysOauthClientDetailsData = (id: string) => {
|
||||
// 获取数据
|
||||
getObj(id).then((res: any) => {
|
||||
Object.assign(form, res.data);
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
45
src/views/admin/client/i18n/en.ts
Normal file
45
src/views/admin/client/i18n/en.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
export default {
|
||||
client: {
|
||||
index: '#',
|
||||
importsysOauthClientDetailsTip: 'import SysOauthClientDetails',
|
||||
id: 'id',
|
||||
clientId: 'client Id',
|
||||
resourceIds: 'resourceIds',
|
||||
clientSecret: 'clientSecret',
|
||||
scope: 'scope',
|
||||
authorizedGrantTypes: 'authorizedGrantTypes',
|
||||
webServerRedirectUri: 'webServerRedirectUri',
|
||||
authorities: 'authorities',
|
||||
accessTokenValidity: 'accessTokenValidity',
|
||||
refreshTokenValidity: 'refreshTokenValidity',
|
||||
additionalInformation: 'additionalInformation',
|
||||
autoapprove: 'autoapprove',
|
||||
delFlag: 'delFlag',
|
||||
createBy: 'createBy',
|
||||
updateBy: 'updateBy',
|
||||
createTime: 'createTime',
|
||||
updateTime: 'updateTime',
|
||||
tenantId: 'tenantId',
|
||||
captchaFlag: 'captchaFlag',
|
||||
encFlag: 'encFlag',
|
||||
onlineQuantity: 'onlineQuantity',
|
||||
inputIdTip: 'input id',
|
||||
inputClientIdTip: 'input clientId',
|
||||
inputResourceIdsTip: 'input resourceIds',
|
||||
inputClientSecretTip: 'input clientSecret',
|
||||
inputScopeTip: 'input scope',
|
||||
inputAuthorizedGrantTypesTip: 'input authorizedGrantTypes',
|
||||
inputWebServerRedirectUriTip: 'input webServerRedirectUri',
|
||||
inputAuthoritiesTip: 'input authorities',
|
||||
inputAccessTokenValidityTip: 'input accessTokenValidity',
|
||||
inputRefreshTokenValidityTip: 'input refreshTokenValidity',
|
||||
inputAdditionalInformationTip: 'input additionalInformation',
|
||||
inputAutoapproveTip: 'input autoapprove',
|
||||
inputDelFlagTip: 'input delFlag',
|
||||
inputCreateByTip: 'input createBy',
|
||||
inputUpdateByTip: 'input updateBy',
|
||||
inputCreateTimeTip: 'input createTime',
|
||||
inputUpdateTimeTip: 'input updateTime',
|
||||
inputTenantIdTip: 'input tenantId',
|
||||
},
|
||||
};
|
||||
43
src/views/admin/client/i18n/zh-cn.ts
Normal file
43
src/views/admin/client/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
export default {
|
||||
client: {
|
||||
index: '#',
|
||||
importsysOauthClientDetailsTip: '导入终端信息表',
|
||||
id: 'ID',
|
||||
clientId: '客户端id',
|
||||
resourceIds: '',
|
||||
clientSecret: '密钥',
|
||||
scope: '域',
|
||||
authorizedGrantTypes: '授权模式',
|
||||
webServerRedirectUri: '回调地址',
|
||||
authorities: '权限',
|
||||
accessTokenValidity: '令牌时效(秒)',
|
||||
refreshTokenValidity: '刷新时效(秒)',
|
||||
additionalInformation: '扩展信息',
|
||||
autoapprove: '自动放行',
|
||||
createBy: '创建人',
|
||||
updateBy: '修改人',
|
||||
createTime: '上传时间',
|
||||
updateTime: '更新时间',
|
||||
tenantId: '所属租户',
|
||||
captchaFlag: '验证码开关',
|
||||
encFlag: '前端密码加密',
|
||||
onlineQuantity: '允许同时在线',
|
||||
inputIdTip: '请输入ID',
|
||||
inputClientIdTip: '请输入客户端id',
|
||||
inputResourceIdsTip: '请输入',
|
||||
inputClientSecretTip: '请输入密钥',
|
||||
inputScopeTip: '请输入域',
|
||||
inputAuthorizedGrantTypesTip: '请输入授权模式',
|
||||
inputWebServerRedirectUriTip: '请输入回调地址',
|
||||
inputAuthoritiesTip: '请输入权限',
|
||||
inputAccessTokenValidityTip: '请输入令牌时效',
|
||||
inputRefreshTokenValidityTip: '请输入刷新时效',
|
||||
inputAdditionalInformationTip: '请输入扩展信息',
|
||||
inputAutoapproveTip: '请输入自动放行',
|
||||
inputCreateByTip: '请输入创建人',
|
||||
inputUpdateByTip: '请输入修改人',
|
||||
inputCreateTimeTip: '请输入上传时间',
|
||||
inputUpdateTimeTip: '请输入更新时间',
|
||||
inputTenantIdTip: '请输入所属租户',
|
||||
},
|
||||
};
|
||||
155
src/views/admin/client/index.vue
Normal file
155
src/views/admin/client/index.vue
Normal file
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row class="ml10" v-show="showSearch">
|
||||
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
|
||||
<el-form-item :label="$t('client.clientId')" prop="clientId">
|
||||
<el-input :placeholder="$t('client.clientId')" style="max-width: 180px" v-model="state.queryForm.clientId" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('client.clientSecret')" prop="clientSecret">
|
||||
<el-input :placeholder="$t('client.clientSecret')" style="max-width: 180px" v-model="state.queryForm.clientSecret" />
|
||||
</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="mb8" style="width: 100%">
|
||||
<el-button v-auth="'sys_client_add'" @click="formDialogRef.openDialog()" class="ml10" icon="folder-add" type="primary">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<el-button v-auth="'sys_client_del'" plain @click="handleRefreshCache()" class="ml10" icon="refresh-left" type="primary">
|
||||
{{ $t('common.refreshCacheBtn') }}
|
||||
</el-button>
|
||||
|
||||
<el-button plain :disabled="multiple" @click="handleDelete(selectObjs)" class="ml10" icon="Delete" type="primary" v-auth="'sys_client_del'">
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
|
||||
<right-toolbar
|
||||
:export="'sys_client_del'"
|
||||
@exportExcel="exportExcel"
|
||||
@queryTable="getDataList"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
v-model:showSearch="showSearch"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
row-key="id"
|
||||
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('client.index')" type="index" width="60" />
|
||||
<el-table-column :label="t('client.clientId')" prop="clientId" show-overflow-tooltip />
|
||||
<el-table-column :label="t('client.clientSecret')" prop="clientSecret" show-overflow-tooltip />
|
||||
<el-table-column :label="t('client.scope')" prop="scope" show-overflow-tooltip />
|
||||
<el-table-column :label="t('client.authorizedGrantTypes')" prop="authorizedGrantTypes" show-overflow-tooltip width="400px">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="grant_types" :value="scope.row.authorizedGrantTypes" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('client.accessTokenValidity')" prop="accessTokenValidity" show-overflow-tooltip />
|
||||
<el-table-column :label="t('client.refreshTokenValidity')" prop="refreshTokenValidity" show-overflow-tooltip />
|
||||
<el-table-column :label="$t('common.action')" width="150">
|
||||
<template #default="scope">
|
||||
<el-button icon="edit-pen" @click="formDialogRef.openDialog(scope.row.clientId)" text type="primary" v-auth="'sys_client_add'"
|
||||
>{{ $t('common.editBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="delete" @click="handleDelete([scope.row.id])" text type="primary" v-auth="'sys_client_del'">
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
<!-- 编辑、新增 -->
|
||||
<form-dialog @refresh="getDataList()" ref="formDialogRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemSysOauthClientDetails" setup>
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { delObj, fetchList, refreshCache } from '/@/api/admin/client';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const { t } = useI18n();
|
||||
// 定义查询字典
|
||||
|
||||
const { grant_types } = useDict('grant_types');
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref();
|
||||
const queryRef = ref();
|
||||
// 搜索变量
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
pageList: fetchList,
|
||||
descs: ['id'],
|
||||
});
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 删除缓存
|
||||
const handleRefreshCache = () => {
|
||||
refreshCache().then(() => {
|
||||
useMessage().success('同步成功');
|
||||
});
|
||||
};
|
||||
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
// state.queryForm = {};
|
||||
selectObjs.value = [];
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/client/export', Object.assign(state.queryForm,{ids:selectObjs}), 'client.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { id: string }[]) => {
|
||||
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 delObj(ids);
|
||||
getDataList();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
133
src/views/admin/dict/dictItem/form.vue
Normal file
133
src/views/admin/dict/dictItem/form.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<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="90px" :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>
|
||||
<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>
|
||||
|
||||
<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";
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['refresh']);
|
||||
const {t} = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const dicDialogFormRef = ref();
|
||||
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
const dataForm = reactive({
|
||||
id: '',
|
||||
dictId: '',
|
||||
dictType: '',
|
||||
value: '',
|
||||
label: '',
|
||||
description: '',
|
||||
sortOrder: 0,
|
||||
remarks: '',
|
||||
});
|
||||
|
||||
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'
|
||||
}],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (row: any, dictForm: any) => {
|
||||
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);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
86
src/views/admin/dict/dictItem/index.vue
Normal file
86
src/views/admin/dict/dictItem/index.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<div class="mb8">
|
||||
<el-button icon="folder-add" type="primary" class="ml10" @click="dictformRef.openDialog(null, state.queryForm)">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<right-toolbar :search="false" class="ml10" style="float: right; margin-right: 20px" @queryTable="getDataList"></right-toolbar>
|
||||
</div>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
style="width: 100%"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column prop="dictType" :label="$t('dictItem.dictType')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="value" :label="$t('dictItem.itemValue')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="label" :label="$t('dictItem.label')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="description" :label="$t('dictItem.description')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="sortOrder" :label="$t('dictItem.sortOrder')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="remarks" :label="$t('dictItem.remarks')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="createTime" :label="$t('dictItem.createTime')" show-overflow-tooltip></el-table-column>
|
||||
|
||||
<el-table-column :label="$t('common.action')" width="150">
|
||||
<template #default="scope">
|
||||
<el-button icon="edit-pen" text type="primary" @click="dictformRef.openDialog(scope.row)"> {{ $t('common.editBtn') }} </el-button>
|
||||
<el-button icon="delete" text type="primary" @click="handleDelete(scope.row)">
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination"> </pagination>
|
||||
<dict-form ref="dictformRef" @refresh="getDataList"></dict-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="dict-item">
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { fetchItemList, delItemObj } from '/@/api/admin/dict';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
const visible = ref(false);
|
||||
const DictForm = defineAsyncComponent(() => import('./form.vue'));
|
||||
const dictformRef = ref();
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
dictId: '',
|
||||
dictType: '',
|
||||
},
|
||||
createdIsNeed: false,
|
||||
pageList: fetchItemList,
|
||||
});
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state);
|
||||
|
||||
const handleDelete = async (row: any) => {
|
||||
try {
|
||||
await useMessageBox().confirm(t('common.delConfirmText'));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delItemObj(row.id);
|
||||
getDataList();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
const open = (row: any) => {
|
||||
state.queryForm.dictId = row.id;
|
||||
state.queryForm.dictType = row.dictType;
|
||||
visible.value = true;
|
||||
getDataList();
|
||||
};
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
open,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
108
src/views/admin/dict/form.vue
Normal file
108
src/views/admin/dict/form.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<el-dialog :title="dataForm.id ? $t('common.editBtn') : $t('common.addBtn')" v-model="visible" width="600">
|
||||
<el-form :model="dataForm" :rules="dataRules" label-width="100px" ref="dicDialogFormRef" v-loading="loading">
|
||||
<el-form-item :label="$t('sysdict.systemFlag')" prop="systemFlag">
|
||||
<el-radio-group v-model="dataForm.systemFlag">
|
||||
<el-radio border :key="index" :label="item.value" v-for="(item, index) in dict_type">
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysdict.dictType')" prop="dictType">
|
||||
<el-input :placeholder="$t('sysdict.inputDictTypeTip')" :disabled="dataForm.id !== ''" clearable v-model="dataForm.dictType"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysdict.description')" prop="description">
|
||||
<el-input :placeholder="$t('sysdict.inputDescriptionTip')" clearable v-model="dataForm.description"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysdict.remarks')" prop="remarks">
|
||||
<el-input type="textarea" maxlength="100" :rows="3" :placeholder="$t('sysdict.inputRemarksTip')" v-model="dataForm.remarks"></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 @click="onSubmit" type="primary" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemDicDialog" setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { addObj, getObj, putObj, validateDictType } from '/@/api/admin/dict';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { rule } from '/@/utils/validate';
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['refresh']);
|
||||
const { dict_type } = useDict('dict_type');
|
||||
const { t } = useI18n();
|
||||
// 定义变量内容
|
||||
const dicDialogFormRef = ref();
|
||||
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
const dataForm = reactive({
|
||||
id: '',
|
||||
dictType: '',
|
||||
description: '',
|
||||
systemFlag: '0',
|
||||
remarks: '',
|
||||
});
|
||||
|
||||
const dataRules = reactive({
|
||||
dictType: [
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
{ required: true, message: '类型不能为空', trigger: 'blur' },
|
||||
{ validator: rule.validatorNameCn, trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateDictType(rule, value, callback, dataForm.id !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
systemFlag: [{ required: true, message: '字典类型不能为空', trigger: 'blur' }],
|
||||
description: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '描述不能为空', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true;
|
||||
dataForm.id = '';
|
||||
nextTick(() => {
|
||||
dicDialogFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
if (id) {
|
||||
getObj(id).then((res) => {
|
||||
Object.assign(dataForm, res.data);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
const valid = await dicDialogFormRef.value.validate().catch(() => {});
|
||||
if (!valid) return false;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
const result = dataForm.id ? await putObj(dataForm) : await addObj(dataForm);
|
||||
useMessage().success(t(dataForm.id ? 'common.editSuccessText' : 'common.addSuccessText'));
|
||||
visible.value = false;
|
||||
emit('refresh', result.data);
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
60
src/views/admin/dict/i18n/en.ts
Normal file
60
src/views/admin/dict/i18n/en.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
export default {
|
||||
sysdict: {
|
||||
index: '#',
|
||||
importsysDictTip: 'import SysDict',
|
||||
id: 'id',
|
||||
dictType: 'dictType',
|
||||
description: 'description',
|
||||
createBy: 'createBy',
|
||||
updateBy: 'updateBy',
|
||||
createTime: 'createTime',
|
||||
updateTime: 'updateTime',
|
||||
remarks: 'remarks',
|
||||
systemFlag: 'systemFlag',
|
||||
inputDictTypeTip: 'input dictType',
|
||||
inputDescriptionTip: 'input description',
|
||||
inputCreateByTip: 'input createBy',
|
||||
inputUpdateByTip: 'input updateBy',
|
||||
inputCreateTimeTip: 'input createTime',
|
||||
inputUpdateTimeTip: 'input updateTime',
|
||||
inputRemarksTip: 'input remarks',
|
||||
inputSystemFlagTip: 'input systemFlag',
|
||||
inputDelFlagTip: 'input delFlag',
|
||||
inputTenantIdTip: 'input tenantId',
|
||||
dictItem: 'dict item',
|
||||
deleteDisabledTip: 'system data cannot be deleted ',
|
||||
},
|
||||
dictItem: {
|
||||
index: '#',
|
||||
name: 'dict item',
|
||||
importsysDictItemTip: 'import SysDictItem',
|
||||
id: 'id',
|
||||
dictId: 'dictId',
|
||||
itemValue: 'itemValue',
|
||||
label: 'label',
|
||||
dictType: 'dictType',
|
||||
description: 'description',
|
||||
sortOrder: 'sortOrder',
|
||||
createBy: 'createBy',
|
||||
updateBy: 'updateBy',
|
||||
createTime: 'createTime',
|
||||
updateTime: 'updateTime',
|
||||
remarks: 'remarks',
|
||||
delFlag: 'delFlag',
|
||||
tenantId: 'tenantId',
|
||||
inputIdTip: 'input id',
|
||||
inputDictIdTip: 'input dictId',
|
||||
inputItemValueTip: 'input itemValue',
|
||||
inputLabelTip: 'input label',
|
||||
inputDictTypeTip: 'input dictType',
|
||||
inputDescriptionTip: 'input description',
|
||||
inputSortOrderTip: 'input sortOrder',
|
||||
inputCreateByTip: 'input createBy',
|
||||
inputUpdateByTip: 'input updateBy',
|
||||
inputCreateTimeTip: 'input createTime',
|
||||
inputUpdateTimeTip: 'input updateTime',
|
||||
inputRemarksTip: 'input remarks',
|
||||
inputDelFlagTip: 'input delFlag',
|
||||
inputTenantIdTip: 'input tenantId',
|
||||
},
|
||||
};
|
||||
63
src/views/admin/dict/i18n/zh-cn.ts
Normal file
63
src/views/admin/dict/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
export default {
|
||||
sysdict: {
|
||||
index: '#',
|
||||
importsysDictTip: '导入字典表',
|
||||
id: '编号',
|
||||
dictType: '字典标识',
|
||||
description: '字典名称',
|
||||
createBy: '创建人',
|
||||
updateBy: '修改人',
|
||||
createTime: '创建时间',
|
||||
updateTime: '更新时间',
|
||||
remarks: '备注',
|
||||
systemFlag: '配置类型',
|
||||
delFlag: ' delFlag',
|
||||
tenantId: '所属租户',
|
||||
inputIdTip: '请输入编号',
|
||||
inputDictTypeTip: '请输入字典类型',
|
||||
inputDescriptionTip: '请输入描述',
|
||||
inputCreateByTip: '请输入创建人',
|
||||
inputUpdateByTip: '请输入修改人',
|
||||
inputCreateTimeTip: '请输入创建时间',
|
||||
inputUpdateTimeTip: '请输入更新时间',
|
||||
inputRemarksTip: '请输入备注',
|
||||
inputSystemFlagTip: '请输入字典类型',
|
||||
inputDelFlagTip: '请输入 delFlag',
|
||||
inputTenantIdTip: '请输入所属租户',
|
||||
dictItem: '字典项',
|
||||
deleteDisabledTip: '系统内置数据不能删除',
|
||||
},
|
||||
dictItem: {
|
||||
index: '#',
|
||||
name: '字典项',
|
||||
importsysDictItemTip: '导入字典项',
|
||||
id: '编号',
|
||||
dictId: ' dictId',
|
||||
itemValue: '数据值',
|
||||
label: '标签名',
|
||||
dictType: '类型',
|
||||
description: '描述',
|
||||
sortOrder: '排序',
|
||||
createBy: '创建人',
|
||||
updateBy: '修改人',
|
||||
createTime: '创建时间',
|
||||
updateTime: '更新时间',
|
||||
remarks: '备注',
|
||||
delFlag: ' delFlag',
|
||||
tenantId: '所属租户',
|
||||
inputIdTip: '请输入编号',
|
||||
inputDictIdTip: '请输入 dictId',
|
||||
inputItemValueTip: '请输入数据值',
|
||||
inputLabelTip: '请输入标签名',
|
||||
inputDictTypeTip: '请输入类型',
|
||||
inputDescriptionTip: '请输入描述',
|
||||
inputSortOrderTip: '请输入排序',
|
||||
inputCreateByTip: '请输入创建人',
|
||||
inputUpdateByTip: '请输入修改人',
|
||||
inputCreateTimeTip: '请输入创建时间',
|
||||
inputUpdateTimeTip: '请输入更新时间',
|
||||
inputRemarksTip: '请输入备注',
|
||||
inputDelFlagTip: '请输入 delFlag',
|
||||
inputTenantIdTip: '请输入所属租户',
|
||||
},
|
||||
};
|
||||
155
src/views/admin/dict/index.vue
Normal file
155
src/views/admin/dict/index.vue
Normal file
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<splitpanes>
|
||||
<pane class="ml10">
|
||||
<splitpanes>
|
||||
<pane size="30">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button @click="dicDialogRef.openDialog()" class="ml10" icon="folder-add" type="primary">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<el-button plain @click="handleRefreshCache()" class="ml10" icon="refresh-left" type="primary">
|
||||
{{ $t('common.refreshCacheBtn') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-scrollbar>
|
||||
<query-tree ref="dictTreeRef" :query="state.queryList" @node-click="handleNodeClick" placeholder="请输入字典项或名称">
|
||||
<template #default="{ data }">
|
||||
<span class="custom-tree-node">
|
||||
<span class="label">{{ data.description }}</span>
|
||||
<span class="code">{{ data.dictType }}</span>
|
||||
<span class="do">
|
||||
<el-button-group>
|
||||
<el-button icon="el-icon-edit" size="small" @click.stop="dicDialogRef.openDialog(data.id)"></el-button>
|
||||
<el-tooltip :content="$t('sysdict.deleteDisabledTip')" :disabled="data.systemFlag === '0'" placement="top">
|
||||
<span style="margin-left: 12px">
|
||||
<el-button
|
||||
:disabled="data.systemFlag !== '0'"
|
||||
icon="el-icon-delete"
|
||||
size="small"
|
||||
@click.stop="handleDelete([data.id])"
|
||||
></el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</el-button-group>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</query-tree>
|
||||
</el-scrollbar>
|
||||
<el-footer style="height: 40px; line-height: 40px">
|
||||
<el-button type="primary" size="small" icon="Download" style="width: 100%" @click="exportExcel">{{
|
||||
$t('common.exportBtn')
|
||||
}}</el-button>
|
||||
</el-footer>
|
||||
</div>
|
||||
</pane>
|
||||
<pane class="ml8">
|
||||
<DicDialog @refresh="handleRefreshTree" ref="dicDialogRef" />
|
||||
<dict-item-dialog ref="dictItemDialogRef"></dict-item-dialog>
|
||||
</pane>
|
||||
</splitpanes>
|
||||
</pane>
|
||||
</splitpanes>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemDic" setup>
|
||||
import { delObj, fetchList, refreshCache } from '/@/api/admin/dict';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { downBlobFile } from '/@/utils/other';
|
||||
|
||||
// 引入组件
|
||||
const DicDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const DictItemDialog = defineAsyncComponent(() => import('./dictItem/index.vue'));
|
||||
const QueryTree = defineAsyncComponent(() => import('/@/components/QueryTree/index.vue'));
|
||||
|
||||
const { t } = useI18n();
|
||||
// 定义变量内容
|
||||
const dicDialogRef = ref();
|
||||
const dictTreeRef = ref();
|
||||
const dictItemDialogRef = ref();
|
||||
const state = reactive({
|
||||
queryForm: {},
|
||||
queryList: (name?: string) => {
|
||||
return fetchList({
|
||||
name: name,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// 导出EXCEL
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/dict/export', state.queryForm, 'dict.xlsx');
|
||||
};
|
||||
|
||||
//刷新缓存
|
||||
const handleRefreshCache = () => {
|
||||
refreshCache().then(() => {
|
||||
useMessage().success('同步成功');
|
||||
});
|
||||
};
|
||||
|
||||
// 点击树
|
||||
const handleNodeClick = (data: any) => {
|
||||
dictItemDialogRef.value.open(data);
|
||||
};
|
||||
|
||||
// 刷新树
|
||||
const handleRefreshTree = async (data: any) => {
|
||||
await dictTreeRef.value.getdeptTree();
|
||||
// 选择当前编辑、新增的节点
|
||||
handleNodeClick(data);
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
try {
|
||||
await useMessageBox().confirm(t('common.delConfirmText'));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObj(ids);
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
dictTreeRef.value.getdeptTree();
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.menu:deep(.el-tree-node__label) {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
.custom-tree-node {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
padding-right: 24px;
|
||||
height: 100%;
|
||||
}
|
||||
.custom-tree-node .code {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
.custom-tree-node .do {
|
||||
display: none;
|
||||
}
|
||||
.custom-tree-node:hover .code {
|
||||
display: none;
|
||||
}
|
||||
.custom-tree-node:hover .do {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
33
src/views/admin/file/i18n/en.ts
Normal file
33
src/views/admin/file/i18n/en.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export default {
|
||||
file: {
|
||||
index: '#',
|
||||
importsysFileTip: 'import SysFile',
|
||||
id: 'id',
|
||||
fileName: 'fileName',
|
||||
bucketName: 'bucketName',
|
||||
original: 'original',
|
||||
type: 'type',
|
||||
fileSize: 'fileSize',
|
||||
createBy: 'createBy',
|
||||
updateBy: 'updateBy',
|
||||
createTime: 'createTime',
|
||||
updateTime: 'updateTime',
|
||||
delFlag: 'delFlag',
|
||||
tenantId: 'tenantId',
|
||||
inputidTip: 'input id',
|
||||
inputfileNameTip: 'input fileName',
|
||||
inputbucketNameTip: 'input bucketName',
|
||||
inputoriginalTip: 'input original',
|
||||
inputtypeTip: 'input type',
|
||||
inputfileSizeTip: 'input fileSize',
|
||||
inputcreateByTip: 'input createBy',
|
||||
inputupdateByTip: 'input updateBy',
|
||||
inputcreateTimeTip: 'input createTime',
|
||||
inputupdateTimeTip: 'input updateTime',
|
||||
inputdelFlagTip: 'input delFlag',
|
||||
inputtenantIdTip: 'input tenantId',
|
||||
image: 'image',
|
||||
video: 'video',
|
||||
file: 'file',
|
||||
},
|
||||
};
|
||||
32
src/views/admin/file/i18n/zh-cn.ts
Normal file
32
src/views/admin/file/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export default {
|
||||
file: {
|
||||
index: '#',
|
||||
importsysFileTip: '导入文件管理表',
|
||||
id: '编号',
|
||||
fileName: '文件名称',
|
||||
bucketName: '桶名称',
|
||||
original: '原文件名',
|
||||
type: '文件类型',
|
||||
fileSize: '文件大小',
|
||||
createBy: '创建人',
|
||||
updateBy: '修改人',
|
||||
createTime: '上传时间',
|
||||
updateTime: '更新时间',
|
||||
delFlag: '${field.fieldComment}',
|
||||
tenantId: '所属租户',
|
||||
inputidTip: '请输入编号',
|
||||
inputfileNameTip: '请输入文件名称',
|
||||
inputbucketNameTip: '请输入桶名称',
|
||||
inputoriginalTip: '请输入原文件名',
|
||||
inputtypeTip: '请输入文件类型',
|
||||
inputfileSizeTip: '请输入文件大小',
|
||||
inputcreateByTip: '请输入创建人',
|
||||
inputupdateByTip: '请输入修改人',
|
||||
inputcreateTimeTip: '请输入上传时间',
|
||||
inputupdateTimeTip: '请输入更新时间',
|
||||
inputtenantIdTip: '请输入所属租户',
|
||||
image: '图片',
|
||||
video: '视频',
|
||||
file: '文件',
|
||||
},
|
||||
};
|
||||
56
src/views/admin/file/index.vue
Normal file
56
src/views/admin/file/index.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="material-index">
|
||||
<el-card class="!border-none" shadow="never">
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane v-for="item in tabsMap" :label="item.name" :name="item.type" :index="item.type" :key="item.type" lazy>
|
||||
<material :type="item.type" mode="page" file-size="120px" :limit="-1" :page-size="20" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="fileCenter">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const Material = defineAsyncComponent(() => import('/@/components/Material/index.vue'));
|
||||
const { t } = useI18n();
|
||||
const tabsMap = [
|
||||
{
|
||||
type: 'image',
|
||||
name: t('file.image'),
|
||||
},
|
||||
{
|
||||
type: 'video',
|
||||
name: t('file.video'),
|
||||
},
|
||||
{
|
||||
type: 'file',
|
||||
name: t('file.file'),
|
||||
},
|
||||
];
|
||||
const activeTab = ref('image');
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.material-index {
|
||||
min-width: 700px;
|
||||
:deep(.el-tabs) {
|
||||
height: calc(100vh - 180px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.el-tabs__header {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.el-tabs__content,
|
||||
.el-tab-pane {
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
128
src/views/admin/i18n/form.vue
Normal file
128
src/views/admin/i18n/form.vue
Normal file
@@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<el-dialog :close-on-click-modal="false" width="600" :title="form.id ? $t('common.editBtn') : $t('common.addBtn')" draggable v-model="visible">
|
||||
<el-form :model="form" :rules="dataRules" formDialogRef label-width="90px" ref="dataFormRef" v-loading="loading">
|
||||
<el-form-item :label="t('i18n.name')" prop="name">
|
||||
<el-input :placeholder="t('i18n.inputKeyTip')" v-model="form.name" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('i18n.zhCn')" prop="zhCn">
|
||||
<el-input :placeholder="t('i18n.inputZhCnTip')" v-model="form.zhCn" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('i18n.en')" prop="en">
|
||||
<el-input :placeholder="t('i18n.inputEnTip')" v-model="form.en" />
|
||||
</el-form-item>
|
||||
</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 lang="ts" name="SysI18nDialog" setup>
|
||||
// 定义子组件向父组件传值/事件
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { addObj, getObj, putObj, validateName, validateZhCn, validateEn } from '/@/api/admin/i18n';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { rule } from '/@/utils/validate';
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
name: '',
|
||||
zhCn: '',
|
||||
en: '',
|
||||
});
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
name: [
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
{ required: true, message: 'name不能为空', trigger: 'blur' },
|
||||
{ validator: rule.noChinese, trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateName(rule, value, callback, form.id !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
zhCn: [
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
{ required: true, message: '中文不能为空', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateZhCn(rule, value, callback, form.id !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
en: [
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
{ required: true, message: '英文不能为空', trigger: 'blur' },
|
||||
{ validator: rule.letter, trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateEn(rule, value, callback, form.id !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true;
|
||||
form.id = '';
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
// 获取sysI18n信息
|
||||
if (id) {
|
||||
form.id = id;
|
||||
getsysI18nData(id);
|
||||
}
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
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(t(form.id ? 'common.editSuccessText' : 'common.addSuccessText'));
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化表单数据
|
||||
const getsysI18nData = (id: string) => {
|
||||
// 获取数据
|
||||
getObj(id).then((res: any) => {
|
||||
Object.assign(form, res.data);
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
24
src/views/admin/i18n/i18n/en.ts
Normal file
24
src/views/admin/i18n/i18n/en.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export default {
|
||||
i18n: {
|
||||
index: '#',
|
||||
importsysI18nTip: 'import SysI18n',
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
zhCn: 'zh-cn',
|
||||
en: 'en',
|
||||
createBy: 'createBy',
|
||||
createTime: 'createTime',
|
||||
updateBy: 'updateBy',
|
||||
updateTime: 'updateTime',
|
||||
delFlag: 'delFlag',
|
||||
inputIdTip: 'input id',
|
||||
inputKeyTip: 'input key',
|
||||
inputZhCnTip: 'input zh-cn',
|
||||
inputEnTip: 'input en',
|
||||
inputCreateByTip: 'input createBy',
|
||||
inputCreateTimeTip: 'input createTime',
|
||||
inputUpdateByTip: 'input updateBy',
|
||||
inputUpdateTimeTip: 'input updateTime',
|
||||
inputDelFlagTip: 'input delFlag',
|
||||
},
|
||||
};
|
||||
24
src/views/admin/i18n/i18n/zh-cn.ts
Normal file
24
src/views/admin/i18n/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export default {
|
||||
i18n: {
|
||||
index: '#',
|
||||
id: 'id',
|
||||
name: '名称',
|
||||
zhCn: '中文',
|
||||
en: '英文',
|
||||
createBy: '创建人',
|
||||
createTime: '创建时间',
|
||||
updateBy: '修改人',
|
||||
updateTime: '更新时间',
|
||||
delFlag: '删除标记',
|
||||
inputIdTip: '请输入id',
|
||||
inputKeyTip: '请输入key',
|
||||
inputZhCnTip: '请输入中文',
|
||||
inputEnTip: '请输入英文',
|
||||
inputCreateByTip: '请输入创建人',
|
||||
inputCreateTimeTip: '请输入创建时间',
|
||||
inputUpdateByTip: '请输入修改人',
|
||||
inputUpdateTimeTip: '请输入更新时间',
|
||||
inputDelFlagTip: '请输入删除标记',
|
||||
importsysI18nTip: '导入系统表-国际化',
|
||||
},
|
||||
};
|
||||
167
src/views/admin/i18n/index.vue
Normal file
167
src/views/admin/i18n/index.vue
Normal file
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row class="ml10" v-show="showSearch">
|
||||
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
|
||||
<el-form-item :label="$t('i18n.name')" prop="name">
|
||||
<el-input :placeholder="t('i18n.inputKeyTip')" style="max-width: 180px" v-model="state.queryForm.name" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('i18n.zhCn')" prop="zhCn">
|
||||
<el-input :placeholder="t('i18n.inputZhCnTip')" style="max-width: 180px" v-model="state.queryForm.zhCn" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('i18n.en')" prop="en">
|
||||
<el-input :placeholder="t('i18n.inputEnTip')" style="max-width: 180px" v-model="state.queryForm.en" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList" formDialogRef 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="mb8" style="width: 100%">
|
||||
<el-button @click="formDialogRef.openDialog()" class="ml10" formDialogRef icon="folder-add" type="primary" v-auth="'sys_i18n_add'">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<el-button plain @click="handleRefreshCache()" class="ml10" icon="refresh-left" type="primary">
|
||||
{{ $t('common.refreshCacheBtn') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
:disabled="multiple"
|
||||
@click="handleDelete(selectObjs)"
|
||||
class="ml10"
|
||||
formDialogRef
|
||||
icon="Delete"
|
||||
type="primary"
|
||||
v-auth="'sys_i18n_del'"
|
||||
>
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
|
||||
<right-toolbar
|
||||
:export="'sys_i18n_export'"
|
||||
@exportExcel="exportExcel"
|
||||
@queryTable="getDataList"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
v-model:showSearch="showSearch"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
@selection-change="handleSelectionChange"
|
||||
@sort-change="sortChangeHandle"
|
||||
style="width: 100%"
|
||||
row-key="id"
|
||||
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('file.index')" type="index" width="60" />
|
||||
<el-table-column :label="t('i18n.name')" prop="name" show-overflow-tooltip />
|
||||
<el-table-column :label="t('i18n.zhCn')" prop="zhCn" show-overflow-tooltip />
|
||||
<el-table-column :label="t('i18n.en')" prop="en" show-overflow-tooltip />
|
||||
<el-table-column :label="t('i18n.createBy')" prop="createBy" show-overflow-tooltip />
|
||||
<el-table-column :label="t('i18n.createTime')" prop="createTime" show-overflow-tooltip />
|
||||
<el-table-column :label="$t('common.action')" width="150">
|
||||
<template #default="scope">
|
||||
<el-button icon="edit-pen" @click="formDialogRef.openDialog(scope.row.id)" text type="primary" v-auth="'sys_i18n_edit'"
|
||||
>{{ $t('common.editBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="delete" @click="handleDelete([scope.row.id])" text type="primary" v-auth="'sys_i18n_del'">
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
|
||||
<!-- 编辑、新增 -->
|
||||
<form-dialog @refresh="getDataList()" ref="formDialogRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemSysI18n" setup>
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { delObj, fetchList, refreshCache } from '/@/api/admin/i18n';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const { t } = useI18n();
|
||||
// 定义查询字典
|
||||
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref();
|
||||
// 搜索变量
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
name: '',
|
||||
zhCn: '',
|
||||
en: '',
|
||||
},
|
||||
pageList: fetchList,
|
||||
descs: ['create_time'],
|
||||
});
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
// 清空搜索条件
|
||||
queryRef.value.resetFields();
|
||||
// 清空排序规则
|
||||
state.queryForm!.descs = [];
|
||||
state.queryForm!.ascs = [];
|
||||
// 清空多选
|
||||
selectObjs.value = [];
|
||||
getDataList();
|
||||
};
|
||||
const handleRefreshCache = () => {
|
||||
refreshCache().then(() => {
|
||||
useMessage().success('同步成功');
|
||||
});
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/i18n/export', Object.assign(state.queryForm,{ids:selectObjs}), 'i18n.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { id: string }[]) => {
|
||||
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 delObj(ids);
|
||||
getDataList();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
100
src/views/admin/log/detail.vue
Normal file
100
src/views/admin/log/detail.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="log-detail">
|
||||
const visible = ref(false);
|
||||
|
||||
const data = reactive({} as any);
|
||||
|
||||
const openDialog = (row: any) => {
|
||||
visible.value = true;
|
||||
Object.assign(data, row);
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
22
src/views/admin/log/i18n/en.ts
Normal file
22
src/views/admin/log/i18n/en.ts
Normal file
@@ -0,0 +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'
|
||||
},
|
||||
};
|
||||
22
src/views/admin/log/i18n/zh-cn.ts
Normal file
22
src/views/admin/log/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export default {
|
||||
syslog: {
|
||||
index: '#',
|
||||
logType: '类型',
|
||||
title: '标题',
|
||||
remoteAddr: 'IP地址',
|
||||
method: '请求方式',
|
||||
ua: '浏览器',
|
||||
serviceId: '客户端',
|
||||
time: '耗时',
|
||||
params: '请求参数',
|
||||
createTime: '请求时间',
|
||||
requestUri: '请求地址',
|
||||
exception: '异常信息',
|
||||
createBy: '操作人',
|
||||
action: '操作',
|
||||
inputLogTypeTip: '请选择类型',
|
||||
inputStartPlaceholderTip: '开始时间',
|
||||
inputEndPlaceholderTip: '结束时间',
|
||||
result: '结果',
|
||||
},
|
||||
};
|
||||
202
src/views/admin/log/index.vue
Normal file
202
src/views/admin/log/index.vue
Normal file
@@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<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>
|
||||
<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>
|
||||
</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';
|
||||
|
||||
const LogDetail = defineAsyncComponent(() => import('./detail.vue'));
|
||||
const LogLineChart = defineAsyncComponent(() => import('./line-chart.vue'));
|
||||
|
||||
const LogDetailRef = ref();
|
||||
|
||||
const {log_type} = useDict('log_type');
|
||||
|
||||
const {t} = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
|
||||
// 多选rows
|
||||
const selectObjs = ref([]) as any;
|
||||
// 是否可以多选
|
||||
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,
|
||||
});
|
||||
|
||||
// table hook
|
||||
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);
|
||||
};
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value?.resetFields();
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/log/export', state.queryForm, 'log.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { id: string }[]) => {
|
||||
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 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();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
pre code.hljs {
|
||||
width: 65%;
|
||||
}
|
||||
</style>
|
||||
202
src/views/admin/log/line-chart.vue
Normal file
202
src/views/admin/log/line-chart.vue
Normal file
@@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<v-chart class="w-full h-80" :option="option" />
|
||||
</template>
|
||||
<script setup lang="ts" name="log-line-chart">
|
||||
import VChart from 'vue-echarts';
|
||||
import { formatPast } from '/@/utils/formatTime';
|
||||
import { getSum } from '/@/api/admin/log';
|
||||
import { use } from 'echarts/core';
|
||||
import { LineChart } from 'echarts/charts';
|
||||
import { GridComponent, LegendComponent, TitleComponent, ToolboxComponent, TooltipComponent } from 'echarts/components';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
|
||||
use([TitleComponent, TooltipComponent, LegendComponent, ToolboxComponent, GridComponent, LineChart, CanvasRenderer]);
|
||||
|
||||
const option = reactive({
|
||||
title: {
|
||||
textStyle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 500,
|
||||
color: '#303133',
|
||||
},
|
||||
padding: [20, 0, 0, 20],
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
backgroundColor: '#ffffff',
|
||||
borderRadius: 8,
|
||||
padding: [12, 16],
|
||||
borderWidth: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 12,
|
||||
shadowOffsetY: 4,
|
||||
textStyle: {
|
||||
color: '#303133',
|
||||
},
|
||||
axisPointer: {
|
||||
type: 'line',
|
||||
lineStyle: {
|
||||
color: '#ebeef5',
|
||||
width: 1,
|
||||
type: 'dashed',
|
||||
},
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
data: ['成功', '失败'],
|
||||
icon: 'circle',
|
||||
itemWidth: 8,
|
||||
itemHeight: 8,
|
||||
textStyle: {
|
||||
color: '#606266',
|
||||
fontSize: 12,
|
||||
},
|
||||
right: '20px',
|
||||
top: '20px',
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
top: '15%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: [],
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#909399',
|
||||
fontSize: 12,
|
||||
margin: 16,
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#ebeef5',
|
||||
type: 'dashed',
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#ebeef5',
|
||||
type: 'dashed',
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#909399',
|
||||
fontSize: 12,
|
||||
margin: 16,
|
||||
},
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '成功',
|
||||
type: 'line',
|
||||
stack: 'Total',
|
||||
data: [],
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
itemStyle: {
|
||||
color: '#79bbff',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(121, 187, 255, 0.2)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(121, 187, 255, 0.02)',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '失败',
|
||||
type: 'line',
|
||||
stack: 'x',
|
||||
data: [],
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
itemStyle: {
|
||||
color: '#909399',
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(144, 147, 153, 0.2)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(144, 147, 153, 0.02)',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
interface LogSumItem {
|
||||
createTime: string;
|
||||
'0'?: number;
|
||||
'9'?: number;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getSum().then((res) => {
|
||||
option.xAxis.data = res.data.map((item: LogSumItem) => formatPast(new Date(item.createTime), 'mm-dd'));
|
||||
option.series[0].data = res.data.map((item: LogSumItem) => item['0'] || 0);
|
||||
option.series[1].data = res.data.map((item: LogSumItem) => item['9'] || 0);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.echarts) {
|
||||
background: transparent;
|
||||
}
|
||||
</style>
|
||||
171
src/views/admin/param/form.vue
Normal file
171
src/views/admin/param/form.vue
Normal file
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<el-dialog :close-on-click-modal="false" :title="form.publicId ? $t('common.editBtn') : $t('common.addBtn')" draggable v-model="visible">
|
||||
<el-form :model="form" :rules="dataRules" formDialogRef label-width="90px" ref="dataFormRef" v-loading="loading">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('param.publicName')" prop="publicName">
|
||||
<el-input :placeholder="t('param.inputpublicNameTip')" v-model="form.publicName" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('param.publicKey')" prop="publicKey">
|
||||
<el-input :placeholder="t('param.inputpublicKeyTip')" v-model="form.publicKey" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('param.publicValue')" prop="publicValue">
|
||||
<el-input :placeholder="t('param.inputpublicValueTip')" v-model="form.publicValue" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('param.status')" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio :label="item.value" border v-for="(item, index) in status_type" :key="index">{{ item.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('param.validateCode')" prop="validateCode">
|
||||
<el-input :placeholder="t('param.inputvalidateCodeTip')" v-model="form.validateCode" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('param.publicType')" prop="publicType">
|
||||
<el-select :placeholder="t('param.inputpublicTypeTip')" v-model="form.publicType">
|
||||
<el-option :key="index" :label="item.label" :value="item.value" v-for="(item, index) in param_type"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('param.systemFlag')" prop="systemFlag">
|
||||
<el-radio-group v-model="form.systemFlag">
|
||||
<el-radio :label="item.value" border v-for="(item, index) in dict_type" :key="index">{{ 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 lang="ts" name="SysPublicParamDialog" setup>
|
||||
// 定义子组件向父组件传值/事件
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { addObj, getObj, putObj, validateParamsCode, validateParamsName } from '/@/api/admin/param';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { rule } from '/@/utils/validate';
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 定义字典
|
||||
const { dict_type, status_type, param_type } = useDict('dict_type', 'status_type', 'param_type');
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
publicId: '',
|
||||
publicName: '',
|
||||
publicKey: '',
|
||||
publicValue: '',
|
||||
status: '0',
|
||||
validateCode: '',
|
||||
publicType: '0',
|
||||
systemFlag: '0',
|
||||
});
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = reactive({
|
||||
publicName: [
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
{ required: true, message: '名称不能为空', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateParamsName(rule, value, callback, form.publicId !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
publicKey: [
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
{ required: true, message: '参数键不能为空', trigger: 'blur' },
|
||||
{ validator: rule.validatorCapital, trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateParamsCode(rule, value, callback, form.publicId !== '');
|
||||
},
|
||||
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' }],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true;
|
||||
form.publicId = '';
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
// 获取sysPublicParam信息
|
||||
if (id) {
|
||||
form.publicId = id;
|
||||
getsysPublicParamData(id);
|
||||
}
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) return false;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
form.publicId ? await putObj(form) : await addObj(form);
|
||||
useMessage().success(t(form.publicId ? 'common.editSuccessText' : 'common.addSuccessText'));
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化表单数据
|
||||
const getsysPublicParamData = (id: string) => {
|
||||
// 获取数据
|
||||
getObj(id).then((res: any) => {
|
||||
Object.assign(form, res.data);
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
34
src/views/admin/param/i18n/en.ts
Normal file
34
src/views/admin/param/i18n/en.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
export default {
|
||||
param: {
|
||||
index: '#',
|
||||
importsysPublicParamTip: 'import SysPublicParam',
|
||||
publicId: 'publicId',
|
||||
publicName: 'publicName',
|
||||
publicKey: 'publicKey',
|
||||
publicValue: 'publicValue',
|
||||
status: 'status',
|
||||
validateCode: 'validateCode',
|
||||
createBy: 'createBy',
|
||||
updateBy: 'updateBy',
|
||||
createTime: 'createTime',
|
||||
updateTime: 'updateTime',
|
||||
publicType: 'publicType',
|
||||
systemFlag: 'systemFlag',
|
||||
delFlag: 'delFlag',
|
||||
tenantId: 'tenantId',
|
||||
inputpublicIdTip: 'input publicId',
|
||||
inputpublicNameTip: 'input publicName',
|
||||
inputpublicKeyTip: 'input publicKey',
|
||||
inputpublicValueTip: 'input publicValue',
|
||||
inputstatusTip: 'input status',
|
||||
inputvalidateCodeTip: 'input validateCode',
|
||||
inputcreateByTip: 'input createBy',
|
||||
inputupdateByTip: 'input updateBy',
|
||||
inputcreateTimeTip: 'input createTime',
|
||||
inputupdateTimeTip: 'input updateTime',
|
||||
inputpublicTypeTip: 'input publicType',
|
||||
inputsystemFlagTip: 'input systemFlag',
|
||||
inputdelFlagTip: 'input delFlag',
|
||||
inputtenantIdTip: 'input tenantId',
|
||||
},
|
||||
};
|
||||
32
src/views/admin/param/i18n/zh-cn.ts
Normal file
32
src/views/admin/param/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export default {
|
||||
param: {
|
||||
index: '#',
|
||||
importsysPublicParamTip: '导入公共参数配置表',
|
||||
publicId: '编号',
|
||||
publicName: '名称',
|
||||
publicKey: '键',
|
||||
publicValue: '值',
|
||||
status: '状态',
|
||||
validateCode: '编码',
|
||||
createBy: '创建人',
|
||||
updateBy: '修改人',
|
||||
createTime: '创建时间',
|
||||
updateTime: '修改时间',
|
||||
publicType: '类型',
|
||||
systemFlag: '类型',
|
||||
tenantId: '租户ID',
|
||||
inputpublicIdTip: '请输入编号',
|
||||
inputpublicNameTip: '请输入名称',
|
||||
inputpublicKeyTip: '请输入键',
|
||||
inputpublicValueTip: '请输入值',
|
||||
inputstatusTip: '请输入状态',
|
||||
inputvalidateCodeTip: '请输入编码',
|
||||
inputcreateByTip: '请输入创建人',
|
||||
inputupdateByTip: '请输入修改人',
|
||||
inputcreateTimeTip: '请输入创建时间',
|
||||
inputupdateTimeTip: '请输入修改时间',
|
||||
inputpublicTypeTip: '请输入类型',
|
||||
inputsystemFlagTip: '请输入类型',
|
||||
inputtenantIdTip: '请输入租户ID',
|
||||
},
|
||||
};
|
||||
192
src/views/admin/param/index.vue
Normal file
192
src/views/admin/param/index.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row class="ml10" v-show="showSearch">
|
||||
<el-form :inline="true" :model="state.queryForm" ref="queryRef">
|
||||
<el-form-item :label="$t('param.publicName')" prop="publicName">
|
||||
<el-input :placeholder="$t('param.inputpublicNameTip')" style="max-width: 180px" v-model="state.queryForm.publicName" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('param.publicKey')" prop="publicKey">
|
||||
<el-input :placeholder="$t('param.inputpublicKeyTip')" style="max-width: 180px" v-model="state.queryForm.publicKey" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('param.systemFlag')" class="ml2" prop="systemFlag">
|
||||
<el-select :placeholder="t('param.inputsystemFlagTip')" v-model="state.queryForm.systemFlag">
|
||||
<el-option :key="index" :label="item.label" :value="item.value" v-for="(item, index) in dict_type"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList" formDialogRef icon="search" type="primary">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button @click="resetQuery" formDialogRef icon="Refresh">{{ $t('common.resetBtn') }} </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button v-auth="'sys_syspublicparam_add'" @click="formDialogRef.openDialog()" class="ml10" icon="folder-add" type="primary">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
|
||||
<el-button plain v-auth="'sys_syspublicparam_del'" @click="handleRefreshCache()" class="ml10" icon="refresh-left" type="primary">
|
||||
{{ $t('common.refreshCacheBtn') }}
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
plain
|
||||
v-auth="'sys_syspublicparam_del'"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete(selectObjs)"
|
||||
class="ml10"
|
||||
icon="Delete"
|
||||
type="primary"
|
||||
>
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
|
||||
<right-toolbar
|
||||
:export="'sys_syspublicparam_del'"
|
||||
@exportExcel="exportExcel"
|
||||
@queryTable="getDataList"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
v-model:showSearch="showSearch"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
row-key="publicId"
|
||||
v-loading="state.loading"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column :selectable="handleSelectable" align="center" type="selection" width="40" />
|
||||
<el-table-column :label="t('param.index')" type="index" width="60" />
|
||||
<el-table-column :label="t('param.publicName')" prop="publicName" show-overflow-tooltip />
|
||||
<el-table-column :label="t('param.publicKey')" prop="publicKey" show-overflow-tooltip />
|
||||
<el-table-column :label="t('param.publicValue')" prop="publicValue" show-overflow-tooltip />
|
||||
<el-table-column :label="t('param.status')" prop="status" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="status_type" :value="scope.row.status"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('param.createTime')" prop="createTime" show-overflow-tooltip />
|
||||
<el-table-column :label="t('param.systemFlag')" prop="systemFlag" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="dict_type" :value="scope.row.systemFlag"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('common.action')" width="150">
|
||||
<template #default="scope">
|
||||
<el-button icon="edit-pen" @click="formDialogRef.openDialog(scope.row.publicId)" text type="primary"
|
||||
>{{ $t('common.editBtn') }}
|
||||
</el-button>
|
||||
|
||||
<el-tooltip :content="$t('sysdict.deleteDisabledTip')" :disabled="scope.row.systemFlag === '0'" placement="top">
|
||||
<span style="margin-left: 12px">
|
||||
<el-button
|
||||
icon="delete"
|
||||
v-auth="'sys_syspublicparam_del'"
|
||||
:disabled="scope.row.systemFlag !== '0'"
|
||||
@click="handleDelete([scope.row.publicId])"
|
||||
text
|
||||
type="primary"
|
||||
>
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
|
||||
<!-- 编辑、新增 -->
|
||||
<form-dialog @refresh="getDataList()" ref="formDialogRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemSysPublicParam" setup>
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { delObj, fetchList, refreshCache } from '/@/api/admin/param';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const { t } = useI18n();
|
||||
// 定义查询字典
|
||||
|
||||
const { dict_type, status_type } = useDict('dict_type', 'status_type');
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref();
|
||||
// 搜索变量
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
systemFlag: '',
|
||||
},
|
||||
pageList: fetchList,
|
||||
descs: ['create_time'],
|
||||
});
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 是否可以多选
|
||||
const handleSelectable = (row: any) => {
|
||||
// 系统类不可多选删除
|
||||
return row.systemFlag !== '1';
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/param/export', Object.assign(state.queryForm,{ids:selectObjs}), 'param.xlsx');
|
||||
};
|
||||
|
||||
const handleRefreshCache = () => {
|
||||
refreshCache().then(() => {
|
||||
useMessage().success('同步成功');
|
||||
});
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { publicId: string }[]) => {
|
||||
selectObjs.value = objs.map(({ publicId }) => publicId);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
131
src/views/admin/sensitive/form.vue
Normal file
131
src/views/admin/sensitive/form.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<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-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>
|
||||
</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';
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
// 定义字典
|
||||
const {sensitive_type} = useDict('sensitive_type')
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
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'}],
|
||||
})
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (sensitiveId: string) => {
|
||||
visible.value = true
|
||||
form.sensitiveId = ''
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
// 获取sysSensitiveWord信息
|
||||
if (sensitiveId) {
|
||||
form.sensitiveId = sensitiveId
|
||||
getsysSensitiveWordData(sensitiveId)
|
||||
}
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
const valid = await dataFormRef.value.validate().catch(() => {
|
||||
});
|
||||
if (!valid) return false;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
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
|
||||
})
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog
|
||||
});
|
||||
</script>
|
||||
165
src/views/admin/sensitive/index.vue
Normal file
165
src/views/admin/sensitive/index.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<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>
|
||||
|
||||
<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)"/>
|
||||
|
||||
<!-- 匹配测试 -->
|
||||
<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';
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const MatchDialog = defineAsyncComponent(() => import('./match.vue'));
|
||||
// 定义查询字典
|
||||
|
||||
const {sensitive_type} = useDict('sensitive_type')
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref()
|
||||
const matchDialogRef = ref()
|
||||
// 搜索变量
|
||||
const queryRef = ref()
|
||||
const showSearch = ref(true)
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any
|
||||
const multiple = ref(true)
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
pageList: fetchList
|
||||
})
|
||||
|
||||
// table hook
|
||||
const {
|
||||
getDataList,
|
||||
currentChangeHandle,
|
||||
sizeChangeHandle,
|
||||
sortChangeHandle,
|
||||
downBlobFile,
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
// 清空搜索条件
|
||||
queryRef.value?.resetFields()
|
||||
// 清空多选
|
||||
selectObjs.value = []
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
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;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
try {
|
||||
await useMessageBox().confirm('此操作将永久删除');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
127
src/views/admin/sensitive/match.vue
Normal file
127
src/views/admin/sensitive/match.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<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>
|
||||
</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';
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const matchResult = ref('')
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
sensitiveId: '',
|
||||
sensitiveWord: '',
|
||||
});
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
sensitiveWord: [{validator: rule.overLength, trigger: 'blur'}, {
|
||||
required: true,
|
||||
message: '敏感词不能为空',
|
||||
trigger: 'blur'
|
||||
}]
|
||||
})
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true
|
||||
form.sensitiveId = ''
|
||||
|
||||
// 重置表单数据
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
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, `
|
||||
<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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog
|
||||
});
|
||||
</script>
|
||||
150
src/views/admin/social/form.vue
Normal file
150
src/views/admin/social/form.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<el-dialog :close-on-click-modal="false" :title="form.id ? $t('common.editBtn') : $t('common.addBtn')" draggable v-model="visible">
|
||||
<el-form :model="form" :rules="dataRules" formDialogRef label-width="90px" ref="dataFormRef" v-loading="loading">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('social.type')" prop="type">
|
||||
<el-select :placeholder="t('social.inputTypeTip')" v-model="form.type">
|
||||
<el-option :key="index" :label="item.label" :value="item.value" v-for="(item, index) in social_type">
|
||||
{{ item.label }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('social.remark')" prop="remark">
|
||||
<el-input :placeholder="t('social.inputRemarkTip')" v-model="form.remark" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('social.appId')" prop="appId">
|
||||
<el-input :placeholder="t('social.inputAppIdTip')" v-model="form.appId" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('social.appSecret')" prop="appSecret">
|
||||
<el-input :placeholder="t('social.inputAppSecretTip')" v-model="form.appSecret" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item :label="t('social.redirectUrl')" prop="redirectUrl">
|
||||
<el-input :placeholder="t('social.inputRedirectUrlTip')" type="textarea" v-model="form.redirectUrl" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item :label="t('social.ext')" prop="ext">
|
||||
<el-input :placeholder="t('social.inputExtTip')" type="textarea" v-model="form.ext" />
|
||||
</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 lang="ts" name="AppSocialDetailsDialog" setup>
|
||||
// 定义子组件向父组件传值/事件
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { addObj, getObj, putObj } from '/@/api/admin/social';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { rule } from '/@/utils/validate';
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 定义字典
|
||||
const { social_type } = useDict('social_type');
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
type: '',
|
||||
remark: '',
|
||||
appId: '' as string | undefined,
|
||||
appSecret: '' as string | undefined,
|
||||
redirectUrl: '',
|
||||
ext: '',
|
||||
});
|
||||
|
||||
// 定义校验规则
|
||||
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' }],
|
||||
redirectUrl: [
|
||||
{ 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' }],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true;
|
||||
form.id = '';
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
// 获取appSocialDetails信息
|
||||
if (id) {
|
||||
form.id = id;
|
||||
getappSocialDetailsData(id);
|
||||
}
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) return false;
|
||||
|
||||
// 隐藏敏感信息
|
||||
form.appSecret = form.appSecret?.includes('******') ? undefined : form.appSecret;
|
||||
form.appId = form.appId?.includes('******') ? undefined : form.appId;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
if (form.id) {
|
||||
await putObj(form);
|
||||
useMessage().success(t('common.editSuccessText'));
|
||||
} else {
|
||||
await addObj(form);
|
||||
useMessage().success(t('common.addSuccessText'));
|
||||
}
|
||||
visible.value = false; // 关闭弹窗
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化表单数据
|
||||
const getappSocialDetailsData = (id: string) => {
|
||||
// 获取数据
|
||||
getObj(id).then((res: any) => {
|
||||
Object.assign(form, res.data);
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
32
src/views/admin/social/i18n/en.ts
Normal file
32
src/views/admin/social/i18n/en.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export default {
|
||||
social: {
|
||||
index: '#',
|
||||
importappSocialDetailsTip: 'import AppSocialDetails',
|
||||
id: 'id',
|
||||
type: 'type',
|
||||
remark: 'remark',
|
||||
appId: 'appId',
|
||||
appSecret: 'appSecret',
|
||||
redirectUrl: 'redirectUrl',
|
||||
ext: 'ext',
|
||||
createBy: 'createBy',
|
||||
updateBy: 'updateBy',
|
||||
createTime: 'createTime',
|
||||
updateTime: 'updateTime',
|
||||
delFlag: 'delFlag',
|
||||
tenantId: 'tenantId',
|
||||
inputIdTip: 'input id',
|
||||
inputTypeTip: 'input type',
|
||||
inputRemarkTip: 'input remark',
|
||||
inputAppIdTip: 'input appId',
|
||||
inputAppSecretTip: 'input appSecret',
|
||||
inputRedirectUrlTip: 'input redirectUrl',
|
||||
inputExtTip: 'input ext',
|
||||
inputCreateByTip: 'input createBy',
|
||||
inputUpdateByTip: 'input updateBy',
|
||||
inputCreateTimeTip: 'input createTime',
|
||||
inputUpdateTimeTip: 'input updateTime',
|
||||
inputDelFlagTip: 'input delFlag',
|
||||
inputTenantIdTip: 'input tenantId',
|
||||
},
|
||||
};
|
||||
32
src/views/admin/social/i18n/zh-cn.ts
Normal file
32
src/views/admin/social/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export default {
|
||||
social: {
|
||||
index: '#',
|
||||
importappSocialDetailsTip: '导入系统社交登录账号表',
|
||||
id: '主鍵',
|
||||
type: '类型',
|
||||
remark: '描述',
|
||||
appId: 'appId',
|
||||
appSecret: 'appSecret',
|
||||
redirectUrl: '回调地址',
|
||||
ext: '拓展字段',
|
||||
createBy: '创建人',
|
||||
updateBy: '修改人',
|
||||
createTime: '创建时间',
|
||||
updateTime: '更新时间',
|
||||
delFlag: '${field.fieldComment}',
|
||||
tenantId: '所属租户',
|
||||
inputIdTip: '请输入主鍵',
|
||||
inputTypeTip: '请输入类型',
|
||||
inputRemarkTip: '请输入描述',
|
||||
inputAppIdTip: '请输入appId',
|
||||
inputAppSecretTip: '请输入appSecret',
|
||||
inputRedirectUrlTip: '请输入回调地址',
|
||||
inputExtTip: '请输入拓展字段',
|
||||
inputCreateByTip: '请输入创建人',
|
||||
inputUpdateByTip: '请输入修改人',
|
||||
inputCreateTimeTip: '请输入创建时间',
|
||||
inputUpdateTimeTip: '请输入更新时间',
|
||||
inputDelFlagTip: '请输入${field.fieldComment}',
|
||||
inputTenantIdTip: '请输入所属租户',
|
||||
},
|
||||
};
|
||||
148
src/views/admin/social/index.vue
Normal file
148
src/views/admin/social/index.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row v-show="showSearch">
|
||||
<el-form ref="queryRef" :inline="true" :model="state.queryForm">
|
||||
<el-form-item :label="t('social.type')" class="ml2" prop="type">
|
||||
<el-select v-model="state.queryForm.type" :placeholder="t('social.inputTypeTip')">
|
||||
<el-option v-for="(item, index) in social_type" :key="index" :label="item.label" :value="item.value"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button formDialogRef icon="search" type="primary" @click="getDataList">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button formDialogRef 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 v-auth="'sys_social_details_add'" class="ml10" icon="folder-add" type="primary" @click="formDialogRef.openDialog()">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-auth="'sys_social_details_del'"
|
||||
:disabled="multiple"
|
||||
class="ml10"
|
||||
icon="Delete"
|
||||
type="primary"
|
||||
@click="handleDelete(selectObjs)"
|
||||
>
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
:export="'sys_social_details_del'"
|
||||
@exportExcel="exportExcel"
|
||||
v-model:showSearch="showSearch"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
v-loading="state.loading"
|
||||
:data="state.dataList"
|
||||
style="width: 100%"
|
||||
row-key="id"
|
||||
@selection-change="handleSelectionChange"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column align="center" type="selection" width="40" />
|
||||
<el-table-column :label="t('social.index')" type="index" width="60" />
|
||||
<el-table-column :label="t('social.type')" prop="type" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="social_type" :value="scope.row.type"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('social.remark')" prop="remark" show-overflow-tooltip />
|
||||
<el-table-column :label="t('social.appId')" prop="appId" show-overflow-tooltip />
|
||||
<el-table-column :label="t('social.appSecret')" prop="appSecret" show-overflow-tooltip />
|
||||
<el-table-column :label="t('social.createTime')" prop="createTime" show-overflow-tooltip />
|
||||
<el-table-column :label="$t('common.action')" width="150">
|
||||
<template #default="scope">
|
||||
<el-button icon="edit-pen" v-auth="'sys_social_details_edit'" text type="primary" @click="formDialogRef.openDialog(scope.row.id)"
|
||||
>{{ $t('common.editBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="delete" v-auth="'sys_social_details_del'" text type="primary" @click="handleDelete([scope.row.id])"
|
||||
>{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination v-bind="state.pagination" @size-change="sizeChangeHandle" @current-change="currentChangeHandle" />
|
||||
</div>
|
||||
|
||||
<!-- 编辑、新增 -->
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList()" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemAppSocialDetails" setup>
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { delObj, fetchList } from '/@/api/admin/social';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const { t } = useI18n();
|
||||
// 定义查询字典
|
||||
|
||||
const { social_type } = useDict('social_type');
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref();
|
||||
// 搜索变量
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
pageList: fetchList,
|
||||
descs: ['create_time'],
|
||||
});
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/social/export', Object.assign(state.queryForm,{ids:selectObjs}), 'social.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { id: string }[]) => {
|
||||
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 delObj(ids);
|
||||
getDataList();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
194
src/views/admin/sysArea/form.vue
Normal file
194
src/views/admin/sysArea/form.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<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-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.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.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>
|
||||
</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";
|
||||
|
||||
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 {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: '街道'}
|
||||
]
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
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',
|
||||
},
|
||||
]
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true
|
||||
form.id = ''
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
// 获取sysArea信息
|
||||
if (id) {
|
||||
form.id = id
|
||||
getsysAreaData(id)
|
||||
}
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
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 ? 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
|
||||
})
|
||||
};
|
||||
|
||||
// 地区选择
|
||||
const handleChange = (data: string) => {
|
||||
let dataArray = data.split(",");
|
||||
form.pid = dataArray[dataArray.length - 1];
|
||||
}
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog
|
||||
});
|
||||
</script>
|
||||
17
src/views/admin/sysArea/i18n/en.ts
Normal file
17
src/views/admin/sysArea/i18n/en.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export default {
|
||||
area: {
|
||||
index: '#',
|
||||
id: 'id',
|
||||
pid: 'pid',
|
||||
name: 'name',
|
||||
adcode:'adcode',
|
||||
areaType:'areaType',
|
||||
areaSort:'areaSort',
|
||||
hot:'hot',
|
||||
areaStatus:'areaStatus',
|
||||
inputAdCodeByTip: 'input adcode',
|
||||
inputPidByTip: 'input pid',
|
||||
inputNameByTip: 'input name',
|
||||
inputAreaSortByTip: 'input sort',
|
||||
},
|
||||
};
|
||||
17
src/views/admin/sysArea/i18n/zh-cn.ts
Normal file
17
src/views/admin/sysArea/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export default {
|
||||
area: {
|
||||
index: '#',
|
||||
id: '主键',
|
||||
pid: '父级地区',
|
||||
name: '名称',
|
||||
adcode:'编码',
|
||||
areaType:'类型',
|
||||
areaSort:'排序值',
|
||||
hot:'热门',
|
||||
areaStatus:'有效',
|
||||
inputAdCodeByTip: '请选择编码',
|
||||
inputPidByTip: '请选择父级地区',
|
||||
inputNameByTip: '请输入地区名称',
|
||||
inputAreaSortByTip: '请输入排序值',
|
||||
},
|
||||
};
|
||||
174
src/views/admin/sysArea/index.vue
Normal file
174
src/views/admin/sysArea/index.vue
Normal file
@@ -0,0 +1,174 @@
|
||||
<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>
|
||||
|
||||
<!-- 编辑、新增 -->
|
||||
<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";
|
||||
|
||||
const {t} = useI18n();
|
||||
|
||||
// 省市区查询组件
|
||||
const ChinaArea = defineAsyncComponent(() => import("/@/components/ChinaArea/index.vue"));
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
// 定义查询字典
|
||||
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: '街道'}
|
||||
]
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref()
|
||||
// 搜索变量
|
||||
const queryRef = ref()
|
||||
const showSearch = ref(true)
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any
|
||||
const multiple = ref(true)
|
||||
const pid = ref()
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
adcode: '',
|
||||
name: ''
|
||||
},
|
||||
ascs: ['adcode'],
|
||||
pageList: fetchList
|
||||
})
|
||||
|
||||
// table hook
|
||||
const {
|
||||
getDataList,
|
||||
currentChangeHandle,
|
||||
sizeChangeHandle,
|
||||
sortChangeHandle,
|
||||
downBlobFile,
|
||||
tableStyle
|
||||
} = useTable(state)
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
// 清空搜索条件
|
||||
queryRef.value?.resetFields()
|
||||
pid.value = ''
|
||||
// 清空多选
|
||||
selectObjs.value = []
|
||||
getDataList()
|
||||
}
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
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;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
// 地区查询
|
||||
const handleChange = (data: string) => {
|
||||
let dataArray = data.split(",");
|
||||
state.queryForm.adcode = dataArray[dataArray.length - 1];
|
||||
}
|
||||
</script>
|
||||
118
src/views/admin/system/dept/form.vue
Normal file
118
src/views/admin/system/dept/form.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<el-dialog :title="dataForm.deptId ? $t('common.editBtn') : $t('common.addBtn')" v-model="visible" width="600">
|
||||
<el-form ref="deptDialogFormRef" :model="dataForm" label-width="90px" :rules="dataRules" v-loading="loading">
|
||||
<el-form-item :label="$t('sysdept.parentId')" prop="parentId">
|
||||
<el-tree-select
|
||||
v-model="dataForm.parentId"
|
||||
:data="parentData"
|
||||
:props="{ value: 'id', label: 'name', children: 'children' }"
|
||||
class="w100"
|
||||
clearable
|
||||
check-strictly
|
||||
:render-after-expand="false"
|
||||
:placeholder="$t('sysdept.inputparentIdTip')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysdept.name')" prop="name">
|
||||
<el-input v-model="dataForm.name" :placeholder="$t('sysdept.inputnameTip')" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysdept.sortOrder')" prop="sortOrder">
|
||||
<el-input-number v-model="dataForm.sortOrder" :placeholder="$t('sysdept.inputsortOrderTip')" clearable />
|
||||
</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>
|
||||
|
||||
<script setup lang="ts" name="systemDeptDialog">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { getObj, deptTree, addObj, putObj } from '/@/api/admin/dept';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import {rule} from "/@/utils/validate";
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['refresh']);
|
||||
const { t } = useI18n();
|
||||
// 定义变量内容
|
||||
const deptDialogFormRef = ref();
|
||||
const dataForm = reactive({
|
||||
parentId: '',
|
||||
deptId: '',
|
||||
name: '',
|
||||
sortOrder: 9999,
|
||||
});
|
||||
const parentData = ref<any[]>([]);
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
const dataRules = ref({
|
||||
parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }],
|
||||
name: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
|
||||
sortOrder: [{ required: true, message: '排序不能为空', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (type: string, id: string) => {
|
||||
visible.value = true;
|
||||
dataForm.deptId = '';
|
||||
|
||||
nextTick(() => {
|
||||
deptDialogFormRef.value?.resetFields();
|
||||
dataForm.parentId = id;
|
||||
});
|
||||
|
||||
if (type === 'edit') {
|
||||
getObj(id)
|
||||
.then((res) => {
|
||||
Object.assign(dataForm, res.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
useMessage().error(err.msg);
|
||||
});
|
||||
}
|
||||
|
||||
getDeptData();
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
const valid = await deptDialogFormRef.value.validate().catch(() => {});
|
||||
if (!valid) return false;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
dataForm.deptId ? await putObj(dataForm) : await addObj(dataForm);
|
||||
useMessage().success(t(dataForm.deptId ? 'common.editSuccessText' : 'common.addSuccessText'));
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 从后端获取菜单信息
|
||||
const getDeptData = async () => {
|
||||
deptTree().then((res) => {
|
||||
parentData.value = [];
|
||||
const dept = {
|
||||
id: '0',
|
||||
name: '根部门',
|
||||
children: [] as any[],
|
||||
};
|
||||
dept.children = res.data;
|
||||
parentData.value.push(dept);
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
21
src/views/admin/system/dept/i18n/en.ts
Normal file
21
src/views/admin/system/dept/i18n/en.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export default {
|
||||
sysdept: {
|
||||
name: 'dept name',
|
||||
parentId: 'parent dept',
|
||||
createTime: 'createTime',
|
||||
weight: 'weight',
|
||||
leaderId: 'dept leader',
|
||||
sortOrder: 'sortOrder',
|
||||
inputdeptNameTip: 'input deptName',
|
||||
inputnameTip: 'input deptName',
|
||||
inputparentIdTip: 'select deptName',
|
||||
inputLeaderIdTip: 'input leader',
|
||||
inputsortOrderTip: 'input sortOrder',
|
||||
importTip: 'import 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',
|
||||
},
|
||||
};
|
||||
21
src/views/admin/system/dept/i18n/zh-cn.ts
Normal file
21
src/views/admin/system/dept/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export default {
|
||||
sysdept: {
|
||||
name: '部门名称',
|
||||
parentId: '上级部门',
|
||||
createTime: '创建时间',
|
||||
weight: '排序',
|
||||
sortOrder: '排序',
|
||||
leaderId: '部门负责人',
|
||||
inputdeptNameTip: '请输入部门名称',
|
||||
inputnameTip: '请输入部门名称',
|
||||
inputLeaderIdTip: '请输入部门领导',
|
||||
inputparentIdTip: '请选择上级部门',
|
||||
inputsortOrderTip: '请输入排序',
|
||||
importTip: '导入部门',
|
||||
addNodeText: '添加部门',
|
||||
editNodeText: '编辑部门',
|
||||
delNodeText: '删除部门',
|
||||
tenantNodeErrorText: '当前节点不可操作,请在租户管理功能中维护',
|
||||
view: '树/表视图'
|
||||
},
|
||||
};
|
||||
130
src/views/admin/system/dept/index.vue
Normal file
130
src/views/admin/system/dept/index.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row shadow="hover" v-show="showSearch" class="ml10">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="filter">
|
||||
<el-form-item prop="deptName" :label="$t('sysdept.name')">
|
||||
<el-input :placeholder="$t('sysdept.inputdeptNameTip')" style="max-width: 180px" v-model="state.queryForm.deptName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="search" type="primary" @click="filter">
|
||||
{{ $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="mb8" style="width: 100%">
|
||||
<el-button icon="folder-add" type="primary" class="top-right-btn" v-if="!defaultTreeViewRef" v-auth="'sys_dept_add'" @click="handleAdd">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<el-button plain icon="upload-filled" type="primary" class="ml10" @click="excelUploadRef.show()">
|
||||
{{ $t('common.importBtn') }}
|
||||
</el-button>
|
||||
<el-button @click="handleExpand"> {{ $t('common.expandBtn') }}</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
:export="'sys_dept_add'"
|
||||
@exportExcel="exportExcel"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
@queryTable="getDataList"
|
||||
>
|
||||
<el-tooltip class="item" effect="dark" :content="$t('queryTree.view')" placement="top">
|
||||
<el-button circle icon="Grid" @click="handleView"></el-button>
|
||||
</el-tooltip>
|
||||
</right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
<tree-view ref="treeViewRef" v-if="defaultTreeViewRef" />
|
||||
<table-view ref="tableViewRef" v-if="!defaultTreeViewRef" />
|
||||
|
||||
<upload-excel
|
||||
ref="excelUploadRef"
|
||||
:title="$t('sysdept.importTip')"
|
||||
url="/admin/dept/import"
|
||||
temp-url="/admin/sys-file/local/file/dept.xlsx"
|
||||
@refreshDataList="getDataList"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemDept" setup>
|
||||
import { downBlobFile } from '/@/utils/other';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const TreeView = defineAsyncComponent(() => import('./tree-view.vue'));
|
||||
const TableView = defineAsyncComponent(() => import('./table-view.vue'));
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 默认树视图展示
|
||||
const defaultTreeViewRef = ref(true);
|
||||
const treeViewRef = ref();
|
||||
const tableViewRef = ref();
|
||||
const excelUploadRef = ref();
|
||||
const showSearch = ref(true);
|
||||
const isExpand = ref(false);
|
||||
const queryRef = ref();
|
||||
|
||||
const state = reactive({
|
||||
queryForm: {
|
||||
deptName: '',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 过滤节点
|
||||
*/
|
||||
const filter = () => {
|
||||
if (defaultTreeViewRef.value) {
|
||||
treeViewRef.value.filter(state.queryForm.deptName);
|
||||
} else {
|
||||
tableViewRef.value.state.queryForm.deptName = state.queryForm.deptName;
|
||||
tableViewRef.value.getDataList();
|
||||
}
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
tableViewRef.value.handleAdd();
|
||||
};
|
||||
const handleView = () => {
|
||||
defaultTreeViewRef.value = !defaultTreeViewRef.value;
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理展开/折叠树
|
||||
*/
|
||||
const handleExpand = () => {
|
||||
if (defaultTreeViewRef.value) {
|
||||
treeViewRef.value.handleExpand();
|
||||
} else {
|
||||
tableViewRef.value.handleExpand();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 导出Excel
|
||||
*/
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/dept/export', state.queryForm, 'dept.xlsx');
|
||||
};
|
||||
|
||||
const getDataList = () => {
|
||||
if (defaultTreeViewRef.value) {
|
||||
treeViewRef.value.getOrgData();
|
||||
} else {
|
||||
tableViewRef.value.getDataList();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置查询条件
|
||||
*/
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
};
|
||||
</script>
|
||||
137
src/views/admin/system/dept/table-view.vue
Normal file
137
src/views/admin/system/dept/table-view.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<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()"/>
|
||||
</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';
|
||||
|
||||
// 引入组件
|
||||
const DeptForm = defineAsyncComponent(() => import('./form.vue'));
|
||||
const {t} = useI18n();
|
||||
// 定义变量内容
|
||||
const tableRef = ref(); // 表格引用
|
||||
const deptDialogRef = ref(); // 部门对话框引用
|
||||
const excelUploadRef = ref(); // Excel上传引用
|
||||
const showSearch = ref(true); // 是否显示搜索栏
|
||||
const isExpand = ref(false); // 是否展开
|
||||
|
||||
/**
|
||||
* 查询部门树方法,返回 Promise 对象
|
||||
* @param params - 查询参数
|
||||
* @returns Promise<any>
|
||||
*/
|
||||
const queryDeptTree = (params?: any) => {
|
||||
return deptTree(params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 定义响应式表格数据
|
||||
*/
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
pageList: queryDeptTree, // 页面列表数据
|
||||
queryForm: {
|
||||
deptName: '', // 部门名称
|
||||
},
|
||||
isPage: false, // 是否分页
|
||||
descs: ['create_time'], // 排序字段
|
||||
});
|
||||
|
||||
/**
|
||||
* 使用 useTable 定义表格相关操作
|
||||
*/
|
||||
const {getDataList, tableStyle} = useTable(state);
|
||||
|
||||
/**
|
||||
* 展开/折叠部门树方法
|
||||
*/
|
||||
const handleExpand = async () => {
|
||||
isExpand.value = !isExpand.value;
|
||||
const dataList = await deptTree();
|
||||
toggleExpand(dataList.data, isExpand.value);
|
||||
};
|
||||
|
||||
/**
|
||||
* 递归方法,用于展开/折叠部门树
|
||||
* @param children - 子节点
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除当前行
|
||||
* @param row - 当前行数据
|
||||
*/
|
||||
const handleDelete = async (row: any) => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAdd = ()=>{
|
||||
deptDialogRef.value.openDialog('add')
|
||||
}
|
||||
|
||||
/**
|
||||
* 暴露组件中的一些方法和变量
|
||||
*/
|
||||
defineExpose({
|
||||
handleAdd, // 新增时间
|
||||
state, // 响应式表格数据
|
||||
getDataList, // 获取列表数据方法
|
||||
handleExpand // 展开/折叠部门树方法
|
||||
});
|
||||
</script>
|
||||
198
src/views/admin/system/dept/tree-view.vue
Normal file
198
src/views/admin/system/dept/tree-view.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<div style="height: 100%">
|
||||
<vue3-tree-org
|
||||
ref="treeOrgRef"
|
||||
:props="props"
|
||||
:data="data"
|
||||
:label-style="style"
|
||||
:define-menus="defineMenus"
|
||||
:default-expand-level="expandLevel"
|
||||
center
|
||||
:horizontal="horizontal"
|
||||
:collapsable="collapsable"
|
||||
:only-one-node="onlyOneNode"
|
||||
:filter-node-method="filterNodeMethod"
|
||||
:clone-node-drag="cloneNodeDrag"
|
||||
:node-add="addNode"
|
||||
:node-delete="delNode"
|
||||
:node-edit="editNode"
|
||||
@on-node-click="onNodeClick"
|
||||
/>
|
||||
</div>
|
||||
<dept-form ref="deptDialogRef" @refresh="getOrgData()" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="treeView" setup>
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { delObj, deptTree } from '/@/api/admin/dept';
|
||||
import { getObj } from '/@/api/admin/tenant';
|
||||
import { Session } from '/@/utils/storage';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '/@/stores/themeConfig';
|
||||
|
||||
const DeptForm = defineAsyncComponent(() => import('./form.vue'));
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义org组件key-value定义
|
||||
const props = reactive({ id: 'id', pid: 'parentId', label: 'name', expand: 'expand', children: 'children' });
|
||||
const data = reactive({});
|
||||
|
||||
// 定义org组件右键定义
|
||||
const defineMenus = reactive([
|
||||
{ name: t('sysdept.addNodeText'), command: 'add' },
|
||||
{ name: t('sysdept.editNodeText'), command: 'edit' },
|
||||
{ name: t('sysdept.delNodeText'), command: 'delete' },
|
||||
]);
|
||||
|
||||
const cloneNodeDrag = ref(true);
|
||||
const collapsable = ref(false);
|
||||
const expandLevel = ref(2); //默认展开层级
|
||||
const horizontal = ref(false);
|
||||
const onlyOneNode = ref(false);
|
||||
const treeOrgRef = ref();
|
||||
const deptDialogRef = ref();
|
||||
|
||||
// 添加主题配置
|
||||
const themeConfig = useThemeConfig();
|
||||
const { themeConfig: theme } = storeToRefs(themeConfig);
|
||||
|
||||
// 添加 style 定义
|
||||
const style = computed(() => ({
|
||||
background: theme.value.isDark ? 'var(--el-bg-color-overlay)' : 'var(--el-bg-color-page)',
|
||||
color: theme.value.isDark ? 'var(--el-text-color-primary)' : '#5e6d82',
|
||||
}));
|
||||
|
||||
/**
|
||||
* 过滤节点
|
||||
*/
|
||||
const filter = (deptName: string) => {
|
||||
treeOrgRef.value.filter(deptName);
|
||||
};
|
||||
|
||||
/**
|
||||
* 节点过滤方法
|
||||
* @param {string} value 过滤条件
|
||||
* @param {object} data 节点数据
|
||||
* @returns {boolean} 返回过滤结果
|
||||
*/
|
||||
const filterNodeMethod = (value, data) => {
|
||||
if (!value) return true;
|
||||
return data.label.indexOf(value) !== -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理展开/折叠树
|
||||
*/
|
||||
const handleExpand = async () => {
|
||||
collapsable.value = !collapsable.value;
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查节点是否是根租户节点
|
||||
* @param {object} node 节点对象
|
||||
* @returns {boolean} 如果节点是根租户节点,返回true;否则返回false
|
||||
*/
|
||||
const checkNode = (node) => {
|
||||
if (node?.id === '0') {
|
||||
useMessage().error(t('sysdept.tenantNodeErrorText'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 当用户左键点击节点,模拟触发组件的右键事件
|
||||
* @param e
|
||||
*/
|
||||
const onNodeClick = (e: any) => {
|
||||
const { clientX, clientY } = e;
|
||||
const rightFun = new MouseEvent('contextmenu', {
|
||||
bubbles: true,
|
||||
cancelable: false,
|
||||
view: window,
|
||||
button: 2,
|
||||
buttons: 0,
|
||||
clientX,
|
||||
clientY,
|
||||
});
|
||||
e.target.dispatchEvent(rightFun);
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加部门
|
||||
* @param {object} node 节点对象
|
||||
*/
|
||||
const addNode = (node) => {
|
||||
deptDialogRef.value.openDialog('add', node?.id);
|
||||
};
|
||||
|
||||
/**
|
||||
* 编辑部门
|
||||
* @param {object} node 节点对象
|
||||
*/
|
||||
const editNode = (node) => {
|
||||
if (!checkNode(node)) {
|
||||
return;
|
||||
}
|
||||
deptDialogRef.value.openDialog('edit', node?.id);
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除部门
|
||||
* @param {object} node 节点对象
|
||||
*/
|
||||
const delNode = async (node) => {
|
||||
if (!checkNode(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await useMessageBox().confirm(t('common.delConfirmText'));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObj(node.id);
|
||||
await getOrgData();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询部门数据
|
||||
*/
|
||||
const getOrgData = async () => {
|
||||
// 查询当前租户信息
|
||||
const tenant = await getObj(Session.getTenant());
|
||||
deptTree().then((res) => {
|
||||
Object.assign(data, { id: '0', name: tenant.data.name });
|
||||
data.children = res.data;
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getOrgData();
|
||||
});
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
getOrgData,
|
||||
handleExpand,
|
||||
filter,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.zm-tree-org) {
|
||||
height: 100%;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
background: var(--el-bg-color);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
260
src/views/admin/system/menu/form.vue
Normal file
260
src/views/admin/system/menu/form.vue
Normal file
@@ -0,0 +1,260 @@
|
||||
<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="30" :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>
|
||||
<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="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";
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['refresh']);
|
||||
const {t} = useI18n();
|
||||
// 引入组件
|
||||
const IconSelector = defineAsyncComponent(() => import('/@/components/IconSelector/index.vue'));
|
||||
|
||||
// 定义变量内容
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const menuDialogFormRef = ref();
|
||||
// 定义需要的数据
|
||||
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[], // 上级菜单数据
|
||||
});
|
||||
|
||||
// 表单校验规则
|
||||
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) => {
|
||||
validateExist(rule, value, callback, state.ruleForm.menuId !== '' || state.ruleForm.menuType === '1');
|
||||
},
|
||||
trigger: 'blur',
|
||||
}],
|
||||
path: [{validator: rule.overLength, trigger: 'blur'}, {required: true, message: '路径不能为空', trigger: 'blur'}],
|
||||
icon: [{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;
|
||||
|
||||
nextTick(() => {
|
||||
menuDialogFormRef.value?.resetFields();
|
||||
state.ruleForm.parentId = row?.id || '-1';
|
||||
});
|
||||
|
||||
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'
|
||||
}
|
||||
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);
|
||||
});
|
||||
};
|
||||
|
||||
// 保存数据
|
||||
const onSubmit = async () => {
|
||||
const valid = await menuDialogFormRef.value.validate().catch(() => {
|
||||
});
|
||||
if (!valid) return false;
|
||||
|
||||
try {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
// 暴露变量 只有暴漏出来的变量 父组件才能使用
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
37
src/views/admin/system/menu/i18n/en.ts
Normal file
37
src/views/admin/system/menu/i18n/en.ts
Normal file
@@ -0,0 +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',
|
||||
},
|
||||
};
|
||||
31
src/views/admin/system/menu/i18n/zh-cn.ts
Normal file
31
src/views/admin/system/menu/i18n/zh-cn.ts
Normal file
@@ -0,0 +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: '菜单包含下级不能删除',
|
||||
},
|
||||
};
|
||||
192
src/views/admin/system/menu/index.vue
Normal file
192
src/views/admin/system/menu/index.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row shadow="hover" v-show="showSearch" class="ml10">
|
||||
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
|
||||
<el-form-item :label="$t('sysmenu.name')" prop="menuName">
|
||||
<el-input :placeholder="$t('sysmenu.inputNameTip')" clearable style="max-width: 180px" v-model="state.queryForm.menuName" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="query" class="ml10" 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="mb8" style="width: 100%">
|
||||
<el-button @click="onOpenAddMenu" class="ml10" icon="folder-add" type="primary" v-auth="'sys_menu_add'">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
@queryTable="getDataList"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="tableList"
|
||||
lazy
|
||||
:load="load"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
row-key="path"
|
||||
style="width: 100%"
|
||||
v-loading="state.loading"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle?.headerCellStyle"
|
||||
>
|
||||
<el-table-column :label="$t('sysmenu.name')" fixed prop="name" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('sysmenu.sortOrder')" prop="sortOrder" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('sysmenu.icon')" prop="icon" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<SvgIcon :name="scope.row.meta.icon" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('sysmenu.path')" prop="path" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('sysmenu.menuType')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.menuType === '0'">左菜单</el-tag>
|
||||
<el-tag v-if="scope.row.menuType === '2'">顶菜单</el-tag>
|
||||
<el-tag type="success" v-if="scope.row.menuType === '1'">按钮</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('sysmenu.keepAlive')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.meta.isKeepAlive">开启</el-tag>
|
||||
<el-tag type="info" v-else>关闭</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('sysmenu.permission')" :show-overflow-tooltip="true" prop="permission"></el-table-column>
|
||||
<el-table-column :label="$t('common.action')" show-overflow-tooltip width="250">
|
||||
<template #default="scope">
|
||||
<el-button icon="folder-add" @click="onOpenAddMenu('add', scope.row)" text type="primary" v-auth="'sys_menu_add'">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="edit-pen" @click="onOpenEditMenu('edit', scope.row)" text type="primary" v-auth="'sys_menu_edit'"
|
||||
>{{ $t('common.editBtn') }}
|
||||
</el-button>
|
||||
|
||||
<el-tooltip icon="delete" :content="$t('sysmenu.deleteDisabledTip')" :disabled="!deleteMenuDisabled(scope.row)" placement="top">
|
||||
<span style="margin-left: 12px">
|
||||
<el-button
|
||||
icon="delete"
|
||||
:disabled="deleteMenuDisabled(scope.row)"
|
||||
@click="handleDelete(scope.row)"
|
||||
text
|
||||
type="primary"
|
||||
v-auth="'sys_menu_del'"
|
||||
>
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<MenuDialog @refresh="query()" ref="menuDialogRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemMenu" setup>
|
||||
import { delObj, pageList } from '/@/api/admin/menu';
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
// 引入组件
|
||||
const MenuDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const { t } = useI18n();
|
||||
// 定义变量内容
|
||||
const tableRef = ref();
|
||||
const menuDialogRef = ref();
|
||||
const showSearch = ref(true);
|
||||
const queryRef = ref();
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
pageList: pageList, // H
|
||||
queryForm: {
|
||||
parentId: -1,
|
||||
menuName: '',
|
||||
},
|
||||
isPage: false,
|
||||
});
|
||||
|
||||
const { getDataList, tableStyle } = useTable(state);
|
||||
|
||||
// 根据类型判断是否有子节点
|
||||
const setHasChildren = (arr: any[]) => {
|
||||
arr.forEach((item) => {
|
||||
// 添加 hasChildren 属性
|
||||
item.hasChildren = item.menuType !== '1';
|
||||
});
|
||||
};
|
||||
const tableList = computed(() => {
|
||||
const list = state.dataList;
|
||||
if (Array.isArray(list)) {
|
||||
setHasChildren(list);
|
||||
}
|
||||
return list;
|
||||
});
|
||||
|
||||
// 打开新增菜单弹窗
|
||||
const onOpenAddMenu = (type?: string, row?: any) => {
|
||||
menuDialogRef.value.openDialog(type, row);
|
||||
};
|
||||
// 打开编辑菜单弹窗
|
||||
const onOpenEditMenu = (type: string, row: any) => {
|
||||
menuDialogRef.value.openDialog(type, row);
|
||||
};
|
||||
|
||||
//是否禁用删除
|
||||
const deleteMenuDisabled = (row: any) => {
|
||||
return (row.children || []).length > 0;
|
||||
};
|
||||
|
||||
// 搜索事件
|
||||
const query = () => {
|
||||
state.dataList = [];
|
||||
state.queryForm.parentId = undefined;
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
state.dataList = [];
|
||||
getDataList();
|
||||
};
|
||||
|
||||
const load = (row: any, treeNode: unknown, resolve: (date: any[]) => void) => {
|
||||
const param = {
|
||||
parentId: row.id,
|
||||
};
|
||||
pageList(param).then((res) => {
|
||||
const childrenList = res.data;
|
||||
if (Array.isArray(childrenList)) {
|
||||
setHasChildren(childrenList);
|
||||
}
|
||||
resolve(childrenList);
|
||||
});
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (row: any) => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
129
src/views/admin/system/post/form.vue
Normal file
129
src/views/admin/system/post/form.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<el-dialog :title="form.postId ? $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" label-width="90px" v-loading="loading">
|
||||
<el-form-item :label="t('post.postCode')" prop="postCode">
|
||||
<el-input v-model="form.postCode" :placeholder="t('post.inputpostCodeTip')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('post.postName')" prop="postName">
|
||||
<el-input v-model="form.postName" :placeholder="t('post.inputpostNameTip')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('post.postSort')" prop="postSort">
|
||||
<el-input-number v-model="form.postSort" :placeholder="t('post.inputpostSortTip')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('post.remark')" prop="remark">
|
||||
<el-input type="textarea" maxlength="100" row="3" v-model="form.remark" :placeholder="t('post.inputremarkTip')" />
|
||||
</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>
|
||||
|
||||
<script setup lang="ts" name="systemPostDialog">
|
||||
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";
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
postId: '',
|
||||
postCode: '',
|
||||
postName: '',
|
||||
postSort: 0,
|
||||
remark: '',
|
||||
delFlag: '',
|
||||
createTime: '',
|
||||
createBy: '',
|
||||
updateTime: '',
|
||||
updateBy: '',
|
||||
});
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
postCode: [
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
{ required: true, message: '岗位编码不能为空', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validatePostCode(rule, value, callback, form.postId !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
postName: [
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
{ required: true, message: '岗位名称不能为空', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validatePostName(rule, value, callback, form.postId !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
postSort: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '岗位排序不能为空', trigger: 'blur' }],
|
||||
remark: [{ validator: rule.overLength, trigger: 'blur' },{ required: true, message: '岗位描述不能为空', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true;
|
||||
form.postId = '';
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
// 获取Post信息
|
||||
if (id) {
|
||||
form.postId = id;
|
||||
getPostData(id);
|
||||
}
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) return false;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
form.postId ? await putObj(form) : await addObj(form);
|
||||
useMessage().success(t(form.postId ? 'common.editSuccessText' : 'common.addSuccessText'));
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化表格数据
|
||||
const getPostData = (id: string) => {
|
||||
// 获取部门数据
|
||||
getObj(id).then((res: any) => {
|
||||
Object.assign(form, res.data);
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
28
src/views/admin/system/post/i18n/en.ts
Normal file
28
src/views/admin/system/post/i18n/en.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
export default {
|
||||
post: {
|
||||
index: '#',
|
||||
importPostTip: ' import Post',
|
||||
postId: 'postId',
|
||||
postCode: 'postCode',
|
||||
postName: 'postName',
|
||||
postSort: 'postSort',
|
||||
remark: 'remark',
|
||||
delFlag: 'delFlag',
|
||||
createTime: 'createTime',
|
||||
createBy: 'createBy',
|
||||
updateTime: 'updateTime',
|
||||
updateBy: 'updateBy',
|
||||
tenantId: 'tenantId',
|
||||
inputpostIdTip: 'input postId',
|
||||
inputpostCodeTip: 'input postCode',
|
||||
inputpostNameTip: 'input postName',
|
||||
inputpostSortTip: 'input postSort',
|
||||
inputremarkTip: 'input remark',
|
||||
inputdelFlagTip: 'input delFlag',
|
||||
inputcreateTimeTip: 'input createTime',
|
||||
inputcreateByTip: 'input createBy',
|
||||
inputupdateTimeTip: 'input updateTime',
|
||||
inputupdateByTip: 'input updateBy',
|
||||
inputtenantIdTip: 'input tenantId',
|
||||
},
|
||||
};
|
||||
28
src/views/admin/system/post/i18n/zh-cn.ts
Normal file
28
src/views/admin/system/post/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
export default {
|
||||
post: {
|
||||
index: '#',
|
||||
importPostTip: '导入岗位管理',
|
||||
postId: '岗位ID',
|
||||
postCode: '岗位编码',
|
||||
postName: '岗位名称',
|
||||
postSort: '岗位排序',
|
||||
remark: '岗位描述',
|
||||
delFlag: '是否删除 -1:已删除 0:正常',
|
||||
createTime: '创建时间',
|
||||
createBy: '创建人',
|
||||
updateTime: '更新时间',
|
||||
updateBy: '更新人',
|
||||
tenantId: '租户ID',
|
||||
inputpostIdTip: '请输入岗位ID',
|
||||
inputpostCodeTip: '请输入岗位编码',
|
||||
inputpostNameTip: '请输入岗位名称',
|
||||
inputpostSortTip: '请输入岗位排序',
|
||||
inputremarkTip: '请输入岗位描述',
|
||||
inputdelFlagTip: '请输入是否删除 -1:已删除 0:正常',
|
||||
inputcreateTimeTip: '请输入创建时间',
|
||||
inputcreateByTip: '请输入创建人',
|
||||
inputupdateTimeTip: '请输入更新时间',
|
||||
inputupdateByTip: '请输入更新人',
|
||||
inputtenantIdTip: '请输入租户ID',
|
||||
},
|
||||
};
|
||||
143
src/views/admin/system/post/index.vue
Normal file
143
src/views/admin/system/post/index.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row class="ml10" v-show="showSearch">
|
||||
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
|
||||
<el-form-item :label="$t('post.postName')" prop="postName">
|
||||
<el-input :placeholder="$t('post.inputpostNameTip')" style="max-width: 180px" v-model="state.queryForm.postName" />
|
||||
</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="mb8" style="width: 100%">
|
||||
<el-button @click="formDialogRef.openDialog()" class="ml10" icon="folder-add" type="primary" v-auth="'sys_post_add'">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<el-button plain @click="excelUploadRef.show()" class="ml10" icon="upload-filled" type="primary" v-auth="'sys_post_add'">
|
||||
{{ $t('common.importBtn') }}
|
||||
</el-button>
|
||||
<el-button plain :disabled="multiple" @click="handleDelete(selectObjs)" class="ml10" icon="Delete" type="primary" v-auth="'sys_post_del'">
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
:export="'sys_post_export'"
|
||||
@exportExcel="exportExcel"
|
||||
@queryTable="getDataList"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
v-model:showSearch="showSearch"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
v-loading="state.loading"
|
||||
row-key="postId"
|
||||
border
|
||||
:cell-style="tableStyle?.cellStyle"
|
||||
:header-cell-style="tableStyle?.headerCellStyle"
|
||||
>
|
||||
<el-table-column align="center" type="selection" width="40" />
|
||||
<el-table-column :label="t('post.index')" type="index" width="60" />
|
||||
<el-table-column :label="t('post.postCode')" prop="postCode" show-overflow-tooltip />
|
||||
<el-table-column :label="t('post.postName')" prop="postName" show-overflow-tooltip />
|
||||
<el-table-column :label="t('post.postSort')" prop="postSort" show-overflow-tooltip />
|
||||
<el-table-column :label="t('post.remark')" prop="remark" show-overflow-tooltip />
|
||||
<el-table-column :label="$t('common.action')" width="200">
|
||||
<template #default="scope">
|
||||
<el-button icon="edit-pen" @click="formDialogRef.openDialog(scope.row.postId)" text type="primary" v-auth="'sys_post_edit'"
|
||||
>{{ $t('common.editBtn') }}
|
||||
</el-button>
|
||||
|
||||
<el-button icon="delete" @click="handleDelete([scope.row.postId])" text type="primary" v-auth="'sys_post_del'"
|
||||
>{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
|
||||
<!-- 编辑、新增 -->
|
||||
<form-dialog @refresh="getDataList()" ref="formDialogRef" />
|
||||
<!-- 导入excel -->
|
||||
<upload-excel
|
||||
:title="$t('post.importPostTip')"
|
||||
@refreshDataList="getDataList"
|
||||
ref="excelUploadRef"
|
||||
temp-url="/admin/sys-file/local/file/post.xlsx"
|
||||
url="/admin/post/import"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemPost" setup>
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { delObj, fetchList } from '/@/api/admin/post';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref();
|
||||
const excelUploadRef = ref();
|
||||
// 搜索变量
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
pageList: fetchList,
|
||||
});
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/post/export', Object.assign(state.queryForm,{ids:selectObjs}), 'post.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { postId: string }[]) => {
|
||||
selectObjs.value = objs.map(({ postId }) => postId);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
203
src/views/admin/system/role/form.vue
Normal file
203
src/views/admin/system/role/form.vue
Normal file
@@ -0,0 +1,203 @@
|
||||
<template>
|
||||
<el-dialog :close-on-click-modal="false" :title="form.roleId ? $t('common.editBtn') : $t('common.addBtn')" width="600" draggable v-model="visible">
|
||||
<el-form :model="form" :rules="dataRules" label-width="90px" ref="dataFormRef" v-loading="loading">
|
||||
<el-form-item :label="$t('sysrole.roleName')" prop="roleName">
|
||||
<el-input :placeholder="$t('sysrole.please_enter_a_role_name')" clearable v-model="form.roleName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysrole.roleCode')" prop="roleCode">
|
||||
<el-input :placeholder="$t('sysrole.please_enter_the_role_Code')" :disabled="form.roleId !== ''" clearable v-model="form.roleCode"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysrole.roleDesc')" prop="roleDesc">
|
||||
<el-input
|
||||
maxlength="100"
|
||||
:rows="3"
|
||||
:placeholder="$t('sysrole.please_enter_the_role_description')"
|
||||
type="textarea"
|
||||
v-model="form.roleDesc"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysrole.menu_authority')" prop="dsType">
|
||||
<el-select :placeholder="$t('sysrole.please_select')" clearable v-model="form.dsType">
|
||||
<el-option :key="item.value" :label="item.label" :value="item.value" v-for="item in dictType" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.dsType === 1">
|
||||
<el-tree
|
||||
:check-strictly="true"
|
||||
:data="dataForm.deptData"
|
||||
:default-checked-keys="dataForm.checkedDsScope"
|
||||
:props="dataForm.deptProps"
|
||||
default-expand-all
|
||||
highlight-current
|
||||
node-key="id"
|
||||
ref="deptTreeRef"
|
||||
show-checkbox
|
||||
/>
|
||||
</el-form-item>
|
||||
</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 lang="ts" name="systemRoleDialog" setup>
|
||||
import { rule } from '/@/utils/validate';
|
||||
import { deptTree } from '/@/api/admin/dept';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { addObj, getObj, putObj, validateRoleCode, validateRoleName } from '/@/api/admin/role';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const deptTreeRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
roleId: '',
|
||||
roleName: '',
|
||||
roleCode: '',
|
||||
roleDesc: '',
|
||||
dsType: 0,
|
||||
dsScope: '',
|
||||
});
|
||||
|
||||
// 页面对应元数据
|
||||
const dataForm = reactive({
|
||||
deptData: [],
|
||||
checkedDsScope: [],
|
||||
deptProps: {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
});
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
roleName: [
|
||||
{ required: true, message: '角色名称不能为空', trigger: 'blur' },
|
||||
{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateRoleName(rule, value, callback, form.roleId !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
roleCode: [
|
||||
{ required: true, message: '角色标识不能为空', trigger: 'blur' },
|
||||
{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' },
|
||||
{ validator: rule.validatorCapital, trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateRoleCode(rule, value, callback, form.roleId !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
roleDesc: [{ validator: rule.overLength, trigger: 'blur' }],
|
||||
dsType: [{ required: true, message: '请选择数据权限类型', trigger: 'blur' }],
|
||||
menu_authority: [{ required: true, message: '数据权限不能为空', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
const dictType = ref([
|
||||
{
|
||||
label: '全部',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '自定义',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '本级及子级',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: '本级',
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
label: '本人',
|
||||
value: 4,
|
||||
},
|
||||
]);
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true;
|
||||
form.roleId = '';
|
||||
|
||||
nextTick(() => {
|
||||
dataFormRef.value.resetFields();
|
||||
});
|
||||
|
||||
// 获取角色信息
|
||||
if (id) {
|
||||
form.roleId = id;
|
||||
getRoleData(id);
|
||||
}
|
||||
|
||||
getDeptData();
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
if (form.dsType === 1) {
|
||||
form.dsScope = deptTreeRef.value.getCheckedKeys().join(',');
|
||||
} else {
|
||||
form.dsScope = '';
|
||||
}
|
||||
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) return false;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
form.roleId ? await putObj(form) : await addObj(form);
|
||||
useMessage().success(t(form.roleId ? 'common.editSuccessText' : 'common.addSuccessText'));
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化角色数据
|
||||
const getRoleData = (id: string) => {
|
||||
// 获取部门数据
|
||||
getObj(id).then((res: any) => {
|
||||
Object.assign(form, res.data);
|
||||
if (res.data.dsScope) {
|
||||
dataForm.checkedDsScope = res.data.dsScope.split(',');
|
||||
} else {
|
||||
dataForm.checkedDsScope = [];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 获取菜单结构数据
|
||||
const getDeptData = () => {
|
||||
deptTree().then((res: any) => {
|
||||
dataForm.deptData = res.data;
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
20
src/views/admin/system/role/i18n/en.ts
Normal file
20
src/views/admin/system/role/i18n/en.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export default {
|
||||
sysrole: {
|
||||
index: '#',
|
||||
roleName: 'roleName',
|
||||
inputRoleNameTip: 'input roleName',
|
||||
permissionTip: 'grant',
|
||||
deleteDisabledTip: 'not allowed to delete',
|
||||
mustCheckOneTip: 'the assign permissions menu must be selected',
|
||||
roleCode: 'roleCode',
|
||||
roleDesc: 'role description',
|
||||
data_authority: 'data authority',
|
||||
createTime: 'createTime',
|
||||
please_enter_a_role_name: 'please enter a role name',
|
||||
please_enter_the_role_Code: 'please enter the role Code',
|
||||
please_enter_the_role_description: 'please enter the role description',
|
||||
menu_authority: 'menu authority',
|
||||
please_select: 'please select',
|
||||
importRoleTip: 'import role',
|
||||
},
|
||||
};
|
||||
20
src/views/admin/system/role/i18n/zh-cn.ts
Normal file
20
src/views/admin/system/role/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export default {
|
||||
sysrole: {
|
||||
index: '#',
|
||||
roleName: '角色名称',
|
||||
inputRoleNameTip: '请输入角色名称',
|
||||
permissionTip: '授权',
|
||||
deleteDisabledTip: '角色不允许删除',
|
||||
mustCheckOneTip: '必须选择【分配权限】菜单',
|
||||
roleCode: '角色标识',
|
||||
roleDesc: '角色描述',
|
||||
data_authority: '数据权限',
|
||||
createTime: '创建时间',
|
||||
please_enter_a_role_name: '请输入角色名称',
|
||||
please_enter_the_role_Code: '请输入角色标识',
|
||||
please_enter_the_role_description: '请输入角色描述',
|
||||
menu_authority: '数据权限',
|
||||
please_select: '请选择',
|
||||
importRoleTip: '导入角色'
|
||||
},
|
||||
};
|
||||
197
src/views/admin/system/role/index.vue
Normal file
197
src/views/admin/system/role/index.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row shadow="hover" v-show="showSearch" class="ml10">
|
||||
<el-form :model="state.queryForm" ref="queryRef" :inline="true" @keyup.enter="getDataList">
|
||||
<el-form-item :label="$t('sysrole.roleName')" prop="roleName">
|
||||
<el-input :placeholder="$t('sysrole.inputRoleNameTip')" v-model="state.queryForm.roleName" />
|
||||
</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="roleDialogRef.openDialog()" v-auth="'sys_role_add'">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<el-button plain icon="upload-filled" type="primary" class="ml10" @click="excelUploadRef.show()" v-auth="'sys_user_add'">
|
||||
{{ $t('common.importBtn') }}
|
||||
</el-button>
|
||||
<el-button plain :disabled="multiple" icon="Delete" type="primary" class="ml10" v-auth="'sys_user_del'" @click="handleDelete(selectObjs)">
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
:export="'sys_role_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%"
|
||||
row-key="roleId"
|
||||
@selection-change="handleSelectionChange"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column type="selection" :selectable="handleSelectable" width="50" align="center" />
|
||||
<el-table-column type="index" :label="$t('sysrole.index')" width="80" />
|
||||
<el-table-column prop="roleName" :label="$t('sysrole.roleName')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="roleCode" :label="$t('sysrole.roleCode')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="roleDesc" :label="$t('sysrole.roleDesc')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="data_authority" :label="$t('sysrole.data_authority')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="dictType" :value="scope.row.dsType"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" :label="$t('sysrole.createTime')" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('common.action')" width="250">
|
||||
<template #default="scope">
|
||||
<el-button text type="primary" icon="edit-pen" v-auth="'sys_role_edit'" @click="roleDialogRef.openDialog(scope.row.roleId)">{{
|
||||
$t('common.editBtn')
|
||||
}}</el-button>
|
||||
|
||||
<el-button text type="primary" icon="turn-off" v-auth="'sys_role_perm'" @click="permessionRef.openDialog(scope.row)">{{
|
||||
$t('sysrole.permissionTip')
|
||||
}}</el-button>
|
||||
|
||||
<el-tooltip :content="$t('sysrole.deleteDisabledTip')" :disabled="scope.row.roleId !== '1'" placement="top">
|
||||
<span style="margin-left: 12px">
|
||||
<el-button
|
||||
text
|
||||
type="primary"
|
||||
icon="delete"
|
||||
:disabled="scope.row.roleId === '1'"
|
||||
v-auth="'sys_role_del'"
|
||||
@click="handleDelete([scope.row.roleId])"
|
||||
>{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
|
||||
<!-- 角色编辑、新增 -->
|
||||
<role-dialog ref="roleDialogRef" @refresh="getDataList()" />
|
||||
<!-- 导入角色 -->
|
||||
<upload-excel
|
||||
ref="excelUploadRef"
|
||||
:title="$t('sysrole.importRoleTip')"
|
||||
url="/admin/role/import"
|
||||
temp-url="/admin/sys-file/local/file/role.xlsx"
|
||||
@refreshDataList="getDataList"
|
||||
/>
|
||||
<!-- 授权 -->
|
||||
<permession ref="permessionRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemRole">
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { pageList, delObj } from '/@/api/admin/role';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
// 引入组件
|
||||
const RoleDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const Permession = defineAsyncComponent(() => import('./permession.vue'));
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const roleDialogRef = ref();
|
||||
const permessionRef = ref();
|
||||
const excelUploadRef = ref();
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选rows
|
||||
const selectObjs = ref([]) as any;
|
||||
// 是否可以多选
|
||||
const multiple = ref(true);
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
roleName: '',
|
||||
},
|
||||
pageList: pageList, // H
|
||||
descs: ['create_time'],
|
||||
});
|
||||
|
||||
const dictType = ref([
|
||||
{
|
||||
label: '全部',
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
label: '自定义',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
label: '本级及子级',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
label: '本级',
|
||||
value: '3',
|
||||
},
|
||||
{
|
||||
label: '本人',
|
||||
value: '4',
|
||||
},
|
||||
]);
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/role/export',Object.assign(state.queryForm,{ids:selectObjs}), 'role.xlsx');
|
||||
};
|
||||
|
||||
// 是否可以多选
|
||||
const handleSelectable = (row: any) => {
|
||||
return row.roleId !== '1';
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { roleId: string }[]) => {
|
||||
selectObjs.value = objs.map(({ roleId }) => roleId);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
137
src/views/admin/system/role/permession.vue
Normal file
137
src/views/admin/system/role/permession.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div class="system-role-dialog-container">
|
||||
<el-dialog width="30%" v-model="state.dialog.isShowDialog" :close-on-click-modal="false" draggable>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>{{ state.dialog.title }}</div>
|
||||
<div class="flex mr-16">
|
||||
<el-checkbox :label="$t('common.expand')" @change="handleExpand"/>
|
||||
<el-checkbox :label="$t('common.selectAll')" @change="handleSelectAll"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-scrollbar class="h-[400px] sm:h-[600px]">
|
||||
<el-tree
|
||||
v-loading="loading"
|
||||
ref="menuTree"
|
||||
:data="state.treeData"
|
||||
:default-checked-keys="state.checkedKeys"
|
||||
:check-strictly="!checkStrictly"
|
||||
:props="state.defaultProps"
|
||||
class="filter-tree"
|
||||
node-key="id"
|
||||
highlight-current
|
||||
show-checkbox
|
||||
/>
|
||||
</el-scrollbar>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="state.dialog.isShowDialog = false">取 消</el-button>
|
||||
<el-button type="primary" @click="onSubmit">{{ state.dialog.submitTxt }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="role-permession">
|
||||
import {fetchRoleTree, permissionUpd} from '/@/api/admin/role';
|
||||
import {pageList} from '/@/api/admin/menu';
|
||||
import {useMessage} from '/@/hooks/message';
|
||||
import {Ref} from 'vue';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import other from '/@/utils/other';
|
||||
import {CheckboxValueType} from 'element-plus';
|
||||
|
||||
const {t} = useI18n();
|
||||
|
||||
const menuTree = ref();
|
||||
const checkStrictly = ref(true);
|
||||
const loading = ref(false);
|
||||
|
||||
const state = reactive({
|
||||
checkedKeys: [] as any[],
|
||||
treeData: [] as any[],
|
||||
defaultProps: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
roleId: '',
|
||||
dialog: {
|
||||
isShowDialog: false,
|
||||
title: '分配权限',
|
||||
submitTxt: '更新',
|
||||
},
|
||||
});
|
||||
|
||||
const checkedKeys: Ref<any[]> = ref([]);
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (row: any) => {
|
||||
state.checkedKeys = [];
|
||||
state.treeData = [];
|
||||
checkedKeys.value = [];
|
||||
state.roleId = row.roleId;
|
||||
loading.value = true;
|
||||
fetchRoleTree(row.roleId)
|
||||
.then((res) => {
|
||||
checkedKeys.value = res.data;
|
||||
return pageList();
|
||||
})
|
||||
.then((r) => {
|
||||
state.treeData = r.data;
|
||||
state.checkedKeys = other.resolveAllEunuchNodeId(state.treeData, checkedKeys.value, []);
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
state.dialog.isShowDialog = true;
|
||||
};
|
||||
|
||||
const handleExpand = (check: CheckboxValueType) => {
|
||||
const treeList = state.treeData;
|
||||
for (let i = 0; i < treeList.length; i++) {
|
||||
//@ts-ignore
|
||||
menuTree.value.store.nodesMap[treeList[i].id].expanded = check;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectAll = (check: CheckboxValueType) => {
|
||||
if (check) {
|
||||
menuTree.value?.setCheckedKeys(state.treeData.map((item) => item.id));
|
||||
} else {
|
||||
menuTree.value?.setCheckedKeys([]);
|
||||
}
|
||||
};
|
||||
|
||||
// 提交授权数据
|
||||
const onSubmit = () => {
|
||||
// 初始角色选择节点必须包含 【分配权限】 菜单
|
||||
if (state.roleId === '1') {
|
||||
if (!menuTree.value.getCheckedNodes().map((item: { name: string }) => {
|
||||
return item.name;
|
||||
}).includes('分配权限')) {
|
||||
useMessage().error(t('sysrole.mustCheckOneTip'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const menuIds = menuTree.value.getCheckedKeys().join(',').concat(',').concat(menuTree.value.getHalfCheckedKeys().join(','));
|
||||
loading.value = true;
|
||||
permissionUpd(state.roleId, menuIds)
|
||||
.then(() => {
|
||||
state.dialog.isShowDialog = false;
|
||||
useMessage().success(t('common.editSuccessText'));
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
244
src/views/admin/system/tenant/form.vue
Normal file
244
src/views/admin/system/tenant/form.vue
Normal file
@@ -0,0 +1,244 @@
|
||||
<template>
|
||||
<el-drawer :title="form.id ? $t('common.editBtn') : $t('common.addBtn')" v-model="visible" :close-on-click-modal="true" draggable size="50%">
|
||||
<el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="90px" v-loading="loading">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('tenant.name')" prop="name">
|
||||
<el-input v-model="form.name" :placeholder="t('tenant.inputnameTip')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('tenant.code')" prop="code">
|
||||
<el-input v-model="form.code" :placeholder="t('tenant.inputcodeTip')" :disabled="form.id !== ''" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<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-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-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :span="12" :label="t('tenant.tenantDomain')" prop="tenantDomain">
|
||||
<el-input v-model="form.tenantDomain" :placeholder="t('tenant.inputtenantDomainTip')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('tenant.status')" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio :label="item.value" v-for="(item, index) in status_type" border :key="index">{{ item.label }} </el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-divider content-position="left" v-if="form.id !== '1'">
|
||||
<div>
|
||||
<span class="mr-4">{{ $t('tenantmenu.name') }}</span>
|
||||
<el-checkbox :label="$t('common.expand')" @change="handleExpand" />
|
||||
<el-checkbox :label="$t('common.selectAll')" @change="handleSelectAll" />
|
||||
</div>
|
||||
</el-divider>
|
||||
<el-scrollbar class="h-[400px] sm:h-[600px] ml-12" v-if="form.id !== '1'">
|
||||
<el-tree
|
||||
show-checkbox
|
||||
ref="menuTreeRef"
|
||||
:disabled="true"
|
||||
:check-strictly="false"
|
||||
:data="menuData"
|
||||
:props="defaultProps"
|
||||
:default-checked-keys="checkedMenu"
|
||||
node-key="id"
|
||||
highlight-current
|
||||
/>
|
||||
</el-scrollbar>
|
||||
</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>
|
||||
|
||||
<script setup lang="ts" name="systemTenantDialog">
|
||||
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 { useI18n } from 'vue-i18n';
|
||||
import other from '/@/utils/other';
|
||||
import { CheckboxValueType } from 'element-plus';
|
||||
import {rule} from "/@/utils/validate";
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['refresh']);
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const menuTreeRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 字典
|
||||
const { status_type } = useDict('status_type');
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
name: '',
|
||||
code: '',
|
||||
tenantDomain: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
status: '0',
|
||||
delFlag: '',
|
||||
createBy: '',
|
||||
updateBy: '',
|
||||
createTime: '',
|
||||
updateTime: '',
|
||||
menuId: '',
|
||||
});
|
||||
|
||||
const menuData = ref<any[]>([]);
|
||||
|
||||
const defaultProps = reactive({
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
disabled: true,
|
||||
});
|
||||
|
||||
const checkedMenu = ref<any[]>([]);
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
name: [
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
{ required: true, message: '名称不能为空', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateTenantName(rule, value, callback, form.id !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
code: [
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
{ required: true, message: '编码不能为空', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateTenantCode(rule, value, callback, form.id !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
startTime: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
|
||||
endTime: [{ required: true, message: '结束时间不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: 'status不能为空', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string): void => {
|
||||
visible.value = true;
|
||||
form.id = '';
|
||||
form.menuId = '';
|
||||
checkedMenu.value = [];
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
if (id) {
|
||||
form.id = id;
|
||||
getTenantData(id);
|
||||
}
|
||||
|
||||
getMenuData();
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) return false;
|
||||
|
||||
if (menuTreeRef.value?.getCheckedKeys().length === 0) {
|
||||
useMessage().error('请选择租户套餐菜单');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (menuTreeRef.value?.getCheckedKeys()) {
|
||||
let checkMenu = [...menuTreeRef.value.getCheckedKeys(), ...menuTreeRef.value.getHalfCheckedKeys()]
|
||||
|
||||
if (!checkMenu.includes('1300')) {
|
||||
useMessage().error('必须分配角色管理功能');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!checkMenu.includes('1302')) {
|
||||
useMessage().error('必须分配角色管理功能');
|
||||
return false;
|
||||
}
|
||||
|
||||
form.menuId = checkMenu.join(',');
|
||||
}
|
||||
|
||||
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 {
|
||||
await fetchList()
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化表格数据。
|
||||
* @param {string} id - 部门 ID。
|
||||
*/
|
||||
const getTenantData = async (id) => {
|
||||
const res = await getObj(id);
|
||||
Object.assign(form, res.data);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取菜单数据
|
||||
*/
|
||||
const getMenuData = async () => {
|
||||
const res = await treemenu();
|
||||
menuData.value = res.data;
|
||||
checkedMenu.value = form.menuId ? other.resolveAllEunuchNodeId(menuData.value, form.menuId.split(','), []) : [];
|
||||
};
|
||||
|
||||
const handleExpand = (check: CheckboxValueType) => {
|
||||
const treeList = menuData.value;
|
||||
for (let i = 0; i < treeList.length; i++) {
|
||||
//@ts-ignore
|
||||
menuTreeRef.value.store.nodesMap[treeList[i].id].expanded = check;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectAll = (check: CheckboxValueType) => {
|
||||
if (check) {
|
||||
menuTreeRef.value?.setCheckedKeys(menuData.value.map((item) => item.id));
|
||||
} else {
|
||||
menuTreeRef.value?.setCheckedKeys([]);
|
||||
}
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
40
src/views/admin/system/tenant/i18n/en.ts
Normal file
40
src/views/admin/system/tenant/i18n/en.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
export default {
|
||||
tenant: {
|
||||
index: '#',
|
||||
importTenantTip: ' import Tenant',
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
code: 'code',
|
||||
tenantDomain: 'tenantDomain',
|
||||
startTime: 'startTime',
|
||||
endTime: 'endTime',
|
||||
status: 'status',
|
||||
delFlag: 'delFlag',
|
||||
createBy: 'createBy',
|
||||
updateBy: 'updateBy',
|
||||
createTime: 'createTime',
|
||||
updateTime: 'updateTime',
|
||||
menuId: 'menuIds',
|
||||
individuationBtn: 'individuation',
|
||||
inputidTip: 'input id',
|
||||
inputnameTip: 'input name',
|
||||
inputcodeTip: 'input code',
|
||||
inputtenantDomainTip: 'input tenantDomain',
|
||||
inputstartTimeTip: 'input startTime',
|
||||
inputendTimeTip: 'input endTime',
|
||||
inputstatusTip: 'input status',
|
||||
inputdelFlagTip: 'input delFlag',
|
||||
inputcreateByTip: 'input createBy',
|
||||
inputupdateByTip: 'input updateBy',
|
||||
inputcreateTimeTip: 'input createTime',
|
||||
inputupdateTimeTip: 'input updateTime',
|
||||
inputmenuIdTip: 'input menuId',
|
||||
deleteDisabledTip: 'base tenants are not allowed to delete',
|
||||
},
|
||||
tenantmenu: {
|
||||
name: 'tenantmenu',
|
||||
index: '#',
|
||||
status: 'status',
|
||||
createTime: 'createTime',
|
||||
},
|
||||
};
|
||||
53
src/views/admin/system/tenant/i18n/zh-cn.ts
Normal file
53
src/views/admin/system/tenant/i18n/zh-cn.ts
Normal file
@@ -0,0 +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: '创建',
|
||||
},
|
||||
|
||||
individuation: {
|
||||
websiteName: '网站名称',
|
||||
miniQr: '移动端二维码',
|
||||
logo: '网站图标',
|
||||
footerAuthor: '页脚信息',
|
||||
background: '登录页背景图',
|
||||
inputIndividuationNameTip: '请输入网站名称',
|
||||
inputMiniQrTip: '请输入网站图标',
|
||||
inputLogoTip: '请输入网站Logo',
|
||||
inputFooterAuthorTip: '请输入页脚信息',
|
||||
inputBackgroundTip: '请输入登录页背景图',
|
||||
}
|
||||
};
|
||||
240
src/views/admin/system/tenant/index.vue
Normal file
240
src/views/admin/system/tenant/index.vue
Normal file
@@ -0,0 +1,240 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row class="ml10" v-show="showSearch">
|
||||
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
|
||||
<el-form-item :label="$t('tenant.name')" prop="name">
|
||||
<el-input :placeholder="$t('tenant.inputnameTip')" style="max-width: 180px" v-model="state.queryForm.name" />
|
||||
</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="mb8" style="width: 100%">
|
||||
<el-button @click="formDialogRef.openDialog()" class="ml10" icon="folder-add" type="primary" v-auth="'sys_systenant_add'">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
|
||||
<el-button plain @click="handleRefreshCache()" class="ml10" icon="refresh-left" type="primary">
|
||||
{{ $t('common.refreshCacheBtn') }}
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
plain
|
||||
:disabled="multiple"
|
||||
@click="handleDelete(selectObjs)"
|
||||
class="ml10"
|
||||
icon="Delete"
|
||||
type="primary"
|
||||
v-auth="'sys_systenant_del'"
|
||||
>
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
|
||||
<right-toolbar
|
||||
:export="'sys_systenant_export'"
|
||||
@exportExcel="exportExcel"
|
||||
@queryTable="getDataList"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
v-model:showSearch="showSearch"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
<el-scrollbar>
|
||||
<div class="mx-auto mt-4">
|
||||
<div class="px-4">
|
||||
<div class="grid sm:grid-cols-2 sm:gap-x-6 lg:grid-cols-3">
|
||||
<div class="p-6 mb-6 bg-gray-100 rounded-lg dark:bg-gray-800" v-for="tenant in state.dataList" :key="tenant.id">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">{{ tenant.name }}</h3>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<svg
|
||||
t="1710908184286"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="5178"
|
||||
class="w-5 h-5 mr-2 text-base text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<path
|
||||
d="M512 1003.52a497.92 497.92 0 1 1 497.92-497.92A498.56 498.56 0 0 1 512 1003.52zM512 71.68a433.92 433.92 0 1 0 433.92 433.92A434.56 434.56 0 0 0 512 71.68z"
|
||||
fill="#323333"
|
||||
p-id="5179"
|
||||
></path>
|
||||
<path
|
||||
d="M152.96 369.92a33.92 33.92 0 0 1 35.2-36.48 39.04 39.04 0 0 1 29.44 16l148.48 198.4V369.92a35.2 35.2 0 1 1 69.76 0V640a30.72 30.72 0 0 1-34.56 33.28 36.48 36.48 0 0 1-29.44-12.8l-147.2-198.4V640a30.72 30.72 0 0 1-34.56 33.28 31.36 31.36 0 0 1-37.12-33.28zM463.36 504.32a162.56 162.56 0 1 1 323.84 0 158.08 158.08 0 0 1-161.92 168.96 159.36 159.36 0 0 1-161.92-168.96z m252.16 0c-3.84-69.12-33.92-104.96-90.24-108.8s-84.48 39.68-88.32 108.8 33.28 104.32 88.32 108.16 86.4-36.48 90.24-108.16zM856.96 603.52A37.12 37.12 0 0 1 896 640c0 21.12-13.44 32-36.48 33.28s-35.84-12.16-37.12-33.28a37.76 37.76 0 0 1 34.56-36.48z"
|
||||
fill="#323333"
|
||||
p-id="5180"
|
||||
></path>
|
||||
</svg>
|
||||
<span class="mr-1 dark:text-gray-300">{{ tenant.code }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="my-3 text-sm font-normal text-gray-500 dark:text-gray-400">
|
||||
状态: {{ status_type.find((item: { value: string }) => item.value === tenant.status)?.label }}
|
||||
</p>
|
||||
<p class="my-3 text-sm font-normal text-gray-500 dark:text-gray-400">
|
||||
有效期: {{ parseDate(tenant.startTime) }} - {{ parseDate(tenant.endTime) }}
|
||||
</p>
|
||||
<div class="flex items-center justify-between mt-6 text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
<div class="flex">
|
||||
<svg
|
||||
t="1710908265535"
|
||||
class="w-6 h-5 mr-1 text-yellow-500"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="6175"
|
||||
width="128"
|
||||
height="128"
|
||||
>
|
||||
<path
|
||||
d="M512.001274 15.045039a497.880869 497.880869 0 0 1 496.99699 497.086142c0 137.285182-55.886849 261.573274-145.67566 351.652466-89.81301 90.098296-214.335444 145.167493-351.32133 145.167494-137.262257 0-261.529972-55.069197-351.31751-145.173862-89.788811-90.079192-145.67566-214.367284-145.67566-351.646098a497.880869 497.880869 0 0 1 496.99317-497.086142zM489.215293 932.144136V771.518957a405.118201 405.118201 0 0 0-129.123952 26.595319 137.138718 137.138718 0 0 0-17.101903 6.511917c3.53042 7.062113 6.785742 14.098754 10.570881 20.887043 25.514032 45.602527 55.33538 80.88762 88.987717 101.223193a372.744559 372.744559 0 0 0 46.671078 5.40898z m320.120674-717.103117a128.736778 128.736778 0 0 0-10.591259-10.061441 441.222218 441.222218 0 0 1-58.315604 36.934404c27.141693 70.795612 43.939205 156.01602 46.672351 247.447775h144.843999a419.629601 419.629601 0 0 0-122.609487-274.320738z m-46.672352-40.41388a415.300634 415.300634 0 0 0-85.71329-49.415687 434.384259 434.384259 0 0 1 33.623044 51.562976c3.805517 7.062113 7.861934 15.201692 11.942549 23.339997 13.571483-8.138305 27.117495-16.556802 40.137509-25.493654z m-181.233302-77.3572c-15.172399-2.404557-30.921738-4.056417-46.64688-5.133883V253.306336a429.022402 429.022402 0 0 0 129.127773-26.588951c5.985921-2.177856 11.392353-4.356986 17.076431-6.787015-2.954752-7.337211-7.036641-14.373851-10.541588-20.887043-24.989309-46.15527-55.610477-80.888893-89.013189-101.774662z m-92.217567-5.133883c-15.723868 1.077466-31.473207 2.729325-46.671078 5.133883-33.652337 20.887043-63.473685 55.619393-88.987717 101.774662-3.781319 6.510644-7.036641 13.547285-10.816686 20.887042 5.684078 2.430029 11.392353 4.609159 17.351529 6.787016a428.859382 428.859382 0 0 0 129.123952 26.595318V92.130962z m-142.141419 33.075396a414.845959 414.845959 0 0 0-85.732395 49.415687c13.020014 8.940673 26.591498 17.355349 40.412607 25.493654a233.26369 233.26369 0 0 1 11.667451-23.339997c10.041064-19.007209 21.708515-35.811089 33.652337-51.562976z m-122.088585 79.763031c-3.255322 3.004423-7.060839 6.536116-10.316162 10.061441a419.628328 419.628328 0 0 0-122.614581 274.32456h144.874565c2.703853-91.430482 19.529385-176.648342 46.370508-247.447775a478.47757 478.47757 0 0 1-58.31433-36.934405zM92.051999 535.189712a420.779662 420.779662 0 0 0 122.614581 274.319464l10.316162 10.293237a425.318773 425.318773 0 0 1 58.318151-37.413278c-26.841123-71.096182-43.666655-155.742195-46.370508-247.19815H92.051999z m169.286933 314.488813a415.88267 415.88267 0 0 0 85.732395 49.383847 360.53328 360.53328 0 0 1-33.652337-51.562976c-4.075521-7.313012-8.137031-15.176219-11.667451-22.788528a416.635367 416.635367 0 0 0-40.412607 24.962563z m273.441954 82.465611a371.780443 371.780443 0 0 0 46.646879-5.40898c33.402711-20.335574 64.02388-55.620666 89.013189-101.223194 3.504948-6.787015 7.586836-13.823656 10.541589-20.887042a133.885943 133.885943 0 0 0-17.076431-6.511918 405.259571 405.259571 0 0 0-129.120132-26.594045v160.625179z m142.169439-33.081764a416.339892 416.339892 0 0 0 85.71329-49.383847 390.678122 390.678122 0 0 0-40.137509-24.962563c-4.075521 7.612308-8.137031 15.475516-11.942548 22.788528-10.592533 18.706639-21.684316 36.362558-33.623045 51.562976z m121.789288-79.259959l10.591259-10.293237a420.780936 420.780936 0 0 0 122.614582-274.319464H787.101455c-2.729325 91.455954-19.530658 176.101968-46.672351 247.198149a395.620965 395.620965 0 0 1 58.315604 37.414552z m-99.82478-558.105599a203.624467 203.624467 0 0 1-19.004662 7.33721 460.617875 460.617875 0 0 1-145.124191 29.826442v190.500018h206.195866c-2.452954-84.393841-17.375727-162.553409-42.064466-227.66367z m-209.69954 37.163652a456.062207 456.062207 0 0 1-144.873292-29.826442 187.929892 187.929892 0 0 1-19.530658-7.33721c-24.412368 65.110261-39.608965 143.269829-42.064466 227.66367H489.215293V298.860466z m0 426.554165v-190.224919H282.746877c2.454228 84.144215 17.652098 161.977742 42.064466 227.388572 6.510644-2.428756 13.020014-5.158081 19.530658-7.33721 45.019218-17.354076 93.864332-27.396413 144.873292-29.826443z m45.570687 0a465.365857 465.365857 0 0 1 144.874566 29.826443c6.50937 2.17913 12.743643 4.908455 19.254287 7.33721 24.688739-65.410831 39.608965-143.244357 42.064466-227.388572H534.78598v190.224919z"
|
||||
fill="#3B3F51"
|
||||
p-id="6176"
|
||||
></path>
|
||||
</svg>
|
||||
<span class="dark:text-gray-300">{{ tenant.tenantDomain }}</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<el-button
|
||||
class="!p-0"
|
||||
icon="HomeFilled"
|
||||
@click="individuationRef.openDialog(tenant.id)"
|
||||
text
|
||||
type="primary"
|
||||
v-auth="'sys_systenant_edit'"
|
||||
>{{ $t('tenant.individuationBtn') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
class="!p-0"
|
||||
icon="edit-pen"
|
||||
@click="formDialogRef.openDialog(tenant.id)"
|
||||
text
|
||||
type="primary"
|
||||
v-auth="'sys_systenant_edit'"
|
||||
>{{ $t('common.editBtn') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
class="!p-0"
|
||||
icon="delete"
|
||||
:disabled="tenant.id === '1'"
|
||||
@click="handleDelete([tenant.id])"
|
||||
text
|
||||
type="primary"
|
||||
v-auth="'sys_systenant_del'"
|
||||
>{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
|
||||
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
|
||||
<!-- 编辑、新增 -->
|
||||
<form-dialog @refresh="getDataList()" ref="formDialogRef" />
|
||||
|
||||
<!-- 编辑、新增 -->
|
||||
<individuation ref="individuationRef" />
|
||||
|
||||
<!-- 导入excel -->
|
||||
<upload-excel
|
||||
:title="$t('tenant.importTenantTip')"
|
||||
@refreshDataList="getDataList"
|
||||
ref="excelUploadRef"
|
||||
temp-url="/admin/sys-file/local/file/tenant.xlsx"
|
||||
url="/admin/tenant/import"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemTenant" setup>
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { delObj, fetchPage, fetchList } from '/@/api/admin/tenant';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const Individuation = defineAsyncComponent(() => import('./individuation.vue'));
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref();
|
||||
const individuationRef = ref();
|
||||
const excelUploadRef = ref();
|
||||
const tenantMenuRef = ref();
|
||||
// 搜索变量
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
// 字典
|
||||
const { status_type } = useDict('status_type');
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
pageList: fetchPage,
|
||||
pagination: {
|
||||
size: 6,
|
||||
pageSizes: [3, 6, 9, 12],
|
||||
},
|
||||
});
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/tenant/export', Object.assign(state.queryForm, { ids: selectObjs }), 'tenant.xlsx');
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
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);
|
||||
} finally {
|
||||
handleRefreshCache();
|
||||
}
|
||||
};
|
||||
|
||||
//刷新缓存
|
||||
const handleRefreshCache = () => {
|
||||
fetchList().then(() => {
|
||||
useMessage().success('同步成功');
|
||||
});
|
||||
};
|
||||
</script>
|
||||
137
src/views/admin/system/tenant/individuation.vue
Normal file
137
src/views/admin/system/tenant/individuation.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<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";
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['refresh']);
|
||||
const {t} = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 字典
|
||||
const {status_type} = useDict('status_type');
|
||||
|
||||
// 导入配置文件
|
||||
const stores = useThemeConfig(pinia);
|
||||
const {themeConfig} = storeToRefs(stores);
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
websiteName: themeConfig.value.globalTitle,
|
||||
background: '',
|
||||
miniQr: '',
|
||||
footer: themeConfig.value.footerAuthor,
|
||||
});
|
||||
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string): void => {
|
||||
visible.value = true;
|
||||
form.id = ''
|
||||
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
if (id) {
|
||||
form.id = id;
|
||||
getTenantData(id);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化表格数据。
|
||||
* @param {string} id - 部门 ID。
|
||||
*/
|
||||
const getTenantData = async (id: any) => {
|
||||
const res = await getObj(id);
|
||||
Object.assign(form, res.data);
|
||||
};
|
||||
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
224
src/views/admin/system/user/form.vue
Normal file
224
src/views/admin/system/user/form.vue
Normal file
@@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<div class="system-user-dialog-container">
|
||||
<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="true" placeholder="请输入用户名" v-model="dataForm.username"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="$t('sysuser.name')" prop="name">
|
||||
<el-input disabled clearable placeholder="请输入姓名" v-model="dataForm.name"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="部门/车队" prop="deptName">
|
||||
<el-input disabled clearable placeholder="请输入手机号" v-model="dataForm.deptName"></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 placeholder="请选择角色" v-model="dataForm.roleId">
|
||||
<el-option :key="item.roleId" :label="item.roleName" :value="item.roleId" v-for="item in roleData" />
|
||||
</el-select>
|
||||
</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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemUserDialog" setup>
|
||||
import { addObj, getObj, putObj, validatePhone, validateUsername } from '/@/api/admin/user';
|
||||
import { list as roleList } from '/@/api/admin/role';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { rule } from '/@/utils/validate';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义刷新表格emit
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const roleData = ref<any[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
const dataForm = reactive({
|
||||
userId: '',
|
||||
username: '',
|
||||
password: '' as String | undefined,
|
||||
salt: '',
|
||||
wxOpenid: '',
|
||||
qqOpenid: '',
|
||||
lockFlag: '0',
|
||||
phone: '' as String | undefined,
|
||||
deptId: '',
|
||||
roleList: [],
|
||||
postList: [],
|
||||
nickname: '',
|
||||
name: '',
|
||||
email: '',
|
||||
post: [] as string[],
|
||||
role: [] as string[],
|
||||
roleId: '',
|
||||
});
|
||||
|
||||
const dataRules = ref({
|
||||
// 用户名校验,不能为空 、长度 5-20、不能和已有数据重复
|
||||
username: [
|
||||
{ required: true, message: '用户名不能为空', trigger: 'blur' },
|
||||
{ min: 5, max: 20, message: '用户名称长度必须介于 5 和 20 之间', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateUsername(rule, value, callback, dataForm.userId !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '密码不能为空', trigger: 'blur' },
|
||||
{
|
||||
min: 6,
|
||||
max: 20,
|
||||
message: '用户密码长度必须介于 5 和 20 之间',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
// 姓名校验,不能为空、只能是中文
|
||||
name: [
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
{ required: true, message: '姓名不能为空', trigger: 'blur' },
|
||||
{ validator: rule.chinese, trigger: 'blur' },
|
||||
],
|
||||
deptId: [{ required: true, message: '部门不能为空', trigger: 'blur' }],
|
||||
role: [{ required: true, message: '角色不能为空', trigger: 'blur' }],
|
||||
post: [{ required: true, message: '岗位不能为空', trigger: 'blur' }],
|
||||
// 手机号校验,不能为空、新增的时不能重复校验
|
||||
phone: [
|
||||
{ required: true, message: '手机号不能为空', trigger: 'blur' },
|
||||
{ validator: rule.validatePhone, trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validatePhone(rule, value, callback, dataForm.userId !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
email: [
|
||||
{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] },
|
||||
{ validator: rule.overLength, trigger: 'blur' },
|
||||
],
|
||||
lockFlag: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async (id: string) => {
|
||||
visible.value = true;
|
||||
dataForm.userId = '';
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
// 修改获取用户信息
|
||||
if (id) {
|
||||
dataForm.userId = id;
|
||||
await getUserData(id);
|
||||
dataForm.password = '******';
|
||||
}
|
||||
|
||||
// 加载使用的数据
|
||||
getRoleData();
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) return false;
|
||||
|
||||
try {
|
||||
const { userId, phone, password, roleId } = dataForm;
|
||||
|
||||
if (userId) {
|
||||
// 清除占位符,避免提交错误的数据
|
||||
if (phone?.includes('*')) dataForm.phone = undefined;
|
||||
if (password?.includes('******')) dataForm.password = undefined;
|
||||
|
||||
loading.value = true;
|
||||
if (roleId) {
|
||||
dataForm.role = [roleId];
|
||||
}
|
||||
await putObj(dataForm);
|
||||
useMessage().success(t('common.editSuccessText'));
|
||||
visible.value = false; // 关闭弹窗
|
||||
emit('refresh');
|
||||
} else {
|
||||
loading.value = true;
|
||||
await addObj(dataForm);
|
||||
useMessage().success(t('common.addSuccessText'));
|
||||
visible.value = false; // 关闭弹窗
|
||||
emit('refresh');
|
||||
}
|
||||
} catch (error: any) {
|
||||
useMessage().error(error.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 从服务器获取用户数据
|
||||
*
|
||||
* @async
|
||||
* @param {string} id - 用户 ID
|
||||
* @return {Promise} - 包含用户数据的 Promise 对象
|
||||
*/
|
||||
const getUserData = async (id: string): Promise<any> => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const { data } = await getObj(id);
|
||||
Object.assign(dataForm, data);
|
||||
if (data.roleList) {
|
||||
dataForm.role = data.roleList.map((item: { roleId: any }) => item.roleId);
|
||||
dataForm.roleId = dataForm.roleList[0].roleId;
|
||||
}
|
||||
if (data.postList) {
|
||||
dataForm.post = data.postList.map((item: { postId: any }) => item.postId);
|
||||
}
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化部门数据
|
||||
// 角色数据
|
||||
const getRoleData = () => {
|
||||
roleList().then((res) => {
|
||||
roleData.value = res.data;
|
||||
// 默认选择第一个
|
||||
if (!dataForm.userId) {
|
||||
dataForm.role = [res.data[0].roleId];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
29
src/views/admin/system/user/i18n/en.ts
Normal file
29
src/views/admin/system/user/i18n/en.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export default {
|
||||
sysuser: {
|
||||
index: '#',
|
||||
username: 'HR No.',
|
||||
name: 'name',
|
||||
phone: 'phone',
|
||||
post: 'post',
|
||||
role: 'role',
|
||||
lockFlag: 'lockFlag',
|
||||
createTime: 'createTime',
|
||||
password: 'password',
|
||||
dept: 'dept',
|
||||
email: 'email',
|
||||
nickname: 'nickname',
|
||||
inputUsernameTip: 'input hr no.',
|
||||
inputPhoneTip: 'input phone',
|
||||
inputPasswordTip: 'input password',
|
||||
inputNameTip: 'input name',
|
||||
importUserTip: 'user import',
|
||||
deleteDisabledTip: 'admin are not allowed to delete',
|
||||
noDataScopeTip: 'no data permissions',
|
||||
passwordBtn: 'password',
|
||||
},
|
||||
personal: {
|
||||
name: 'personal info',
|
||||
passwordRule: 'The two passwords are inconsistent',
|
||||
passwordScore: 'Password level is too low',
|
||||
},
|
||||
};
|
||||
29
src/views/admin/system/user/i18n/zh-cn.ts
Normal file
29
src/views/admin/system/user/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export default {
|
||||
sysuser: {
|
||||
index: '#',
|
||||
username: 'HR编码',
|
||||
name: '姓名',
|
||||
phone: '手机号',
|
||||
post: '岗位',
|
||||
role: '角色',
|
||||
lockFlag: '启用',
|
||||
createTime: '创建时间',
|
||||
password: '密码',
|
||||
dept: '部门',
|
||||
email: '邮箱',
|
||||
nickname: '昵称',
|
||||
inputUsernameTip: '请输入HR编码',
|
||||
inputPhoneTip: '请输入手机号',
|
||||
inputNameTip: '请输入姓名',
|
||||
inputPasswordTip: '请输入6-20位合法密码',
|
||||
importUserTip: '用户导入',
|
||||
deleteDisabledTip: 'admin 不允许被删除',
|
||||
noDataScopeTip: '没有数据权限',
|
||||
passwordBtn: '密码',
|
||||
},
|
||||
personal: {
|
||||
name: '个人信息',
|
||||
passwordRule: '两次输入密码不一致',
|
||||
passwordScore: '密码等级太低',
|
||||
},
|
||||
};
|
||||
262
src/views/admin/system/user/index.vue
Normal file
262
src/views/admin/system/user/index.vue
Normal file
@@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<splitpanes>
|
||||
<pane size="15">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-scrollbar>
|
||||
<query-tree :placeholder="$t('common.queryDeptTip')" :query="deptData.queryList" :show-expand="true" @node-click="handleNodeClick">
|
||||
<!-- 没有数据权限提示 -->
|
||||
<template #default="{ node, data }">
|
||||
<el-tooltip v-if="data.isLock" class="item" effect="dark" :content="$t('sysuser.noDataScopeTip')" placement="right-start">
|
||||
<span
|
||||
>{{ node.label }}
|
||||
<SvgIcon name="ele-Lock" />
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span v-if="!data.isLock">{{ node.label }}</span>
|
||||
</template>
|
||||
</query-tree>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</pane>
|
||||
<pane class="ml8">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row v-show="showSearch">
|
||||
<el-form ref="queryRef" :inline="true" :model="state.queryForm" @keyup.enter="getDataList">
|
||||
<el-form-item :label="$t('sysuser.username')" prop="username">
|
||||
<el-input v-model="state.queryForm.username" :placeholder="$t('sysuser.inputUsernameTip')" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('sysuser.name')" prop="name">
|
||||
<el-input v-model="state.queryForm.name" :placeholder="$t('sysuser.inputNameTip')" clearable />
|
||||
</el-form-item>
|
||||
<!-- <el-form-item :label="$t('sysuser.phone')" prop="phone">
|
||||
<el-input v-model="state.queryForm.phone" :placeholder="$t('sysuser.inputPhoneTip')" clearable />
|
||||
</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 v-auth="'sys_user_add'" icon="folder-add" type="primary" @click="userDialogRef.openDialog()">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<el-button plain v-auth="'sys_user_add'" class="ml10" icon="upload-filled" type="primary" @click="excelUploadRef.show()">
|
||||
{{ $t('common.importBtn') }}
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
plain
|
||||
v-auth="'sys_user_del'"
|
||||
:disabled="multiple"
|
||||
class="ml10"
|
||||
icon="Delete"
|
||||
type="primary"
|
||||
@click="handleDelete(selectObjs)"
|
||||
>
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
:export="'sys_user_export'"
|
||||
@exportExcel="exportExcel"
|
||||
@queryTable="getDataList"
|
||||
class="ml10 mr20"
|
||||
style="float: right"
|
||||
/>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
v-loading="state.loading"
|
||||
:data="state.dataList"
|
||||
@selection-change="handleSelectionChange"
|
||||
row-key="userId"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column :selectable="handleSelectable" type="selection" width="40" />
|
||||
<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="name" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="公司" prop="companyName" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="部门" prop="deptName" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="岗位" prop="gangwei" show-overflow-tooltip></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>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('sysuser.lockFlag')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.lockFlag" @change="changeSwitch(scope.row)" active-value="0" inactive-value="9"></el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('common.action')" width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
<div style="display: flex">
|
||||
<!-- 重置密码 -->
|
||||
<popover-input v-model="inputPassword" @confirm="changePassword(scope.row)">
|
||||
<template #default>
|
||||
<el-button v-auth="'sys_user_edit'" icon="RefreshLeft" text type="primary" class="mr-4">
|
||||
{{ $t('sysuser.passwordBtn') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</popover-input>
|
||||
<!-- 修改信息 -->
|
||||
<el-button v-auth="'sys_user_edit'" icon="edit-pen" text type="primary" @click="userDialogRef.openDialog(scope.row.userId)">
|
||||
{{ $t('common.editBtn') }}
|
||||
</el-button>
|
||||
<!-- 删除用户 -->
|
||||
<el-tooltip :content="$t('sysuser.deleteDisabledTip')" :disabled="scope.row.userId !== '1'" placement="top">
|
||||
<span style="margin-left: 12px">
|
||||
<el-button
|
||||
icon="delete"
|
||||
v-auth="'sys_user_del'"
|
||||
:disabled="scope.row.username === 'admin'"
|
||||
text
|
||||
type="primary"
|
||||
@click="handleDelete([scope.row.userId])"
|
||||
>{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination v-bind="state.pagination" @current-change="currentChangeHandle" @size-change="sizeChangeHandle"> </pagination>
|
||||
</div>
|
||||
</pane>
|
||||
</splitpanes>
|
||||
|
||||
<user-form ref="userDialogRef" @refresh="getDataList(false)" :deptId="state.queryForm.deptId" />
|
||||
|
||||
<upload-excel
|
||||
ref="excelUploadRef"
|
||||
:title="$t('sysuser.importUserTip')"
|
||||
temp-url="/admin/sys-file/local/file/user.xlsx"
|
||||
url="/admin/user/import"
|
||||
@refreshDataList="getDataList"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemUser" setup>
|
||||
import { delObj, pageList, putObj } from '/@/api/admin/user';
|
||||
import { deptTree } from '/@/api/admin/dept';
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
// 动态引入组件
|
||||
const UserForm = defineAsyncComponent(() => import('./form.vue'));
|
||||
const QueryTree = defineAsyncComponent(() => import('/@/components/QueryTree/index.vue'));
|
||||
const PopoverInput = defineAsyncComponent(() => import('/@/components/PopoverInput/index.vue'));
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const userDialogRef = ref();
|
||||
const excelUploadRef = ref();
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
const inputPassword = ref();
|
||||
|
||||
// 多选rows
|
||||
const selectObjs = ref([]) as any;
|
||||
// 是否可以多选
|
||||
const multiple = ref(true);
|
||||
|
||||
// 定义表格查询、后台调用的API
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
deptId: '',
|
||||
username: '',
|
||||
name: '',
|
||||
},
|
||||
pageList: pageList,
|
||||
});
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 部门树使用的数据
|
||||
const deptData = reactive({
|
||||
queryList: async (name: String) => {
|
||||
const res = await deptTree({ deptName: name });
|
||||
return res.data || [];
|
||||
},
|
||||
});
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value?.resetFields();
|
||||
state.queryForm.deptId = '';
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 点击树
|
||||
const handleNodeClick = (e: any) => {
|
||||
state.queryForm.deptId = e.id;
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/admin/user/export', Object.assign(state.queryForm, { ids: selectObjs }), 'users.xlsx');
|
||||
};
|
||||
|
||||
// 是否可以多选
|
||||
const handleSelectable = (row: any) => {
|
||||
return row.username !== 'admin';
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { userId: string }[]) => {
|
||||
selectObjs.value = objs.map(({ userId }) => userId);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
//表格内开关 (用户状态)
|
||||
const changeSwitch = async (row: any) => {
|
||||
// 不修改密码
|
||||
row.password = undefined;
|
||||
row.name = undefined;
|
||||
await putObj(row);
|
||||
useMessage().success(t('common.optSuccessText'));
|
||||
getDataList();
|
||||
};
|
||||
|
||||
//修改用户密码
|
||||
const changePassword = async (row: any) => {
|
||||
if (!inputPassword.value || inputPassword.value.length < 6 || inputPassword.value.length > 20) {
|
||||
useMessage().error(t('sysuser.inputPasswordTip'));
|
||||
return;
|
||||
}
|
||||
|
||||
row.name = undefined;
|
||||
row.password = inputPassword.value;
|
||||
await putObj(row);
|
||||
useMessage().success(t('common.optSuccessText'));
|
||||
getDataList();
|
||||
};
|
||||
</script>
|
||||
565
src/views/admin/system/user/personal.vue
Normal file
565
src/views/admin/system/user/personal.vue
Normal file
@@ -0,0 +1,565 @@
|
||||
<template>
|
||||
<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>
|
||||
<el-form :model="formData" :rules="ruleForm" label-width="100px" class="mt30" ref="formdataRef">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item prop="avatar">
|
||||
<ImageUpload v-model:imageUrl="formData.avatar" borderRadius="50%">
|
||||
<template #empty>
|
||||
<el-icon><Avatar /></el-icon>
|
||||
<span>请上传头像</span>
|
||||
</template>
|
||||
</ImageUpload>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input v-model="formData.username" clearable disabled></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="手机" prop="phone">
|
||||
<el-input v-model="formData.phone" placeholder="请输入手机" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="formData.email" placeholder="请输入邮箱" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="昵称" prop="nickname">
|
||||
<el-input v-model="formData.nickname" placeholder="请输入昵称" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入姓名" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSaveUser"> 更新个人信息 </el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</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>
|
||||
<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-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="新密码" prop="newpassword1">
|
||||
<strength-meter
|
||||
v-model="passwordFormData.newpassword1"
|
||||
:minlength="6"
|
||||
:maxlength="16"
|
||||
placeholder="请输入新密码"
|
||||
@score="passwordScore"
|
||||
></strength-meter>
|
||||
<!-- <el-input v-model="passwordFormData.newpassword1" clearable type="password"></el-input>-->
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item label="确认密码" prop="newpassword2">
|
||||
<strength-meter v-model="passwordFormData.newpassword2" :minlength="6" :maxlength="16" placeholder="请重复密码"></strength-meter>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleChangePassword"> 修改密码 </el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</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>
|
||||
<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>
|
||||
<el-table-column label="状态">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.openId"> 已绑定 </el-tag>
|
||||
<el-tag v-else> 未绑定 </el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="action" label="操作">
|
||||
<template #default="scope">
|
||||
<el-button @click="unbinding(scope.row.type)" text type="primary" v-if="scope.row.openId"> 解绑 </el-button>
|
||||
<el-button @click="handleClick(scope.row.type)" text type="primary" v-else> 绑定 </el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-drawer>
|
||||
</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 other from '/@/utils/other';
|
||||
import {Session} from '/@/utils/storage';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {getLoginAppList} from "/@/api/admin/social";
|
||||
import { SocialLoginEnum } from '/@/api/login';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const ImageUpload = defineAsyncComponent(() => import('/@/components/Upload/Image.vue'));
|
||||
const StrengthMeter = defineAsyncComponent(() => import('/@/components/StrengthMeter/index.vue'));
|
||||
|
||||
const visible = ref(false);
|
||||
|
||||
// 定义变量内容
|
||||
const formData = ref({
|
||||
userId: '',
|
||||
username: '',
|
||||
name: '',
|
||||
email: '',
|
||||
avatar: '',
|
||||
nickname: '',
|
||||
wxDingUserid: '',
|
||||
wxCpUserid: '',
|
||||
phone: ('' as string) || undefined,
|
||||
});
|
||||
|
||||
const showPassword = ref(false);
|
||||
const passwordFormData = reactive({
|
||||
password: '',
|
||||
newpassword1: '',
|
||||
newpassword2: '',
|
||||
});
|
||||
|
||||
const formdataRef = ref();
|
||||
const passwordFormdataRef = ref();
|
||||
|
||||
const ruleForm = reactive({
|
||||
phone: [
|
||||
{ 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' }],
|
||||
});
|
||||
const validatorPassword2 = (rule: any, value: any, callback: any) => {
|
||||
if (value !== passwordFormData.newpassword1) {
|
||||
callback(new Error(t('personal.passwordRule')));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const validatorScore = (rule: any, value: any, callback: any) => {
|
||||
if (score.value <= 1) {
|
||||
callback(new Error(t('personal.passwordScore')));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const passwordRuleForm = reactive({
|
||||
password: [{ required: true, message: '密码不能为空', trigger: 'blur' }],
|
||||
newpassword1: [
|
||||
{
|
||||
min: 6,
|
||||
max: 20,
|
||||
message: '用户密码长度必须介于 6 和 20 之间',
|
||||
trigger: 'blur',
|
||||
},
|
||||
{ validator: validatorScore, trigger: 'blur' },
|
||||
],
|
||||
newpassword2: [
|
||||
{
|
||||
min: 6,
|
||||
max: 20,
|
||||
message: '用户密码长度必须介于 6 和 20 之间',
|
||||
trigger: 'blur',
|
||||
},
|
||||
{ validator: validatorPassword2, trigger: 'blur' },
|
||||
],
|
||||
});
|
||||
|
||||
const score = ref(0);
|
||||
|
||||
const passwordScore = (e: any) => {
|
||||
score.value = e;
|
||||
};
|
||||
|
||||
const handleChangePassword = () => {
|
||||
passwordFormdataRef.value.validate((valid: boolean) => {
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
password(passwordFormData)
|
||||
.then(() => {
|
||||
useMessage().success('修改成功');
|
||||
// 需要重新登录
|
||||
// 清除缓存/token等
|
||||
Session.clear();
|
||||
// 使用 reload 时,不需要调用 resetRoute() 重置路由
|
||||
window.location.reload();
|
||||
})
|
||||
.catch((err) => {
|
||||
useMessage().error(err.msg);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 保存用户
|
||||
const handleSaveUser = () => {
|
||||
formdataRef.value.validate((valid: boolean) => {
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (formData.value.phone && formData.value.phone.includes('*')) {
|
||||
formData.value.phone = undefined;
|
||||
}
|
||||
|
||||
editInfo(formData.value)
|
||||
.then(() => {
|
||||
useMessage().success('修改成功');
|
||||
// 更新上下文的 user信息
|
||||
useUserInfo().setUserInfos();
|
||||
})
|
||||
.catch((err) => {
|
||||
useMessage().error(err.msg);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const socialList = ref([] as any);
|
||||
|
||||
const initSocialList = () => {
|
||||
socialList.value = [
|
||||
{
|
||||
name: '企业微信',
|
||||
type: SocialLoginEnum.WEIXIN_CP,
|
||||
openId: formData.value.wxCpUserid,
|
||||
},
|
||||
{
|
||||
name: '钉钉办公',
|
||||
type: SocialLoginEnum.DINGTALK,
|
||||
openId: formData.value.wxDingUserid,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const handleClick = async (thirdpart: SocialLoginEnum) => {
|
||||
// 获取租户配置的账号信息
|
||||
const { data } = await getLoginAppList();
|
||||
const result = data.find((item: any) => item.type === thirdpart);
|
||||
if (validateNull(result)) {
|
||||
useMessage().error(t('scan.appErrorTip'));
|
||||
return;
|
||||
}
|
||||
|
||||
let redirect_uri, url;
|
||||
redirect_uri = encodeURIComponent(window.location.origin + '/#/authredirect');
|
||||
|
||||
if (thirdpart === SocialLoginEnum.WEIXIN_CP) {
|
||||
url = `https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=${result.appId}&agentid=${result.ext}&redirect_uri=${redirect_uri}&state=${SocialLoginEnum.WEIXIN_CP}-BIND`;
|
||||
}
|
||||
|
||||
if (thirdpart === SocialLoginEnum.DINGTALK) {
|
||||
url = `https://login.dingtalk.com/oauth2/auth?redirect_uri=${redirect_uri}&response_type=code&client_id=${result.appId}&scope=openid&state=${SocialLoginEnum.DINGTALK}-BIND&prompt=consent`;
|
||||
}
|
||||
|
||||
if (url) {
|
||||
other.openWindow(url, thirdpart, 540, 540);
|
||||
}
|
||||
};
|
||||
|
||||
const unbinding = (type: SocialLoginEnum) => {
|
||||
unbindingUser(type)
|
||||
.then(() => {
|
||||
useMessage().success('解绑成功');
|
||||
})
|
||||
.catch((err) => {
|
||||
useMessage().error(err.msg);
|
||||
})
|
||||
.finally(() => {
|
||||
initUserInfo(formData.value.userId);
|
||||
});
|
||||
};
|
||||
|
||||
const open = () => {
|
||||
visible.value = true;
|
||||
const data = useUserInfo().userInfos;
|
||||
initUserInfo(data.user.userId);
|
||||
// Object.assign(formData, data.user);
|
||||
};
|
||||
|
||||
const loading = ref(false);
|
||||
const initUserInfo = (userId: any) => {
|
||||
loading.value = true;
|
||||
getObj(userId)
|
||||
.then((res) => {
|
||||
formData.value = res.data;
|
||||
initSocialList();
|
||||
})
|
||||
.catch((err) => {
|
||||
useMessage().error(err.msg);
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
open,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/theme/mixins/index.scss';
|
||||
|
||||
.personal {
|
||||
.personal-user {
|
||||
height: 130px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.personal-user-left {
|
||||
width: 180px;
|
||||
height: 130px;
|
||||
border-radius: 3px;
|
||||
|
||||
:deep(.el-upload) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.personal-user-left-upload {
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
img {
|
||||
animation: logoAnimation 0.3s ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.personal-user-right {
|
||||
flex: 1;
|
||||
padding: 0 15px;
|
||||
|
||||
.personal-title {
|
||||
font-size: 18px;
|
||||
@include text-ellipsis(1);
|
||||
}
|
||||
|
||||
.personal-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
|
||||
.personal-item-label {
|
||||
color: var(--el-text-color-secondary);
|
||||
@include text-ellipsis(1);
|
||||
}
|
||||
|
||||
.personal-item-value {
|
||||
@include text-ellipsis(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.personal-info {
|
||||
.personal-info-more {
|
||||
float: right;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 13px;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.personal-info-box {
|
||||
height: 130px;
|
||||
overflow: hidden;
|
||||
|
||||
.personal-info-ul {
|
||||
list-style: none;
|
||||
|
||||
.personal-info-li {
|
||||
font-size: 13px;
|
||||
padding-bottom: 10px;
|
||||
|
||||
.personal-info-li-title {
|
||||
display: inline-block;
|
||||
@include text-ellipsis(1);
|
||||
color: var(--el-text-color-secondary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
& a:hover {
|
||||
color: var(--el-color-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.personal-recommend-row {
|
||||
.personal-recommend-col {
|
||||
.personal-recommend {
|
||||
position: relative;
|
||||
height: 100px;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
i {
|
||||
right: 0px !important;
|
||||
bottom: 0px !important;
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
bottom: -10px;
|
||||
font-size: 70px;
|
||||
transform: rotate(-30deg);
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
|
||||
.personal-recommend-auto {
|
||||
padding: 15px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 5%;
|
||||
color: var(--next-color-white);
|
||||
|
||||
.personal-recommend-msg {
|
||||
font-size: 12px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.personal-edit {
|
||||
.personal-edit-title {
|
||||
position: relative;
|
||||
padding-left: 10px;
|
||||
color: var(--el-text-color-regular);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 2px;
|
||||
height: 10px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.personal-edit-safe-box {
|
||||
border-bottom: 1px solid var(--el-border-color-light, #ebeef5);
|
||||
padding: 15px 0;
|
||||
|
||||
.personal-edit-safe-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.personal-edit-safe-item-left {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.personal-edit-safe-item-left-label {
|
||||
color: var(--el-text-color-regular);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.personal-edit-safe-item-left-value {
|
||||
color: var(--el-text-color-secondary);
|
||||
@include text-ellipsis(1);
|
||||
margin-right: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-icon.avatar-uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 178px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
14
src/views/admin/token/i18n/en.ts
Normal file
14
src/views/admin/token/i18n/en.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export default {
|
||||
systoken: {
|
||||
index: '#',
|
||||
userId: 'userId',
|
||||
username: 'username',
|
||||
clientId: 'clientId',
|
||||
accessToken: 'accessToken',
|
||||
expiresAt: 'expiresAt',
|
||||
inputUsernameTip: 'input Username',
|
||||
offlineBtn: 'offline',
|
||||
offlineConfirmText: 'offline confirm',
|
||||
offlineSuccessText: 'offline success',
|
||||
},
|
||||
};
|
||||
14
src/views/admin/token/i18n/zh-cn.ts
Normal file
14
src/views/admin/token/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export default {
|
||||
systoken: {
|
||||
index: '#',
|
||||
userId: '用户ID',
|
||||
username: '用户名',
|
||||
clientId: '客户端',
|
||||
accessToken: '令牌',
|
||||
expiresAt: '过期时间',
|
||||
inputUsernameTip: '请输入用户名',
|
||||
offlineBtn: '下线',
|
||||
offlineConfirmText: '确认下线',
|
||||
offlineSuccessText: '下线成功',
|
||||
},
|
||||
};
|
||||
122
src/views/admin/token/index.vue
Normal file
122
src/views/admin/token/index.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row class="ml10" v-show="showSearch">
|
||||
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
|
||||
<el-form-item :label="$t('systoken.username')" prop="username">
|
||||
<el-input :placeholder="$t('systoken.inputUsernameTip')" v-model="state.queryForm.username"></el-input>
|
||||
</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="mb8" style="width: 100%">
|
||||
<el-button :disabled="multiple" @click="handleDelete(selectObjs)" class="ml10" icon="Delete" type="primary" v-auth="'sys_user_del'">
|
||||
{{ $t('systoken.offlineBtn') }}
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
@queryTable="getDataList"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
v-model:showSearch="showSearch"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
@selection-change="handleSelectionChange"
|
||||
@sort-change="sortChangeHandle"
|
||||
style="width: 100%"
|
||||
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('systoken.index')" type="index" width="60" />
|
||||
<el-table-column :label="$t('systoken.username')" prop="username" show-overflow-tooltip width="150"></el-table-column>
|
||||
<el-table-column :label="$t('systoken.clientId')" prop="clientId" show-overflow-tooltip width="100"></el-table-column>
|
||||
<el-table-column :label="$t('systoken.accessToken')" prop="accessToken" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<el-button link type="danger" v-if="filterOwnToken(scope.row)">
|
||||
{{ scope.row.accessToken }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('systoken.expiresAt')" prop="expiresAt" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('common.action')" width="100">
|
||||
<template #default="scope">
|
||||
<el-button icon="delete" @click="handleDelete([scope.row.accessToken])" size="small" text type="primary" v-auth="'sys_user_del'">
|
||||
{{ $t('systoken.offlineBtn') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination"> </pagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { delObj, fetchList } from '/@/api/admin/token';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { Session } from '/@/utils/storage';
|
||||
|
||||
const { t } = useI18n();
|
||||
// 定义变量内容
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选rows
|
||||
const selectObjs = ref([]) as any;
|
||||
// 是否可以多选
|
||||
const multiple = ref(true);
|
||||
|
||||
// table hook
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
username: '',
|
||||
},
|
||||
pageList: fetchList,
|
||||
});
|
||||
const { getDataList, currentChangeHandle, sortChangeHandle, sizeChangeHandle, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value?.resetFields();
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { accessToken: string }[]) => {
|
||||
selectObjs.value = objs.map(({ accessToken }) => accessToken);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (accessTokens: string[]) => {
|
||||
try {
|
||||
await useMessageBox().confirm(t('systoken.offlineConfirmText'));
|
||||
} catch {
|
||||
return; // 取消删除则直接跳过此方法
|
||||
}
|
||||
|
||||
try {
|
||||
await delObj(accessTokens);
|
||||
getDataList();
|
||||
useMessage().success(t('systoken.offlineSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
|
||||
// 判断当前token 是否和登录token一致
|
||||
const filterOwnToken = (row: any) => {
|
||||
return Session.getToken() === row.accessToken;
|
||||
};
|
||||
</script>
|
||||
204
src/views/biz/app/appArticle/form.vue
Normal file
204
src/views/biz/app/appArticle/form.vue
Normal file
@@ -0,0 +1,204 @@
|
||||
<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>
|
||||
|
||||
<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';
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
const route = useRoute();
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
// 定义字典
|
||||
|
||||
const articleCateList = ref([]);
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
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'}],
|
||||
});
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化表单数据
|
||||
const getAppArticleData = (id: string) => {
|
||||
// 获取数据
|
||||
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;
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getAppCateList();
|
||||
if (route.query?.id) {
|
||||
getAppArticleData(route.query?.id);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.footer-btns {
|
||||
height: 60px;
|
||||
|
||||
&__content {
|
||||
bottom: 0;
|
||||
height: 60px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 99;
|
||||
@apply flex justify-center items-center shadow;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
6
src/views/biz/app/appArticle/i18n/en.ts
Normal file
6
src/views/biz/app/appArticle/i18n/en.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
article: {
|
||||
edit: 'edit article',
|
||||
add: 'add article',
|
||||
},
|
||||
};
|
||||
6
src/views/biz/app/appArticle/i18n/zh-cn.ts
Normal file
6
src/views/biz/app/appArticle/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
article: {
|
||||
edit: '编辑文章',
|
||||
add: '发布文章',
|
||||
},
|
||||
};
|
||||
133
src/views/biz/app/appArticle/index.vue
Normal file
133
src/views/biz/app/appArticle/index.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<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="title">
|
||||
<el-input placeholder="请输入标题" v-model="state.queryForm.title" />
|
||||
</el-form-item>
|
||||
<el-form-item label="作者" prop="author">
|
||||
<el-input placeholder="请输入作者" v-model="state.queryForm.author" />
|
||||
</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 v-auth="'app_appArticle_add'" icon="folder-add" type="primary" class="ml10" @click="addOrUpdate()"> 新 增 </el-button>
|
||||
|
||||
<el-button plain :disabled="multiple" icon="Delete" type="primary" v-auth="'app_appArticle_del'" @click="handleDelete(selectObjs)">
|
||||
删除
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
:export="'app_appArticle_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"
|
||||
@selection-change="handleSelectionChange"
|
||||
@sort-change="sortChangeHandle"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
>
|
||||
<el-table-column type="selection" width="40" align="center" />
|
||||
<el-table-column type="index" label="#" width="40" />
|
||||
<el-table-column prop="cname" label="分类" show-overflow-tooltip />
|
||||
<el-table-column prop="title" label="标题" show-overflow-tooltip />
|
||||
<el-table-column prop="author" label="作者" show-overflow-tooltip />
|
||||
<el-table-column prop="visit" label="浏览" show-overflow-tooltip />
|
||||
<el-table-column prop="sort" label="排序" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="150">
|
||||
<template #default="scope">
|
||||
<el-button icon="edit-pen" text type="primary" v-auth="'app_appArticle_edit'" @click="addOrUpdate(scope.row.id)">编辑</el-button>
|
||||
<el-button icon="delete" text type="primary" v-auth="'app_appArticle_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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemAppArticle">
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { fetchList, delObjs } from '/@/api/app/appArticle';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
// 定义查询字典
|
||||
|
||||
// 定义变量内容
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
// 搜索变量
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
pageList: fetchList,
|
||||
});
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
// 清空搜索条件
|
||||
queryRef.value?.resetFields();
|
||||
// 清空多选
|
||||
selectObjs.value = [];
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/app/appArticle/export', state.queryForm, 'appArticle.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { id: string }[]) => {
|
||||
selectObjs.value = objs.map(({ id }) => id);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
try {
|
||||
await useMessageBox().confirm('此操作将永久删除');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObjs(ids);
|
||||
getDataList();
|
||||
useMessage().success('删除成功');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
|
||||
// 跳转发布页
|
||||
const addOrUpdate = (id?: string) => {
|
||||
const tagsViewName = id ? `${t('article.edit')}:${id}` : t('article.add');
|
||||
router.push({
|
||||
path: '/biz/app/appArticle/form',
|
||||
query: { id: id, tagsViewName: tagsViewName },
|
||||
});
|
||||
};
|
||||
</script>
|
||||
104
src/views/biz/app/appArticleCategory/form.vue
Normal file
104
src/views/biz/app/appArticleCategory/form.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<el-dialog :title="form.id ? '编辑' : '新增'" 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="名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number :min="1" :max="1000" v-model="form.sort" placeholder="请输入排序"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否显示" prop="isShow">
|
||||
<el-radio-group v-model="form.isShow">
|
||||
<el-radio :label="Number(item.value)" v-for="(item, index) in yes_no_type" border :key="index">{{ item.label }} </el-radio>
|
||||
</el-radio-group>
|
||||
</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="AppArticleCategoryDialog">
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { getObj, addObj, putObj } from '/@/api/app/appArticleCategory';
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
// 定义字典
|
||||
const { yes_no_type } = useDict('yes_no_type');
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
name: '',
|
||||
sort: 0,
|
||||
isShow: 1,
|
||||
});
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
|
||||
isShow: [{ required: true, message: '是否显示', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true;
|
||||
form.id = '';
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
// 获取appArticleCategory信息
|
||||
if (id) {
|
||||
form.id = id;
|
||||
getappArticleCategoryData(id);
|
||||
}
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
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');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化表单数据
|
||||
const getappArticleCategoryData = (id: string) => {
|
||||
// 获取数据
|
||||
loading.value = true;
|
||||
getObj(id)
|
||||
.then((res: any) => {
|
||||
Object.assign(form, res.data);
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
124
src/views/biz/app/appArticleCategory/index.vue
Normal file
124
src/views/biz/app/appArticleCategory/index.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<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-input placeholder="请输入名称" v-model="state.queryForm.name" />
|
||||
</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_appArticleCategory_add'">
|
||||
新 增
|
||||
</el-button>
|
||||
<el-button plain :disabled="multiple" icon="Delete" type="primary" v-auth="'app_appArticleCategory_del'" @click="handleDelete(selectObjs)">
|
||||
删除
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
:export="'app_appArticleCategory_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"
|
||||
>
|
||||
<el-table-column type="selection" width="40" align="center" />
|
||||
<el-table-column type="index" label="#" width="40" />
|
||||
<el-table-column prop="name" label="名称" show-overflow-tooltip />
|
||||
<el-table-column prop="sort" label="排序" show-overflow-tooltip />
|
||||
<el-table-column prop="isShow" label="是否显示" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="yes_no_type" :value="scope.row.isShow"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150">
|
||||
<template #default="scope">
|
||||
<el-button icon="edit-pen" text type="primary" v-auth="'app_appArticleCategory_edit'" @click="formDialogRef.openDialog(scope.row.id)"
|
||||
>编辑</el-button
|
||||
>
|
||||
<el-button icon="delete" text type="primary" v-auth="'app_appArticleCategory_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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="systemAppArticleCategory">
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { fetchList, delObjs } from '/@/api/app/appArticleCategory';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
// 定义查询字典
|
||||
|
||||
const { yes_no_type } = useDict('yes_no_type');
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref();
|
||||
// 搜索变量
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
pageList: fetchList,
|
||||
});
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
// 清空搜索条件
|
||||
queryRef.value?.resetFields();
|
||||
// 清空多选
|
||||
selectObjs.value = [];
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/app/appArticleCategory/export', state.queryForm, 'appArticleCategory.xlsx');
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
try {
|
||||
await useMessageBox().confirm('此操作将永久删除');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObjs(ids);
|
||||
getDataList();
|
||||
useMessage().success('删除成功');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
135
src/views/biz/app/approle/form.vue
Normal file
135
src/views/biz/app/approle/form.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<el-dialog :close-on-click-modal="false" :title="form.roleId ? $t('common.editBtn') : $t('common.addBtn')" width="600" draggable v-model="visible">
|
||||
<el-form :model="form" :rules="dataRules" label-width="90px" ref="dataFormRef" v-loading="loading">
|
||||
<el-form-item :label="$t('approle.roleName')" prop="roleName">
|
||||
<el-input :placeholder="$t('approle.please_enter_a_role_name')" clearable v-model="form.roleName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('approle.roleCode')" prop="roleCode">
|
||||
<el-input :placeholder="$t('approle.please_enter_the_role_Code')" clearable v-model="form.roleCode"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('approle.roleDesc')" prop="roleDesc">
|
||||
<el-input :placeholder="$t('approle.please_enter_the_role_description')" maxlength="150" type="textarea" v-model="form.roleDesc"></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 @click="onSubmit" type="primary" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemRoleDialog" setup>
|
||||
import { rule } from '/@/utils/validate';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { addObj, getObj, putObj, validateAppRoleCode, validateApproleName } from '/@/api/app/approle';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
roleId: '',
|
||||
roleName: '',
|
||||
roleCode: '',
|
||||
roleDesc: '',
|
||||
});
|
||||
|
||||
// 页面对应元数据
|
||||
const dataForm = reactive({
|
||||
deptData: [],
|
||||
checkedDsScope: [],
|
||||
deptProps: {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
});
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
roleName: [
|
||||
{ required: true, message: '角色名称不能为空', trigger: 'blur' },
|
||||
{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateApproleName(rule, value, callback, form.roleId !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
roleCode: [
|
||||
{ required: true, message: '角色标识不能为空', trigger: 'blur' },
|
||||
{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' },
|
||||
{ validator: rule.validatorCapital, trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateAppRoleCode(rule, value, callback, form.roleId !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
roleDesc: [{ max: 128, message: '长度在 128 个字符内', trigger: 'blur' }],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true;
|
||||
form.roleId = '';
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
// 获取角色信息
|
||||
if (id) {
|
||||
form.roleId = id;
|
||||
getRoleData(id);
|
||||
}
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) return false;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
form.roleId ? await putObj(form) : await addObj(form);
|
||||
useMessage().success(t(form.roleId ? 'common.editSuccessText' : 'common.addSuccessText'));
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化角色数据
|
||||
const getRoleData = (id: string) => {
|
||||
// 获取部门数据
|
||||
getObj(id).then((res: any) => {
|
||||
Object.assign(form, res.data);
|
||||
if (res.data.dsScope) {
|
||||
dataForm.checkedDsScope = res.data.dsScope.split(',');
|
||||
} else {
|
||||
dataForm.checkedDsScope = [];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
15
src/views/biz/app/approle/i18n/en.ts
Normal file
15
src/views/biz/app/approle/i18n/en.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export default {
|
||||
approle: {
|
||||
index: '#',
|
||||
roleName: 'roleName',
|
||||
inputRoleNameTip: 'input roleName',
|
||||
permissionTip: 'grant',
|
||||
|
||||
roleCode: 'roleCode',
|
||||
roleDesc: 'role description',
|
||||
createTime: 'createTime',
|
||||
please_enter_a_role_name: 'please enter a role name',
|
||||
please_enter_the_role_Code: 'please enter the role Code',
|
||||
please_enter_the_role_description: 'please enter the role description',
|
||||
},
|
||||
};
|
||||
15
src/views/biz/app/approle/i18n/zh-cn.ts
Normal file
15
src/views/biz/app/approle/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export default {
|
||||
approle: {
|
||||
index: '#',
|
||||
roleName: '角色名',
|
||||
inputRoleNameTip: '请输入角色名称',
|
||||
permissionTip: '授权',
|
||||
|
||||
roleCode: '用户标识',
|
||||
roleDesc: '用户描述',
|
||||
createTime: '创建时间',
|
||||
please_enter_a_role_name: '请输入角色名称',
|
||||
please_enter_the_role_Code: '请输入角色标识',
|
||||
please_enter_the_role_description: '请输入角色描述',
|
||||
},
|
||||
};
|
||||
153
src/views/biz/app/approle/index.vue
Normal file
153
src/views/biz/app/approle/index.vue
Normal file
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row class="ml10" v-show="showSearch">
|
||||
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
|
||||
<el-form-item :label="$t('approle.roleName')" prop="roleName">
|
||||
<el-input :placeholder="$t('approle.inputRoleNameTip')" style="max-width: 180px" v-model="state.queryForm.roleName" />
|
||||
</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="mb8" style="width: 100%">
|
||||
<el-button @click="roleDialogRef.openDialog()" class="ml10" icon="folder-add" type="primary" v-auth="'app_approle_add'">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<el-button plain @click="excelUploadRef.show()" class="ml10" icon="upload-filled" type="primary" v-auth="'app_approle_export'">
|
||||
{{ $t('common.importBtn') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
plain
|
||||
:disabled="multiple"
|
||||
@click="handleDelete(selectObjs)"
|
||||
class="ml10"
|
||||
icon="Delete"
|
||||
type="primary"
|
||||
v-auth="'app_approle_del'"
|
||||
>
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
:export="'app_approle_export'"
|
||||
@exportExcel="exportExcel"
|
||||
@queryTable="getDataList"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
v-model:showSearch="showSearch"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
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('approle.index')" type="index" width="60" />
|
||||
<el-table-column :label="$t('approle.roleName')" prop="roleName" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('approle.roleCode')" prop="roleCode" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('approle.roleDesc')" prop="roleDesc" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('approle.createTime')" prop="createTime" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('common.action')" width="250">
|
||||
<template #default="scope">
|
||||
<el-button icon="edit-pen" @click="roleDialogRef.openDialog(scope.row.roleId)" text type="primary" v-auth="'app_approle_edit'"
|
||||
>{{ $t('common.editBtn') }}
|
||||
</el-button>
|
||||
|
||||
<el-button icon="delete" @click="handleDelete([scope.row.roleId])" text type="primary" v-auth="'app_approle_del'"
|
||||
>{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
<!-- 角色编辑、新增 -->
|
||||
<role-dialog @refresh="getDataList()" ref="roleDialogRef" />
|
||||
<!-- 导入角色 -->
|
||||
<upload-excel
|
||||
:title="$t('sysuser.importUserTip')"
|
||||
@refreshDataList="getDataList"
|
||||
ref="excelUploadRef"
|
||||
temp-url="/admin/sys-file/local/file/approle.xlsx"
|
||||
url="/admin/approle/import"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemRole" setup>
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { delObj, fetchList } from '/@/api/app/approle';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
// 引入组件
|
||||
const RoleDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const roleDialogRef = ref();
|
||||
const excelUploadRef = ref();
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选rows
|
||||
const selectObjs = ref([]) as any;
|
||||
|
||||
// 是否可以多选
|
||||
const multiple = ref(true);
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
roleName: '',
|
||||
},
|
||||
pageList: fetchList, // H
|
||||
descs: ['create_time'],
|
||||
});
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/app/approle/export', state.queryForm, 'approle.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { roleId: string }[]) => {
|
||||
selectObjs.value = objs.map(({ roleId }) => roleId);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
// 删除操作
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
151
src/views/biz/app/appsocial/form.vue
Normal file
151
src/views/biz/app/appsocial/form.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<el-dialog :close-on-click-modal="false" :title="form.id ? $t('common.editBtn') : $t('common.addBtn')" draggable v-model="visible">
|
||||
<el-form :model="form" :rules="dataRules" formDialogRef label-width="90px" ref="dataFormRef" v-loading="loading">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('appsocial.type')" prop="type">
|
||||
<el-select :placeholder="t('appsocial.inputTypeTip')" v-model="form.type">
|
||||
<el-option :key="index" :label="item.label" :value="item.value" v-for="(item, index) in app_social_type"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('appsocial.remark')" prop="remark">
|
||||
<el-input :placeholder="t('appsocial.inputRemarkTip')" v-model="form.remark" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('appsocial.appId')" prop="appId">
|
||||
<el-input :placeholder="t('appsocial.inputAppIdTip')" v-model="form.appId" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="t('appsocial.appSecret')" prop="appSecret">
|
||||
<el-input :placeholder="t('appsocial.inputAppSecretTip')" v-model="form.appSecret" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item :label="t('appsocial.redirectUrl')" prop="redirectUrl">
|
||||
<el-input :placeholder="t('appsocial.inputRedirectUrlTip')" type="textarea" v-model="form.redirectUrl" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24" class="mb20">
|
||||
<el-form-item :label="t('appsocial.ext')" prop="ext">
|
||||
<el-input :placeholder="t('appsocial.inputExtTip')" type="textarea" v-model="form.ext" />
|
||||
</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 lang="ts" name="AppSocialDetailsDialog" setup>
|
||||
// 定义子组件向父组件传值/事件
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { addObj, getObj, putObj } from '/@/api/app/appsocial';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { rule } from '/@/utils/validate';
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 定义字典
|
||||
const { app_social_type } = useDict('app_social_type');
|
||||
|
||||
// 提交表单数据
|
||||
const form = reactive({
|
||||
id: '',
|
||||
type: '',
|
||||
remark: '',
|
||||
appId: '' as string | undefined,
|
||||
appSecret: '' as string | undefined,
|
||||
redirectUrl: '',
|
||||
ext: '',
|
||||
});
|
||||
|
||||
// 定义校验规则
|
||||
const dataRules = ref({
|
||||
type: [{ required: true, message: '类型不能为空', trigger: 'blur' }],
|
||||
appId: [{ required: true, message: 'appId不能为空', trigger: 'blur' }],
|
||||
appSecret: [{ required: true, message: 'app秘钥不能为空', trigger: 'blur' }],
|
||||
|
||||
redirectUrl: [
|
||||
{ required: true, message: '回调地址不能为空', trigger: 'blur' },
|
||||
{ validator: rule.url, trigger: 'blur' },
|
||||
],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true;
|
||||
form.id = '';
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value?.resetFields();
|
||||
});
|
||||
|
||||
// 获取appSocialDetails信息
|
||||
if (id) {
|
||||
form.id = id;
|
||||
getappSocialDetailsData(id);
|
||||
}
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) return false;
|
||||
|
||||
// 隐藏敏感信息
|
||||
form.appSecret = form.appSecret?.includes('******') ? undefined : form.appSecret;
|
||||
form.appId = form.appId?.includes('******') ? undefined : form.appId;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
if (form.id) {
|
||||
await putObj(form);
|
||||
useMessage().success(t('common.editSuccessText'));
|
||||
} else {
|
||||
await addObj(form);
|
||||
useMessage().success(t('common.addSuccessText'));
|
||||
}
|
||||
visible.value = false; // 关闭弹窗
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化表单数据
|
||||
const getappSocialDetailsData = (id: string) => {
|
||||
// 获取数据
|
||||
getObj(id).then((res: any) => {
|
||||
Object.assign(form, res.data);
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
32
src/views/biz/app/appsocial/i18n/en.ts
Normal file
32
src/views/biz/app/appsocial/i18n/en.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export default {
|
||||
appsocial: {
|
||||
index: '#',
|
||||
importappSocialDetailsTip: 'import AppSocialDetails',
|
||||
id: 'id',
|
||||
type: 'type',
|
||||
remark: 'remark',
|
||||
appId: 'appId',
|
||||
appSecret: 'appSecret',
|
||||
redirectUrl: 'redirectUrl',
|
||||
ext: 'ext',
|
||||
createBy: 'createBy',
|
||||
updateBy: 'updateBy',
|
||||
createTime: 'createTime',
|
||||
updateTime: 'updateTime',
|
||||
delFlag: 'delFlag',
|
||||
tenantId: 'tenantId',
|
||||
inputIdTip: 'input id',
|
||||
inputTypeTip: 'input type',
|
||||
inputRemarkTip: 'input remark',
|
||||
inputAppIdTip: 'input appId',
|
||||
inputAppSecretTip: 'input appSecret',
|
||||
inputRedirectUrlTip: 'input redirectUrl',
|
||||
inputExtTip: 'input ext',
|
||||
inputCreateByTip: 'input createBy',
|
||||
inputUpdateByTip: 'input updateBy',
|
||||
inputCreateTimeTip: 'input createTime',
|
||||
inputUpdateTimeTip: 'input updateTime',
|
||||
inputDelFlagTip: 'input delFlag',
|
||||
inputTenantIdTip: 'input tenantId',
|
||||
},
|
||||
};
|
||||
32
src/views/biz/app/appsocial/i18n/zh-cn.ts
Normal file
32
src/views/biz/app/appsocial/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export default {
|
||||
appsocial: {
|
||||
index: '#',
|
||||
importappSocialDetailsTip: '导入系统社交登录账号表',
|
||||
id: '主鍵',
|
||||
type: '类型',
|
||||
remark: '描述',
|
||||
appId: 'appId',
|
||||
appSecret: 'app秘钥',
|
||||
redirectUrl: '回调地址',
|
||||
ext: '拓展字段',
|
||||
createBy: '创建人',
|
||||
updateBy: '修改人',
|
||||
createTime: '创建时间',
|
||||
updateTime: '更新时间',
|
||||
delFlag: '${field.fieldComment}',
|
||||
tenantId: '所属租户',
|
||||
inputIdTip: '请输入主鍵',
|
||||
inputTypeTip: '请输入类型',
|
||||
inputRemarkTip: '请输入描述',
|
||||
inputAppIdTip: '请输入appId',
|
||||
inputAppSecretTip: '请输入appSecret',
|
||||
inputRedirectUrlTip: '请输入回调地址',
|
||||
inputExtTip: '请输入拓展字段',
|
||||
inputCreateByTip: '请输入创建人',
|
||||
inputUpdateByTip: '请输入修改人',
|
||||
inputCreateTimeTip: '请输入创建时间',
|
||||
inputUpdateTimeTip: '请输入更新时间',
|
||||
inputDelFlagTip: '请输入${field.fieldComment}',
|
||||
inputTenantIdTip: '请输入所属租户',
|
||||
},
|
||||
};
|
||||
162
src/views/biz/app/appsocial/index.vue
Normal file
162
src/views/biz/app/appsocial/index.vue
Normal file
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row class="ml10" v-show="showSearch">
|
||||
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
|
||||
<el-form-item :label="t('appsocial.type')" class="ml2" prop="type">
|
||||
<el-select :placeholder="t('appsocial.inputTypeTip')" v-model="state.queryForm.type">
|
||||
<el-option :key="index" :label="item.label" :value="item.value" v-for="(item, index) in app_social_type"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList" formDialogRef icon="search" type="primary">
|
||||
{{ $t('common.queryBtn') }}
|
||||
</el-button>
|
||||
<el-button @click="resetQuery" formDialogRef icon="Refresh">{{ $t('common.resetBtn') }} </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button
|
||||
@click="formDialogRef.openDialog()"
|
||||
class="ml10"
|
||||
formDialogRef
|
||||
icon="folder-add"
|
||||
type="primary"
|
||||
v-auth="'app_social_details_add'"
|
||||
>
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
plain
|
||||
:disabled="multiple"
|
||||
@click="handleDelete(selectObjs)"
|
||||
class="ml10"
|
||||
formDialogRef
|
||||
icon="Delete"
|
||||
type="primary"
|
||||
v-auth="'app_social_details_del'"
|
||||
>
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
:export="'app_social_details_del'"
|
||||
@exportExcel="exportExcel"
|
||||
@queryTable="getDataList"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
v-model:showSearch="showSearch"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
@selection-change="handleSelectionChange"
|
||||
@sort-change="sortChangeHandle"
|
||||
style="width: 100%"
|
||||
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('appsocial.index')" type="index" width="60" />
|
||||
<el-table-column :label="t('appsocial.type')" prop="type" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="app_social_type" :value="scope.row.type"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('appsocial.remark')" prop="remark" show-overflow-tooltip />
|
||||
<el-table-column :label="t('appsocial.appId')" prop="appId" show-overflow-tooltip />
|
||||
<el-table-column :label="t('appsocial.appSecret')" prop="appSecret" show-overflow-tooltip />
|
||||
<el-table-column :label="t('appsocial.createTime')" prop="createTime" show-overflow-tooltip />
|
||||
<el-table-column :label="$t('common.action')" width="150">
|
||||
<template #default="scope">
|
||||
<el-button icon="edit-pen" @click="formDialogRef.openDialog(scope.row.id)" text type="primary" v-auth="'app_social_details_edit'"
|
||||
>{{ $t('common.editBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="delete" @click="handleDelete([scope.row.id])" text type="primary" v-auth="'app_social_details_del'"
|
||||
>{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination" />
|
||||
</div>
|
||||
|
||||
<!-- 编辑、新增 -->
|
||||
<form-dialog @refresh="getDataList()" ref="formDialogRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemAppSocialDetails" setup>
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { delObj, fetchList } from '/@/api/app/appsocial';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
// 引入组件
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
const { t } = useI18n();
|
||||
// 定义查询字典
|
||||
|
||||
const { app_social_type } = useDict('app_social_type');
|
||||
// 定义变量内容
|
||||
const formDialogRef = ref();
|
||||
// 搜索变量
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
// 多选变量
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
type: '',
|
||||
},
|
||||
pageList: fetchList,
|
||||
descs: ['create_time'],
|
||||
});
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
// 清空搜索条件
|
||||
queryRef.value.resetFields();
|
||||
// 清空多选
|
||||
selectObjs.value = [];
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/app/appsocial/export', state.queryForm, 'appsocial.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { id: string }[]) => {
|
||||
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 delObj(ids);
|
||||
getDataList();
|
||||
useMessage().success(t('common.delSuccessText'));
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
218
src/views/biz/app/appuser/form.vue
Normal file
218
src/views/biz/app/appuser/form.vue
Normal file
@@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<div class="system-user-dialog-container">
|
||||
<el-dialog v-model="visible" :close-on-click-modal="false" :title="dataForm.userId ? $t('common.editBtn') : $t('common.addBtn')" draggable>
|
||||
<el-form ref="dataFormRef" v-loading="loading" :model="dataForm" :rules="dataRules" label-width="90px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="$t('appuser.username')" prop="username">
|
||||
<el-input v-model="dataForm.username" :disabled="dataForm.userId !== ''" :placeholder="$t('appuser.inputUserNameTip')"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="$t('appuser.password')" prop="password">
|
||||
<el-input v-model="dataForm.password" :placeholder="$t('appuser.inputPasswordTip')" clearable type="password"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="$t('appuser.name')" prop="name">
|
||||
<el-input v-model="dataForm.name" :placeholder="$t('appuser.inputNameTip')" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="$t('appuser.phone')" prop="phone">
|
||||
<el-input v-model="dataForm.phone" :placeholder="$t('appuser.inputPhoneTip')" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="$t('appuser.role')" prop="role">
|
||||
<el-select v-model="dataForm.role" :placeholder="$t('appuser.inputRoleTip')" clearable multiple>
|
||||
<el-option v-for="item in roleData" :key="item.roleId" :label="item.roleName" :value="item.roleId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="$t('appuser.email')" prop="email">
|
||||
<el-input v-model="dataForm.email" :placeholder="$t('appuser.inputEmailTip')" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="$t('appuser.nickname')" prop="nickname">
|
||||
<el-input v-model="dataForm.nickname" :placeholder="$t('appuser.inputNickNameTip')" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item :label="$t('appuser.lockFlag')" prop="lockFlag">
|
||||
<el-radio-group v-model="dataForm.lockFlag">
|
||||
<el-radio v-for="(item, index) in lock_flag" :key="index" :label="item.value" border>{{ 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 type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemUserDialog" setup>
|
||||
import { addObj, getObj, putObj, validatePhone, validateUsername } from '/@/api/app/appuser';
|
||||
import { list as roleList } from '/@/api/app/approle';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { rule } from '/@/utils/validate';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义刷新表格emit
|
||||
const emit = defineEmits(['refresh']);
|
||||
// @ts-ignore
|
||||
const { lock_flag } = useDict('lock_flag');
|
||||
|
||||
// 定义变量内容
|
||||
const dataFormRef = ref();
|
||||
const visible = ref(false);
|
||||
const roleData = ref<any[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
const dataForm = reactive({
|
||||
userId: '',
|
||||
username: '',
|
||||
password: '' as String | undefined,
|
||||
salt: '',
|
||||
wxOpenid: '',
|
||||
qqOpenid: '',
|
||||
lockFlag: '0',
|
||||
phone: '' as String | undefined,
|
||||
deptId: '',
|
||||
roleList: [],
|
||||
postList: [],
|
||||
nickname: '',
|
||||
name: '',
|
||||
email: '',
|
||||
post: [] as string[],
|
||||
role: [] as string[],
|
||||
});
|
||||
|
||||
const dataRules = ref({
|
||||
username: [
|
||||
{ required: true, message: '用户名不能为空', trigger: 'blur' },
|
||||
{ min: 3, max: 20, message: '用户名称长度必须介于 3 和 20 之间', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validateUsername(rule, value, callback, dataForm.userId !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
|
||||
password: [
|
||||
{ required: true, message: '密码不能为空', trigger: 'blur' },
|
||||
{
|
||||
min: 6,
|
||||
max: 20,
|
||||
message: '用户密码长度必须介于 6 和 20 之间',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
name: [
|
||||
{ required: true, message: '姓名不能为空', trigger: 'blur' },
|
||||
{ validator: rule.chinese, trigger: 'blur' },
|
||||
],
|
||||
role: [{ required: true, message: '角色不能为空', trigger: 'blur' }],
|
||||
phone: [
|
||||
{ required: true, message: '手机号不能为空', trigger: 'blur' },
|
||||
{ validator: rule.validatePhone, trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
validatePhone(rule, value, callback, dataForm.userId !== '');
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
|
||||
nickname: [{ required: true, message: '姓名不能为空', nickname: 'blur' }],
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = (id: string) => {
|
||||
visible.value = true;
|
||||
dataForm.userId = '';
|
||||
|
||||
// 重置表单数据
|
||||
nextTick(() => {
|
||||
dataFormRef.value.resetFields();
|
||||
});
|
||||
|
||||
// 修改获取用户信息
|
||||
if (id) {
|
||||
dataForm.userId = id;
|
||||
getUserData(id);
|
||||
}
|
||||
getRoleData();
|
||||
};
|
||||
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||||
if (!valid) return false;
|
||||
|
||||
try {
|
||||
const { userId, phone, password } = dataForm;
|
||||
|
||||
if (userId) {
|
||||
// 清除占位符,避免提交错误的数据
|
||||
if (phone?.includes('*')) dataForm.phone = undefined;
|
||||
if (password?.includes('******')) dataForm.password = undefined;
|
||||
|
||||
loading.value = true;
|
||||
await putObj(dataForm);
|
||||
useMessage().success(t('common.editSuccessText'));
|
||||
visible.value = false; // 关闭弹窗
|
||||
emit('refresh');
|
||||
} else {
|
||||
loading.value = true;
|
||||
await addObj(dataForm);
|
||||
useMessage().success(t('common.addSuccessText'));
|
||||
visible.value = false; // 关闭弹窗
|
||||
emit('refresh');
|
||||
}
|
||||
} catch (error: any) {
|
||||
useMessage().error(error.msg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
// 初始化用户信息数据
|
||||
const getUserData = async (id: string) => {
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const { data } = await getObj(id);
|
||||
Object.assign(dataForm, data);
|
||||
dataForm.password = '******';
|
||||
if (data.roleList) {
|
||||
dataForm.role = data.roleList.map((item: any) => item.roleId);
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 角色数据
|
||||
const getRoleData = () => {
|
||||
roleList().then((res) => {
|
||||
roleData.value = res.data;
|
||||
});
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
25
src/views/biz/app/appuser/i18n/en.ts
Normal file
25
src/views/biz/app/appuser/i18n/en.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export default {
|
||||
appuser: {
|
||||
index: '#',
|
||||
username: 'username',
|
||||
name: 'name',
|
||||
phone: 'phone',
|
||||
post: 'post',
|
||||
role: 'role',
|
||||
lockFlag: 'lockFlag',
|
||||
createTime: 'createTime',
|
||||
password: 'password',
|
||||
dept: 'dept',
|
||||
email: 'email',
|
||||
avatar: 'avatar',
|
||||
nickname: 'nickname',
|
||||
inputNameTip: 'input name',
|
||||
inputRoleTip: 'input role',
|
||||
inputUserNameTip: 'input username',
|
||||
inputPasswordTip: 'input Password',
|
||||
inputPhoneTip: 'input phone',
|
||||
inputEmailTip: 'input Email',
|
||||
inputNickNameTip: 'input NickName',
|
||||
importUserTip: 'import user',
|
||||
},
|
||||
};
|
||||
25
src/views/biz/app/appuser/i18n/zh-cn.ts
Normal file
25
src/views/biz/app/appuser/i18n/zh-cn.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export default {
|
||||
appuser: {
|
||||
index: '#',
|
||||
username: '用户名',
|
||||
name: '姓名',
|
||||
phone: '手机号',
|
||||
post: '岗位',
|
||||
role: '角色',
|
||||
lockFlag: '状态',
|
||||
createTime: '创建时间',
|
||||
password: '密码',
|
||||
dept: '部门',
|
||||
email: '邮箱',
|
||||
nickname: '昵称',
|
||||
avatar: '头像',
|
||||
inputNameTip: '请输入姓名',
|
||||
inputRoleTip: '请选择角色',
|
||||
inputUserNameTip: '请输入用户名',
|
||||
inputPasswordTip: '请输入密码',
|
||||
inputEmailTip: '请输入邮箱',
|
||||
inputPhoneTip: '请输入手机号码',
|
||||
inputNickNameTip: '请输入昵称',
|
||||
importUserTip: '导入用户',
|
||||
},
|
||||
};
|
||||
168
src/views/biz/app/appuser/index.vue
Normal file
168
src/views/biz/app/appuser/index.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
<el-row class="ml10" v-show="showSearch">
|
||||
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
|
||||
<el-form-item :label="$t('appuser.username')" prop="username">
|
||||
<el-input :placeholder="$t('appuser.inputUserNameTip')" @keyup.enter="getDataList" clearable v-model="state.queryForm.username" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('appuser.phone')" prop="phone">
|
||||
<el-input :placeholder="$t('appuser.inputPhoneTip')" @keyup.enter="getDataList" clearable v-model="state.queryForm.phone" />
|
||||
</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="mb8" style="width: 100%">
|
||||
<el-button @click="userDialogRef.openDialog()" class="ml10" icon="folder-add" type="primary" v-auth="'app_appuser_add'">
|
||||
{{ $t('common.addBtn') }}
|
||||
</el-button>
|
||||
<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'">
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
:export="'app_appuser_export'"
|
||||
@exportExcel="exportExcel"
|
||||
@queryTable="getDataList"
|
||||
class="ml10"
|
||||
style="float: right; margin-right: 20px"
|
||||
v-model:showSearch="showSearch"
|
||||
></right-toolbar>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
row-key="userId"
|
||||
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('appuser.index')" type="index" width="60" />
|
||||
<el-table-column :label="$t('appuser.username')" prop="username" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('appuser.nickname')" prop="nickname" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('appuser.avatar')" prop="avatar" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<div style="display: flex; justify-content: center">
|
||||
<ImageUpload v-model:imageUrl="scope.row.avatar" height="50px" width="50px" disabled />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('appuser.role')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<el-tag :key="index" v-for="(item, index) in scope.row.roleList">{{ item.roleName }} </el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('appuser.lockFlag')" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<dict-tag :options="lock_flag" :value="scope.row.lockFlag"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('appuser.createTime')" prop="createTime" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column :label="$t('common.action')" width="150">
|
||||
<template #default="scope">
|
||||
<el-button icon="edit-pen" @click="userDialogRef.openDialog(scope.row.userId)" text type="primary" v-auth="'app_appuser_edit'">
|
||||
{{ $t('common.editBtn') }}
|
||||
</el-button>
|
||||
<el-button icon="delete" @click="handleDelete([scope.row.userId])" text type="primary" v-auth="'app_appuser_del'">
|
||||
{{ $t('common.delBtn') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination"> </pagination>
|
||||
</div>
|
||||
<user-form @refresh="getDataList()" ref="userDialogRef" />
|
||||
|
||||
<upload-excel
|
||||
:title="$t('appuser.importUserTip')"
|
||||
@refreshDataList="getDataList"
|
||||
ref="excelUploadRef"
|
||||
temp-url="/admin/sys-file/local/file/appuser.xlsx"
|
||||
url="/admin/appuser/import"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="systemUser" setup>
|
||||
import { delObj, fetchList } from '/@/api/app/appuser';
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
// 动态引入组件
|
||||
const UserForm = defineAsyncComponent(() => import('./form.vue'));
|
||||
const ImageUpload = defineAsyncComponent(() => import('/@/components/Upload/Image.vue'));
|
||||
|
||||
const { lock_flag } = useDict('lock_flag');
|
||||
const { t } = useI18n();
|
||||
|
||||
// 定义变量内容
|
||||
const userDialogRef = ref();
|
||||
const excelUploadRef = ref();
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
|
||||
// 多选rows
|
||||
const selectObjs = ref([]) as any;
|
||||
// 是否可以多选
|
||||
const multiple = ref(true);
|
||||
|
||||
// 定义表格查询、后台调用的API
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {
|
||||
username: '',
|
||||
phone: '',
|
||||
},
|
||||
pageList: fetchList,
|
||||
descs: ['create_time'],
|
||||
});
|
||||
|
||||
// table hook
|
||||
const { getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle } = useTable(state);
|
||||
|
||||
// 清空搜索条件
|
||||
const resetQuery = () => {
|
||||
queryRef.value.resetFields();
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 导出excel
|
||||
const exportExcel = () => {
|
||||
downBlobFile('/app/appuser/export',Object.assign( state.queryForm,{ids:selectObjs}), 'users.xlsx');
|
||||
};
|
||||
|
||||
// 多选事件
|
||||
const handleSelectionChange = (objs: { userId: string }[]) => {
|
||||
selectObjs.value = objs.map(({ userId }) => userId);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
// 删除用户
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
80
src/views/biz/app/page/component/add-nav.vue
Normal file
80
src/views/biz/app/page/component/add-nav.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div>
|
||||
<div>
|
||||
<draggable class="draggable" v-model="navLists" item-key="index" animation="300">
|
||||
<template v-slot:item="{ element: item, index }">
|
||||
<del-wrap class="max-w-[400px]" :key="index" @close="handleDelete(index)">
|
||||
<div class="flex items-center w-full p-4 mb-4 cursor-move bg-fill-light">
|
||||
<upload-img v-model:imageUrl="item.image" height="50px" width="50px" iconSize="12" />
|
||||
|
||||
<div class="flex-1 ml-3">
|
||||
<div class="flex">
|
||||
<span class="flex-none mr-3 text-tx-regular">名称</span>
|
||||
<el-input v-model="item.name" placeholder="请输入名称" />
|
||||
</div>
|
||||
<div class="flex mt-[18px]">
|
||||
<span class="flex-none mr-3 text-tx-regular">链接</span>
|
||||
<link-picker v-model="item.link" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</del-wrap>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
<div>
|
||||
<el-button type="primary" @click="handleAdd">添加</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue';
|
||||
import Draggable from 'vuedraggable';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
const LinkPicker = defineAsyncComponent(() => import('/@/components/Link/picker.vue'));
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: () => [],
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
min: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
const navLists = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', value);
|
||||
},
|
||||
});
|
||||
|
||||
const handleAdd = () => {
|
||||
if (props.modelValue?.length < props.max) {
|
||||
navLists.value.push({
|
||||
image: '',
|
||||
name: '导航名称',
|
||||
link: {},
|
||||
});
|
||||
} else {
|
||||
useMessage().error(`最多添加${props.max}个`);
|
||||
}
|
||||
};
|
||||
const handleDelete = (index: number) => {
|
||||
if (props.modelValue?.length <= props.min) {
|
||||
return useMessage().error(`最少保留${props.min}个`);
|
||||
}
|
||||
navLists.value.splice(index, 1);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
57
src/views/biz/app/page/component/decoration-img.vue
Normal file
57
src/views/biz/app/page/component/decoration-img.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<el-image :style="styles" v-bind="props" :src="src.includes('http') ? src : baseURL + src">
|
||||
<template #placeholder>
|
||||
<div class="image-slot"></div>
|
||||
</template>
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<el-icon><Picture /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { imageProps } from 'element-plus';
|
||||
import other from '/@/utils/other';
|
||||
const props = defineProps({
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: 'auto',
|
||||
},
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: 'auto',
|
||||
},
|
||||
radius: {
|
||||
type: [String, Number],
|
||||
default: 0,
|
||||
},
|
||||
...imageProps,
|
||||
});
|
||||
|
||||
const styles = computed<CSSProperties>(() => {
|
||||
return {
|
||||
width: other.addUnit(props.width),
|
||||
height: other.addUnit(props.height),
|
||||
borderRadius: other.addUnit(props.radius),
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-image {
|
||||
display: block;
|
||||
.image-slot {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fafafa;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
25
src/views/biz/app/page/component/pages/attr-setting.vue
Normal file
25
src/views/biz/app/page/component/pages/attr-setting.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div class="pages-setting">
|
||||
<div class="title flex items-center before:w-[3px] before:h-[14px] before:block before:bg-primary before:mr-2">
|
||||
{{ widget?.title }}
|
||||
</div>
|
||||
<keep-alive>
|
||||
<component class="pt-5 pr-4" :is="widgets[widget?.name]?.attr" :content="widget?.content" :styles="widget?.styles" :type="type" />
|
||||
</keep-alive>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue';
|
||||
import widgets from '../widgets';
|
||||
|
||||
defineProps({
|
||||
widget: {
|
||||
type: Object as PropType<Record<string, any>>,
|
||||
default: () => ({}),
|
||||
},
|
||||
type: {
|
||||
type: String as PropType<'mobile' | 'pc'>,
|
||||
default: 'mobile',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
40
src/views/biz/app/page/component/pages/menu.vue
Normal file
40
src/views/biz/app/page/component/pages/menu.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<el-menu :default-active="modelValue" class="w-[160px] min-h-[668px] pages-menu" @select="handleSelect">
|
||||
<el-menu-item v-for="(item, key) in menus" :index="key" :key="item.id">
|
||||
<span>{{ item.name }}</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
defineProps({
|
||||
menus: {
|
||||
type: Object as PropType<Record<string, any>>,
|
||||
default: () => ({}),
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: '1',
|
||||
},
|
||||
});
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: string): void;
|
||||
}>();
|
||||
const handleSelect = (index: string) => {
|
||||
emit('update:modelValue', index);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pages-menu {
|
||||
:deep(.el-menu-item) {
|
||||
border-color: transparent;
|
||||
&.is-active {
|
||||
border-right-width: 2px;
|
||||
border-color: var(--el-color-primary);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
61
src/views/biz/app/page/component/pages/preview.vue
Normal file
61
src/views/biz/app/page/component/pages/preview.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="shadow mx-[30px] pages-preview">
|
||||
<div
|
||||
v-for="(widget, index) in pageData"
|
||||
:key="widget.id"
|
||||
class="relative"
|
||||
:class="{
|
||||
'cursor-pointer': !widget?.disabled,
|
||||
}"
|
||||
@click="handleClick(widget, index)"
|
||||
>
|
||||
<div
|
||||
class="absolute w-full h-full z-[100] border-dashed"
|
||||
:class="{
|
||||
select: index == modelValue,
|
||||
'border-[#dcdfe6] border-2': !widget?.disabled,
|
||||
}"
|
||||
></div>
|
||||
<slot>
|
||||
<component :is="widgets[widget?.name]?.content" :content="widget.content" :styles="widget.styles" :key="widget.id" />
|
||||
</slot>
|
||||
</div>
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import widgets from '../widgets';
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
defineProps({
|
||||
pageData: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: () => [],
|
||||
},
|
||||
modelValue: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: number): void;
|
||||
}>();
|
||||
|
||||
const handleClick = (widget: any, index: number) => {
|
||||
if (widget.disabled) return;
|
||||
emit('update:modelValue', index);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pages-preview {
|
||||
background-color: #f8f8f8;
|
||||
width: 360px;
|
||||
height: 585px;
|
||||
color: #333;
|
||||
.select {
|
||||
@apply border-primary border-solid;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
84
src/views/biz/app/page/component/widgets/banner/attr.vue
Normal file
84
src/views/biz/app/page/component/widgets/banner/attr.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-scrollbar style="height: 550px">
|
||||
<el-form label-width="70px">
|
||||
<el-form-item label="是否启用" v-if="type == 'mobile'">
|
||||
<el-radio-group v-model="content.enabled">
|
||||
<el-radio :label="1">开启</el-radio>
|
||||
<el-radio :label="0">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="图片设置">
|
||||
<div class="flex-1">
|
||||
<div class="form-tips">最多添加5张,建议图片尺寸:750px*340px</div>
|
||||
<draggable class="draggable" v-model="content.data" item-key="index" animation="300">
|
||||
<template v-slot:item="{ element: item, index }">
|
||||
<del-wrap :key="index" @close="handleDelete(index)" class="max-w-[640px]">
|
||||
<div class="flex items-center w-full p-1 mt-4 cursor-move bg-fill-light">
|
||||
<upload-img v-model:imageUrl="item.image" />
|
||||
<div class="flex-1 ml-3">
|
||||
<el-form-item label="图片名称">
|
||||
<el-input v-model="item.name" placeholder="请输入名称" />
|
||||
</el-form-item>
|
||||
<el-form-item class="mt-[18px]" label="图片链接">
|
||||
<link-picker v-if="type == 'mobile'" v-model="item.link" />
|
||||
<el-input v-if="type == 'pc'" placeholder="请输入链接" v-model="item.link.path" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</del-wrap>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="content.data?.length < limit">
|
||||
<el-button type="primary" @click="handleAdd">添加图片</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue';
|
||||
import type options from './options';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import Draggable from 'vuedraggable';
|
||||
const LinkPicker = defineAsyncComponent(() => import('/@/components/Link/picker.vue'));
|
||||
|
||||
const limit = 5;
|
||||
type OptionsType = ReturnType<typeof options>;
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object as PropType<OptionsType['content']>,
|
||||
default: () => ({}),
|
||||
},
|
||||
styles: {
|
||||
type: Object as PropType<OptionsType['styles']>,
|
||||
default: () => ({}),
|
||||
},
|
||||
type: {
|
||||
type: String as PropType<'mobile' | 'pc'>,
|
||||
default: 'mobile',
|
||||
},
|
||||
});
|
||||
|
||||
const handleAdd = () => {
|
||||
if (props.content.data?.length < limit) {
|
||||
props.content.data.push({
|
||||
image: '',
|
||||
name: '',
|
||||
link: {},
|
||||
});
|
||||
} else {
|
||||
useMessage().error(`最多添加${limit}张图片`);
|
||||
}
|
||||
};
|
||||
const handleDelete = (index: number) => {
|
||||
if (props.content.data?.length <= 1) {
|
||||
return useMessage().error('最少保留一张图片');
|
||||
}
|
||||
props.content.data.splice(index, 1);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
37
src/views/biz/app/page/component/widgets/banner/content.vue
Normal file
37
src/views/biz/app/page/component/widgets/banner/content.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div class="banner" :style="styles">
|
||||
<div class="banner-image w-full h-full">
|
||||
<decoration-img width="100%" :height="styles.height || height" :src="getImage" fit="contain" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue';
|
||||
import type options from './options';
|
||||
import DecorationImg from '../../decoration-img.vue';
|
||||
type OptionsType = ReturnType<typeof options>;
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object as PropType<OptionsType['content']>,
|
||||
default: () => ({}),
|
||||
},
|
||||
styles: {
|
||||
type: Object as PropType<OptionsType['styles']>,
|
||||
default: () => ({}),
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '170px',
|
||||
},
|
||||
});
|
||||
|
||||
const getImage = computed(() => {
|
||||
const { data } = props.content;
|
||||
if (Array.isArray(data)) {
|
||||
return data[0] ? data[0].image : '';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
8
src/views/biz/app/page/component/widgets/banner/index.ts
Normal file
8
src/views/biz/app/page/component/widgets/banner/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import attr from './attr.vue';
|
||||
import content from './content.vue';
|
||||
import options from './options';
|
||||
export default {
|
||||
attr,
|
||||
content,
|
||||
options,
|
||||
};
|
||||
15
src/views/biz/app/page/component/widgets/banner/options.ts
Normal file
15
src/views/biz/app/page/component/widgets/banner/options.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export default () => ({
|
||||
title: '首页轮播图',
|
||||
name: 'banner',
|
||||
content: {
|
||||
enabled: 1,
|
||||
data: [
|
||||
{
|
||||
image: '',
|
||||
name: '',
|
||||
link: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
styles: {},
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form label-width="90px">
|
||||
<el-form-item label="客服标题">
|
||||
<el-input class="w-[400px]" v-model="content.title" />
|
||||
</el-form-item>
|
||||
<el-form-item label="服务时间">
|
||||
<el-input class="w-[400px]" v-model="content.time" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话">
|
||||
<el-input class="w-[400px]" v-model="content.mobile" />
|
||||
</el-form-item>
|
||||
<el-form-item label="客服二维码">
|
||||
<div>
|
||||
<upload-img v-model:imageUrl="content.qrcode" />
|
||||
<div class="form-tips">建议图片尺寸:200*200像素;图片格式:jpg、png、jpeg</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue';
|
||||
import type options from './options';
|
||||
type OptionsType = ReturnType<typeof options>;
|
||||
defineProps({
|
||||
content: {
|
||||
type: Object as PropType<OptionsType['content']>,
|
||||
default: () => ({}),
|
||||
},
|
||||
styles: {
|
||||
type: Object as PropType<OptionsType['styles']>,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user