init: 初始系统模板
This commit is contained in:
248
src/views/account/components/base-info.vue
Normal file
248
src/views/account/components/base-info.vue
Normal file
@@ -0,0 +1,248 @@
|
||||
<script lang="ts" setup>
|
||||
import { Modal, message } from 'ant-design-vue/lib';
|
||||
import { FileType } from 'ant-design-vue/lib/upload/interface';
|
||||
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
|
||||
import { onMounted, reactive, ref, toRaw } from 'vue';
|
||||
import { updateUserProfile, uploadAvatar } from '@/api/profile';
|
||||
import { regExpEmail, regExpMobile, regExpNick } from '@/utils/regular-utils';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
const uerStore = useUserStore();
|
||||
const { getDict } = useDictStore();
|
||||
|
||||
/**用户性别字典 */
|
||||
let sysUserSex = ref<DictType[]>([
|
||||
{ label: '未知', value: '0', elTagType: '', elTagClass: '' },
|
||||
{ label: '男', value: '1', elTagType: '', elTagClass: '' },
|
||||
{ label: '女', value: '2', elTagType: '', elTagClass: '' },
|
||||
]);
|
||||
|
||||
/**表单数据状态 */
|
||||
let stateForm = reactive({
|
||||
/**表单属性 */
|
||||
form: {
|
||||
nickName: '',
|
||||
email: '',
|
||||
phonenumber: '',
|
||||
sex: undefined,
|
||||
},
|
||||
/**表单提交点击状态 */
|
||||
formClick: false,
|
||||
});
|
||||
|
||||
/**表单数据状态初始化 */
|
||||
function fnInitstateForm() {
|
||||
stateForm.form = Object.assign(stateForm.form, uerStore.getBaseInfo);
|
||||
stateForm.formClick = false;
|
||||
}
|
||||
|
||||
/**表单验证通过 */
|
||||
function fnFinish() {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认要提交修改用户基本信息吗?`,
|
||||
onOk() {
|
||||
stateForm.formClick = true;
|
||||
// 发送请求
|
||||
const hide = message.loading('请稍等...', 0);
|
||||
const form = toRaw(stateForm.form);
|
||||
updateUserProfile(form).then(res => {
|
||||
hide();
|
||||
stateForm.formClick = false;
|
||||
if (res.code === 200) {
|
||||
Modal.success({
|
||||
title: '提示',
|
||||
content: `用户基本信息修改成功!`,
|
||||
okText: '我知道了',
|
||||
onOk() {
|
||||
uerStore.setBaseInfo(form);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.error(`${res.msg}`, 3);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**上传状态 */
|
||||
let upState = ref<boolean>(false);
|
||||
|
||||
/**上传前检查或转换压缩 */
|
||||
function fnBeforeUpload(file: FileType) {
|
||||
if (upState.value) return false;
|
||||
const isJpgOrPng = ['image/jpeg', 'image/png'].includes(file.type);
|
||||
if (!isJpgOrPng) {
|
||||
message.error('只支持上传图片格式(jpg、png)', 3);
|
||||
}
|
||||
const isLt2M = file.size / 1024 / 1024 < 2;
|
||||
if (!isLt2M) {
|
||||
message.error('图片文件大小必须小于 2MB', 3);
|
||||
}
|
||||
return isJpgOrPng && isLt2M;
|
||||
}
|
||||
|
||||
/**上传变更 */
|
||||
function fnUpload(up: UploadRequestOption) {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认要上传/变更用户头像吗?`,
|
||||
onOk() {
|
||||
// 发送请求
|
||||
const hide = message.loading('请稍等...', 0);
|
||||
upState.value = true;
|
||||
let formData = new FormData();
|
||||
formData.append('file', up.file);
|
||||
uploadAvatar(formData).then(res => {
|
||||
upState.value = false;
|
||||
hide();
|
||||
if (res.code === 200) {
|
||||
message.success('头像上传/变更成功', 3);
|
||||
uerStore.setAvatar(res.data);
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
getDict('sys_user_sex').then(res => {
|
||||
if (res.length > 0) {
|
||||
sysUserSex.value = res;
|
||||
}
|
||||
});
|
||||
// 初始表单值
|
||||
fnInitstateForm();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-form
|
||||
:model="stateForm.form"
|
||||
name="stateForm"
|
||||
layout="vertical"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
@finish="fnFinish"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24" style="margin-bottom: 30px">
|
||||
<a-form-item
|
||||
label="用户昵称"
|
||||
name="nickName"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
pattern: regExpNick,
|
||||
message: '昵称只能包含字母、数字、中文和下划线,且不少于2位',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="stateForm.form.nickName"
|
||||
allow-clear
|
||||
:maxlength="26"
|
||||
placeholder="请输入用户昵称"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="手机号码"
|
||||
name="phonenumber"
|
||||
:rules="[
|
||||
{
|
||||
required: false,
|
||||
pattern: regExpMobile,
|
||||
message: '请输入正确手机号码',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="stateForm.form.phonenumber"
|
||||
allow-clear
|
||||
:maxlength="11"
|
||||
placeholder="请输入手机号码"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="电子邮箱"
|
||||
name="email"
|
||||
:rules="[
|
||||
{
|
||||
required: false,
|
||||
pattern: regExpEmail,
|
||||
message: '请输入正确电子邮箱',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="stateForm.form.email"
|
||||
allow-clear
|
||||
:maxlength="40"
|
||||
placeholder="请输入电子邮箱"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="用户性别"
|
||||
name="sex"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择用户性别',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="stateForm.form.sex"
|
||||
placeholder="用户性别"
|
||||
:options="sysUserSex"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-space :size="8">
|
||||
<a-button
|
||||
block
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
:loading="stateForm.formClick"
|
||||
>
|
||||
确认修改
|
||||
</a-button>
|
||||
<a-button
|
||||
type="default"
|
||||
@click="fnInitstateForm"
|
||||
:disabled="stateForm.formClick"
|
||||
>
|
||||
重置
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-space direction="vertical" :size="16">
|
||||
<a-image :width="128" :height="128" :src="uerStore.getAvatar" />
|
||||
<span>请选择等比大小图片作为头像,如200x200、400x400</span>
|
||||
<a-upload
|
||||
name="file"
|
||||
list-type="picture"
|
||||
:max-count="1"
|
||||
:show-upload-list="false"
|
||||
:before-upload="fnBeforeUpload"
|
||||
:custom-request="fnUpload"
|
||||
>
|
||||
<a-button type="default" :loading="upState">
|
||||
上传/变更图片
|
||||
</a-button>
|
||||
</a-upload>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
158
src/views/account/components/reset-passwd.vue
Normal file
158
src/views/account/components/reset-passwd.vue
Normal file
@@ -0,0 +1,158 @@
|
||||
<script lang="ts" setup>
|
||||
import { Modal, message } from 'ant-design-vue/lib';
|
||||
import { reactive } from 'vue';
|
||||
import { updateUserPwd } from '@/api/profile';
|
||||
import { regExpPasswd } from '@/utils/regular-utils';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import { useRouter } from 'vue-router';
|
||||
const { userName, fnLogOut } = useUserStore();
|
||||
const router = useRouter();
|
||||
|
||||
let state = reactive({
|
||||
/**表单属性 */
|
||||
form: {
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
},
|
||||
/**表单提交点击状态 */
|
||||
formClick: false,
|
||||
});
|
||||
|
||||
/**表单验证确认密码是否一致 */
|
||||
function fnEqualToPassword(
|
||||
rule: Record<string, any>,
|
||||
value: string,
|
||||
callback: (error?: string) => void
|
||||
) {
|
||||
if (!value) {
|
||||
return Promise.reject('请输入确认新密码');
|
||||
}
|
||||
if (state.form.oldPassword === value) {
|
||||
return Promise.reject('与旧密码一致,请重新输入新密码');
|
||||
}
|
||||
if (state.form.newPassword === value) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject('两次输入的新密码不一致');
|
||||
}
|
||||
|
||||
/**表单验证通过 */
|
||||
function fnFinish() {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认要提交修改密码吗?`,
|
||||
onOk() {
|
||||
state.formClick = true;
|
||||
// 发送请求
|
||||
const hide = message.loading('请稍等...', 0);
|
||||
updateUserPwd(state.form.oldPassword, state.form.confirmPassword)
|
||||
.then(res => {
|
||||
hide();
|
||||
if (res.code === 200) {
|
||||
Modal.success({
|
||||
title: '提示',
|
||||
content: `恭喜您,${userName} 账号密码修改成功!`,
|
||||
okText: '重新登录',
|
||||
onOk() {
|
||||
fnLogOut().finally(() => router.push({ name: 'Login' }));
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.error(`${res.msg}`, 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
state.formClick = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-form
|
||||
:model="state.form"
|
||||
name="stateForm"
|
||||
layout="vertical"
|
||||
:wrapper-col="{ span: 9 }"
|
||||
@finish="fnFinish"
|
||||
>
|
||||
<a-form-item
|
||||
label="旧密码"
|
||||
name="oldPassword"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
min: 6,
|
||||
max: 26,
|
||||
message: '旧密码不能为空,且不少于6位',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="state.form.oldPassword"
|
||||
placeholder="请输入旧密码"
|
||||
:maxlength="26"
|
||||
>
|
||||
<template #prefix>
|
||||
<LockOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="新密码"
|
||||
name="newPassword"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
pattern: regExpPasswd,
|
||||
message: '密码至少包含大小写字母、数字、特殊符号,且不少于6位',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="state.form.newPassword"
|
||||
placeholder="请输入新密码"
|
||||
:maxlength="26"
|
||||
>
|
||||
<template #prefix>
|
||||
<LockOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="确认新密码"
|
||||
name="confirmPassword"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
validator: fnEqualToPassword,
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="state.form.confirmPassword"
|
||||
placeholder="请确认新密码"
|
||||
:maxlength="26"
|
||||
>
|
||||
<template #prefix>
|
||||
<LockOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :wrapper-col="{ span: 3 }">
|
||||
<a-button
|
||||
block
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
:loading="state.formClick"
|
||||
>
|
||||
提交修改
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
165
src/views/account/components/style-layout.vue
Normal file
165
src/views/account/components/style-layout.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { getLocalColor, changePrimaryColor } from '@/hooks/useTheme';
|
||||
import useLayoutStore from '@/store/modules/layout';
|
||||
const { proConfig, changeConf } = useLayoutStore();
|
||||
|
||||
let color = ref<string>(getLocalColor());
|
||||
|
||||
/**改变主题色 */
|
||||
function fnColorChange(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
if (target.nodeName === 'INPUT') {
|
||||
changePrimaryColor(target.value ?? '#1890ff');
|
||||
} else {
|
||||
changePrimaryColor();
|
||||
}
|
||||
color.value = getLocalColor();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-divider orientation="left">布局属性</a-divider>
|
||||
<a-list item-layout="vertical" size="large" row-key="title">
|
||||
<a-list-item>
|
||||
整体布局
|
||||
<template #actions> 导航模式模块设置 </template>
|
||||
<template #extra>
|
||||
<a-radio-group
|
||||
style="margin-bottom: 12px"
|
||||
:value="proConfig.layout"
|
||||
@change="(e:any) => changeConf('layout', e.target.value)"
|
||||
>
|
||||
<a-radio value="side">左侧菜单布局</a-radio>
|
||||
<a-radio value="top">顶部菜单布局</a-radio>
|
||||
<a-radio value="mix">混合菜单布局</a-radio>
|
||||
</a-radio-group>
|
||||
</template>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
风格配色
|
||||
<template #actions> 整体风格配色设置 </template>
|
||||
<template #extra>
|
||||
<a-space :size="16" align="end" direction="horizontal">
|
||||
<a-button type="primary" size="small" @click="fnColorChange">
|
||||
<BgColorsOutlined /> 随机
|
||||
</a-button>
|
||||
<input type="color" :value="color" @input="fnColorChange" />
|
||||
</a-space>
|
||||
</template>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
深色菜单
|
||||
<template #actions> 只能改变导航模式的菜单 </template>
|
||||
<template #extra>
|
||||
<a-switch
|
||||
checked-children="是"
|
||||
un-checked-children="否"
|
||||
:checked="proConfig.navTheme === 'dark'"
|
||||
@change="
|
||||
(checked:any) => changeConf('navTheme', checked ? 'dark' : 'light')
|
||||
"
|
||||
></a-switch>
|
||||
</template>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
固定顶部导航栏
|
||||
<template #actions> 顶部导航栏是否固定,不随滚动条移动 </template>
|
||||
<template #extra>
|
||||
<a-switch
|
||||
checked-children="是"
|
||||
un-checked-children="否"
|
||||
:checked="proConfig.fixedHeader"
|
||||
@change="(checked:any) => changeConf('fixedHeader', checked)"
|
||||
></a-switch>
|
||||
</template>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
固定左侧菜单
|
||||
<template #actions> 左侧菜单是否固定,仅左侧菜单布局时有效 </template>
|
||||
<template #extra>
|
||||
<a-switch
|
||||
checked-children="是"
|
||||
un-checked-children="否"
|
||||
:checked="proConfig.fixSiderbar"
|
||||
@change="(checked:any) => changeConf('fixSiderbar', checked)"
|
||||
></a-switch>
|
||||
</template>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
自动分割菜单
|
||||
<template #actions>
|
||||
顶部有多级菜单时显示左侧菜单,仅混合菜单布局时有效
|
||||
</template>
|
||||
<template #extra>
|
||||
<a-switch
|
||||
checked-children="是"
|
||||
un-checked-children="否"
|
||||
:checked="proConfig.splitMenus"
|
||||
@change="(checked:any) => changeConf('splitMenus', checked)"
|
||||
></a-switch>
|
||||
</template>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
<a-divider orientation="left">内容区域</a-divider>
|
||||
<a-list item-layout="vertical" size="large" row-key="title">
|
||||
<a-list-item>
|
||||
顶栏
|
||||
<template #actions> 是否显示顶部导航栏 </template>
|
||||
<template #extra>
|
||||
<a-switch
|
||||
checked-children="显示"
|
||||
un-checked-children="隐藏"
|
||||
:checked="proConfig.headerRender === undefined"
|
||||
@change="
|
||||
(checked:any) => changeConf('headerRender', checked === true && undefined)
|
||||
"
|
||||
></a-switch>
|
||||
</template>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
页脚
|
||||
<template #actions> 是否显示底部导航栏 </template>
|
||||
<template #extra>
|
||||
<a-switch
|
||||
checked-children="显示"
|
||||
un-checked-children="隐藏"
|
||||
:checked="proConfig.footerRender === undefined"
|
||||
@change="
|
||||
(checked:any) => changeConf('footerRender', checked === true && undefined)
|
||||
"
|
||||
></a-switch>
|
||||
</template>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
菜单头
|
||||
<template #actions> 是否显示左侧菜单栏顶部LOGO区域 </template>
|
||||
<template #extra>
|
||||
<a-switch
|
||||
checked-children="显示"
|
||||
un-checked-children="隐藏"
|
||||
:checked="proConfig.menuHeaderRender === undefined"
|
||||
@change="
|
||||
(checked:any) => changeConf('menuHeaderRender', checked === true && undefined)
|
||||
"
|
||||
></a-switch>
|
||||
</template>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
导航标签项
|
||||
<template #actions> 是否显示顶部Tab导航标签项 </template>
|
||||
<template #extra>
|
||||
<a-switch
|
||||
checked-children="显示"
|
||||
un-checked-children="隐藏"
|
||||
:checked="proConfig.tabRender === undefined"
|
||||
@change="
|
||||
(checked:any) => changeConf('tabRender', checked === true && undefined)
|
||||
"
|
||||
></a-switch>
|
||||
</template>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
198
src/views/account/profile.vue
Normal file
198
src/views/account/profile.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<script lang="ts" setup>
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import { message } from 'ant-design-vue/lib';
|
||||
import { getUserProfile } from '@/api/profile';
|
||||
import { reactive, ref, onMounted } from 'vue';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
|
||||
/**加载状态 */
|
||||
let loading = ref<boolean>(true);
|
||||
|
||||
/**Tab标签激活 */
|
||||
let activeKey = ref<string>('list');
|
||||
|
||||
/**个人信息数据状态 */
|
||||
let state = reactive<{
|
||||
user: Record<string, any>;
|
||||
postGroup: string[];
|
||||
roleGroup: string[];
|
||||
}>({
|
||||
user: {},
|
||||
postGroup: [],
|
||||
roleGroup: [],
|
||||
});
|
||||
|
||||
/**列表数据 */
|
||||
let listData = ref([
|
||||
{
|
||||
id: 'Vue',
|
||||
title: 'Vue.js - 渐进式 JavaScript 框架 | Vue.js',
|
||||
description:
|
||||
'基于标准 HTML、CSS 和 JavaScript 构建,提供容易上手的 API 和一流的文档。 性能出色 经过编译器优化、完全响应式的渲染系统,几乎不需要手动优化。',
|
||||
},
|
||||
{
|
||||
id: 'Vue Router',
|
||||
title: 'Vue Router | Vue.js 的官方路由',
|
||||
description:
|
||||
'为Vue.js 提供富有表现力、可配置的、方便的路由,用直观且强大的语法来定义静态或动态路由。',
|
||||
},
|
||||
{
|
||||
id: 'Pinia',
|
||||
title: 'Pinia | The intuitive store for Vue.js',
|
||||
description:
|
||||
'Pinia hooks into Vue devtools to give you an enhanced development experience in both Vue 2 and Vue 3. ',
|
||||
},
|
||||
{
|
||||
id: 'Vite',
|
||||
title: 'Vite | 下一代的前端工具链',
|
||||
description:
|
||||
'Vite(法语意为 "快速的",发音 /vit/,发音同 "veet")是一种新型前端构建工具,能够显著提升前端开发体验',
|
||||
},
|
||||
]);
|
||||
|
||||
/**查询用户个人信息 */
|
||||
function fnGetProfile() {
|
||||
getUserProfile().then(res => {
|
||||
if (res.code === 200 && res.data) {
|
||||
const { user, roleGroup, postGroup } = res.data;
|
||||
state.user = user;
|
||||
state.roleGroup = roleGroup;
|
||||
state.postGroup = postGroup;
|
||||
// 头像解析
|
||||
state.user.avatar = useUserStore().fnAvatar(user.avatar);
|
||||
loading.value = false;
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取信息
|
||||
fnGetProfile();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer :loading="loading">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="6" :xs="24">
|
||||
<a-card :body-style="{ padding: '0px' }" style="margin-bottom: 16px">
|
||||
<template #title>
|
||||
<div class="info-top">
|
||||
<div class="info-top-no">No:{{ state.user.userId }}</div>
|
||||
<a-avatar
|
||||
shape="circle"
|
||||
:size="96"
|
||||
:src="state.user.avatar"
|
||||
:alt="state.user.userName"
|
||||
></a-avatar>
|
||||
<div class="info-top-nickname" :title="state.user.nickName">
|
||||
{{ state.user.nickName }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-descriptions
|
||||
size="small"
|
||||
layout="vertical"
|
||||
:bordered="true"
|
||||
:column="1"
|
||||
>
|
||||
<a-descriptions-item label="手机号码">
|
||||
{{ state.user.phonenumber || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="用户邮箱">
|
||||
{{ state.user.email || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="所属部门">
|
||||
{{ state.user.dept?.deptName || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="拥有岗位">
|
||||
<span v-if="state.postGroup.length === 0">-</span>
|
||||
<a-tag v-else v-for="v in state.postGroup" :key="v">
|
||||
{{ v }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="拥有角色">
|
||||
<span v-if="state.roleGroup.length === 0">-</span>
|
||||
<a-tag v-else v-for="v in state.roleGroup" :key="v">
|
||||
{{ v }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="登录地址">
|
||||
{{ state.user.loginIp || '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="登录时间">
|
||||
<span v-if="+state.user.loginDate > 0">
|
||||
{{ parseDateToStr(+state.user.loginDate) }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :lg="18" :md="18" :xs="24">
|
||||
<a-card>
|
||||
<a-tabs
|
||||
tab-position="top"
|
||||
:destroy-inactive-tab-pane="true"
|
||||
v-model:activeKey="activeKey"
|
||||
>
|
||||
<a-tab-pane key="list" tab="列表">
|
||||
<a-list
|
||||
item-layout="horizontal"
|
||||
:data-source="listData"
|
||||
row-key="id"
|
||||
>
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<a-list-item-meta>
|
||||
<template #title>
|
||||
{{ item.title }}
|
||||
</template>
|
||||
<template #description>
|
||||
{{ item.description }}
|
||||
</template>
|
||||
<template #avatar>
|
||||
<a-avatar>{{ item.id }}</a-avatar>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="empty" tab="空状态">
|
||||
<a-empty>
|
||||
<template #description> 暂无数据,尝试刷新看看 </template>
|
||||
<a-button type="primary">刷新</a-button>
|
||||
</a-empty>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.info-top {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
&-no {
|
||||
align-self: flex-start;
|
||||
font-size: 14px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
&-nickname {
|
||||
margin-top: 16px;
|
||||
font-size: 24px;
|
||||
align-self: flex-start;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
30
src/views/account/settings.vue
Normal file
30
src/views/account/settings.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import BaseInfo from './components/base-info.vue';
|
||||
import ResetPasswd from './components/reset-passwd.vue';
|
||||
import StyleLayout from './components/style-layout.vue';
|
||||
|
||||
/**Tab标签激活 */
|
||||
let activeKey = ref<string>('base-info');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card>
|
||||
<a-tabs tab-position="left" v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="base-info" tab="基础信息">
|
||||
<BaseInfo></BaseInfo>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="reset-passwd" tab="重置密码">
|
||||
<ResetPasswd></ResetPasswd>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="style-layout" tab="个性化">
|
||||
<StyleLayout></StyleLayout>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
27
src/views/dome/dome1.vue
Normal file
27
src/views/dome/dome1.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-result
|
||||
status="404"
|
||||
:style="{
|
||||
height: '100%',
|
||||
background: '#fff',
|
||||
}"
|
||||
title="Hello World"
|
||||
sub-title="Sorry, you are not authorized to access this page."
|
||||
>
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="handleClick">Back Home</a-button>
|
||||
</template>
|
||||
</a-result>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import { message } from 'ant-design-vue/lib';
|
||||
|
||||
const handleClick = () => {
|
||||
console.log('info');
|
||||
message.info('BackHome button clicked!');
|
||||
};
|
||||
</script>
|
||||
61
src/views/dome/dome2.vue
Normal file
61
src/views/dome/dome2.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<a-layout class="layout">
|
||||
<a-layout-header>
|
||||
<div class="logo" />
|
||||
<a-menu
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
theme="dark"
|
||||
mode="horizontal"
|
||||
:style="{ lineHeight: '64px' }"
|
||||
>
|
||||
<a-menu-item key="1">nav 1</a-menu-item>
|
||||
<a-menu-item key="2">nav 2</a-menu-item>
|
||||
<a-menu-item key="3">nav 3</a-menu-item>
|
||||
</a-menu>
|
||||
</a-layout-header>
|
||||
<a-layout-content style="padding: 0 50px">
|
||||
<a-breadcrumb style="margin: 16px 0">
|
||||
<a-breadcrumb-item>Home</a-breadcrumb-item>
|
||||
<a-breadcrumb-item>List</a-breadcrumb-item>
|
||||
<a-breadcrumb-item>App</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
<div :style="{ background: '#fff', padding: '24px', minHeight: '280px' }">
|
||||
Content {{ selectedKeys }}
|
||||
</div>
|
||||
</a-layout-content>
|
||||
<a-layout-footer style="text-align: center">
|
||||
Ant Design ©2018 Created by Ant UED
|
||||
</a-layout-footer>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
const selectedKeys = ref<string[]>(['2']);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.site-layout-content {
|
||||
min-height: 280px;
|
||||
padding: 24px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
#components-layout-demo-top .logo {
|
||||
float: left;
|
||||
width: 120px;
|
||||
height: 31px;
|
||||
margin: 16px 24px 16px 0;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.ant-row-rtl #components-layout-demo-top .logo {
|
||||
float: right;
|
||||
margin: 16px 0 16px 24px;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .site-layout-content {
|
||||
background: #141414;
|
||||
}
|
||||
</style>
|
||||
30
src/views/dome/dome3.vue
Normal file
30
src/views/dome/dome3.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<PageContainer title="Version" sub-title="show current project dependencies">
|
||||
<template #content>
|
||||
<strong>Content Area</strong>
|
||||
</template>
|
||||
<template #extra>
|
||||
<strong>Extra Area</strong>
|
||||
</template>
|
||||
<template #extraContent>
|
||||
<strong>ExtraContent Area</strong>
|
||||
</template>
|
||||
<template #tags>
|
||||
<a-tag>Tag1</a-tag>
|
||||
<a-tag color="pink">Tag2</a-tag>
|
||||
</template>
|
||||
<a-card title="Info">
|
||||
<p v-for="i in list" :key="i">
|
||||
text block...
|
||||
<a-tag>{{ i }}</a-tag>
|
||||
</p>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
|
||||
const list = ref<number>(50);
|
||||
</script>
|
||||
87
src/views/domes/dynamic-match.vue
Normal file
87
src/views/domes/dynamic-match.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<PageContainer :title="`${route.meta.title} ${route.params.id}`">
|
||||
<template #content>
|
||||
<a-descriptions size="small" :column="2">
|
||||
<a-descriptions-item label="创建人">张三</a-descriptions-item>
|
||||
<a-descriptions-item label="联系方式">
|
||||
<a>421421</a>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">2017-01-10</a-descriptions-item>
|
||||
<a-descriptions-item label="更新时间">2017-10-10</a-descriptions-item>
|
||||
<a-descriptions-item label="备注">
|
||||
中国浙江省杭州市西湖区古翠路
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</template>
|
||||
<template #extra>
|
||||
<a-button key="3">操作</a-button>
|
||||
<a-button key="2">操作</a-button>
|
||||
<a-button key="1" type="primary">主操作</a-button>
|
||||
</template>
|
||||
<template #extraContent>
|
||||
<a-space>
|
||||
<a-statistic title="Feedback" :value="1128">
|
||||
<template #prefix>
|
||||
<LikeOutlined />
|
||||
</template>
|
||||
</a-statistic>
|
||||
<a-statistic title="Unmerged" :value="93" suffix="/ 100" />
|
||||
</a-space>
|
||||
</template>
|
||||
<!-- 主内容区 -->
|
||||
<div style="height: 300px">
|
||||
<p>路由参数联动 分页器 组件</p>
|
||||
<a-space>
|
||||
<a-button type="dashed" @click="prev">跳转上一页</a-button>
|
||||
<a-button type="dashed" @click="next">跳转下一页</a-button>
|
||||
</a-space>
|
||||
<a-divider />
|
||||
<a-pagination
|
||||
:current="currentId"
|
||||
:total="total"
|
||||
show-less-items
|
||||
@change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import { computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const currentId = computed(() => {
|
||||
let id = route.params && (route.params.id as string);
|
||||
return Number.parseInt(id as string, 10) || 1;
|
||||
});
|
||||
const total = computed(() => {
|
||||
const v = currentId.value * 20;
|
||||
if (v >= Number.MAX_SAFE_INTEGER) {
|
||||
return Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
return v;
|
||||
});
|
||||
|
||||
const next = () => {
|
||||
router.push({
|
||||
name: 'DynamicMatch',
|
||||
params: { id: currentId.value + 1 },
|
||||
});
|
||||
};
|
||||
const prev = () => {
|
||||
router.push({
|
||||
name: 'DynamicMatch',
|
||||
params: { id: currentId.value > 1 ? currentId.value - 1 : 1 },
|
||||
});
|
||||
};
|
||||
const handlePageChange = (currentPage: number) => {
|
||||
router.push({
|
||||
name: 'DynamicMatch',
|
||||
params: { id: currentPage },
|
||||
});
|
||||
};
|
||||
</script>
|
||||
58
src/views/domes/page-info.vue
Normal file
58
src/views/domes/page-info.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<PageContainer :title="title">
|
||||
<template #content>
|
||||
<a-descriptions size="small" :column="2">
|
||||
<a-descriptions-item label="创建人">张三</a-descriptions-item>
|
||||
<a-descriptions-item label="联系方式">
|
||||
<a>421421</a>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">2017-01-10</a-descriptions-item>
|
||||
<a-descriptions-item label="更新时间">2017-10-10</a-descriptions-item>
|
||||
<a-descriptions-item label="备注">
|
||||
中国浙江省杭州市西湖区古翠路
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</template>
|
||||
<template #extra>
|
||||
<a-button key="3">操作</a-button>
|
||||
<a-button key="2">操作</a-button>
|
||||
<a-button key="1" type="primary">主操作</a-button>
|
||||
</template>
|
||||
<template #extraContent>
|
||||
<a-space>
|
||||
<a-statistic title="Feedback" :value="1128">
|
||||
<template #prefix>
|
||||
<LikeOutlined />
|
||||
</template>
|
||||
</a-statistic>
|
||||
<a-statistic title="Unmerged" :value="93" suffix="/ 100" />
|
||||
</a-space>
|
||||
</template>
|
||||
<!-- 主内容区 -->
|
||||
<div style="height: 120vh">
|
||||
<a-result
|
||||
status="404"
|
||||
:style="{
|
||||
height: '100%',
|
||||
background: '#fff',
|
||||
}"
|
||||
title="Hello World"
|
||||
sub-title="Sorry, you are not authorized to access this page."
|
||||
>
|
||||
<template #extra>
|
||||
<a-button type="primary">Back Home</a-button>
|
||||
</template>
|
||||
</a-result>
|
||||
</div>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import { ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
const route = useRoute();
|
||||
|
||||
/**路由标题 */
|
||||
let title = ref<string>(route.meta.title ?? '标题');
|
||||
</script>
|
||||
102
src/views/domes/page-typography.vue
Normal file
102
src/views/domes/page-typography.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<a-card>
|
||||
<a-typography>
|
||||
<a-typography-title>Introduction</a-typography-title>
|
||||
<a-typography-paragraph>
|
||||
In the process of internal desktop applications development, many
|
||||
different design specs and implementations would be involved, which
|
||||
might cause designers and developers difficulties and duplication and
|
||||
reduce the efficiency of development.
|
||||
</a-typography-paragraph>
|
||||
<a-typography-paragraph>
|
||||
After massive project practice and summaries, Ant Design, a design
|
||||
language for background applications, is refined by Ant UED Team, which
|
||||
aims to
|
||||
<a-typography-text strong>
|
||||
uniform the user interface specs for internal background projects,
|
||||
lower the unnecessary cost of design differences and implementation
|
||||
and liberate the resources of design and front-end development.
|
||||
</a-typography-text>
|
||||
</a-typography-paragraph>
|
||||
<a-typography-title :level="2"
|
||||
>Guidelines and Resources</a-typography-title
|
||||
>
|
||||
<a-typography-paragraph>
|
||||
We supply a series of design principles, practical patterns and high
|
||||
quality design resources (
|
||||
<a-typography-text code>Sketch</a-typography-text>
|
||||
and
|
||||
<a-typography-text code>Axure</a-typography-text>
|
||||
), to help people create their product prototypes beautifully and
|
||||
efficiently.
|
||||
</a-typography-paragraph>
|
||||
|
||||
<a-typography-paragraph>
|
||||
<ul>
|
||||
<li>
|
||||
<a-typography-link href="/docs/resources"
|
||||
>Resource Download</a-typography-link
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</a-typography-paragraph>
|
||||
|
||||
<a-typography-paragraph>
|
||||
Press
|
||||
<a-typography-text keyboard>Esc</a-typography-text>
|
||||
to exit...
|
||||
</a-typography-paragraph>
|
||||
|
||||
<a-divider />
|
||||
|
||||
<a-typography-title>介绍</a-typography-title>
|
||||
<a-typography-paragraph>
|
||||
蚂蚁的企业级产品是一个庞大且复杂的体系。这类产品不仅量级巨大且功能复杂,而且变动和并发频繁,常常需要设计与开发能够快速的做出响应。同时这类产品中有存在很多类似的页面以及组件,可以通过抽象得到一些稳定且高复用性的内容。
|
||||
</a-typography-paragraph>
|
||||
<a-typography-paragraph>
|
||||
随着商业化的趋势,越来越多的企业级产品对更好的用户体验有了进一步的要求。带着这样的一个终极目标,我们(蚂蚁金服体验技术部)经过大量的项目实践和总结,逐步打磨出一个服务于企业级产品的设计体系
|
||||
Ant Design。基于
|
||||
<a-typography-text mark>『确定』和『自然』</a-typography-text>
|
||||
的设计价值观,通过模块化的解决方案,降低冗余的生产成本,让设计者专注于
|
||||
<a-typography-text strong>更好的用户体验</a-typography-text>
|
||||
。
|
||||
</a-typography-paragraph>
|
||||
<a-typography-title :level="2">设计资源</a-typography-title>
|
||||
<a-typography-paragraph>
|
||||
我们提供完善的设计原则、最佳实践和设计资源文件(
|
||||
<a-typography-text code>Sketch</a-typography-text>
|
||||
和
|
||||
<a-typography-text code>Axure</a-typography-text>
|
||||
),来帮助业务快速设计出高质量的产品原型。
|
||||
</a-typography-paragraph>
|
||||
|
||||
<a-typography-paragraph>
|
||||
<ul>
|
||||
<li>
|
||||
<a-typography-link href="/docs/resources-cn"
|
||||
>设计资源</a-typography-link
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</a-typography-paragraph>
|
||||
|
||||
<a-typography-paragraph>
|
||||
<blockquote>{{ blockContent }}</blockquote>
|
||||
<pre>{{ blockContent }}</pre>
|
||||
</a-typography-paragraph>
|
||||
|
||||
<a-typography-paragraph>
|
||||
按
|
||||
<a-typography-text keyboard>Esc</a-typography-text>
|
||||
键退出阅读……
|
||||
</a-typography-paragraph>
|
||||
</a-typography>
|
||||
</a-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
const blockContent = ref<string>(`AntV 是蚂蚁金服全新一代数据可视化解决方案,致力于提供一套简单方便、专业可靠、不限可能的数据可视化最佳实践。得益于丰富的业务场景和用户需求挑战,AntV 经历多年积累与不断打磨,已支撑整个阿里集团内外 20000+ 业务系统,通过了日均千万级 UV 产品的严苛考验。
|
||||
我们正在基础图表,图分析,图编辑,地理空间可视化,智能可视化等各个可视化的领域耕耘,欢迎同路人一起前行。` );
|
||||
|
||||
</script>
|
||||
21
src/views/error/403.vue
Normal file
21
src/views/error/403.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router';
|
||||
const router = useRouter();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-result
|
||||
status="403"
|
||||
title="没有访问权限"
|
||||
sub-title="请不要进行非法操作,您可以返回主页面或返回"
|
||||
>
|
||||
<template #extra>
|
||||
<RouterLink :to="{ name: 'Index' }" :replace="true">
|
||||
<a-button type="primary"> 返回首页 </a-button>
|
||||
</RouterLink>
|
||||
<a-button type="default" @click="() => router.back()"> 返回 </a-button>
|
||||
</template>
|
||||
</a-result>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
26
src/views/error/404.vue
Normal file
26
src/views/error/404.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<a-result
|
||||
status="404"
|
||||
title="找不到匹配页面"
|
||||
sub-title="对不起,您正在寻找的页面不存在。"
|
||||
>
|
||||
<template #extra>
|
||||
<RouterLink :to="{ name: 'Index' }" :replace="true">
|
||||
<a-button type="primary"> 返回首页 </a-button>
|
||||
</RouterLink>
|
||||
</template>
|
||||
<a-typography>
|
||||
<a-typography-title> 找不到网页? </a-typography-title>
|
||||
<a-typography-paragraph>
|
||||
1. 尝试检查URL的错误,然后按浏览器上的刷新按钮。
|
||||
</a-typography-paragraph>
|
||||
<a-typography-paragraph>
|
||||
2. 尝试在我们的应用程序中找到其他内容。
|
||||
</a-typography-paragraph>
|
||||
</a-typography>
|
||||
</a-result>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
123
src/views/index.vue
Normal file
123
src/views/index.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<script setup lang="ts">
|
||||
import donate from '@/assets/donate.jpg';
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
const userStore = useUserStore();
|
||||
const { appName, appVersion } = useAppStore();
|
||||
|
||||
/**跳转 */
|
||||
function goTarget(type: string) {
|
||||
let url = '';
|
||||
if (type === 'code') {
|
||||
url = 'https://gitee.com/TsMask/';
|
||||
}
|
||||
if (type === 'issues') {
|
||||
url = 'https://gitee.com/TsMask/mask_antd_vue3/issues';
|
||||
}
|
||||
window.open(url, '__blank');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer :breadcrumb="false" :title="appName" sub-title="by TsMask">
|
||||
<template #tags>
|
||||
<a-tag>当前版本:{{ appVersion }}</a-tag>
|
||||
<a-tag color="magenta"><PayCircleOutlined /> 免费开源</a-tag>
|
||||
</template>
|
||||
<template #extra>
|
||||
<a-button type="primary" @click="goTarget('code')">开源仓库</a-button>
|
||||
<a-button type="default" @click="goTarget('issues')">提些建议</a-button>
|
||||
</template>
|
||||
<template #content>
|
||||
<a-space :size="16" align="center">
|
||||
<a-avatar
|
||||
shape="circle"
|
||||
:size="72"
|
||||
:src="userStore.getAvatar"
|
||||
:alt="userStore.userName"
|
||||
></a-avatar>
|
||||
<span class="nickname">
|
||||
{{ userStore.nickName }} ,想必你那里一切安好吧。
|
||||
</span>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #extraContent>
|
||||
<a-space :size="16">
|
||||
<a-statistic title="在线用户" :value="545486" />
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="16" :md="16" :xs="24">
|
||||
<a-card title="项目简介" style="margin-bottom: 16px">
|
||||
<a-typography>
|
||||
<a-typography-paragraph>
|
||||
<a-typography-text mark> Vue3 </a-typography-text>
|
||||
技术组合,支持按钮及数据权限,可自定义部门数据权限。
|
||||
</a-typography-paragraph>
|
||||
<a-typography-paragraph>
|
||||
内置模块如:部门管理、角色用户、菜单及按钮授权、数据权限、系统参数、日志管理等,支持在线定时任务配置。
|
||||
</a-typography-paragraph>
|
||||
<a-typography-paragraph>
|
||||
使用 <a-typography-text mark> Ant-Design-Vue </a-typography-text>
|
||||
组件库,搭建的前后端分离极速后台管理系统。
|
||||
</a-typography-paragraph>
|
||||
</a-typography>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="8" :xs="24">
|
||||
<a-card title="快速开始" style="margin-bottom: 16px">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-button
|
||||
type="link"
|
||||
target="_blank"
|
||||
title="开发手册"
|
||||
href="https://juejin.cn/column/7188761626017792056"
|
||||
>
|
||||
开发手册
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-button
|
||||
type="link"
|
||||
target="_blank"
|
||||
title="来自Apifox的接口文档"
|
||||
href="https://mask-api-midwayjs.apifox.cn/"
|
||||
>
|
||||
接口文档
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-button
|
||||
type="link"
|
||||
target="_blank"
|
||||
title="Middwayjs版本服务端"
|
||||
href="https://gitee.com/TsMask/mask_api_midwayjs"
|
||||
>
|
||||
Node后端
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-button type="text"> 相关待定 </a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
<a-card title="捐赠鼓励" style="margin-top: 16px">
|
||||
<a-image width="100%" :src="donate" />
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.nickname {
|
||||
margin-bottom: 12px;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
</style>
|
||||
469
src/views/login.vue
Normal file
469
src/views/login.vue
Normal file
@@ -0,0 +1,469 @@
|
||||
<script lang="ts" setup>
|
||||
import { message } from 'ant-design-vue/lib';
|
||||
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import { getCaptchaImage } from '@/api/login';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { regExpMobile, validMobile } from '@/utils/regular-utils';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
const { t, changeLocale } = useI18n();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const codeImgFall =
|
||||
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
||||
|
||||
/**登录后重定向页面 */
|
||||
const redirectPath =
|
||||
(route.query && (route.query.redirect as string)) || '/index';
|
||||
|
||||
/**Tab默认激活 */
|
||||
let activeKey = ref<'username' | 'phonenumber'>('username');
|
||||
|
||||
let state = reactive({
|
||||
/**表单属性 */
|
||||
from: {
|
||||
/**账号 */
|
||||
username: 'maskAdmin',
|
||||
/**密码 */
|
||||
password: 'Admin@1234',
|
||||
/**手机号 */
|
||||
phonenumber: '',
|
||||
/**验证码 */
|
||||
code: '',
|
||||
/**验证码uuid */
|
||||
uuid: '',
|
||||
},
|
||||
/**表单提交点击状态 */
|
||||
fromClick: false,
|
||||
/**验证码状态 */
|
||||
captcha: {
|
||||
/**验证码开关 */
|
||||
enabled: false,
|
||||
/**验证码图片地址 */
|
||||
codeImg: '',
|
||||
codeImgFall: codeImgFall,
|
||||
},
|
||||
/**验证码点击状态 */
|
||||
captchaClick: false,
|
||||
});
|
||||
|
||||
/**表单验证通过 */
|
||||
function fnFinish() {
|
||||
state.fromClick = true;
|
||||
let form = {};
|
||||
// 账号密码登录
|
||||
if (activeKey.value === 'username') {
|
||||
form = {
|
||||
username: state.from.username,
|
||||
password: state.from.password,
|
||||
code: state.from.code,
|
||||
uuid: state.from.uuid,
|
||||
};
|
||||
}
|
||||
// 手机号登录
|
||||
if (activeKey.value === 'phonenumber') {
|
||||
form = {
|
||||
phonenumber: state.from.phonenumber,
|
||||
code: state.from.code,
|
||||
uuid: state.from.uuid,
|
||||
};
|
||||
}
|
||||
// 发送请求
|
||||
useUserStore()
|
||||
.fnLogin(form)
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success(t('views.login.loginSuccess'), 3);
|
||||
router.push({ path: redirectPath });
|
||||
} else {
|
||||
message.error(`${res.msg}`, 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
state.fromClick = false;
|
||||
// 刷新验证码
|
||||
if (state.captcha.enabled) {
|
||||
state.from.code = '';
|
||||
fnGetCaptcha();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码
|
||||
*/
|
||||
function fnGetCaptcha() {
|
||||
if (state.captchaClick) return;
|
||||
state.captchaClick = true;
|
||||
getCaptchaImage().then(res => {
|
||||
state.captchaClick = false;
|
||||
if (res.code != 200) {
|
||||
message.warning(`${res.msg}`, 3);
|
||||
return;
|
||||
}
|
||||
state.captcha.enabled = Boolean(res.captchaEnabled);
|
||||
if (state.captcha.enabled) {
|
||||
state.captcha.codeImg = res.img;
|
||||
state.from.uuid = res.uuid;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**短信验证码定时器 */
|
||||
let smsInterval: any = undefined;
|
||||
|
||||
/**短信验证码信息状态 */
|
||||
let smsState = reactive({
|
||||
/**点击状态 */
|
||||
click: false,
|
||||
/**发送倒计时 */
|
||||
time: 120,
|
||||
});
|
||||
|
||||
/**获取短信验证码 */
|
||||
function fnGetSmsCaptcha() {
|
||||
if (smsState.click) return;
|
||||
if (!validMobile(state.from.phonenumber)) {
|
||||
message.warning(t('valid.phoneReg'), 3);
|
||||
return;
|
||||
}
|
||||
smsState.click = true;
|
||||
|
||||
setTimeout(() => {
|
||||
// start 得到发送结果启动定时
|
||||
message.success(t('valid.codeSmsSend'), 3);
|
||||
state.from.uuid = '短信校验id';
|
||||
smsInterval = setInterval(() => {
|
||||
if (smsState.time <= 0) {
|
||||
smsState.time = 120;
|
||||
smsState.click = false;
|
||||
clearTimeout(smsInterval);
|
||||
} else {
|
||||
smsState.time--;
|
||||
}
|
||||
}, 1000);
|
||||
// end
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/**改变多语言 */
|
||||
function fnChangeLocale(e: any) {
|
||||
changeLocale(e.key);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fnGetCaptcha();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
smsState.time = 120;
|
||||
smsState.click = false;
|
||||
clearTimeout(smsInterval);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="top">
|
||||
<div class="header">
|
||||
<a href="/" target="_self"
|
||||
><img src="@/assets/logo.png" class="logo" alt="logo" />
|
||||
<span class="title">{{ t('common.title') }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="desc">{{ t('common.desc') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<a-form :model="state.from" name="stateFrom" @finish="fnFinish">
|
||||
<a-tabs
|
||||
v-model:activeKey="activeKey"
|
||||
tabPosition="top"
|
||||
type="line"
|
||||
:centered="true"
|
||||
:destroy-inactive-tab-pane="true"
|
||||
>
|
||||
<a-tab-pane key="username" :tab="t('views.login.tabPane1')">
|
||||
<a-form-item
|
||||
name="username"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
min: 2,
|
||||
max: 30,
|
||||
message: t('valid.userNamePlease'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="state.from.username"
|
||||
size="large"
|
||||
:placeholder="t('valid.userNameHit')"
|
||||
:maxlength="30"
|
||||
>
|
||||
<template #prefix>
|
||||
<UserOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
name="password"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
min: 6,
|
||||
max: 26,
|
||||
message: t('valid.passwordPlease'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="state.from.password"
|
||||
size="large"
|
||||
:placeholder="t('valid.passwordHit')"
|
||||
:maxlength="26"
|
||||
>
|
||||
<template #prefix>
|
||||
<LockOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-row :gutter="8" v-if="state.captcha.enabled">
|
||||
<a-col :span="16">
|
||||
<a-form-item
|
||||
name="code"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
message: t('valid.codePlease'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="state.from.code"
|
||||
size="large"
|
||||
:placeholder="t('valid.codeHit')"
|
||||
:maxlength="6"
|
||||
>
|
||||
<template #prefix>
|
||||
<RobotOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-image
|
||||
:alt="t('valid.codeHit')"
|
||||
style="cursor: pointer; border-radius: 2px"
|
||||
width="120px"
|
||||
height="40px"
|
||||
:preview="false"
|
||||
:src="state.captcha.codeImg"
|
||||
:fallback="state.captcha.codeImgFall"
|
||||
@click="fnGetCaptcha"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row
|
||||
:gutter="8"
|
||||
justify="space-between"
|
||||
align="middle"
|
||||
style="margin-bottom: 16px"
|
||||
>
|
||||
<a-col :span="12">
|
||||
<a-button
|
||||
type="link"
|
||||
target="_self"
|
||||
:title="t('views.login.registerBtn')"
|
||||
@click="() => router.push({ name: 'Register' })"
|
||||
>
|
||||
{{ t('views.login.registerBtn') }}
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="phonenumber" :tab="t('views.login.tabPane2')">
|
||||
<a-form-item
|
||||
name="phonenumber"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
pattern: regExpMobile,
|
||||
message: t('valid.phonePlease'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="state.from.phonenumber"
|
||||
size="large"
|
||||
:placeholder="t('valid.phoneHit')"
|
||||
:maxlength="11"
|
||||
>
|
||||
<template #prefix>
|
||||
<MobileOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
name="code"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
min: 4,
|
||||
message: t('valid.codePlease'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="state.from.code"
|
||||
size="large"
|
||||
:placeholder="t('valid.codeHit')"
|
||||
:maxlength="6"
|
||||
>
|
||||
<template #prefix>
|
||||
<RobotOutlined class="prefix-icon" />
|
||||
</template>
|
||||
<template #suffix>
|
||||
<a-button
|
||||
size="small"
|
||||
type="link"
|
||||
:disabled="smsState.click"
|
||||
@click="fnGetSmsCaptcha"
|
||||
>
|
||||
{{
|
||||
smsState.click
|
||||
? `${smsState.time} s`
|
||||
: t('valid.codeText')
|
||||
}}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
<a-button
|
||||
block
|
||||
type="primary"
|
||||
size="large"
|
||||
html-type="submit"
|
||||
:loading="state.fromClick"
|
||||
>
|
||||
{{ t('views.login.loginBtn') }}
|
||||
</a-button>
|
||||
|
||||
<a-row
|
||||
:gutter="8"
|
||||
justify="space-between"
|
||||
align="middle"
|
||||
style="margin-top: 18px"
|
||||
>
|
||||
<a-col :span="18">
|
||||
<span>{{ t('views.login.loginMethod') }}</span>
|
||||
<a-tooltip :title="t('views.login.loginMethodWX')">
|
||||
<a-button shape="circle" size="middle" type="link">
|
||||
<template #icon>
|
||||
<WechatOutlined
|
||||
:style="{ color: '#52c41a', fontSize: '18px' }"
|
||||
/>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="t('views.login.loginMethodQQ')">
|
||||
<a-button shape="circle" size="middle" type="link">
|
||||
<template #icon>
|
||||
<QqOutlined :style="{ color: '#40a9ff', fontSize: '18px' }" />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-dropdown :trigger="['click', 'hover']">
|
||||
<a-button size="small" type="default">
|
||||
{{ t('i18n') }}
|
||||
<DownOutlined />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu @click="fnChangeLocale">
|
||||
<a-menu-item key="zh_CN">中文</a-menu-item>
|
||||
<a-menu-item key="en_US">English</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
padding: 110px 0 144px;
|
||||
background-image: url(../assets/background.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center 110px;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.top {
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
.logo {
|
||||
height: 44px;
|
||||
margin-right: 16px;
|
||||
vertical-align: top;
|
||||
border-style: none;
|
||||
border-radius: 6.66px;
|
||||
}
|
||||
.title {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-weight: 600;
|
||||
font-size: 33px;
|
||||
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 40px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
width: 368px;
|
||||
min-width: 260px;
|
||||
margin: 0 auto;
|
||||
|
||||
.prefix-icon {
|
||||
color: #8c8c8c;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
498
src/views/monitor/cache/index.vue
vendored
Normal file
498
src/views/monitor/cache/index.vue
vendored
Normal file
@@ -0,0 +1,498 @@
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router';
|
||||
import { reactive, ref, onMounted } from 'vue';
|
||||
import {
|
||||
listCacheName,
|
||||
listCacheKey,
|
||||
getCacheValue,
|
||||
clearCacheName,
|
||||
clearCacheKey,
|
||||
clearCacheSafe,
|
||||
} from '@/api/monitor/cache';
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table/Table';
|
||||
import { message } from 'ant-design-vue/lib';
|
||||
import { hasPermissions } from '@/plugins/auth-user';
|
||||
const route = useRoute();
|
||||
|
||||
/**路由标题 */
|
||||
let title = ref<string>(route.meta.title ?? '标题');
|
||||
|
||||
/**请求点击 */
|
||||
let isClick = ref<boolean>(false);
|
||||
|
||||
/**缓存内容信息 */
|
||||
let cacheKeyInfo = reactive({
|
||||
loading: true,
|
||||
data: {
|
||||
cacheKey: '',
|
||||
cacheName: '',
|
||||
cacheValue: '',
|
||||
remark: '',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 查询缓存内容详细
|
||||
* @param cacheKey
|
||||
*/
|
||||
function fnCacheKeyInfo(cacheKey: string) {
|
||||
if (!hasPermissions(['monitor:cache:query'])) return;
|
||||
if (isClick.value) return;
|
||||
isClick.value = true;
|
||||
cacheKeyInfo.loading = true;
|
||||
getCacheValue(cacheKeyTable.cacheName, cacheKey).then(res => {
|
||||
isClick.value = false;
|
||||
if (res.code === 200) {
|
||||
cacheKeyInfo.data = Object.assign(cacheKeyInfo.data, res.data);
|
||||
cacheKeyInfo.loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**键名列表表格字段列 */
|
||||
let cacheKeyTableColumns: ColumnsType = [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'num',
|
||||
width: '50px',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
return opt.index + 1;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '缓存键名',
|
||||
dataIndex: 'cacheKey',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
// 渲染值处理
|
||||
customRender(opt) {
|
||||
return opt.text;
|
||||
},
|
||||
// 自定义过滤控件
|
||||
customFilterDropdown: true,
|
||||
onFilter: (value, record) => {
|
||||
if (typeof value === 'string') {
|
||||
const nameLower = record.cacheKey.toLowerCase();
|
||||
return nameLower.includes(value.toLowerCase());
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'option',
|
||||
align: 'center',
|
||||
width: '50px',
|
||||
},
|
||||
];
|
||||
|
||||
/**键名列表表格数据 */
|
||||
let cacheKeyTable = reactive({
|
||||
loading: true,
|
||||
data: [],
|
||||
/**当前键名列表的缓存名称 */
|
||||
cacheName: '',
|
||||
});
|
||||
|
||||
/**
|
||||
* 清理指定缓存键名
|
||||
* @param cacheKey 键名列表中的缓存键名
|
||||
*/
|
||||
function fnCacheKeyClear(cacheKey: string) {
|
||||
if (isClick.value) return;
|
||||
isClick.value = true;
|
||||
const hide = message.loading('请稍等...', 0);
|
||||
clearCacheKey(cacheKeyTable.cacheName, cacheKey).then(res => {
|
||||
hide();
|
||||
isClick.value = false;
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `已删除缓存键名 ${cacheKey}`,
|
||||
duration: 3,
|
||||
});
|
||||
// 缓存内容显示且是删除的缓存键名,需要进行加载显示
|
||||
if (!cacheKeyInfo.loading && cacheKeyInfo.data.cacheKey === cacheKey) {
|
||||
cacheKeyInfo.loading = true;
|
||||
}
|
||||
} else {
|
||||
message.error({
|
||||
content: res.msg,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
fnCacheKeyList();
|
||||
});
|
||||
}
|
||||
|
||||
/** 查询缓存键名列表 */
|
||||
function fnCacheKeyList(cacheName: string = 'load') {
|
||||
if (cacheName === 'load') {
|
||||
cacheName = cacheKeyTable.cacheName;
|
||||
}
|
||||
if (!cacheName) {
|
||||
message.warning('请在缓存列表中选择数据项!', 3);
|
||||
return;
|
||||
}
|
||||
if (isClick.value) return;
|
||||
isClick.value = true;
|
||||
cacheKeyTable.loading = true;
|
||||
listCacheKey(cacheName).then(res => {
|
||||
isClick.value = false;
|
||||
if (res.code === 200 && res.data) {
|
||||
cacheKeyTable.cacheName = cacheName;
|
||||
cacheKeyTable.data = res.data;
|
||||
cacheKeyTable.loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**缓存列表表格数据 */
|
||||
let cacheNameTable = reactive({
|
||||
loading: true,
|
||||
data: [],
|
||||
});
|
||||
|
||||
/**缓存列表表格字段列 */
|
||||
let cacheNameTableColumns: ColumnsType = [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'num',
|
||||
width: '50px',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
return opt.index + 1;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '缓存名称',
|
||||
dataIndex: 'cacheName',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
// 渲染值处理
|
||||
customRender(opt) {
|
||||
return opt.text;
|
||||
},
|
||||
// 自定义过滤控件
|
||||
customFilterDropdown: true,
|
||||
onFilter: (value, record) => {
|
||||
if (typeof value === 'string') {
|
||||
const nameLower = record.cacheName.toLowerCase();
|
||||
return nameLower.includes(value.toLowerCase());
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'remark',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'option',
|
||||
align: 'center',
|
||||
width: '50px',
|
||||
},
|
||||
];
|
||||
|
||||
/**安全清理缓存 */
|
||||
function fnClearCacheSafe() {
|
||||
if (isClick.value) return;
|
||||
isClick.value = true;
|
||||
const hide = message.loading('请稍等...', 0);
|
||||
clearCacheSafe().then(res => {
|
||||
hide();
|
||||
isClick.value = false;
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: '已完成安全清理缓存',
|
||||
duration: 3,
|
||||
});
|
||||
cacheKeyTable.loading = true;
|
||||
cacheKeyInfo.loading = true;
|
||||
} else {
|
||||
message.error({
|
||||
content: res.msg,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理指定缓存名称
|
||||
* @param cacheName 缓存名称
|
||||
*/
|
||||
function fnCacheNameClear(cacheName: string) {
|
||||
if (isClick.value) return;
|
||||
isClick.value = true;
|
||||
const hide = message.loading('请稍等...', 0);
|
||||
clearCacheName(cacheName).then(res => {
|
||||
hide();
|
||||
isClick.value = false;
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `已清理缓存名称 ${cacheName}`,
|
||||
duration: 3,
|
||||
});
|
||||
// 缓存内容显示且是删除的缓存名称,需要进行加载显示
|
||||
if (!cacheKeyInfo.loading && cacheKeyInfo.data.cacheName === cacheName) {
|
||||
cacheKeyInfo.loading = true;
|
||||
}
|
||||
} else {
|
||||
message.error({
|
||||
content: res.msg,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
fnCacheKeyList(cacheName);
|
||||
});
|
||||
}
|
||||
|
||||
/**查询缓存名称列表 */
|
||||
function fnCacheNameList() {
|
||||
if (isClick.value) return;
|
||||
isClick.value = true;
|
||||
cacheNameTable.loading = true;
|
||||
listCacheName().then(res => {
|
||||
isClick.value = false;
|
||||
if (res.code === 200 && res.data) {
|
||||
cacheNameTable.data = res.data;
|
||||
cacheNameTable.loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fnCacheNameList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer :title="title">
|
||||
<template #content>
|
||||
<a-typography-paragraph>
|
||||
系统在缓存
|
||||
<a-typography-text code>Redis</a-typography-text>
|
||||
应用程序中的可控的缓存信息
|
||||
</a-typography-paragraph>
|
||||
</template>
|
||||
|
||||
<a-row :gutter="20">
|
||||
<a-col :lg="8" :md="8" :xs="24">
|
||||
<a-card
|
||||
title="缓存列表"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', padding: 0 }"
|
||||
>
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>刷新</template>
|
||||
<a-button type="text" @click.prevent="fnCacheNameList">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>安全清理</template>
|
||||
<a-popconfirm
|
||||
placement="bottomRight"
|
||||
title="确认要执行可安全清理的缓存下所有键名吗?`"
|
||||
ok-text="确认"
|
||||
cancel-text="取消"
|
||||
@confirm="fnClearCacheSafe()"
|
||||
>
|
||||
<a-button type="text" v-perms:has="['monitor:cache:remove']">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
<a-table
|
||||
row-key="cacheName"
|
||||
size="small"
|
||||
:columns="cacheNameTableColumns"
|
||||
:data-source="cacheNameTable.data"
|
||||
:loading="cacheNameTable.loading"
|
||||
:row-selection="{
|
||||
type: 'radio',
|
||||
onChange: (selectedRowKeys: (string|number)[]) => fnCacheKeyList(selectedRowKeys[0] as string),
|
||||
}"
|
||||
:pagination="false"
|
||||
>
|
||||
<template
|
||||
#customFilterDropdown="{
|
||||
setSelectedKeys,
|
||||
selectedKeys,
|
||||
confirm,
|
||||
clearFilters,
|
||||
column,
|
||||
}"
|
||||
>
|
||||
<div style="padding: 8px">
|
||||
<a-input
|
||||
:placeholder="`模糊过滤 ${column.title}`"
|
||||
:value="selectedKeys[0]"
|
||||
style="width: 188px; margin-bottom: 8px; display: block"
|
||||
@change="
|
||||
e => setSelectedKeys(e.target.value ? [e.target.value] : [])
|
||||
"
|
||||
@pressEnter="confirm()"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
size="small"
|
||||
style="width: 90px; margin-right: 8px"
|
||||
@click="confirm()"
|
||||
>
|
||||
过滤
|
||||
</a-button>
|
||||
<a-button
|
||||
size="small"
|
||||
style="width: 90px"
|
||||
@click="clearFilters({ confirm: true })"
|
||||
>
|
||||
重置
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'option'">
|
||||
<a-popconfirm
|
||||
placement="topRight"
|
||||
title="确认要清理该缓存名称下的所有键名吗?`"
|
||||
ok-text="确认"
|
||||
cancel-text="取消"
|
||||
@confirm="fnCacheNameClear(record.cacheName)"
|
||||
>
|
||||
<a-button type="text" v-perms:has="['monitor:cache:remove']">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col :lg="8" :md="8" :xs="24">
|
||||
<a-card
|
||||
title="键名列表"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', padding: 0 }"
|
||||
>
|
||||
<template #extra>
|
||||
<a-tooltip>
|
||||
<template #title>刷新</template>
|
||||
<a-button type="text" @click.prevent="fnCacheKeyList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-table
|
||||
row-key="cacheKey"
|
||||
size="small"
|
||||
:columns="cacheKeyTableColumns"
|
||||
:data-source="cacheKeyTable.data"
|
||||
:loading="cacheKeyTable.loading"
|
||||
:row-selection="{
|
||||
type: 'radio',
|
||||
onChange: (selectedRowKeys: (string|number)[]) => fnCacheKeyInfo(selectedRowKeys[0] as string),
|
||||
}"
|
||||
:pagination="false"
|
||||
>
|
||||
<template
|
||||
#customFilterDropdown="{
|
||||
setSelectedKeys,
|
||||
selectedKeys,
|
||||
confirm,
|
||||
clearFilters,
|
||||
column,
|
||||
}"
|
||||
>
|
||||
<div style="padding: 8px">
|
||||
<a-input
|
||||
:placeholder="`模糊过滤 ${column.title}`"
|
||||
:value="selectedKeys[0]"
|
||||
style="width: 188px; margin-bottom: 8px; display: block"
|
||||
@change="
|
||||
e => setSelectedKeys(e.target.value ? [e.target.value] : [])
|
||||
"
|
||||
@pressEnter="confirm()"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
size="small"
|
||||
style="width: 90px; margin-right: 8px"
|
||||
@click="confirm()"
|
||||
>
|
||||
过滤
|
||||
</a-button>
|
||||
<a-button
|
||||
size="small"
|
||||
style="width: 90px"
|
||||
@click="clearFilters({ confirm: true })"
|
||||
>
|
||||
重置
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'option'">
|
||||
<a-popconfirm
|
||||
placement="topRight"
|
||||
title="确认要删除该缓存键吗?`"
|
||||
ok-text="确认"
|
||||
cancel-text="取消"
|
||||
@confirm="fnCacheKeyClear(record.cacheKey)"
|
||||
>
|
||||
<a-button type="text" v-perms:has="['monitor:cache:remove']">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col :lg="8" :md="8" :xs="24" v-perms:has="['monitor:cache:query']">
|
||||
<a-card
|
||||
title="缓存内容"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', padding: 0 }"
|
||||
:loading="cacheKeyInfo.loading"
|
||||
>
|
||||
<a-descriptions
|
||||
size="small"
|
||||
layout="vertical"
|
||||
:bordered="true"
|
||||
:column="1"
|
||||
>
|
||||
<a-descriptions-item label="缓存名称">
|
||||
{{ cacheKeyInfo.data.cacheName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="缓存键名">
|
||||
{{ cacheKeyInfo.data.cacheKey }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="缓存内容">
|
||||
<a-typography-paragraph>
|
||||
<a-textarea
|
||||
:value="cacheKeyInfo.data.cacheValue"
|
||||
:auto-size="{ minRows: 4, maxRows: 10 }"
|
||||
:maxlength="4000"
|
||||
:disabled="true"
|
||||
placeholder="显示缓存内容"
|
||||
/>
|
||||
</a-typography-paragraph>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
222
src/views/monitor/cache/info.vue
vendored
Normal file
222
src/views/monitor/cache/info.vue
vendored
Normal file
@@ -0,0 +1,222 @@
|
||||
<script setup lang="ts">
|
||||
import * as echarts from 'echarts/core';
|
||||
import {
|
||||
ToolboxComponent,
|
||||
ToolboxComponentOption,
|
||||
TooltipComponent,
|
||||
TooltipComponentOption,
|
||||
LegendComponent,
|
||||
LegendComponentOption,
|
||||
} from 'echarts/components';
|
||||
import { PieChart, PieSeriesOption } from 'echarts/charts';
|
||||
import { LabelLayout } from 'echarts/features';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import { getCache } from '@/api/monitor/cache';
|
||||
import { reactive, ref, onMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
const route = useRoute();
|
||||
|
||||
echarts.use([
|
||||
ToolboxComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
PieChart,
|
||||
CanvasRenderer,
|
||||
LabelLayout,
|
||||
]);
|
||||
|
||||
/**路由标题 */
|
||||
let title = ref<string>(route.meta.title ?? '标题');
|
||||
|
||||
/**加载状态 */
|
||||
let loading = ref<boolean>(true);
|
||||
|
||||
/**数据参数类型 */
|
||||
type CacheType = {
|
||||
/**服务信息 */
|
||||
info: InfoType;
|
||||
/**当前连接可用键Key总数 */
|
||||
dbSize: number;
|
||||
/**命令状态 */
|
||||
commandStats: Record<string, string>[];
|
||||
};
|
||||
|
||||
/**数据参数服务信息类型 */
|
||||
type InfoType = {
|
||||
clients: Record<string, string>;
|
||||
cluster: Record<string, string>;
|
||||
cpu: Record<string, string>;
|
||||
errorstats: Record<string, string>;
|
||||
keyspace: Record<string, string>;
|
||||
memory: Record<string, string>;
|
||||
modules: Record<string, string>;
|
||||
persistence: Record<string, string>;
|
||||
replication: Record<string, string>;
|
||||
server: Record<string, string>;
|
||||
stats: Record<string, string>;
|
||||
};
|
||||
|
||||
let cache: CacheType = reactive({
|
||||
info: {
|
||||
clients: {},
|
||||
cluster: {},
|
||||
cpu: {},
|
||||
errorstats: {},
|
||||
keyspace: {},
|
||||
memory: {},
|
||||
modules: {},
|
||||
persistence: {},
|
||||
replication: {},
|
||||
server: {},
|
||||
stats: {},
|
||||
},
|
||||
dbSize: 0,
|
||||
commandStats: [],
|
||||
});
|
||||
|
||||
/**生成命令统计图 */
|
||||
function commandStatsChart() {
|
||||
const commandStatsDom = document.getElementById('commandstats');
|
||||
if (!commandStatsDom) return;
|
||||
const commandStatsEchart = echarts.init(commandStatsDom);
|
||||
const option: echarts.ComposeOption<
|
||||
| ToolboxComponentOption
|
||||
| TooltipComponentOption
|
||||
| LegendComponentOption
|
||||
| PieSeriesOption
|
||||
> = {
|
||||
// 鼠标悬浮提示
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b} : {c} ({d}%)',
|
||||
},
|
||||
// 左侧标签
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
},
|
||||
// 右侧工具
|
||||
toolbox: {
|
||||
show: true,
|
||||
feature: {
|
||||
mark: { show: true },
|
||||
dataView: { show: true, readOnly: false },
|
||||
restore: { show: true },
|
||||
saveAsImage: { show: true },
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '命令',
|
||||
type: 'pie',
|
||||
radius: ['5%', '80%'],
|
||||
center: ['60%', '50%'],
|
||||
roseType: 'area',
|
||||
itemStyle: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
data: cache.commandStats,
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
commandStatsEchart.setOption(option);
|
||||
window.addEventListener('resize', function () {
|
||||
commandStatsEchart.resize();
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getCache()
|
||||
.then(res => {
|
||||
if (res.code === 200 && res.data) {
|
||||
cache.info = res.data.info;
|
||||
cache.dbSize = res.data.dbSize;
|
||||
cache.commandStats = res.data.commandStats;
|
||||
// 加载状态
|
||||
loading.value = false;
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
// 加载结束后生成图
|
||||
commandStatsChart();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer :title="title" :loading="loading">
|
||||
<template #content>
|
||||
<a-typography-paragraph>
|
||||
缓存
|
||||
<a-typography-text code>Redis</a-typography-text>
|
||||
应用程序的信息
|
||||
</a-typography-paragraph>
|
||||
</template>
|
||||
|
||||
<a-card
|
||||
title="基本信息"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', padding: 0 }"
|
||||
>
|
||||
<a-descriptions
|
||||
size="middle"
|
||||
layout="horizontal"
|
||||
:label-style="{ width: '140px' }"
|
||||
:bordered="true"
|
||||
:column="{ lg: 4, md: 2, xs: 1 }"
|
||||
>
|
||||
<a-descriptions-item label="Redis版本">
|
||||
{{ cache.info.server.redis_version }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="运行模式">
|
||||
{{ cache.info.server.redis_mode == 'standalone' ? '单机' : '集群' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="端口">
|
||||
{{ cache.info.server.tcp_port }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="客户端数">
|
||||
{{ cache.info.clients.connected_clients }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="运行时间(天)">
|
||||
{{ cache.info.server.uptime_in_days }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="使用内存">
|
||||
{{ cache.info.memory.used_memory_human }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="使用CPU">
|
||||
{{ parseFloat(cache.info.cpu.used_cpu_user_children).toFixed(2) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="内存配置">
|
||||
{{ cache.info.memory.maxmemory_human }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="AOF是否开启">
|
||||
{{ cache.info.persistence.aof_enabled == '0' ? '否' : '是' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="RDB是否成功">
|
||||
{{ cache.info.persistence.rdb_last_bgsave_status }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="Key数量">
|
||||
{{ cache.dbSize }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="网络入口/出口">
|
||||
{{ cache.info.stats.instantaneous_input_kbps }} kps /
|
||||
{{ cache.info.stats.instantaneous_output_kbps }} kps
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
|
||||
<a-card title="命令统计" :bordered="false">
|
||||
<div id="commandstats" style="height: 400px; width: 100%"></div>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
1087
src/views/monitor/job/index.vue
Normal file
1087
src/views/monitor/job/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
682
src/views/monitor/job/log.vue
Normal file
682
src/views/monitor/job/log.vue
Normal file
@@ -0,0 +1,682 @@
|
||||
<script setup lang="ts">
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import { message, Modal } from 'ant-design-vue/lib';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import {
|
||||
exportJobLog,
|
||||
listJobLog,
|
||||
delJobLog,
|
||||
cleanJobLog,
|
||||
} from '@/api/monitor/jobLog';
|
||||
import { getJob } from '@/api/monitor/job';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useTabsStore from '@/store/modules/tabs';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
const tabsStore = useTabsStore();
|
||||
const { getDict } = useDictStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
// 获取地址栏参数
|
||||
const jobId = route.params && (route.params.jobId as string);
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**任务组名 */
|
||||
sysJobGroup: DictType[];
|
||||
/**执行状态 */
|
||||
sysCommonStatus: DictType[];
|
||||
} = reactive({
|
||||
sysJobGroup: [],
|
||||
sysCommonStatus: [],
|
||||
});
|
||||
|
||||
/**记录开始结束时间 */
|
||||
let queryRangePicker = ref<[string, string]>(['', '']);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**任务名称 */
|
||||
jobName: '',
|
||||
/**任务组名 */
|
||||
jobGroup: undefined,
|
||||
/**执行状态 */
|
||||
status: undefined,
|
||||
/**记录开始时间 */
|
||||
beginTime: '',
|
||||
/**记录结束时间 */
|
||||
endTime: '',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
if (jobId && jobId !== '0') {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
status: undefined,
|
||||
beginTime: '',
|
||||
endTime: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
} else {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
jobName: '',
|
||||
jobGroup: undefined,
|
||||
status: undefined,
|
||||
beginTime: '',
|
||||
endTime: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
}
|
||||
queryRangePicker.value = ['', ''];
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**斑马纹 */
|
||||
striped: boolean;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
striped: false,
|
||||
seached: true,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: '日志编号',
|
||||
dataIndex: 'jobLogId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '任务名称',
|
||||
dataIndex: 'jobName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '任务组名',
|
||||
dataIndex: 'jobGroup',
|
||||
key: 'jobGroup',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '调用目标',
|
||||
dataIndex: 'invokeTarget',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '执行状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '记录时间',
|
||||
dataIndex: 'createTime',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
if (+opt.value <= 0) return '';
|
||||
return parseDateToStr(+opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '消耗时间',
|
||||
dataIndex: 'costTime',
|
||||
key: 'costTime',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
return `${opt.value} ms`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'jobLogId',
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => `总共 ${total} 条`,
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格斑马纹 */
|
||||
function fnTableStriped(_record: unknown, index: number) {
|
||||
return tableState.striped && index % 2 === 1 ? 'table-striped' : undefined;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**详情框是否显示 */
|
||||
visibleByView: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
visibleByView: false,
|
||||
title: '任务日志',
|
||||
from: {
|
||||
jobLogId: undefined,
|
||||
jobName: '',
|
||||
jobGroup: 'DEFAULT',
|
||||
invokeTarget: '',
|
||||
targetParams: '',
|
||||
status: '0',
|
||||
jobMsg: '',
|
||||
createTime: 0,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 查看
|
||||
* @param row 调度日志信息对象
|
||||
*/
|
||||
function fnModalVisibleByVive(row: Record<string, string>) {
|
||||
modalState.from = Object.assign(modalState.from, row);
|
||||
modalState.title = '调度日志信息';
|
||||
modalState.visibleByView = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.visibleByView = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务删除
|
||||
*/
|
||||
function fnRecordDelete() {
|
||||
const ids = tableState.selectedRowKeys.join(',');
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认删除调度日志编号为 【${ids}】 的数据项吗?`,
|
||||
onOk() {
|
||||
const key = 'delJobLog';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
delJobLog(ids).then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `删除成功`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**列表清空 */
|
||||
function fnCleanList() {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认清空所有调度日志数据项吗?`,
|
||||
onOk() {
|
||||
const key = 'cleanJobLog';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
cleanJobLog().then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `清空成功`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList() {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认根据搜索条件导出xlsx表格文件吗?`,
|
||||
onOk() {
|
||||
const key = 'exportJobLog';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
exportJobLog(toRaw(queryParams)).then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `已完成导出`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `job_log_${Date.now()}.xlsx`);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**关闭跳转 */
|
||||
function fnClose() {
|
||||
const to = tabsStore.tabClose(route.path);
|
||||
if (to) {
|
||||
router.push(to);
|
||||
} else {
|
||||
router.back();
|
||||
}
|
||||
}
|
||||
|
||||
/**查询调度日志列表 */
|
||||
function fnGetList() {
|
||||
tableState.loading = true;
|
||||
queryParams.beginTime = queryRangePicker.value[0];
|
||||
queryParams.endTime = queryRangePicker.value[1];
|
||||
listJobLog(toRaw(queryParams)).then(res => {
|
||||
if (res.code === 200) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.rows;
|
||||
tableState.loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([
|
||||
getDict('sys_job_group'),
|
||||
getDict('sys_common_status'),
|
||||
]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.sysJobGroup = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.sysCommonStatus = resArr[1].value;
|
||||
}
|
||||
});
|
||||
// 指定任务id数据列表
|
||||
if (jobId && jobId !== '0') {
|
||||
getJob(jobId).then(res => {
|
||||
if (res.code === 200) {
|
||||
queryParams.jobName = res.data.jobName;
|
||||
queryParams.jobGroup = res.data.jobGroup;
|
||||
fnGetList();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="任务名称" name="jobName">
|
||||
<a-input
|
||||
v-model:value="queryParams.jobName"
|
||||
:allow-clear="jobId === '0'"
|
||||
:disabled="jobId !== '0'"
|
||||
placeholder="请输入任务名称"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="任务组名" name="jobGroup">
|
||||
<a-select
|
||||
v-model:value="queryParams.jobGroup"
|
||||
allow-clear
|
||||
placeholder="请选择菜单状态"
|
||||
:options="dict.sysJobGroup"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="执行状态" name="status">
|
||||
<a-select
|
||||
v-model:value="queryParams.status"
|
||||
allow-clear
|
||||
placeholder="请选择执行状态"
|
||||
:options="dict.sysCommonStatus"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="记录时间" name="queryRangePicker">
|
||||
<a-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
allow-clear
|
||||
bordered
|
||||
value-format="YYYY-MM-DD"
|
||||
:placeholder="['记录开始', '记录结束']"
|
||||
style="width: 100%"
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button type="default" @click.prevent="fnClose()">
|
||||
<template #icon><CloseOutlined /></template>
|
||||
关闭
|
||||
</a-button>
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
@click.prevent="fnRecordDelete()"
|
||||
v-perms:has="['monitor:job:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
删除
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
danger
|
||||
@click.prevent="fnCleanList()"
|
||||
v-perms:has="['monitor:job:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
清空
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
@click.prevent="fnExportList()"
|
||||
v-perms:has="['monitor:job:export']"
|
||||
>
|
||||
<template #icon><ExportOutlined /></template>
|
||||
导出
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>搜索栏</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
checked-children="显"
|
||||
un-checked-children="隐"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>表格斑马纹</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.striped"
|
||||
checked-children="开"
|
||||
un-checked-children="关"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>刷新</template>
|
||||
<a-button type="text" @click.prevent="fnGetList">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>密度</template>
|
||||
<a-dropdown trigger="click">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">默认</a-menu-item>
|
||||
<a-menu-item key="middle">中等</a-menu-item>
|
||||
<a-menu-item key="small">紧凑</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="jobLogId"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:row-class-name="fnTableStriped"
|
||||
:scroll="{ x: true }"
|
||||
:pagination="tablePagination"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'jobGroup'">
|
||||
<DictTag :options="dict.sysJobGroup" :value="record.jobGroup" />
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="+record.status ? 'success' : 'error'">
|
||||
{{ ['失败', '正常'][+record.status] }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'jobLogId'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>查看详情</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByVive(record)"
|
||||
v-perms:has="['monitor:job:query']"
|
||||
>
|
||||
<template #icon><ProfileOutlined /></template>
|
||||
详情
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 详情框 -->
|
||||
<a-modal
|
||||
width="800px"
|
||||
:visible="modalState.visibleByView"
|
||||
:title="modalState.title"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="日志编号" name="jobLogId">
|
||||
{{ modalState.from.jobLogId }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="执行状态" name="status">
|
||||
<a-tag :color="+modalState.from.status ? 'success' : 'error'">
|
||||
{{ ['失败', '正常'][+modalState.from.status] }}
|
||||
</a-tag>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="任务名称" name="jobName">
|
||||
{{ modalState.from.jobName }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="任务组名" name="jobGroup">
|
||||
<DictTag
|
||||
:options="dict.sysJobGroup"
|
||||
:value="modalState.from.jobGroup"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="调用目标" name="invokeTarget">
|
||||
{{ modalState.from.invokeTarget }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="记录时间" name="createTime">
|
||||
<span v-if="+modalState.from.createTime > 0">
|
||||
{{ parseDateToStr(+modalState.from.createTime) }}
|
||||
</span>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="传入参数" name="targetParams">
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.targetParams"
|
||||
:auto-size="{ minRows: 2, maxRows: 6 }"
|
||||
placeholder="传入参数"
|
||||
:disabled="true"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="日志信息" name="jobMsg">
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.jobMsg"
|
||||
:auto-size="{ minRows: 2, maxRows: 6 }"
|
||||
placeholder="日志信息"
|
||||
:disabled="true"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button key="cancel" @click="fnModalCancel">关闭</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.table-striped) td {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
546
src/views/monitor/logininfor/index.vue
Normal file
546
src/views/monitor/logininfor/index.vue
Normal file
@@ -0,0 +1,546 @@
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router';
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import { message, Modal } from 'ant-design-vue/lib';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import {
|
||||
exportLogininfor,
|
||||
listLogininfor,
|
||||
delLogininfor,
|
||||
cleanLogininfor,
|
||||
unlockLogininfor,
|
||||
} from '@/api/monitor/logininfor';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
const { getDict } = useDictStore();
|
||||
const route = useRoute();
|
||||
|
||||
/**路由标题 */
|
||||
let title = ref<string>(route.meta.title ?? '标题');
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**登录状态 */
|
||||
sysCommonStatus: DictType[];
|
||||
} = reactive({
|
||||
sysCommonStatus: [],
|
||||
});
|
||||
|
||||
/**开始结束时间 */
|
||||
let queryRangePicker = ref<[string, string]>(['', '']);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**登录地址 */
|
||||
ipaddr: '',
|
||||
/**登录账号 */
|
||||
userName: '',
|
||||
/**登录状态 */
|
||||
status: undefined,
|
||||
/**开始时间 */
|
||||
beginTime: '',
|
||||
/**结束时间 */
|
||||
endTime: '',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
ipaddr: '',
|
||||
userName: '',
|
||||
status: undefined,
|
||||
beginTime: '',
|
||||
endTime: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
queryRangePicker.value = ['', ''];
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**斑马纹 */
|
||||
striped: boolean;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
/**勾选单个的登录账号 */
|
||||
selectedUserName: string;
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
striped: false,
|
||||
seached: false,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
selectedUserName: '',
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: '日志编号',
|
||||
dataIndex: 'infoId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '登录账号',
|
||||
dataIndex: 'userName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '登录地址',
|
||||
dataIndex: 'ipaddr',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '登录地点',
|
||||
dataIndex: 'loginLocation',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作系统',
|
||||
dataIndex: 'os',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '浏览器',
|
||||
dataIndex: 'browser',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '登录状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '登录信息',
|
||||
dataIndex: 'msg',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '登录时间',
|
||||
dataIndex: 'loginTime',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
if (+opt.value <= 0) return '';
|
||||
return parseDateToStr(+opt.value);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => `总共 ${total} 条`,
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格斑马纹 */
|
||||
function fnTableStriped(_record: unknown, index: number) {
|
||||
return tableState.striped && index % 2 === 1 ? 'table-striped' : undefined;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRows(
|
||||
_: (string | number)[],
|
||||
rows: Record<string, string>[]
|
||||
) {
|
||||
tableState.selectedRowKeys = rows.map(item => item.infoId);
|
||||
// 针对单个登录账号解锁
|
||||
if (rows.length === 1) {
|
||||
tableState.selectedUserName = rows[0].userName;
|
||||
} else {
|
||||
tableState.selectedUserName = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**记录删除 */
|
||||
function fnRecordDelete() {
|
||||
const ids = tableState.selectedRowKeys.join(',');
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认删除访问编号为 【${ids}】 的数据项吗?`,
|
||||
onOk() {
|
||||
const hide = message.loading('请稍等...', 0);
|
||||
delLogininfor(ids).then(res => {
|
||||
hide();
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `删除成功`,
|
||||
duration: 3,
|
||||
});
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
fnGetList();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**列表清空 */
|
||||
function fnCleanList() {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认清空所有登录日志数据项?`,
|
||||
onOk() {
|
||||
const hide = message.loading('请稍等...', 0);
|
||||
cleanLogininfor().then(res => {
|
||||
hide();
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `清空成功`,
|
||||
duration: 3,
|
||||
});
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
fnGetList();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**登录账号解锁 */
|
||||
function fnUnlock() {
|
||||
const username = tableState.selectedUserName;
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认解锁用户 【${username}】 数据项?`,
|
||||
onOk() {
|
||||
const hide = message.loading('请稍等...', 0);
|
||||
unlockLogininfor(username).then(res => {
|
||||
hide();
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `${username} 解锁成功`,
|
||||
duration: 3,
|
||||
});
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList() {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认根据搜索条件导出xlsx表格文件吗?`,
|
||||
onOk() {
|
||||
const hide = message.loading('正在打开...', 0);
|
||||
exportLogininfor(toRaw(queryParams)).then(res => {
|
||||
hide();
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `已完成导出`,
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `logininfor_${Date.now()}.xlsx`);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**查询登录日志列表 */
|
||||
function fnGetList() {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
queryParams.beginTime = queryRangePicker.value[0];
|
||||
queryParams.endTime = queryRangePicker.value[1];
|
||||
listLogininfor(toRaw(queryParams)).then(res => {
|
||||
if (res.code === 200 && Array.isArray(res.rows)) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.rows;
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('sys_common_status')]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.sysCommonStatus = resArr[0].value;
|
||||
}
|
||||
});
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer :title="title">
|
||||
<template #content>
|
||||
<a-typography-paragraph>
|
||||
对登录进行日志收集,登录锁定的信息存入
|
||||
<a-typography-text code>Redis</a-typography-text>
|
||||
可对登录账号进行解锁。
|
||||
</a-typography-paragraph>
|
||||
</template>
|
||||
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="登录地址" name="ipaddr">
|
||||
<a-input
|
||||
v-model:value="queryParams.ipaddr"
|
||||
allow-clear
|
||||
:maxlength="128"
|
||||
placeholder="请输入登录地址"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="登录账号" name="userName">
|
||||
<a-input
|
||||
v-model:value="queryParams.userName"
|
||||
allow-clear
|
||||
:maxlength="30"
|
||||
placeholder="请输入登录账号"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="登录状态" name="status">
|
||||
<a-select
|
||||
v-model:value="queryParams.status"
|
||||
allow-clear
|
||||
placeholder="请选择登录状态"
|
||||
:options="dict.sysCommonStatus"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="登录时间" name="queryRangePicker">
|
||||
<a-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
allow-clear
|
||||
bordered
|
||||
value-format="YYYY-MM-DD"
|
||||
:placeholder="['登录开始', '登录结束']"
|
||||
style="width: 100%"
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
搜索</a-button
|
||||
>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
重置</a-button
|
||||
>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="!tableState.selectedUserName"
|
||||
@click.prevent="fnUnlock()"
|
||||
v-perms:has="['monitor:logininfor:unlock']"
|
||||
>
|
||||
<template #icon><UnlockOutlined /></template>
|
||||
解锁
|
||||
</a-button>
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
@click.prevent="fnRecordDelete()"
|
||||
v-perms:has="['monitor:logininfor:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
删除
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
danger
|
||||
@click.prevent="fnCleanList()"
|
||||
v-perms:has="['monitor:logininfor:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
清空
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
@click.prevent="fnExportList()"
|
||||
v-perms:has="['monitor:logininfor:export']"
|
||||
>
|
||||
<template #icon><ExportOutlined /></template>
|
||||
导出
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>搜索栏</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
checked-children="显"
|
||||
un-checked-children="隐"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>表格斑马纹</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.striped"
|
||||
checked-children="开"
|
||||
un-checked-children="关"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>刷新</template>
|
||||
<a-button type="text" @click.prevent="fnGetList">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>密度</template>
|
||||
<a-dropdown trigger="click">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">默认</a-menu-item>
|
||||
<a-menu-item key="middle">中等</a-menu-item>
|
||||
<a-menu-item key="small">紧凑</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="infoId"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:row-class-name="fnTableStriped"
|
||||
:scroll="{ x: true }"
|
||||
:pagination="tablePagination"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
onChange: fnTableSelectedRows,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<DictTag :options="dict.sysCommonStatus" :value="record.status" />
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.table-striped) td {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
338
src/views/monitor/online/index.vue
Normal file
338
src/views/monitor/online/index.vue
Normal file
@@ -0,0 +1,338 @@
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router';
|
||||
import { reactive, ref, onMounted } from 'vue';
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import { message, Modal } from 'ant-design-vue/lib';
|
||||
import { forceLogout, listOnline } from '@/api/monitor/online';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
const route = useRoute();
|
||||
|
||||
/**路由标题 */
|
||||
let title = ref<string>(route.meta.title ?? '标题');
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**登录主机 */
|
||||
ipaddr: '',
|
||||
/**登录账号 */
|
||||
userName: '',
|
||||
});
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**斑马纹 */
|
||||
striped: boolean;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
striped: false,
|
||||
seached: false,
|
||||
data: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'num',
|
||||
width: '50px',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
const idxNum = (tablePagination.current - 1) * tablePagination.pageSize;
|
||||
return idxNum + opt.index + 1;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '会话编号',
|
||||
dataIndex: 'tokenId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '登录账号',
|
||||
dataIndex: 'userName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '所属部门',
|
||||
dataIndex: 'deptName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '登录主机',
|
||||
dataIndex: 'ipaddr',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '登录地点',
|
||||
dataIndex: 'loginLocation',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作系统',
|
||||
dataIndex: 'os',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '浏览器',
|
||||
dataIndex: 'browser',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '登录时间',
|
||||
dataIndex: 'loginTime',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
if (+opt.value <= 0) return '';
|
||||
return parseDateToStr(+opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'tokenId',
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = {
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: true,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => `总共 ${total} 条`,
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
},
|
||||
};
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格斑马纹 */
|
||||
function fnTableStriped(_record: unknown, index: number) {
|
||||
return tableState.striped && index % 2 === 1 ? 'table-striped' : undefined;
|
||||
}
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams.ipaddr = '';
|
||||
queryParams.userName = '';
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/** 查询在线用户列表 */
|
||||
function fnGetList() {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
listOnline(queryParams).then(res => {
|
||||
if (res.code === 200 && Array.isArray(res.rows)) {
|
||||
tableState.data = res.rows;
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/** 强退按钮操作 */
|
||||
function fnForceLogout(row: Record<string, string>) {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认强退登录账号为 ${row.userName} 的用户?`,
|
||||
onOk() {
|
||||
const hide = message.loading('正在打开...', 0);
|
||||
forceLogout(row.tokenId).finally(() => {
|
||||
hide();
|
||||
message.error({
|
||||
content: `已强退用户 ${row.userName}`,
|
||||
duration: 2,
|
||||
});
|
||||
});
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer :title="title">
|
||||
<template #content>
|
||||
<a-typography-paragraph>
|
||||
登录用户
|
||||
<a-typography-text code>Token</a-typography-text>
|
||||
授权标识记录,存储在
|
||||
<a-typography-text code>Redis</a-typography-text>
|
||||
中,可撤销对用户的授权,拒绝用户请求并强制退出。
|
||||
</a-typography-paragraph>
|
||||
</template>
|
||||
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="登录账号" name="userName">
|
||||
<a-input
|
||||
v-model:value="queryParams.userName"
|
||||
allow-clear
|
||||
:maxlength="30"
|
||||
placeholder="请输入登录账号"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="登录主机" name="ipaddr">
|
||||
<a-input
|
||||
v-model:value="queryParams.ipaddr"
|
||||
allow-clear
|
||||
:maxlength="128"
|
||||
placeholder="请输入登录主机"
|
||||
></a-input> </a-form-item
|
||||
></a-col>
|
||||
<a-col :lg="12" :md="24" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
{{ title }}
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>搜索栏</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
checked-children="显"
|
||||
un-checked-children="隐"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>表格斑马纹</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.striped"
|
||||
checked-children="开"
|
||||
un-checked-children="关"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>刷新</template>
|
||||
<a-button type="text" @click.prevent="fnGetList">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>密度</template>
|
||||
<a-dropdown trigger="click">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">默认</a-menu-item>
|
||||
<a-menu-item key="middle">中等</a-menu-item>
|
||||
<a-menu-item key="small">紧凑</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="tokenId"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:row-class-name="fnTableStriped"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: true }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'tokenId'">
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnForceLogout(record)"
|
||||
v-perms:has="['monitor:online:forceLogout']"
|
||||
>
|
||||
<template #icon><LogoutOutlined /></template>
|
||||
强退
|
||||
</a-button>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.table-striped) td {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
692
src/views/monitor/operlog/index.vue
Normal file
692
src/views/monitor/operlog/index.vue
Normal file
@@ -0,0 +1,692 @@
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router';
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import { message, Modal } from 'ant-design-vue/lib';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import {
|
||||
exportOperlog,
|
||||
listOperlog,
|
||||
delOperlog,
|
||||
cleanOperlog,
|
||||
} from '@/api/monitor/operlog';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
const { getDict } = useDictStore();
|
||||
const route = useRoute();
|
||||
|
||||
/**路由标题 */
|
||||
let title = ref<string>(route.meta.title ?? '标题');
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**业务类型 */
|
||||
sysBusinessType: DictType[];
|
||||
/**登录状态 */
|
||||
sysCommonStatus: DictType[];
|
||||
} = reactive({
|
||||
sysBusinessType: [],
|
||||
sysCommonStatus: [],
|
||||
});
|
||||
|
||||
/**开始结束时间 */
|
||||
let queryRangePicker = ref<[string, string]>(['', '']);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**操作模块 */
|
||||
title: '',
|
||||
/**操作人员 */
|
||||
operName: '',
|
||||
/**业务类型 */
|
||||
businessType: undefined,
|
||||
/**操作状态 */
|
||||
status: undefined,
|
||||
/**开始时间 */
|
||||
beginTime: '',
|
||||
/**结束时间 */
|
||||
endTime: '',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
title: '',
|
||||
operName: '',
|
||||
businessType: undefined,
|
||||
status: undefined,
|
||||
beginTime: '',
|
||||
endTime: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
queryRangePicker.value = ['', ''];
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**斑马纹 */
|
||||
striped: boolean;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
striped: false,
|
||||
seached: false,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: '日志编号',
|
||||
dataIndex: 'operId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '模块名称',
|
||||
dataIndex: 'title',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '业务类型',
|
||||
dataIndex: 'businessType',
|
||||
key: 'businessType',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作人员',
|
||||
dataIndex: 'operName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '请求方式',
|
||||
dataIndex: 'requestMethod',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '请求主机',
|
||||
dataIndex: 'operIp',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作日期',
|
||||
dataIndex: 'operTime',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
if (+opt.value <= 0) return '';
|
||||
return parseDateToStr(+opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '消耗时间',
|
||||
dataIndex: 'costTime',
|
||||
key: 'costTime',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
return `${opt.value} ms`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'operId',
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => `总共 ${total} 条`,
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格斑马纹 */
|
||||
function fnTableStriped(_record: unknown, index: number) {
|
||||
return tableState.striped && index % 2 === 1 ? 'table-striped' : undefined;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**详情框是否显示 */
|
||||
visibleByView: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
visibleByView: false,
|
||||
title: '操作日志',
|
||||
from: {
|
||||
operId: undefined,
|
||||
businessType: 0,
|
||||
deptName: '',
|
||||
method: '',
|
||||
operIp: '',
|
||||
operLocation: '',
|
||||
operMsg: '',
|
||||
operName: '',
|
||||
operParam: '',
|
||||
operTime: 0,
|
||||
operUrl: '',
|
||||
operatorType: 1,
|
||||
requestMethod: 'PUT',
|
||||
status: 1,
|
||||
title: '',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 查看
|
||||
* @param row 操作日志信息对象
|
||||
*/
|
||||
function fnModalVisibleByVive(row: Record<string, string>) {
|
||||
modalState.from = Object.assign(modalState.from, row);
|
||||
modalState.title = '操作日志信息';
|
||||
modalState.visibleByView = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.visibleByView = false;
|
||||
}
|
||||
|
||||
/**记录删除 */
|
||||
function fnRecordDelete() {
|
||||
const ids = tableState.selectedRowKeys.join(',');
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认删除访问编号为 【${ids}】 的数据项吗?`,
|
||||
onOk() {
|
||||
const key = 'delOperlog';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
delOperlog(ids).then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: '删除成功',
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**列表清空 */
|
||||
function fnCleanList() {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认清空所有登录日志数据项?`,
|
||||
onOk() {
|
||||
const key = 'cleanOperlog';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
cleanOperlog().then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: '清空成功',
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList() {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认根据搜索条件导出xlsx表格文件吗?`,
|
||||
onOk() {
|
||||
const key = 'exportOperlog';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
exportOperlog(toRaw(queryParams)).then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `已完成导出`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `operlog_${Date.now()}.xlsx`);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**查询登录日志列表 */
|
||||
function fnGetList() {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
queryParams.beginTime = queryRangePicker.value[0];
|
||||
queryParams.endTime = queryRangePicker.value[1];
|
||||
listOperlog(toRaw(queryParams)).then(res => {
|
||||
if (res.code === 200 && Array.isArray(res.rows)) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.rows;
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([
|
||||
getDict('sys_oper_type'),
|
||||
getDict('sys_common_status'),
|
||||
]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.sysBusinessType = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.sysCommonStatus = resArr[1].value;
|
||||
}
|
||||
});
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer :title="title">
|
||||
<template #content>
|
||||
<a-typography-paragraph>
|
||||
对接口请求进行日志收集,统计高频接口分析优化等操作。
|
||||
</a-typography-paragraph>
|
||||
</template>
|
||||
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="操作模块" name="title">
|
||||
<a-input
|
||||
v-model:value="queryParams.title"
|
||||
allow-clear
|
||||
placeholder="请输入操作模块"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="操作人员" name="operName">
|
||||
<a-input
|
||||
v-model:value="queryParams.operName"
|
||||
allow-clear
|
||||
placeholder="请输入操作人员"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="操作类型" name="businessType">
|
||||
<a-select
|
||||
v-model:value="queryParams.businessType"
|
||||
allow-clear
|
||||
placeholder="请选择操作类型"
|
||||
:options="dict.sysBusinessType"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="操作状态" name="status">
|
||||
<a-select
|
||||
v-model:value="queryParams.status"
|
||||
allow-clear
|
||||
placeholder="请选择操作状态"
|
||||
:options="dict.sysCommonStatus"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="操作时间" name="queryRangePicker">
|
||||
<a-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
allow-clear
|
||||
bordered
|
||||
value-format="YYYY-MM-DD"
|
||||
:placeholder="['操作开始', '操作结束']"
|
||||
style="width: 100%"
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
搜索</a-button
|
||||
>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
重置</a-button
|
||||
>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
@click.prevent="fnRecordDelete()"
|
||||
v-perms:has="['monitor:operlog:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
删除
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
danger
|
||||
@click.prevent="fnCleanList()"
|
||||
v-perms:has="['monitor:operlog:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
清空
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
@click.prevent="fnExportList()"
|
||||
v-perms:has="['monitor:operlog:export']"
|
||||
>
|
||||
<template #icon><ExportOutlined /></template>
|
||||
导出
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>搜索栏</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
checked-children="显"
|
||||
un-checked-children="隐"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>表格斑马纹</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.striped"
|
||||
checked-children="开"
|
||||
un-checked-children="关"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>刷新</template>
|
||||
<a-button type="text" @click.prevent="fnGetList">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>密度</template>
|
||||
<a-dropdown trigger="click">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">默认</a-menu-item>
|
||||
<a-menu-item key="middle">中等</a-menu-item>
|
||||
<a-menu-item key="small">紧凑</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="operId"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:row-class-name="fnTableStriped"
|
||||
:scroll="{ x: true }"
|
||||
:pagination="tablePagination"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'businessType'">
|
||||
<DictTag
|
||||
:options="dict.sysBusinessType"
|
||||
:value="record.businessType"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<DictTag :options="dict.sysCommonStatus" :value="record.status" />
|
||||
</template>
|
||||
<template v-if="column.key === 'operId'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>查看详情</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByVive(record)"
|
||||
v-perms:has="['monitor:operlog:query']"
|
||||
>
|
||||
<template #icon><ProfileOutlined /></template>
|
||||
详情
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 详情框 -->
|
||||
<a-modal
|
||||
width="800px"
|
||||
:visible="modalState.visibleByView"
|
||||
:title="modalState.title"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="日志编号" name="operId">
|
||||
{{ modalState.from.operId }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="执行状态" name="status">
|
||||
<a-tag :color="+modalState.from.status ? 'success' : 'error'">
|
||||
{{ ['失败', '正常'][+modalState.from.status] }}
|
||||
</a-tag>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="业务类型" name="businessType">
|
||||
{{ modalState.from.title }} /
|
||||
<DictTag
|
||||
:options="dict.sysBusinessType"
|
||||
:value="modalState.from.businessType"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="操作人员" name="operName">
|
||||
{{ modalState.from.operName }} / {{ modalState.from.operIp }} /
|
||||
{{ modalState.from.operLocation }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="请求地址" name="operUrl">
|
||||
{{ modalState.from.requestMethod }} -
|
||||
{{ modalState.from.operUrl }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="操作时间" name="operTime">
|
||||
<span v-if="+modalState.from.operTime > 0">
|
||||
{{ parseDateToStr(+modalState.from.operTime) }}
|
||||
</span>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="请求耗时" name="costTime">
|
||||
{{ modalState.from.costTime }} ms
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="操作方法" name="method">
|
||||
{{ modalState.from.method }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="请求参数" name="operParam">
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.operParam"
|
||||
:auto-size="{ minRows: 2, maxRows: 6 }"
|
||||
placeholder="请求参数"
|
||||
:disabled="true"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="操作信息" name="operMsg">
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.operMsg"
|
||||
:auto-size="{ minRows: 2, maxRows: 6 }"
|
||||
placeholder="操作信息"
|
||||
:disabled="true"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button key="cancel" @click="fnModalCancel">关闭</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.table-striped) td {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
329
src/views/monitor/server/info.vue
Normal file
329
src/views/monitor/server/info.vue
Normal file
@@ -0,0 +1,329 @@
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router';
|
||||
import { reactive, ref, onMounted } from 'vue';
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import { getServer } from '@/api/monitor/server';
|
||||
const route = useRoute();
|
||||
|
||||
/**路由标题 */
|
||||
let title = ref<string>(route.meta.title ?? '标题');
|
||||
|
||||
/**加载状态 */
|
||||
let loading = ref<boolean>(true);
|
||||
|
||||
/**磁盘信息表格字段列 */
|
||||
let diskTableColumns: ColumnsType = [
|
||||
{
|
||||
title: '路径盘符',
|
||||
dataIndex: 'target',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '总大小',
|
||||
dataIndex: 'size',
|
||||
align: 'center',
|
||||
},
|
||||
|
||||
{
|
||||
title: '剩余大小',
|
||||
dataIndex: 'avail',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '已使用大小',
|
||||
dataIndex: 'used',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '空间使用率(%)',
|
||||
dataIndex: 'pcent',
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
|
||||
/**数据参数类型 */
|
||||
type ServerType = {
|
||||
/**CPU */
|
||||
cpu: Record<string, string | number>;
|
||||
/**磁盘 */
|
||||
disk: Record<string, string>[];
|
||||
/**内存 */
|
||||
memory: Record<string, string | number>;
|
||||
/**网络 */
|
||||
network: Record<string, string>;
|
||||
/**项目 */
|
||||
project: Record<string, string>;
|
||||
/**系统 */
|
||||
system: Record<string, string | number>;
|
||||
/**时间 */
|
||||
time: Record<string, string | number>;
|
||||
};
|
||||
|
||||
let server: ServerType = reactive({
|
||||
cpu: {},
|
||||
disk: [],
|
||||
memory: {},
|
||||
network: {},
|
||||
project: {},
|
||||
system: {},
|
||||
time: {},
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getServer().then(res => {
|
||||
if (res.code === 200 && res.data) {
|
||||
// CPU信息
|
||||
let cpu = res.data.cpu;
|
||||
cpu.coreUsed = cpu.coreUsed.map((item: string) => item).join(' / ');
|
||||
server.cpu = cpu;
|
||||
// 磁盘信息
|
||||
server.disk = res.data.disk;
|
||||
// 内存信息
|
||||
server.memory = res.data.memory;
|
||||
// 网络信息
|
||||
server.network = res.data.network;
|
||||
// 项目信息
|
||||
server.project = res.data.project;
|
||||
// 系统信息
|
||||
server.system = res.data.system;
|
||||
// 时间信息
|
||||
server.time = res.data.time;
|
||||
// 加载状态
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer :title="title" :loading="loading">
|
||||
<template #content>
|
||||
<a-typography-paragraph> 服务器与应用程序的信息 </a-typography-paragraph>
|
||||
</template>
|
||||
|
||||
<a-card
|
||||
title="项目信息"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', padding: 0 }"
|
||||
>
|
||||
<a-descriptions
|
||||
size="middle"
|
||||
layout="horizontal"
|
||||
:label-style="{ width: '140px' }"
|
||||
:bordered="true"
|
||||
:column="{ lg: 2, md: 2, xs: 1 }"
|
||||
>
|
||||
<a-descriptions-item label="项目名称">
|
||||
{{ server.project.name }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="项目版本">
|
||||
{{ server.project.version }}
|
||||
</a-descriptions-item>
|
||||
|
||||
<a-descriptions-item label="项目环境">
|
||||
{{ server.project.env }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="项目路径">
|
||||
{{ server.project.appDir }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="项目依赖">
|
||||
<a-tag
|
||||
v-for="(value, name) in server.project.dependencies"
|
||||
:key="name"
|
||||
>
|
||||
{{ name }}:{{ value }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
|
||||
<a-card
|
||||
title="系统信息"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', padding: 0 }"
|
||||
>
|
||||
<a-descriptions
|
||||
size="middle"
|
||||
layout="horizontal"
|
||||
:label-style="{ width: '140px' }"
|
||||
:column="{ lg: 2, md: 2, xs: 1 }"
|
||||
:bordered="true"
|
||||
>
|
||||
<a-descriptions-item
|
||||
label="GO版本"
|
||||
:span="2"
|
||||
v-if="server.system && server.system.go"
|
||||
>
|
||||
{{ server.system.go }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item
|
||||
label="Node版本"
|
||||
v-if="server.system && server.system.node"
|
||||
>
|
||||
{{ server.system.node }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item
|
||||
label="V8版本"
|
||||
v-if="server.system && server.system.v8"
|
||||
>
|
||||
{{ server.system.v8 }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="进程PID号">
|
||||
{{ server.system.processId }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="运行平台">
|
||||
{{ server.system.platform }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="系统架构">
|
||||
{{ server.system.arch }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="系统平台">
|
||||
{{ server.system.uname }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="系统发行版本">
|
||||
{{ server.system.release }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="主机名称">
|
||||
{{ server.system.hostname }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="主机用户目录" :span="2">
|
||||
{{ server.system.homeDir }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="项目路径" :span="2">
|
||||
{{ server.system.cmd }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="执行命令" :span="2">
|
||||
{{ server.system.execCommand }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
|
||||
<a-card
|
||||
title="CPU信息"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', padding: 0 }"
|
||||
>
|
||||
<a-descriptions
|
||||
size="middle"
|
||||
layout="horizontal"
|
||||
:label-style="{ width: '140px' }"
|
||||
:column="1"
|
||||
:bordered="true"
|
||||
>
|
||||
<a-descriptions-item label="型号">
|
||||
{{ server.cpu.model }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="速率Hz">
|
||||
{{ server.cpu.speed }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="核心数">
|
||||
{{ server.cpu.core }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="使用率(%)">
|
||||
{{ server.cpu.coreUsed }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
|
||||
<a-card
|
||||
title="内存信息"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', padding: 0 }"
|
||||
>
|
||||
<a-descriptions
|
||||
size="middle"
|
||||
layout="horizontal"
|
||||
:label-style="{ width: '140px' }"
|
||||
:column="{ lg: 2, md: 2, xs: 1 }"
|
||||
:bordered="true"
|
||||
>
|
||||
<a-descriptions-item label="总内存">
|
||||
{{ server.memory.totalmem }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="剩余内存">
|
||||
{{ server.memory.freemem }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="使用率(%)">
|
||||
{{ server.memory.usage }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="进程总内存">
|
||||
{{ server.memory.rss }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="堆的总大小">
|
||||
{{ server.memory.heapTotal }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="堆已分配">
|
||||
{{ server.memory.heapUsed }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="链接库占用">
|
||||
{{ server.memory.external }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
|
||||
<a-card
|
||||
title="时间信息"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', padding: 0 }"
|
||||
>
|
||||
<a-descriptions
|
||||
size="middle"
|
||||
layout="horizontal"
|
||||
:label-style="{ width: '140px' }"
|
||||
:column="{ lg: 2, md: 2, xs: 1 }"
|
||||
:bordered="true"
|
||||
>
|
||||
<a-descriptions-item label="时区">
|
||||
{{ server.time.timezone }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="时间">
|
||||
{{ server.time.current }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="时区名称">
|
||||
{{ server.time.timezoneName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="程序启动时间">
|
||||
{{ server.time.uptime }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
|
||||
<a-card
|
||||
title="网络信息"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', padding: 0 }"
|
||||
>
|
||||
<a-descriptions
|
||||
size="middle"
|
||||
layout="horizontal"
|
||||
:label-style="{ width: '140px' }"
|
||||
:column="1"
|
||||
:bordered="true"
|
||||
>
|
||||
<a-descriptions-item
|
||||
:label="name"
|
||||
v-for="(value, name) in server.network"
|
||||
:key="name"
|
||||
>
|
||||
{{ value }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
|
||||
<a-card title="磁盘信息" :bordered="false" :body-style="{ padding: 0 }">
|
||||
<a-table
|
||||
class="disk"
|
||||
row-key="target"
|
||||
size="middle"
|
||||
:columns="diskTableColumns"
|
||||
:data-source="server.disk"
|
||||
:pagination="false"
|
||||
:scroll="{ x: true }"
|
||||
>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
11
src/views/redirect/index.vue
Normal file
11
src/views/redirect/index.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router';
|
||||
const router = useRouter();
|
||||
const { params, query } = router.currentRoute.value;
|
||||
|
||||
router.replace({ path: `/${params.path}`, query });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>稍等...</span>
|
||||
</template>
|
||||
313
src/views/register.vue
Normal file
313
src/views/register.vue
Normal file
@@ -0,0 +1,313 @@
|
||||
<script lang="ts" setup>
|
||||
import { Modal, message } from 'ant-design-vue/lib';
|
||||
import { reactive, onMounted, toRaw } from 'vue';
|
||||
import { getCaptchaImage, register } from '@/api/login';
|
||||
import { regExpPasswd, regExpUserName } from '@/utils/regular-utils';
|
||||
import { useRouter } from 'vue-router';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const codeImgFall =
|
||||
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
||||
|
||||
let state = reactive({
|
||||
/**表单属性 */
|
||||
form: {
|
||||
/**账号 */
|
||||
username: '',
|
||||
/**密码 */
|
||||
password: '',
|
||||
/**确认密码 */
|
||||
confirmPassword: '',
|
||||
/**验证码 */
|
||||
code: '',
|
||||
/**验证码uuid */
|
||||
uuid: '',
|
||||
},
|
||||
/**表单提交点击状态 */
|
||||
formClick: false,
|
||||
/**验证码状态 */
|
||||
captcha: {
|
||||
/**验证码开关 */
|
||||
enabled: false,
|
||||
/**验证码图片地址 */
|
||||
codeImg: '',
|
||||
codeImgFall: codeImgFall,
|
||||
},
|
||||
/**验证码点击状态 */
|
||||
captchaClick: false,
|
||||
});
|
||||
|
||||
/**表单验证确认密码是否一致 */
|
||||
function fnEqualToPassword(
|
||||
rule: Record<string, any>,
|
||||
value: string,
|
||||
callback: (error?: string) => void
|
||||
) {
|
||||
if (!value) {
|
||||
return Promise.reject(t('views.register.passwordErr'));
|
||||
}
|
||||
if (state.form.password === value) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(t('views.register.passwordConfirmErr'));
|
||||
}
|
||||
|
||||
/**表单验证通过 */
|
||||
function fnFinish() {
|
||||
state.formClick = true;
|
||||
// 发送请求
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
register(toRaw(state.form))
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
Modal.success({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.register.tipContent', {
|
||||
username: state.form.username,
|
||||
}),
|
||||
okText: t('views.register.tipBtn'),
|
||||
onOk() {
|
||||
router.push({ name: 'Login' });
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.error(`${res.msg}`, 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
state.formClick = false;
|
||||
// 刷新验证码
|
||||
if (state.captcha.enabled) {
|
||||
state.form.code = '';
|
||||
fnGetCaptcha();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码
|
||||
*/
|
||||
function fnGetCaptcha() {
|
||||
if (state.captchaClick) return;
|
||||
state.captchaClick = true;
|
||||
getCaptchaImage().then(res => {
|
||||
state.captchaClick = false;
|
||||
if (res.code != 200) {
|
||||
message.warning(`${res.msg}`, 3);
|
||||
return;
|
||||
}
|
||||
state.captcha.enabled = Boolean(res.captchaEnabled);
|
||||
if (state.captcha.enabled) {
|
||||
state.captcha.codeImg = res.img;
|
||||
state.form.uuid = res.uuid;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fnGetCaptcha();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="top">
|
||||
<div class="header">
|
||||
<a href="/" target="_self"
|
||||
><img src="@/assets/logo.png" class="logo" alt="logo" />
|
||||
<span class="title">{{ t('common.title') }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="desc">{{ t('common.desc') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<a-form :model="state.form" name="tabForm" @finish="fnFinish">
|
||||
<a-form-item
|
||||
name="username"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
pattern: regExpUserName,
|
||||
message: t('valid.userNameReg'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="state.form.username"
|
||||
size="large"
|
||||
:placeholder="t('valid.userNameHit')"
|
||||
:maxlength="30"
|
||||
>
|
||||
<template #prefix>
|
||||
<UserOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
name="password"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
pattern: regExpPasswd,
|
||||
message: t('valid.passwordReg'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="state.form.password"
|
||||
size="large"
|
||||
:placeholder="t('valid.passwordHit')"
|
||||
:maxlength="26"
|
||||
>
|
||||
<template #prefix>
|
||||
<LockOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
name="confirmPassword"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
min: 6,
|
||||
max: 26,
|
||||
validator: fnEqualToPassword,
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="state.form.confirmPassword"
|
||||
size="large"
|
||||
:placeholder="t('valid.passwordConfirmHit')"
|
||||
:maxlength="26"
|
||||
>
|
||||
<template #prefix>
|
||||
<LockOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-row :gutter="8" v-if="state.captcha.enabled">
|
||||
<a-col :span="16">
|
||||
<a-form-item
|
||||
name="code"
|
||||
:rules="[
|
||||
{ required: true, min: 1, message: t('valid.codePlease') },
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="state.form.code"
|
||||
size="large"
|
||||
:placeholder="t('valid.codeHit')"
|
||||
:maxlength="6"
|
||||
>
|
||||
<template #prefix>
|
||||
<RobotOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-image
|
||||
:alt="t('valid.codeHit')"
|
||||
style="cursor: pointer; border-radius: 2px"
|
||||
width="120px"
|
||||
height="40px"
|
||||
:preview="false"
|
||||
:src="state.captcha.codeImg"
|
||||
:fallback="state.captcha.codeImgFall"
|
||||
@click="fnGetCaptcha"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-button
|
||||
block
|
||||
type="primary"
|
||||
size="large"
|
||||
html-type="submit"
|
||||
:loading="state.formClick"
|
||||
>
|
||||
{{ t('views.register.registerBtn') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
block
|
||||
type="default"
|
||||
size="large"
|
||||
style="margin-top: 16px"
|
||||
@click="() => router.push({ name: 'Login' })"
|
||||
>
|
||||
{{ t('views.register.loginBtn') }}
|
||||
</a-button>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
padding: 110px 0 144px;
|
||||
background-image: url(../assets/background.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center 110px;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.top {
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
.logo {
|
||||
height: 44px;
|
||||
margin-right: 16px;
|
||||
vertical-align: top;
|
||||
border-style: none;
|
||||
border-radius: 6.66px;
|
||||
}
|
||||
.title {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
font-weight: 600;
|
||||
font-size: 33px;
|
||||
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 40px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
width: 368px;
|
||||
min-width: 260px;
|
||||
margin: 0 auto;
|
||||
|
||||
.prefix-icon {
|
||||
color: #8c8c8c;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
809
src/views/system/config/index.vue
Normal file
809
src/views/system/config/index.vue
Normal file
@@ -0,0 +1,809 @@
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router';
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import { message, Modal, Form } from 'ant-design-vue/lib';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import {
|
||||
exportConfig,
|
||||
listConfig,
|
||||
getConfig,
|
||||
delConfig,
|
||||
addConfig,
|
||||
updateConfig,
|
||||
refreshCache,
|
||||
} from '@/api/system/config';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
const { getDict } = useDictStore();
|
||||
const route = useRoute();
|
||||
|
||||
/**路由标题 */
|
||||
let title = ref<string>(route.meta.title ?? '标题');
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**系统内置 */
|
||||
sysYesNo: DictType[];
|
||||
} = reactive({
|
||||
sysYesNo: [],
|
||||
});
|
||||
|
||||
/**开始结束时间 */
|
||||
let queryRangePicker = ref<[string, string]>(['', '']);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**参数名称 */
|
||||
configName: '',
|
||||
/**参数键名 */
|
||||
configKey: '',
|
||||
/**系统内置 */
|
||||
configType: undefined,
|
||||
/**记录开始时间 */
|
||||
beginTime: '',
|
||||
/**记录结束时间 */
|
||||
endTime: '',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
configName: '',
|
||||
configKey: '',
|
||||
configType: undefined,
|
||||
beginTime: '',
|
||||
endTime: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
queryRangePicker.value = ['', ''];
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**斑马纹 */
|
||||
striped: boolean;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
striped: false,
|
||||
seached: false,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: '参数编号',
|
||||
dataIndex: 'configId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '参数名称',
|
||||
dataIndex: 'configName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '参数键名',
|
||||
dataIndex: 'configKey',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '参数键值',
|
||||
dataIndex: 'configValue',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '系统内置',
|
||||
dataIndex: 'configType',
|
||||
key: 'configType',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
if (+opt.value <= 0) return '';
|
||||
return parseDateToStr(+opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'configId',
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => `总共 ${total} 条`,
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格斑马纹 */
|
||||
function fnTableStriped(_record: unknown, index: number) {
|
||||
return tableState.striped && index % 2 === 1 ? 'table-striped' : undefined;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**详情框是否显示 */
|
||||
visibleByView: boolean;
|
||||
/**新增框或修改框是否显示 */
|
||||
visibleByEdit: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
visibleByView: false,
|
||||
visibleByEdit: false,
|
||||
title: '参数配置',
|
||||
from: {
|
||||
configId: undefined,
|
||||
configName: '',
|
||||
configKey: '',
|
||||
configValue: '',
|
||||
configType: 'N',
|
||||
remark: undefined,
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
reactive({
|
||||
configName: [
|
||||
{ required: true, min: 1, max: 50, message: '请正确输入参数名称' },
|
||||
],
|
||||
configKey: [
|
||||
{ required: true, min: 1, max: 50, message: '请正确输入参数键名' },
|
||||
],
|
||||
configValue: [
|
||||
{ required: true, min: 1, max: 50, message: '请正确输入参数键值' },
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 查看
|
||||
* @param configId 参数编号id
|
||||
*/
|
||||
function fnModalVisibleByVive(configId: string | number) {
|
||||
if (!configId) {
|
||||
message.error(`参数配置记录存在错误`, 2);
|
||||
return;
|
||||
}
|
||||
getConfig(configId).then(res => {
|
||||
if (res.code === 200 && res.data) {
|
||||
modalState.from = Object.assign(modalState.from, res.data);
|
||||
modalState.title = '参数配置信息';
|
||||
modalState.visibleByView = true;
|
||||
} else {
|
||||
message.error(`获取参数配置信息失败`, 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param configId 参数编号id, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByEdit(configId?: string | number) {
|
||||
if (!configId) {
|
||||
modalStateFrom.resetFields();
|
||||
modalState.title = '添加参数配置';
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
if (modalState.confirmLoading) return;
|
||||
const hide = message.loading('正在打开...', 0);
|
||||
modalState.confirmLoading = true;
|
||||
getConfig(configId).then(res => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
if (res.code === 200 && res.data) {
|
||||
modalState.from = Object.assign(modalState.from, res.data);
|
||||
modalState.title = '修改参数配置';
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
message.error(`获取参数配置信息失败`, 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalOk() {
|
||||
modalStateFrom
|
||||
.validate()
|
||||
.then(() => {
|
||||
modalState.confirmLoading = true;
|
||||
const from = toRaw(modalState.from);
|
||||
const config = from.configId ? updateConfig(from) : addConfig(from);
|
||||
const key = 'config';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
config
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `${modalState.title}成功`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
modalState.visibleByEdit = false;
|
||||
modalStateFrom.resetFields();
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(`请正确填写 ${e.errorFields.length} 处必填信息!`, 2);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.visibleByEdit = false;
|
||||
modalState.visibleByView = false;
|
||||
modalStateFrom.resetFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数配置删除
|
||||
* @param configId 参数编号ID
|
||||
*/
|
||||
function fnRecordDelete(configId: string = '0') {
|
||||
if (configId === '0') {
|
||||
configId = tableState.selectedRowKeys.join(',');
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认删除参数编号为 【${configId}】 的数据项?`,
|
||||
onOk() {
|
||||
const key = 'delConfig';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
delConfig(configId).then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `删除成功`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key: key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList() {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认根据搜索条件导出xlsx表格文件吗?`,
|
||||
onOk() {
|
||||
const key = 'exportConfig';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
exportConfig(toRaw(queryParams)).then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `已完成导出`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `config_${Date.now()}.xlsx`);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新缓存
|
||||
*/
|
||||
function fnRefreshCache() {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确定要刷新参数配置缓存吗?`,
|
||||
onOk() {
|
||||
const key = 'refreshCache';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
refreshCache().then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `刷新缓存成功`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key: key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**查询参数配置列表 */
|
||||
function fnGetList() {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
queryParams.beginTime = queryRangePicker.value[0];
|
||||
queryParams.endTime = queryRangePicker.value[1];
|
||||
listConfig(toRaw(queryParams)).then(res => {
|
||||
if (res.code === 200 && Array.isArray(res.rows)) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.rows;
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('sys_yes_no')]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.sysYesNo = resArr[0].value;
|
||||
}
|
||||
});
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer :title="title">
|
||||
<template #content>
|
||||
<a-typography-paragraph>
|
||||
系统内可配置的参数变量。
|
||||
</a-typography-paragraph>
|
||||
</template>
|
||||
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="参数名称" name="configName">
|
||||
<a-input
|
||||
v-model:value="queryParams.configName"
|
||||
allow-clear
|
||||
placeholder="请输入参数名称"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="参数键名" name="configKey">
|
||||
<a-input
|
||||
v-model:value="queryParams.configKey"
|
||||
allow-clear
|
||||
placeholder="请输入参数键名"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="4" :md="12" :xs="24">
|
||||
<a-form-item label="系统内置" name="configType">
|
||||
<a-select
|
||||
v-model:value="queryParams.configType"
|
||||
allow-clear
|
||||
placeholder="请选择"
|
||||
:options="dict.sysYesNo"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item label="创建时间" name="queryRangePicker">
|
||||
<a-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
allow-clear
|
||||
bordered
|
||||
value-format="YYYY-MM-DD"
|
||||
:placeholder="['创建开始', '创建结束']"
|
||||
style="width: 100%"
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
搜索</a-button
|
||||
>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
重置</a-button
|
||||
>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click.prevent="fnModalVisibleByEdit()"
|
||||
v-perms:has="['system:config:add']"
|
||||
>
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新建
|
||||
</a-button>
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
@click.prevent="fnRecordDelete()"
|
||||
v-perms:has="['system:config:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
删除
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
danger
|
||||
@click.prevent="fnRefreshCache"
|
||||
v-perms:has="['system:config:remove']"
|
||||
>
|
||||
<template #icon><SyncOutlined /></template>
|
||||
刷新缓存
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
@click.prevent="fnExportList()"
|
||||
v-perms:has="['system:config:export']"
|
||||
>
|
||||
<template #icon><ExportOutlined /></template>
|
||||
导出
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>搜索栏</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
checked-children="显"
|
||||
un-checked-children="隐"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>表格斑马纹</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.striped"
|
||||
checked-children="开"
|
||||
un-checked-children="关"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>刷新</template>
|
||||
<a-button type="text" @click.prevent="fnGetList">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>密度</template>
|
||||
<a-dropdown trigger="click">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">默认</a-menu-item>
|
||||
<a-menu-item key="middle">中等</a-menu-item>
|
||||
<a-menu-item key="small">紧凑</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="configId"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:row-class-name="fnTableStriped"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: true }"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'configType'">
|
||||
<DictTag :options="dict.sysYesNo" :value="record.configType" />
|
||||
</template>
|
||||
<template v-if="column.key === 'configId'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>查看详情</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByVive(record.configId)"
|
||||
v-perms:has="['system:config:query']"
|
||||
>
|
||||
<template #icon><ProfileOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>编辑</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByEdit(record.configId)"
|
||||
v-perms:has="['system:config:edit']"
|
||||
>
|
||||
<template #icon><FormOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>删除</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordDelete(record.configId)"
|
||||
v-perms:has="['system:config:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 详情框 -->
|
||||
<a-modal
|
||||
width="800px"
|
||||
:visible="modalState.visibleByView"
|
||||
:title="modalState.title"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="参数名称" name="configName">
|
||||
{{ modalState.from.configName }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="系统内置" name="configType">
|
||||
<DictTag
|
||||
:options="dict.sysYesNo"
|
||||
:value="modalState.from.configType"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="参数键名" name="configKey">
|
||||
{{ modalState.from.configKey }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="参数键值" name="configValue">
|
||||
{{ modalState.from.configValue }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="参数说明" name="remark">
|
||||
{{ modalState.from.remark }}
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button key="cancel" @click="fnModalCancel">关闭</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
|
||||
<!-- 新增框或修改框 -->
|
||||
<a-modal
|
||||
width="800px"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:visible="modalState.visibleByEdit"
|
||||
:title="modalState.title"
|
||||
:confirm-loading="modalState.confirmLoading"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form name="modalStateFrom" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="参数名称"
|
||||
name="configName"
|
||||
v-bind="modalStateFrom.validateInfos.configName"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.configName"
|
||||
allow-clear
|
||||
placeholder="请输入参数名称"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="系统内置" name="configType">
|
||||
<a-select
|
||||
v-model:value="modalState.from.configType"
|
||||
default-value="N"
|
||||
placeholder="系统内置"
|
||||
:options="dict.sysYesNo"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="参数键名"
|
||||
name="configKey"
|
||||
v-bind="modalStateFrom.validateInfos.configKey"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.configKey"
|
||||
allow-clear
|
||||
placeholder="请输入参数名称"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="参数键值"
|
||||
name="configValue"
|
||||
v-bind="modalStateFrom.validateInfos.configValue"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.configValue"
|
||||
allow-clear
|
||||
placeholder="请输入参数键值"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="参数说明" name="remark">
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.remark"
|
||||
:auto-size="{ minRows: 4, maxRows: 6 }"
|
||||
:maxlength="450"
|
||||
:show-count="true"
|
||||
placeholder="请输入参数说明"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.table-striped) td {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
800
src/views/system/dept/index.vue
Normal file
800
src/views/system/dept/index.vue
Normal file
@@ -0,0 +1,800 @@
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router';
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import { message, Modal, Form } from 'ant-design-vue/lib';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import {
|
||||
listDept,
|
||||
getDept,
|
||||
delDept,
|
||||
addDept,
|
||||
updateDept,
|
||||
listDeptExcludeChild,
|
||||
} from '@/api/system/dept';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { regExpMobile, regExpEmail } from '@/utils/regular-utils';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import { parseDataToTree } from '@/utils/parse-tree-utils';
|
||||
const { getDict } = useDictStore();
|
||||
const route = useRoute();
|
||||
|
||||
/**路由标题 */
|
||||
let title = ref<string>(route.meta.title ?? '标题');
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**状态 */
|
||||
sysNormalDisable: DictType[];
|
||||
} = reactive({
|
||||
sysNormalDisable: [],
|
||||
});
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**部门名称 */
|
||||
deptName: '',
|
||||
/**部门状态 */
|
||||
status: undefined,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
deptName: '',
|
||||
status: undefined,
|
||||
});
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格全展开行key */
|
||||
let expandedRowKeys: string[] = [];
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**斑马纹 */
|
||||
striped: boolean;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**全展开 */
|
||||
expandedRowAll: boolean;
|
||||
/**展开行key */
|
||||
expandedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
striped: false,
|
||||
seached: false,
|
||||
data: [],
|
||||
expandedRowAll: false,
|
||||
expandedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: '部门名称',
|
||||
dataIndex: 'deptName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '部门编号',
|
||||
dataIndex: 'deptId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '部门排序',
|
||||
dataIndex: 'orderNum',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '岗位状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
if (+opt.value <= 0) return '';
|
||||
return parseDateToStr(+opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'deptId',
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格斑马纹 */
|
||||
function fnTableStriped(_record: unknown, index: number) {
|
||||
return tableState.striped && index % 2 === 1 ? 'table-striped' : undefined;
|
||||
}
|
||||
|
||||
/**表格展开行key */
|
||||
function fnTableExpandedRowsAll(checked: boolean | string | number) {
|
||||
tableState.expandedRowKeys = checked ? expandedRowKeys : [];
|
||||
}
|
||||
|
||||
/**表格展开行key */
|
||||
function fnTableExpandedRowsChange(expandedRows: (string | number)[]) {
|
||||
tableState.expandedRowKeys = expandedRows;
|
||||
}
|
||||
|
||||
/**初始上级部门选择树 */
|
||||
let treeDataAll: Record<string, any>[] = [];
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**详情框是否显示 */
|
||||
visibleByView: boolean;
|
||||
/**新增框或修改框是否显示 */
|
||||
visibleByEdit: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
/**上级部门选择树 */
|
||||
treeData: Record<string, any>[];
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
visibleByView: false,
|
||||
visibleByEdit: false,
|
||||
title: '部门',
|
||||
from: {
|
||||
deptId: undefined,
|
||||
deptName: '',
|
||||
email: '',
|
||||
leader: '',
|
||||
orderNum: 0,
|
||||
parentId: '100',
|
||||
ancestors: '',
|
||||
parentName: null,
|
||||
phone: '',
|
||||
status: '0',
|
||||
},
|
||||
confirmLoading: false,
|
||||
treeData: [],
|
||||
});
|
||||
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
reactive({
|
||||
parentId: [{ required: true, message: '上级部门不能为空' }],
|
||||
deptName: [
|
||||
{ required: true, min: 1, max: 30, message: '请正确输入部门名称' },
|
||||
],
|
||||
email: [
|
||||
{
|
||||
required: false,
|
||||
pattern: regExpEmail,
|
||||
message: '请输入正确的邮箱地址',
|
||||
},
|
||||
],
|
||||
phone: [
|
||||
{
|
||||
required: false,
|
||||
pattern: regExpMobile,
|
||||
message: '请输入正确的手机号码',
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 查看
|
||||
* @param deptId 部门编号id
|
||||
*/
|
||||
function fnModalVisibleByVive(deptId: string | number) {
|
||||
if (!deptId) {
|
||||
message.error(`部门记录存在错误`, 2);
|
||||
return;
|
||||
}
|
||||
if (modalState.confirmLoading) return;
|
||||
const hide = message.loading('正在打开...', 0);
|
||||
modalState.confirmLoading = true;
|
||||
getDept(deptId).then(res => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
if (res.code === 200 && res.data) {
|
||||
if (res.data.parentId === '0') {
|
||||
modalState.treeData = [
|
||||
{ deptId: '0', parentId: '0', deptName: '根节点' },
|
||||
];
|
||||
} else {
|
||||
modalState.treeData = treeDataAll;
|
||||
}
|
||||
modalState.from = Object.assign(modalState.from, res.data);
|
||||
modalState.title = '部门信息';
|
||||
modalState.visibleByView = true;
|
||||
} else {
|
||||
message.error(`获取部门信息失败`, 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param deptId 部门编号id, 不传为新增
|
||||
* @param parentId 上级部门id
|
||||
*/
|
||||
function fnModalVisibleByEdit(
|
||||
deptId?: string | number,
|
||||
parentId?: string | number
|
||||
) {
|
||||
if (!deptId) {
|
||||
modalStateFrom.resetFields();
|
||||
if (parentId) {
|
||||
modalState.from.parentId = parentId;
|
||||
}
|
||||
modalState.treeData = treeDataAll;
|
||||
modalState.title = '添加部门信息';
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
if (modalState.confirmLoading) return;
|
||||
const hide = message.loading('正在打开...', 0);
|
||||
modalState.confirmLoading = true;
|
||||
// 获取部门信息同时查询部门列表(排除节点)
|
||||
Promise.all([getDept(deptId), listDeptExcludeChild(deptId)])
|
||||
.then(resArr => {
|
||||
if (resArr[0].code === 200 && resArr[0].data) {
|
||||
modalState.from = Object.assign(modalState.from, resArr[0].data);
|
||||
if (resArr[1].code === 200 && Array.isArray(resArr[1].data)) {
|
||||
if (resArr[1].data.length === 0) {
|
||||
modalState.treeData = [
|
||||
{ deptId: '0', parentId: '0', deptName: '根节点' },
|
||||
];
|
||||
} else {
|
||||
modalState.treeData = parseDataToTree(resArr[1].data, 'deptId');
|
||||
}
|
||||
}
|
||||
modalState.title = '修改部门信息';
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
message.error(`获取部门信息失败`, 2);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalOk() {
|
||||
modalStateFrom
|
||||
.validate()
|
||||
.then(() => {
|
||||
modalState.confirmLoading = true;
|
||||
const from = toRaw(modalState.from);
|
||||
const dept = from.deptId ? updateDept(from) : addDept(from);
|
||||
const hide = message.loading('请稍等...', 0);
|
||||
dept
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `${modalState.title}成功`,
|
||||
duration: 2,
|
||||
});
|
||||
modalState.visibleByEdit = false;
|
||||
// 新增时清空上级部门树重新获取
|
||||
if (!from.deptId) {
|
||||
treeDataAll = [];
|
||||
}
|
||||
modalStateFrom.resetFields();
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(`请正确填写 ${e.errorFields.length} 处必填信息!`, 2);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.visibleByEdit = false;
|
||||
modalState.visibleByView = false;
|
||||
modalStateFrom.resetFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门删除
|
||||
* @param deptId 部门编号id
|
||||
*/
|
||||
function fnRecordDelete(deptId: string | number) {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认删除部门编号为 【${deptId}】 的数据项?`,
|
||||
onOk() {
|
||||
const hide = message.loading('请稍等...', 0);
|
||||
delDept(deptId).then(res => {
|
||||
hide();
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `删除成功`,
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**查询部门列表 */
|
||||
function fnGetList() {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
listDept(toRaw(queryParams)).then(res => {
|
||||
if (res.code === 200 && Array.isArray(res.data)) {
|
||||
const treeData = parseDataToTree(res.data, 'deptId');
|
||||
// 初始上级部门和展开编号key
|
||||
if (treeDataAll.length <= 0) {
|
||||
// 转换树状数据
|
||||
treeDataAll = treeData;
|
||||
// 展开编号key
|
||||
expandedRowKeys = [...new Set(res.data.map(item => item.parentId))];
|
||||
fnTableExpandedRowsAll(tableState.expandedRowAll);
|
||||
}
|
||||
tableState.data = treeData;
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('sys_normal_disable')]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.sysNormalDisable = resArr[0].value;
|
||||
}
|
||||
});
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer :title="title">
|
||||
<template #content>
|
||||
<a-typography-paragraph> 给予用户部门标记 </a-typography-paragraph>
|
||||
</template>
|
||||
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="部门名称" name="deptName">
|
||||
<a-input
|
||||
v-model:value="queryParams.deptName"
|
||||
allow-clear
|
||||
placeholder="请输入部门名称"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="岗位状态" name="status">
|
||||
<a-select
|
||||
v-model:value="queryParams.status"
|
||||
allow-clear
|
||||
placeholder="请选择"
|
||||
:options="dict.sysNormalDisable"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
搜索</a-button
|
||||
>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
重置</a-button
|
||||
>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click.prevent="fnModalVisibleByEdit()"
|
||||
v-perms:has="['system:dept:add']"
|
||||
>
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新建
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>展开/折叠</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.expandedRowAll"
|
||||
checked-children="展"
|
||||
un-checked-children="折"
|
||||
size="small"
|
||||
@change="fnTableExpandedRowsAll"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>搜索栏</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
checked-children="显"
|
||||
un-checked-children="隐"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>表格斑马纹</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.striped"
|
||||
checked-children="开"
|
||||
un-checked-children="关"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>刷新</template>
|
||||
<a-button type="text" @click.prevent="fnGetList">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>密度</template>
|
||||
<a-dropdown trigger="click">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">默认</a-menu-item>
|
||||
<a-menu-item key="middle">中等</a-menu-item>
|
||||
<a-menu-item key="small">紧凑</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="deptId"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:row-class-name="fnTableStriped"
|
||||
:pagination="false"
|
||||
:scroll="{ x: true }"
|
||||
children-column-name="children"
|
||||
:expanded-row-keys="tableState.expandedRowKeys"
|
||||
@expandedRowsChange="fnTableExpandedRowsChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<DictTag :options="dict.sysNormalDisable" :value="record.status" />
|
||||
</template>
|
||||
<template v-if="column.key === 'deptId'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>查看详情</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByVive(record.deptId)"
|
||||
v-perms:has="['system:dept:query']"
|
||||
>
|
||||
<template #icon><ProfileOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>编辑</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByEdit(record.deptId)"
|
||||
v-perms:has="['system:dept:edit']"
|
||||
>
|
||||
<template #icon><FormOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="record.parentId !== '0'">
|
||||
<template #title>删除</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordDelete(record.deptId)"
|
||||
v-perms:has="['system:dept:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="record.status !== '0'">
|
||||
<template #title>新增子部门</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="
|
||||
fnModalVisibleByEdit(undefined, record.deptId)
|
||||
"
|
||||
v-perms:has="['system:dept:add']"
|
||||
>
|
||||
<template #icon><PlusOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 详情框 -->
|
||||
<a-modal
|
||||
width="800px"
|
||||
:visible="modalState.visibleByView"
|
||||
:title="modalState.title"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="上级部门" name="parentId">
|
||||
<a-tree-select
|
||||
:value="modalState.from.parentId"
|
||||
placeholder="上级部门"
|
||||
disabled
|
||||
:tree-data="modalState.treeData"
|
||||
:field-names="{
|
||||
children: 'children',
|
||||
label: 'deptName',
|
||||
value: 'deptId',
|
||||
}"
|
||||
tree-node-label-prop="deptName"
|
||||
>
|
||||
<template #suffixIcon></template>
|
||||
</a-tree-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="显示排序" name="orderNum">
|
||||
{{ modalState.from.orderNum }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="部门状态" name="status">
|
||||
<DictTag
|
||||
:options="dict.sysNormalDisable"
|
||||
:value="modalState.from.status"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="部门编号" name="deptId">
|
||||
{{ modalState.from.deptId }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="部门名称" name="deptName">
|
||||
{{ modalState.from.deptName }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="负责人" name="leader">
|
||||
{{ modalState.from.leader }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="联系电话" name="phone">
|
||||
{{ modalState.from.phone }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="邮箱" name="email">
|
||||
{{ modalState.from.email }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button key="cancel" @click="fnModalCancel">关闭</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
|
||||
<!-- 新增框或修改框 -->
|
||||
<a-modal
|
||||
width="800px"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:visible="modalState.visibleByEdit"
|
||||
:title="modalState.title"
|
||||
:confirm-loading="modalState.confirmLoading"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form name="modalStateFrom" layout="horizontal">
|
||||
<a-form-item
|
||||
label="上级部门"
|
||||
name="parentId"
|
||||
v-bind="modalStateFrom.validateInfos.parentId"
|
||||
>
|
||||
<a-tree-select
|
||||
v-model:value="modalState.from.parentId"
|
||||
placeholder="上级部门"
|
||||
show-search
|
||||
tree-default-expand-all
|
||||
:tree-data="modalState.treeData"
|
||||
:field-names="{
|
||||
children: 'children',
|
||||
label: 'deptName',
|
||||
value: 'deptId',
|
||||
}"
|
||||
tree-node-label-prop="deptName"
|
||||
tree-node-filter-prop="deptName"
|
||||
style="width: 100%"
|
||||
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
>
|
||||
</a-tree-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="部门名称"
|
||||
name="deptName"
|
||||
v-bind="modalStateFrom.validateInfos.deptName"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.deptName"
|
||||
allow-clear
|
||||
:maxlength="30"
|
||||
placeholder="请输入部门名称"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="负责人"
|
||||
name="leader"
|
||||
v-bind="modalStateFrom.validateInfos.leader"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.leader"
|
||||
allow-clear
|
||||
placeholder="请输入负责人名称"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="6" :xs="24">
|
||||
<a-form-item label="岗位状态" name="status">
|
||||
<a-select
|
||||
v-model:value="modalState.from.status"
|
||||
default-value="0"
|
||||
placeholder="岗位状态"
|
||||
:options="dict.sysNormalDisable"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="6" :xs="24">
|
||||
<a-form-item label="显示顺序" name="orderNum">
|
||||
<a-input-number
|
||||
v-model:value="modalState.from.orderNum"
|
||||
:min="0"
|
||||
:max="9999"
|
||||
:step="1"
|
||||
placeholder="排序值"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="联系电话"
|
||||
name="phone"
|
||||
v-bind="modalStateFrom.validateInfos.phone"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.phone"
|
||||
allow-clear
|
||||
:maxlength="11"
|
||||
placeholder="请输入负责人联系电话"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="邮箱"
|
||||
name="email"
|
||||
v-bind="modalStateFrom.validateInfos.email"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.email"
|
||||
allow-clear
|
||||
:maxlength="40"
|
||||
placeholder="请输入负责人邮箱"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.table-striped) td {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
</style>
|
||||
883
src/views/system/dict/data.vue
Normal file
883
src/views/system/dict/data.vue
Normal file
@@ -0,0 +1,883 @@
|
||||
<script setup lang="ts">
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import { Form, message, Modal } from 'ant-design-vue/lib';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import {
|
||||
exportData,
|
||||
listData,
|
||||
getData,
|
||||
delData,
|
||||
addData,
|
||||
updateData,
|
||||
} from '@/api/system/dict/data';
|
||||
import { getDictOptionselect, getType } from '@/api/system/dict/type';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useTabsStore from '@/store/modules/tabs';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
const tabsStore = useTabsStore();
|
||||
const { parseDataDict, getDict } = useDictStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
// 获取地址栏参数
|
||||
const dictId = route.params && (route.params.dictId as string);
|
||||
|
||||
/**标签类型数据固定项 */
|
||||
const tagTypeOptions = ref([
|
||||
{ value: '', label: '普通文本' },
|
||||
{ value: 'default', label: '默认(default)' },
|
||||
{ value: 'blue ', label: '蓝色(blue)' },
|
||||
{ value: 'cyan', label: '青色(cyan)' },
|
||||
{ value: 'gold', label: '金色(gold)' },
|
||||
{ value: 'green', label: '绿色(green)' },
|
||||
{ value: 'lime', label: '亮绿(lime)' },
|
||||
{ value: 'magenta', label: '紫红(magenta)' },
|
||||
{ value: 'orange', label: '橘黄(orange)' },
|
||||
{ value: 'pink', label: '粉色(pink)' },
|
||||
{ value: 'purple', label: '紫色(purple)' },
|
||||
{ value: 'red', label: '红色(red)' },
|
||||
{ value: 'yellow', label: '黄色(yellow)' },
|
||||
{ value: 'geekblue', label: '深蓝(geekblue)' },
|
||||
{ value: 'volcano', label: '棕色(volcano)' },
|
||||
{ value: 'processing', label: '进行(processing)' },
|
||||
{ value: 'warning', label: '警告(warning)' },
|
||||
{ value: 'error', label: '错误(error)' },
|
||||
{ value: 'success', label: '成功(success)' },
|
||||
]);
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**数据状态 */
|
||||
sysNormalDisable: DictType[];
|
||||
/**字典名称 */
|
||||
sysDictType: DictType[];
|
||||
} = reactive({
|
||||
sysNormalDisable: [],
|
||||
sysDictType: [],
|
||||
});
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**字典名称 */
|
||||
dictType: '',
|
||||
/**数据标签 */
|
||||
dictLabel: '',
|
||||
/**数据状态 */
|
||||
status: undefined,
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
if (dictId && dictId !== '0') {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
dictLabel: '',
|
||||
status: undefined,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
} else {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
dictType: '',
|
||||
dictLabel: '',
|
||||
status: undefined,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
}
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**斑马纹 */
|
||||
striped: boolean;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
striped: false,
|
||||
seached: true,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: '数据代码',
|
||||
dataIndex: 'dictCode',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '数据标签',
|
||||
dataIndex: 'dictLabel',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '数据键值',
|
||||
dataIndex: 'dictValue',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '数据排序',
|
||||
dataIndex: 'dictSort',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '数据状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
if (+opt.value <= 0) return '';
|
||||
return parseDateToStr(+opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'dictCode',
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => `总共 ${total} 条`,
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格斑马纹 */
|
||||
function fnTableStriped(_record: unknown, index: number) {
|
||||
return tableState.striped && index % 2 === 1 ? 'table-striped' : undefined;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**详情框是否显示 */
|
||||
visibleByView: boolean;
|
||||
/**新增框或修改框是否显示 */
|
||||
visibleByEdit: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
visibleByView: false,
|
||||
visibleByEdit: false,
|
||||
title: '字典数据',
|
||||
from: {
|
||||
dictCode: undefined,
|
||||
dictLabel: '',
|
||||
dictSort: 0,
|
||||
dictType: 'sys_oper_type',
|
||||
dictValue: '',
|
||||
tagClass: '',
|
||||
tagType: '',
|
||||
remark: '',
|
||||
status: '0',
|
||||
createTime: 0,
|
||||
createBy: undefined,
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
reactive({
|
||||
dictLabel: [
|
||||
{ required: true, min: 1, max: 50, message: '请正确输入数据标签' },
|
||||
],
|
||||
dictValue: [
|
||||
{ required: true, min: 1, max: 50, message: '请正确输入数据键值' },
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 查看
|
||||
* @param row 调度日志信息对象
|
||||
*/
|
||||
function fnModalVisibleByVive(row: Record<string, string>) {
|
||||
modalState.from = Object.assign(modalState.from, row);
|
||||
modalState.title = '字典数据信息';
|
||||
modalState.visibleByView = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param dictCode 数据编号id, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByEdit(dictCode?: string | number) {
|
||||
if (!dictCode) {
|
||||
modalStateFrom.resetFields();
|
||||
modalState.from.dictType = queryParams.dictType;
|
||||
modalState.title = '添加字典数据';
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
if (modalState.confirmLoading) return;
|
||||
const hide = message.loading('正在打开...', 0);
|
||||
modalState.confirmLoading = true;
|
||||
getData(dictCode).then(res => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
if (res.code === 200) {
|
||||
modalState.from = Object.assign(modalState.from, res.data);
|
||||
modalState.title = '修改字典数据';
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
message.error(`获取字典数据信息失败`, 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalOk() {
|
||||
modalStateFrom
|
||||
.validate()
|
||||
.then(() => {
|
||||
modalState.confirmLoading = true;
|
||||
const from = toRaw(modalState.from);
|
||||
const dictData = from.dictCode ? updateData(from) : addData(from);
|
||||
const key = 'dictData';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
dictData
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `${modalState.title}成功`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
modalState.visibleByEdit = false;
|
||||
modalStateFrom.resetFields();
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(`请正确填写 ${e.errorFields.length} 处必填信息!`, 2);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.visibleByEdit = false;
|
||||
modalState.visibleByView = false;
|
||||
modalStateFrom.resetFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典删除
|
||||
* @param dictCode 字典代码
|
||||
*/
|
||||
function fnRecordDelete(dictCode: string = '0') {
|
||||
if (dictCode === '0') {
|
||||
dictCode = tableState.selectedRowKeys.join(',');
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认删除字典数据代码为 【${dictCode}】 的数据项?`,
|
||||
onOk() {
|
||||
const key = 'delData';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
delData(dictCode).then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `删除成功`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key: key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList() {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认根据搜索条件导出xlsx表格文件吗?`,
|
||||
onOk() {
|
||||
const key = 'exportData';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
exportData(toRaw(queryParams)).then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `已完成导出`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `dict_data_${Date.now()}.xlsx`);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**关闭跳转 */
|
||||
function fnClose() {
|
||||
const to = tabsStore.tabClose(route.path);
|
||||
if (to) {
|
||||
router.push(to);
|
||||
} else {
|
||||
router.back();
|
||||
}
|
||||
}
|
||||
|
||||
/**查询字典数据列表 */
|
||||
function fnGetList() {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
listData(toRaw(queryParams)).then(res => {
|
||||
if (res.code === 200 && Array.isArray(res.rows)) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.rows;
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([
|
||||
getDict('sys_normal_disable'),
|
||||
getDictOptionselect(),
|
||||
]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.sysNormalDisable = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
const dicts = resArr[1].value;
|
||||
if (dicts.code === 200) {
|
||||
dict.sysDictType = dicts.data;
|
||||
}
|
||||
}
|
||||
});
|
||||
// 指定字典id列表数据
|
||||
if (dictId && dictId !== '0') {
|
||||
getType(dictId).then(res => {
|
||||
if (res.code === 200 && res.data) {
|
||||
queryParams.dictType = res.data.dictType;
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error(`获取字典类型信息失败`, 3);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="字典名称" name="dictType">
|
||||
<a-select
|
||||
v-model:value="queryParams.dictType"
|
||||
:allow-clear="dictId === '0'"
|
||||
:disabled="dictId !== '0'"
|
||||
placeholder="请选择字典名称"
|
||||
:options="dict.sysDictType"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="数据标签" name="dictLabel">
|
||||
<a-input
|
||||
v-model:value="queryParams.dictLabel"
|
||||
allow-clear
|
||||
placeholder="请输入数据标签"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="数据状态" name="status">
|
||||
<a-select
|
||||
v-model:value="queryParams.status"
|
||||
allow-clear
|
||||
placeholder="请选择数据状态"
|
||||
:options="dict.sysNormalDisable"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
搜索</a-button
|
||||
>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
重置</a-button
|
||||
>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button type="default" @click.prevent="fnClose()">
|
||||
<template #icon><CloseOutlined /></template>
|
||||
关闭
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click.prevent="fnModalVisibleByEdit()"
|
||||
v-perms:has="['system:dict:add']"
|
||||
>
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新增
|
||||
</a-button>
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
@click.prevent="fnRecordDelete()"
|
||||
v-perms:has="['system:dict:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
删除
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
@click.prevent="fnExportList()"
|
||||
v-perms:has="['system:dict:export']"
|
||||
>
|
||||
<template #icon><ExportOutlined /></template>
|
||||
导出
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>搜索栏</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
checked-children="显"
|
||||
un-checked-children="隐"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>表格斑马纹</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.striped"
|
||||
checked-children="开"
|
||||
un-checked-children="关"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>刷新</template>
|
||||
<a-button type="text" @click.prevent="fnGetList">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>密度</template>
|
||||
<a-dropdown trigger="click">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">默认</a-menu-item>
|
||||
<a-menu-item key="middle">中等</a-menu-item>
|
||||
<a-menu-item key="small">紧凑</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="dictCode"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:row-class-name="fnTableStriped"
|
||||
:scroll="{ x: true }"
|
||||
:pagination="tablePagination"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<DictTag :options="dict.sysNormalDisable" :value="record.status" />
|
||||
</template>
|
||||
<template v-if="column.key === 'dictCode'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>查看详情</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByVive(record)"
|
||||
v-perms:has="['system:dict:query']"
|
||||
>
|
||||
<template #icon><ProfileOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>编辑</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByEdit(record.dictCode)"
|
||||
v-perms:has="['system:dict:edit']"
|
||||
>
|
||||
<template #icon><FormOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>删除</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordDelete(record.dictCode)"
|
||||
v-perms:has="['system:dict:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 详情框 -->
|
||||
<a-modal
|
||||
width="800px"
|
||||
:visible="modalState.visibleByView"
|
||||
:title="modalState.title"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="字典名称" name="dictType">
|
||||
{{
|
||||
dict.sysDictType.find(
|
||||
item => item.value === modalState.from.dictType
|
||||
)?.label
|
||||
}}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="创建时间" name="createTime">
|
||||
<span v-if="+modalState.from.createTime > 0">
|
||||
{{ parseDateToStr(+modalState.from.createTime) }}
|
||||
</span>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="数据代码" name="dictCode">
|
||||
{{ modalState.from.dictCode }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="数据状态" name="status">
|
||||
<DictTag
|
||||
:options="dict.sysNormalDisable"
|
||||
:value="modalState.from.status"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="数据标签" name="dictLabel">
|
||||
{{ modalState.from.dictLabel }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="数据键值" name="dictValue">
|
||||
{{ modalState.from.dictValue }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="标签类型" name="tagType">
|
||||
<DictTag
|
||||
:options="tagTypeOptions"
|
||||
:value="modalState.from.tagType"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="样式属性" name="tagClass">
|
||||
{{ modalState.from.tagClass }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="回显预览" name="tagType">
|
||||
<DictTag
|
||||
:options="parseDataDict(modalState.from)"
|
||||
:value="modalState.from.dictValue"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="数据排序" name="dictSort">
|
||||
{{ modalState.from.dictSort }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="数据说明" name="remark">
|
||||
{{ modalState.from.remark }}
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button key="cancel" @click="fnModalCancel">关闭</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
|
||||
<!-- 新增框或修改框 -->
|
||||
<a-modal
|
||||
width="800px"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:visible="modalState.visibleByEdit"
|
||||
:title="modalState.title"
|
||||
:confirm-loading="modalState.confirmLoading"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form name="modalStateFrom" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="字典类型" name="dictType">
|
||||
<a-select
|
||||
v-model:value="modalState.from.dictType"
|
||||
default-value="sys_oper_type"
|
||||
placeholder="字典类型"
|
||||
:options="dict.sysDictType"
|
||||
:disabled="true"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="数据状态" name="status">
|
||||
<a-select
|
||||
v-model:value="modalState.from.status"
|
||||
default-value="0"
|
||||
placeholder="数据状态"
|
||||
:options="dict.sysNormalDisable"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="数据标签"
|
||||
name="dictLabel"
|
||||
v-bind="modalStateFrom.validateInfos.dictLabel"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.dictLabel"
|
||||
allow-clear
|
||||
placeholder="请输入数据标签"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="数据键值"
|
||||
name="dictValue"
|
||||
v-bind="modalStateFrom.validateInfos.dictValue"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.dictValue"
|
||||
allow-clear
|
||||
placeholder="请输入数据键值"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="标签类型" name="tagType">
|
||||
<a-select
|
||||
v-model:value="modalState.from.tagType"
|
||||
placeholder="标签类型"
|
||||
:options="tagTypeOptions"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="数据排序" name="dictSort">
|
||||
<a-input
|
||||
v-model:value="modalState.from.dictSort"
|
||||
allow-clear
|
||||
placeholder="请输入数据排序"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="样式属性" name="tagClass">
|
||||
<a-input
|
||||
v-model:value="modalState.from.tagClass"
|
||||
allow-clear
|
||||
placeholder="请输入样式属性"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="数据说明" name="remark">
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.remark"
|
||||
:auto-size="{ minRows: 4, maxRows: 6 }"
|
||||
:maxlength="450"
|
||||
:show-count="true"
|
||||
placeholder="请输入数据说明"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.table-striped) td {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
809
src/views/system/dict/index.vue
Normal file
809
src/views/system/dict/index.vue
Normal file
@@ -0,0 +1,809 @@
|
||||
<script setup lang="ts">
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import { message, Modal, Form } from 'ant-design-vue/lib';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import {
|
||||
exportType,
|
||||
listType,
|
||||
getType,
|
||||
delType,
|
||||
addType,
|
||||
updateType,
|
||||
refreshCache,
|
||||
} from '@/api/system/dict/type';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import { MENU_PATH_INLINE } from '@/constants/menu-constants';
|
||||
const { getDict } = useDictStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
/**路由标题 */
|
||||
let title = ref<string>(route.meta.title ?? '标题');
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**字典状态 */
|
||||
sysNormalDisable: DictType[];
|
||||
} = reactive({
|
||||
sysNormalDisable: [],
|
||||
});
|
||||
|
||||
/**开始结束时间 */
|
||||
let queryRangePicker = ref<[string, string]>(['', '']);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**字典名称 */
|
||||
dictName: '',
|
||||
/**字典类型 */
|
||||
dictType: '',
|
||||
/**字典状态 */
|
||||
status: undefined,
|
||||
/**记录开始时间 */
|
||||
beginTime: '',
|
||||
/**记录结束时间 */
|
||||
endTime: '',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
dictName: '',
|
||||
dictType: '',
|
||||
status: undefined,
|
||||
beginTime: '',
|
||||
endTime: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
queryRangePicker.value = ['', ''];
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**斑马纹 */
|
||||
striped: boolean;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
striped: false,
|
||||
seached: false,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: '字典编号',
|
||||
dataIndex: 'dictId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '字典名称',
|
||||
dataIndex: 'dictName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '字典类型',
|
||||
dataIndex: 'dictType',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '字典状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
if (+opt.value <= 0) return '';
|
||||
return parseDateToStr(+opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'dictId',
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => `总共 ${total} 条`,
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格斑马纹 */
|
||||
function fnTableStriped(_record: unknown, index: number) {
|
||||
return tableState.striped && index % 2 === 1 ? 'table-striped' : undefined;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**详情框是否显示 */
|
||||
visibleByView: boolean;
|
||||
/**新增框或修改框是否显示 */
|
||||
visibleByEdit: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
visibleByView: false,
|
||||
visibleByEdit: false,
|
||||
title: '字典类型',
|
||||
from: {
|
||||
dictId: undefined,
|
||||
dictName: '',
|
||||
dictType: undefined,
|
||||
status: '0',
|
||||
remark: undefined,
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
reactive({
|
||||
dictName: [
|
||||
{ required: true, min: 1, max: 50, message: '请正确输入字典名称' },
|
||||
],
|
||||
dictType: [
|
||||
{ required: true, min: 1, max: 50, message: '请正确输入字典类型' },
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 查看
|
||||
* @param dictId 字典编号id
|
||||
*/
|
||||
function fnModalVisibleByVive(dictId: string | number) {
|
||||
if (!dictId) {
|
||||
message.error(`字典类型记录存在错误`, 2);
|
||||
return;
|
||||
}
|
||||
if (modalState.confirmLoading) return;
|
||||
const hide = message.loading('正在打开...', 0);
|
||||
modalState.confirmLoading = true;
|
||||
getType(dictId).then(res => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
if (res.code === 200 && res.data) {
|
||||
modalState.from = Object.assign(modalState.from, res.data);
|
||||
modalState.title = '字典类型信息';
|
||||
modalState.visibleByView = true;
|
||||
} else {
|
||||
message.error(`获取字典类型信息失败`, 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param dictId 字典编号id, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByEdit(dictId?: string | number) {
|
||||
if (!dictId) {
|
||||
modalStateFrom.resetFields();
|
||||
modalState.title = '添加字典类型';
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
if (modalState.confirmLoading) return;
|
||||
const hide = message.loading('正在打开...', 0);
|
||||
modalState.confirmLoading = true;
|
||||
getType(dictId).then(res => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
if (res.code === 200 && res.data) {
|
||||
modalState.from = Object.assign(modalState.from, res.data);
|
||||
modalState.title = '修改字典类型';
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
message.error(`获取字典类型信息失败`, 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalOk() {
|
||||
modalStateFrom
|
||||
.validate()
|
||||
.then(() => {
|
||||
modalState.confirmLoading = true;
|
||||
const from = toRaw(modalState.from);
|
||||
const dictType = from.dictId ? updateType(from) : addType(from);
|
||||
const key = 'dictType';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
dictType
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `${modalState.title}成功`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
modalState.visibleByEdit = false;
|
||||
modalStateFrom.resetFields();
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(`请正确填写 ${e.errorFields.length} 处必填信息!`, 2);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.visibleByEdit = false;
|
||||
modalState.visibleByView = false;
|
||||
modalStateFrom.resetFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典删除
|
||||
* @param dictId 字典编号ID
|
||||
*/
|
||||
function fnRecordDelete(dictId: string = '0') {
|
||||
if (dictId === '0') {
|
||||
dictId = tableState.selectedRowKeys.join(',');
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认删除参数编号为 【${dictId}】 的数据项?`,
|
||||
onOk() {
|
||||
const key = 'delType';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
delType(dictId).then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `删除成功`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key: key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList() {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认根据搜索条件导出xlsx表格文件吗?`,
|
||||
onOk() {
|
||||
const key = 'exportType';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
exportType(toRaw(queryParams)).then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `已完成导出`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `dict_${Date.now()}.xlsx`);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新缓存
|
||||
*/
|
||||
function fnRefreshCache() {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确定要刷新字典数据缓存吗?`,
|
||||
onOk() {
|
||||
const key = 'refreshCache';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
refreshCache().then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `刷新缓存成功`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key: key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**跳转字典数据页面 */
|
||||
function fnDataView(dictId: string | number = '0') {
|
||||
router.push(`/system/dict${MENU_PATH_INLINE}/data/${dictId}`);
|
||||
}
|
||||
|
||||
/**查询参数配置列表 */
|
||||
function fnGetList() {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
queryParams.beginTime = queryRangePicker.value[0];
|
||||
queryParams.endTime = queryRangePicker.value[1];
|
||||
listType(toRaw(queryParams)).then(res => {
|
||||
if (res.code === 200 && Array.isArray(res.rows)) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.rows;
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('sys_normal_disable')]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.sysNormalDisable = resArr[0].value;
|
||||
}
|
||||
});
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer :title="title">
|
||||
<template #content>
|
||||
<a-typography-paragraph>
|
||||
数据字典类型,数据名称对应的代码值映射数据。
|
||||
</a-typography-paragraph>
|
||||
</template>
|
||||
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="字典名称" name="dictName">
|
||||
<a-input
|
||||
v-model:value="queryParams.dictName"
|
||||
allow-clear
|
||||
placeholder="请输入字典名称"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="字典类型" name="dictType">
|
||||
<a-input
|
||||
v-model:value="queryParams.dictType"
|
||||
allow-clear
|
||||
placeholder="请输入字典类型"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="4" :md="12" :xs="24">
|
||||
<a-form-item label="字典状态" name="status">
|
||||
<a-select
|
||||
v-model:value="queryParams.status"
|
||||
allow-clear
|
||||
placeholder="请选择"
|
||||
:options="dict.sysNormalDisable"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item label="创建时间" name="queryRangePicker">
|
||||
<a-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
allow-clear
|
||||
bordered
|
||||
value-format="YYYY-MM-DD"
|
||||
:placeholder="['创建开始', '创建结束']"
|
||||
style="width: 100%"
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
搜索</a-button
|
||||
>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
重置</a-button
|
||||
>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click.prevent="fnModalVisibleByEdit()"
|
||||
v-perms:has="['system:dict:add']"
|
||||
>
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新建
|
||||
</a-button>
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
@click.prevent="fnRecordDelete()"
|
||||
v-perms:has="['system:dict:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
删除
|
||||
</a-button>
|
||||
<a-button
|
||||
type="default"
|
||||
@click.prevent="fnDataView()"
|
||||
v-perms:has="['system:dict:data']"
|
||||
>
|
||||
<template #icon><ContainerOutlined /></template>
|
||||
字典数据
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
danger
|
||||
@click.prevent="fnRefreshCache"
|
||||
v-perms:has="['system:dict:remove']"
|
||||
>
|
||||
<template #icon><SyncOutlined /></template>
|
||||
刷新缓存
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
@click.prevent="fnExportList()"
|
||||
v-perms:has="['system:dict:export']"
|
||||
>
|
||||
<template #icon><ExportOutlined /></template>
|
||||
导出
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>搜索栏</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
checked-children="显"
|
||||
un-checked-children="隐"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>表格斑马纹</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.striped"
|
||||
checked-children="开"
|
||||
un-checked-children="关"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>刷新</template>
|
||||
<a-button type="text" @click.prevent="fnGetList">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>密度</template>
|
||||
<a-dropdown trigger="click">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">默认</a-menu-item>
|
||||
<a-menu-item key="middle">中等</a-menu-item>
|
||||
<a-menu-item key="small">紧凑</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="dictId"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:row-class-name="fnTableStriped"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: true }"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<DictTag :options="dict.sysNormalDisable" :value="record.status" />
|
||||
</template>
|
||||
<template v-if="column.key === 'dictId'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>查看详情</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByVive(record.dictId)"
|
||||
v-perms:has="['system:dict:query']"
|
||||
>
|
||||
<template #icon><ProfileOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>编辑</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByEdit(record.dictId)"
|
||||
v-perms:has="['system:dict:edit']"
|
||||
>
|
||||
<template #icon><FormOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>删除</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordDelete(record.dictId)"
|
||||
v-perms:has="['system:dict:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>字典数据</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnDataView(record.dictId)"
|
||||
v-perms:has="['system:dict:data']"
|
||||
>
|
||||
<template #icon><ContainerOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 详情框 -->
|
||||
<a-modal
|
||||
width="800px"
|
||||
:visible="modalState.visibleByView"
|
||||
:title="modalState.title"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="字典编号" name="dictId">
|
||||
{{ modalState.from.dictId }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="字典状态" name="status">
|
||||
<DictTag
|
||||
:options="dict.sysNormalDisable"
|
||||
:value="modalState.from.status"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="字典名称" name="dictName">
|
||||
{{ modalState.from.dictName }}
|
||||
</a-form-item>
|
||||
<a-form-item label="字典类型" name="dictType">
|
||||
{{ modalState.from.dictType }}
|
||||
</a-form-item>
|
||||
<a-form-item label="字典说明" name="remark">
|
||||
{{ modalState.from.remark }}
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button key="cancel" @click="fnModalCancel">关闭</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
|
||||
<!-- 新增框或修改框 -->
|
||||
<a-modal
|
||||
width="800px"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:visible="modalState.visibleByEdit"
|
||||
:title="modalState.title"
|
||||
:confirm-loading="modalState.confirmLoading"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form name="modalStateFrom" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="18" :md="18" :xs="24">
|
||||
<a-form-item
|
||||
label="字典名称"
|
||||
name="dictName"
|
||||
v-bind="modalStateFrom.validateInfos.dictName"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.dictName"
|
||||
allow-clear
|
||||
placeholder="请输入字典名称"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="6" :xs="24">
|
||||
<a-form-item label="字典状态" name="status">
|
||||
<a-select
|
||||
v-model:value="modalState.from.status"
|
||||
default-value="0"
|
||||
placeholder="字典状态"
|
||||
:options="dict.sysNormalDisable"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="18" :md="18" :xs="24">
|
||||
<a-form-item
|
||||
label="字典类型"
|
||||
name="dictType"
|
||||
v-bind="modalStateFrom.validateInfos.dictType"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.dictType"
|
||||
allow-clear
|
||||
placeholder="请输入字典类型"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="字典说明" name="remark">
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.remark"
|
||||
:auto-size="{ minRows: 4, maxRows: 6 }"
|
||||
:maxlength="450"
|
||||
:show-count="true"
|
||||
placeholder="请输入参数说明"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.table-striped) td {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
1103
src/views/system/menu/index.vue
Normal file
1103
src/views/system/menu/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
734
src/views/system/notice/index.vue
Normal file
734
src/views/system/notice/index.vue
Normal file
@@ -0,0 +1,734 @@
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router';
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import { message, Modal, Form } from 'ant-design-vue/lib';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import {
|
||||
listNotice,
|
||||
getNotice,
|
||||
delNotice,
|
||||
addNotice,
|
||||
updateNotice,
|
||||
} from '@/api/system/notice';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
const { getDict } = useDictStore();
|
||||
const route = useRoute();
|
||||
|
||||
/**路由标题 */
|
||||
let title = ref<string>(route.meta.title ?? '标题');
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**公告类型 */
|
||||
sysNoticeType: DictType[];
|
||||
/**公告状态 */
|
||||
sysNoticeStatus: DictType[];
|
||||
} = reactive({
|
||||
sysNoticeType: [],
|
||||
sysNoticeStatus: [],
|
||||
});
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**公告标题 */
|
||||
noticeTitle: '',
|
||||
/**创建者 */
|
||||
createBy: undefined,
|
||||
/**公告类型 */
|
||||
noticeType: undefined,
|
||||
/**公告状态 */
|
||||
status: undefined,
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
noticeTitle: '',
|
||||
createBy: '',
|
||||
noticeType: undefined,
|
||||
status: undefined,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**斑马纹 */
|
||||
striped: boolean;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
striped: false,
|
||||
seached: false,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: '公告编号',
|
||||
dataIndex: 'noticeId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '公告标题',
|
||||
dataIndex: 'noticeTitle',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '公告类型',
|
||||
dataIndex: 'noticeType',
|
||||
key: 'noticeType',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '创建者',
|
||||
dataIndex: 'createBy',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
if(+opt.value <= 0) return ''
|
||||
return parseDateToStr(+opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'noticeId',
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => `总共 ${total} 条`,
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格斑马纹 */
|
||||
function fnTableStriped(_record: unknown, index: number) {
|
||||
return tableState.striped && index % 2 === 1 ? 'table-striped' : undefined;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**详情框是否显示 */
|
||||
visibleByView: boolean;
|
||||
/**新增框或修改框是否显示 */
|
||||
visibleByEdit: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
visibleByView: false,
|
||||
visibleByEdit: false,
|
||||
title: '公告',
|
||||
from: {
|
||||
noticeId: undefined,
|
||||
noticeTitle: '',
|
||||
noticeContent: '',
|
||||
noticeType: '2',
|
||||
status: '1',
|
||||
delFlag: '0',
|
||||
remark: '',
|
||||
createBy: undefined,
|
||||
createTime: undefined,
|
||||
updateBy: undefined,
|
||||
updateTime: undefined,
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
reactive({
|
||||
noticeTitle: [
|
||||
{ required: true, min: 2, max: 50, message: '请正确输入公告标题' },
|
||||
],
|
||||
noticeType: [{ required: true, message: '请选择公告类型' }],
|
||||
noticeContent: [
|
||||
{
|
||||
required: true,
|
||||
min: 2,
|
||||
max: 3000,
|
||||
message: '请正确输入公告内容,限10-3000个字符',
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 查看
|
||||
* @param noticeId 公告id
|
||||
*/
|
||||
function fnModalVisibleByVive(noticeId: string | number) {
|
||||
if (!noticeId) {
|
||||
message.error(`公告记录存在错误`, 2);
|
||||
return;
|
||||
}
|
||||
if (modalState.confirmLoading) return;
|
||||
const hide = message.loading('正在打开...', 0);
|
||||
modalState.confirmLoading = true;
|
||||
getNotice(noticeId).then(res => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
if (res.code === 200) {
|
||||
modalState.from = Object.assign(modalState.from, res.data);
|
||||
modalState.title = '公告信息';
|
||||
modalState.visibleByView = true;
|
||||
} else {
|
||||
message.error(`获取公告信息失败`, 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param noticeId 公告id, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByEdit(noticeId?: string | number) {
|
||||
if (!noticeId) {
|
||||
modalStateFrom.resetFields();
|
||||
modalState.title = '添加公告';
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
if (modalState.confirmLoading) return;
|
||||
const hide = message.loading('正在打开...', 0);
|
||||
modalState.confirmLoading = true;
|
||||
getNotice(noticeId).then(res => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
if (res.code === 200) {
|
||||
modalState.from = Object.assign(modalState.from, res.data);
|
||||
modalState.title = '修改公告';
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
message.error(`获取公告信息失败`, 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalOk() {
|
||||
modalStateFrom
|
||||
.validate()
|
||||
.then(() => {
|
||||
modalState.confirmLoading = true;
|
||||
const from = toRaw(modalState.from);
|
||||
const notice = from.noticeId ? updateNotice(from) : addNotice(from);
|
||||
const key = 'notice';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
notice
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `${modalState.title}成功`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
modalState.visibleByEdit = false;
|
||||
modalStateFrom.resetFields();
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(`请正确填写 ${e.errorFields.length} 处必填信息!`, 2);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.visibleByEdit = false;
|
||||
modalState.visibleByView = false;
|
||||
modalStateFrom.resetFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* 公告删除
|
||||
* @param noticeId 公告编号ID
|
||||
*/
|
||||
function fnRecordDelete(noticeId: string = '0') {
|
||||
if (noticeId === '0') {
|
||||
noticeId = tableState.selectedRowKeys.join(',');
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认删除公告编号为 【${noticeId}】 的数据项?`,
|
||||
onOk() {
|
||||
const key = 'delNotice';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
delNotice(noticeId).then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `删除成功`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key: key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**查询公告列表 */
|
||||
function fnGetList() {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
listNotice(toRaw(queryParams)).then(res => {
|
||||
if (res.code === 200 && Array.isArray(res.rows)) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.rows;
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([
|
||||
getDict('sys_notice_type'),
|
||||
getDict('sys_notice_status'),
|
||||
]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.sysNoticeType = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.sysNoticeStatus = resArr[1].value;
|
||||
}
|
||||
});
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer :title="title">
|
||||
<template #content>
|
||||
<a-typography-paragraph>
|
||||
发布公告给內部用户的通知。
|
||||
</a-typography-paragraph>
|
||||
</template>
|
||||
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="公告标题" name="noticeTitle">
|
||||
<a-input
|
||||
v-model:value="queryParams.noticeTitle"
|
||||
allow-clear
|
||||
placeholder="请输入公告标题"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="创建者" name="createBy">
|
||||
<a-input
|
||||
v-model:value="queryParams.createBy"
|
||||
allow-clear
|
||||
placeholder="请输入创建者"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="公告类型" name="noticeType">
|
||||
<a-select
|
||||
v-model:value="queryParams.noticeType"
|
||||
allow-clear
|
||||
placeholder="请选择公告类型"
|
||||
:options="dict.sysNoticeType"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="公告状态" name="status">
|
||||
<a-select
|
||||
v-model:value="queryParams.status"
|
||||
allow-clear
|
||||
placeholder="请选择公告状态"
|
||||
:options="dict.sysNoticeStatus"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
搜索</a-button
|
||||
>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
重置</a-button
|
||||
>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click.prevent="fnModalVisibleByEdit()"
|
||||
v-perms:has="['system:notice:add']"
|
||||
>
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新建
|
||||
</a-button>
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
@click.prevent="fnRecordDelete()"
|
||||
v-perms:has="['system:notice:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>搜索栏</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
checked-children="显"
|
||||
un-checked-children="隐"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>表格斑马纹</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.striped"
|
||||
checked-children="开"
|
||||
un-checked-children="关"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>刷新</template>
|
||||
<a-button type="text" @click.prevent="fnGetList">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>密度</template>
|
||||
<a-dropdown trigger="click">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">默认</a-menu-item>
|
||||
<a-menu-item key="middle">中等</a-menu-item>
|
||||
<a-menu-item key="small">紧凑</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="noticeId"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:row-class-name="fnTableStriped"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: true }"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'noticeType'">
|
||||
<DictTag :options="dict.sysNoticeType" :value="record.noticeType" />
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<DictTag :options="dict.sysNoticeStatus" :value="record.status" />
|
||||
</template>
|
||||
<template v-if="column.key === 'noticeId'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>查看详情</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByVive(record.noticeId)"
|
||||
v-perms:has="['system:notice:query']"
|
||||
>
|
||||
<template #icon><ProfileOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>编辑</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByEdit(record.noticeId)"
|
||||
v-perms:has="['system:notice:edit']"
|
||||
>
|
||||
<template #icon><FormOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>删除</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordDelete(record.noticeId)"
|
||||
v-perms:has="['system:notice:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 详情框 -->
|
||||
<a-modal
|
||||
width="800px"
|
||||
:visible="modalState.visibleByView"
|
||||
:title="modalState.title"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="公告标题" name="noticeTitle">
|
||||
{{ modalState.from.noticeTitle }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="6" :xs="24">
|
||||
<a-form-item label="公告类型" name="noticeType">
|
||||
<DictTag
|
||||
:options="dict.sysNoticeType"
|
||||
:value="modalState.from.noticeType"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="6" :xs="24">
|
||||
<a-form-item label="公告状态" name="status">
|
||||
<DictTag
|
||||
:options="dict.sysNoticeStatus"
|
||||
:value="modalState.from.status"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="公告内容" name="noticeContent">
|
||||
{{ modalState.from.noticeContent }}
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button key="cancel" @click="fnModalCancel">关闭</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
|
||||
<!-- 新增框或修改框 -->
|
||||
<a-modal
|
||||
width="800px"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:visible="modalState.visibleByEdit"
|
||||
:title="modalState.title"
|
||||
:confirm-loading="modalState.confirmLoading"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form name="modalStateFrom" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="公告标题"
|
||||
name="noticeTitle"
|
||||
v-bind="modalStateFrom.validateInfos.noticeTitle"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.noticeTitle"
|
||||
allow-clear
|
||||
placeholder="请输入公告标题"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="6" :xs="24">
|
||||
<a-form-item
|
||||
label="公告类型"
|
||||
name="noticeType"
|
||||
v-bind="modalStateFrom.validateInfos.noticeType"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="modalState.from.noticeType"
|
||||
default-value="1"
|
||||
placeholder="公告类型"
|
||||
:options="dict.sysNoticeType"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="6" :xs="24">
|
||||
<a-form-item label="公告状态" name="status">
|
||||
<a-select
|
||||
v-model:value="modalState.from.status"
|
||||
default-value="0"
|
||||
placeholder="公告状态"
|
||||
:options="dict.sysNoticeStatus"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
label="公告内容"
|
||||
name="noticeContent"
|
||||
v-bind="modalStateFrom.validateInfos.noticeContent"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.noticeContent"
|
||||
:auto-size="{ minRows: 4, maxRows: 14 }"
|
||||
:maxlength="3000"
|
||||
:show-count="true"
|
||||
placeholder="请输入公告内容"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.table-striped) td {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
759
src/views/system/post/index.vue
Normal file
759
src/views/system/post/index.vue
Normal file
@@ -0,0 +1,759 @@
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router';
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import { message, Modal, Form } from 'ant-design-vue/lib';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import {
|
||||
exportPost,
|
||||
listPost,
|
||||
addPost,
|
||||
delPost,
|
||||
getPost,
|
||||
updatePost,
|
||||
} from '@/api/system/post';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
const { getDict } = useDictStore();
|
||||
const route = useRoute();
|
||||
|
||||
/**路由标题 */
|
||||
let title = ref<string>(route.meta.title ?? '标题');
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**状态 */
|
||||
sysNormalDisable: DictType[];
|
||||
} = reactive({
|
||||
sysNormalDisable: [],
|
||||
});
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**岗位编码 */
|
||||
postCode: '',
|
||||
/**岗位名称 */
|
||||
postName: '',
|
||||
/**岗位状态 */
|
||||
status: undefined,
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
postCode: '',
|
||||
postName: '',
|
||||
status: undefined,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**斑马纹 */
|
||||
striped: boolean;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
striped: false,
|
||||
seached: false,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: '岗位编号',
|
||||
dataIndex: 'postId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '岗位编码',
|
||||
dataIndex: 'postCode',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '岗位名称',
|
||||
dataIndex: 'postName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '岗位排序',
|
||||
dataIndex: 'postSort',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '岗位状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
if (+opt.value <= 0) return '';
|
||||
return parseDateToStr(+opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'postId',
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => `总共 ${total} 条`,
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格斑马纹 */
|
||||
function fnTableStriped(_record: unknown, index: number) {
|
||||
return tableState.striped && index % 2 === 1 ? 'table-striped' : undefined;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**详情框是否显示 */
|
||||
visibleByView: boolean;
|
||||
/**新增框或修改框是否显示 */
|
||||
visibleByEdit: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
visibleByView: false,
|
||||
visibleByEdit: false,
|
||||
title: '岗位',
|
||||
from: {
|
||||
postId: undefined,
|
||||
postName: '',
|
||||
postCode: '',
|
||||
postSort: 0,
|
||||
status: '0',
|
||||
remark: '',
|
||||
createTime: 0,
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
reactive({
|
||||
postName: [
|
||||
{ required: true, min: 1, max: 50, message: '请正确输入岗位编码' },
|
||||
],
|
||||
postCode: [
|
||||
{ required: true, min: 1, max: 50, message: '请正确输入岗位名称' },
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 查看
|
||||
* @param postId 岗位编号id
|
||||
*/
|
||||
function fnModalVisibleByVive(postId: string | number) {
|
||||
if (!postId) {
|
||||
message.error(`岗位记录存在错误`, 2);
|
||||
return;
|
||||
}
|
||||
if (modalState.confirmLoading) return;
|
||||
const hide = message.loading('正在打开...', 0);
|
||||
modalState.confirmLoading = true;
|
||||
getPost(postId).then(res => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
if (res.code === 200 && res.data) {
|
||||
modalState.from = Object.assign(modalState.from, res.data);
|
||||
modalState.title = '岗位信息';
|
||||
modalState.visibleByView = true;
|
||||
} else {
|
||||
message.error(`获取岗位信息失败`, 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param postId 岗位编号id, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByEdit(postId?: string | number) {
|
||||
if (!postId) {
|
||||
modalStateFrom.resetFields();
|
||||
modalState.title = '添加岗位信息';
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
if (modalState.confirmLoading) return;
|
||||
const hide = message.loading('正在打开...', 0);
|
||||
modalState.confirmLoading = true;
|
||||
getPost(postId).then(res => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
if (res.code === 200 && res.data) {
|
||||
modalState.from = Object.assign(modalState.from, res.data);
|
||||
modalState.title = '修改岗位信息';
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
message.error(`获取岗位信息失败`, 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalOk() {
|
||||
modalStateFrom
|
||||
.validate()
|
||||
.then(() => {
|
||||
modalState.confirmLoading = true;
|
||||
const from = toRaw(modalState.from);
|
||||
const post = from.postId ? updatePost(from) : addPost(from);
|
||||
const key = 'notice';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
post
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `${modalState.title}成功`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
modalState.visibleByEdit = false;
|
||||
modalStateFrom.resetFields();
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(`请正确填写 ${e.errorFields.length} 处必填信息!`, 2);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.visibleByEdit = false;
|
||||
modalState.visibleByView = false;
|
||||
modalStateFrom.resetFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* 岗位删除
|
||||
* @param postId 岗位编号ID
|
||||
*/
|
||||
function fnRecordDelete(postId: string = '0') {
|
||||
if (postId === '0') {
|
||||
postId = tableState.selectedRowKeys.join(',');
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认删除岗位编号为 【${postId}】 的数据项?`,
|
||||
onOk() {
|
||||
const key = 'delPost';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
delPost(postId).then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `删除成功`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key: key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList() {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认根据搜索条件导出xlsx表格文件吗?`,
|
||||
onOk() {
|
||||
const key = 'exportPost';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
exportPost(toRaw(queryParams)).then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `已完成导出`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `post_${Date.now()}.xlsx`);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**查询岗位列表 */
|
||||
function fnGetList() {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
listPost(toRaw(queryParams)).then(res => {
|
||||
if (res.code === 200 && Array.isArray(res.rows)) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.rows;
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('sys_normal_disable')]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.sysNormalDisable = resArr[0].value;
|
||||
}
|
||||
});
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer :title="title">
|
||||
<template #content>
|
||||
<a-typography-paragraph> 给予用户岗位标记 </a-typography-paragraph>
|
||||
</template>
|
||||
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="岗位编码" name="postCode">
|
||||
<a-input
|
||||
v-model:value="queryParams.postCode"
|
||||
allow-clear
|
||||
placeholder="请输入岗位编码"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="岗位名称" name="postName">
|
||||
<a-input
|
||||
v-model:value="queryParams.postName"
|
||||
allow-clear
|
||||
placeholder="请输入岗位名称"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="岗位状态" name="status">
|
||||
<a-select
|
||||
v-model:value="queryParams.status"
|
||||
allow-clear
|
||||
placeholder="请选择"
|
||||
:options="dict.sysNormalDisable"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
搜索</a-button
|
||||
>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
重置</a-button
|
||||
>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click.prevent="fnModalVisibleByEdit()"
|
||||
v-perms:has="['system:post:add']"
|
||||
>
|
||||
<template #icon><PlusOutlined /></template>
|
||||
新建
|
||||
</a-button>
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
@click.prevent="fnRecordDelete()"
|
||||
v-perms:has="['system:post:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
删除
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
@click.prevent="fnExportList()"
|
||||
v-perms:has="['system:post:export']"
|
||||
>
|
||||
<template #icon><ExportOutlined /></template>
|
||||
导出
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>搜索栏</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
checked-children="显"
|
||||
un-checked-children="隐"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>表格斑马纹</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.striped"
|
||||
checked-children="开"
|
||||
un-checked-children="关"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>刷新</template>
|
||||
<a-button type="text" @click.prevent="fnGetList">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>密度</template>
|
||||
<a-dropdown trigger="click">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">默认</a-menu-item>
|
||||
<a-menu-item key="middle">中等</a-menu-item>
|
||||
<a-menu-item key="small">紧凑</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="postId"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:row-class-name="fnTableStriped"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: true }"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<DictTag :options="dict.sysNormalDisable" :value="record.status" />
|
||||
</template>
|
||||
<template v-if="column.key === 'postId'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>查看详情</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByVive(record.postId)"
|
||||
v-perms:has="['system:post:query']"
|
||||
>
|
||||
<template #icon><ProfileOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>编辑</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByEdit(record.postId)"
|
||||
v-perms:has="['system:post:edit']"
|
||||
>
|
||||
<template #icon><FormOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>删除</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordDelete(record.postId)"
|
||||
v-perms:has="['system:post:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 详情框 -->
|
||||
<a-modal
|
||||
width="800px"
|
||||
:visible="modalState.visibleByView"
|
||||
:title="modalState.title"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="岗位编号" name="postId">
|
||||
{{ modalState.from.postId }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="创建时间" name="createTime">
|
||||
<span v-if="+modalState.from.createTime > 0">
|
||||
{{ parseDateToStr(+modalState.from.createTime) }}
|
||||
</span>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="岗位顺序" name="postSort">
|
||||
{{ modalState.from.postSort }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="岗位状态" name="status">
|
||||
<DictTag
|
||||
:options="dict.sysNormalDisable"
|
||||
:value="modalState.from.status"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="岗位编码" name="postCode">
|
||||
{{ modalState.from.postCode }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="岗位名称" name="postName">
|
||||
{{ modalState.from.postName }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item label="岗位说明" name="remark">
|
||||
{{ modalState.from.remark }}
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button key="cancel" @click="fnModalCancel">关闭</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
|
||||
<!-- 新增框或修改框 -->
|
||||
<a-modal
|
||||
width="800px"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:visible="modalState.visibleByEdit"
|
||||
:title="modalState.title"
|
||||
:confirm-loading="modalState.confirmLoading"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form name="modalStateFrom" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="岗位编码"
|
||||
name="postCode"
|
||||
v-bind="modalStateFrom.validateInfos.postCode"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.postCode"
|
||||
allow-clear
|
||||
placeholder="请输入岗位编码"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="岗位状态" name="status">
|
||||
<a-select
|
||||
v-model:value="modalState.from.status"
|
||||
default-value="0"
|
||||
placeholder="岗位状态"
|
||||
:options="dict.sysNormalDisable"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="岗位名称"
|
||||
name="postName"
|
||||
v-bind="modalStateFrom.validateInfos.postName"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.postName"
|
||||
allow-clear
|
||||
placeholder="请输入岗位名称"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="岗位顺序" name="postSort">
|
||||
<a-input-number
|
||||
v-model:value="modalState.from.postSort"
|
||||
:min="0"
|
||||
:max="9999"
|
||||
:step="1"
|
||||
placeholder="排序值"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="岗位说明" name="remark">
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.remark"
|
||||
:auto-size="{ minRows: 4, maxRows: 6 }"
|
||||
:maxlength="450"
|
||||
:show-count="true"
|
||||
placeholder="请输入岗位说明"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.table-striped) td {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
508
src/views/system/role/auth-user.vue
Normal file
508
src/views/system/role/auth-user.vue
Normal file
@@ -0,0 +1,508 @@
|
||||
<script setup lang="ts">
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { reactive, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import { message, Modal } from 'ant-design-vue/lib';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import AuthUserSelect from './components/auth-user-select.vue';
|
||||
import { authUserAllocatedList, authUserChecked } from '@/api/system/role';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useTabsStore from '@/store/modules/tabs';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
const tabsStore = useTabsStore();
|
||||
const { getDict } = useDictStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
// 获取地址栏参数
|
||||
const roleId = route.params && (route.params.roleId as string);
|
||||
const roleName = route.query && (route.query.roleName as string);
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**状态 */
|
||||
sysNormalDisable: DictType[];
|
||||
} = reactive({
|
||||
sysNormalDisable: [],
|
||||
});
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**登录账号 */
|
||||
userName: '',
|
||||
/**手机号码 */
|
||||
phonenumber: '',
|
||||
/**用户状态 */
|
||||
status: undefined,
|
||||
/**角色ID */
|
||||
roleId: roleId,
|
||||
/**是否已分配 */
|
||||
allocated: true,
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
userName: '',
|
||||
phonenumber: '',
|
||||
status: undefined,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**斑马纹 */
|
||||
striped: boolean;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
striped: false,
|
||||
seached: true,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: '用户编号',
|
||||
dataIndex: 'userId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '登录账号',
|
||||
dataIndex: 'userName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '用户昵称',
|
||||
dataIndex: 'nickName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '手机号码',
|
||||
dataIndex: 'phonenumber',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '电子邮箱',
|
||||
dataIndex: 'email',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '用户状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
if (+opt.value <= 0) return '';
|
||||
return parseDateToStr(+opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'userId',
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => `总共 ${total} 条`,
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格斑马纹 */
|
||||
function fnTableStriped(_record: unknown, index: number) {
|
||||
return tableState.striped && index % 2 === 1 ? 'table-striped' : undefined;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**选择用户框是否显示 */
|
||||
visibleBySelectUser: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
visibleBySelectUser: false,
|
||||
title: '选择用户',
|
||||
});
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 选择用户
|
||||
*/
|
||||
function fnModalVisibleBySelectUser() {
|
||||
modalState.visibleBySelectUser = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 授权用户
|
||||
*/
|
||||
function fnModalOk(userIds: string[] | number[]) {
|
||||
if (userIds.length <= 0) {
|
||||
message.error(`请选择要分配的用户`, 2);
|
||||
return;
|
||||
}
|
||||
const hide = message.loading('请稍等...', 0);
|
||||
authUserChecked({
|
||||
checked: true,
|
||||
userIds: userIds.join(','),
|
||||
roleId: roleId,
|
||||
}).then(res => {
|
||||
hide();
|
||||
if (res.code === 200) {
|
||||
modalState.visibleBySelectUser = false;
|
||||
message.success({
|
||||
content: `授权用户添加成功`,
|
||||
duration: 3,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消授权
|
||||
* @param userId 用户编号ID
|
||||
*/
|
||||
function fnRecordDelete(userId: string | number) {
|
||||
if (userId === '0') {
|
||||
userId = tableState.selectedRowKeys.join(',');
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认取消用户编号为 【${userId}】 的数据项授权?`,
|
||||
onOk() {
|
||||
const hide = message.loading('请稍等...', 0);
|
||||
authUserChecked({ checked: false, userIds: userId, roleId: roleId }).then(
|
||||
res => {
|
||||
hide();
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `取消授权成功`,
|
||||
duration: 3,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**关闭跳转 */
|
||||
function fnClose() {
|
||||
const to = tabsStore.tabClose(route.path);
|
||||
if (to) {
|
||||
router.push(to);
|
||||
} else {
|
||||
router.back();
|
||||
}
|
||||
}
|
||||
|
||||
/**查询角色已授权用户列表 */
|
||||
function fnGetList() {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
authUserAllocatedList(toRaw(queryParams)).then(res => {
|
||||
if (res.code === 200 && Array.isArray(res.rows)) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.rows;
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('sys_normal_disable')]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.sysNormalDisable = resArr[0].value;
|
||||
}
|
||||
});
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="角色名称" name="roleName">
|
||||
<a-input
|
||||
:value="roleName"
|
||||
disabled
|
||||
placeholder="请输入角色名称"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="登录账号" name="userName">
|
||||
<a-input
|
||||
v-model:value="queryParams.userName"
|
||||
allow-clear
|
||||
:maxlength="30"
|
||||
placeholder="请输入登录账号"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="手机号码" name="phonenumber">
|
||||
<a-input
|
||||
v-model:value="queryParams.phonenumber"
|
||||
allow-clear
|
||||
:maxlength="11"
|
||||
placeholder="请输入手机号码"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="用户状态" name="status">
|
||||
<a-select
|
||||
v-model:value="queryParams.status"
|
||||
allow-clear
|
||||
placeholder="请选择用户状态"
|
||||
:options="dict.sysNormalDisable"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
搜索</a-button
|
||||
>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
重置</a-button
|
||||
>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button type="default" @click.prevent="fnClose()">
|
||||
<template #icon><CloseOutlined /></template>
|
||||
关闭
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click.prevent="fnModalVisibleBySelectUser()"
|
||||
v-perms:has="['system:role:add']"
|
||||
>
|
||||
<template #icon><UsergroupAddOutlined /></template>
|
||||
分配用户
|
||||
</a-button>
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
@click.prevent="fnRecordDelete('0')"
|
||||
v-perms:has="['system:role:remove']"
|
||||
>
|
||||
<template #icon><UsergroupDeleteOutlined /></template>
|
||||
批量取消授权
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>搜索栏</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
checked-children="显"
|
||||
un-checked-children="隐"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>表格斑马纹</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.striped"
|
||||
checked-children="开"
|
||||
un-checked-children="关"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>刷新</template>
|
||||
<a-button type="text" @click.prevent="fnGetList">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>密度</template>
|
||||
<a-dropdown trigger="click">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">默认</a-menu-item>
|
||||
<a-menu-item key="middle">中等</a-menu-item>
|
||||
<a-menu-item key="small">紧凑</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="userId"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:row-class-name="fnTableStriped"
|
||||
:scroll="{ x: true }"
|
||||
:pagination="tablePagination"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<DictTag :options="dict.sysNormalDisable" :value="record.status" />
|
||||
</template>
|
||||
<template v-if="column.key === 'userId'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>取消授权</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordDelete(record.userId)"
|
||||
v-perms:has="['system:role:remove']"
|
||||
>
|
||||
<template #icon><UserDeleteOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 分配用户选择框 -->
|
||||
<AuthUserSelect
|
||||
:role-id="roleId"
|
||||
:title="modalState.title"
|
||||
v-model:visible="modalState.visibleBySelectUser"
|
||||
@ok="fnModalOk"
|
||||
/>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.table-striped) td {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
301
src/views/system/role/components/auth-user-select.vue
Normal file
301
src/views/system/role/components/auth-user-select.vue
Normal file
@@ -0,0 +1,301 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, toRaw, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue/lib';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import { authUserAllocatedList } from '@/api/system/role';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
const { getDict } = useDictStore();
|
||||
const emit = defineEmits(['ok', 'cancel', 'update:visible']);
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '标题',
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
roleId: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**状态 */
|
||||
sysNormalDisable: DictType[];
|
||||
} = reactive({
|
||||
sysNormalDisable: [],
|
||||
});
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**登录账号 */
|
||||
userName: '',
|
||||
/**手机号码 */
|
||||
phonenumber: '',
|
||||
/**用户状态 */
|
||||
status: undefined,
|
||||
/**角色ID */
|
||||
roleId: props.roleId,
|
||||
/**是否已分配 */
|
||||
allocated: false,
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
userName: '',
|
||||
phonenumber: '',
|
||||
status: undefined,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'small',
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: '用户编号',
|
||||
dataIndex: 'userId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '登录账号',
|
||||
dataIndex: 'userName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '用户昵称',
|
||||
dataIndex: 'nickName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '手机号码',
|
||||
dataIndex: 'phonenumber',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '用户状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
if (+opt.value <= 0) return '';
|
||||
return parseDateToStr(+opt.value);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => `总共 ${total} 条`,
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
}
|
||||
|
||||
/**查询角色未授权用户列表 */
|
||||
function fnGetList() {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
authUserAllocatedList(toRaw(queryParams)).then(res => {
|
||||
if (res.code === 200 && Array.isArray(res.rows)) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.rows;
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**弹框确认按钮事件 */
|
||||
function fnModalOk() {
|
||||
const userIds = tableState.selectedRowKeys;
|
||||
if (userIds.length <= 0) {
|
||||
message.error(`请选择要分配的用户`, 2);
|
||||
return;
|
||||
}
|
||||
emit('update:visible', false);
|
||||
emit('ok', userIds);
|
||||
}
|
||||
|
||||
/**弹框取消按钮事件 */
|
||||
function fnModalCancel() {
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
}
|
||||
|
||||
/**显示弹框时初始数据 */
|
||||
function init() {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('sys_normal_disable')]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.sysNormalDisable = resArr[0].value;
|
||||
}
|
||||
});
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**监听是否显示,初始数据 */
|
||||
watch(
|
||||
() => props.visible,
|
||||
val => {
|
||||
if (val) init();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-modal
|
||||
width="800px"
|
||||
:title="props.title"
|
||||
:visible="props.visible"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item label="登录账号" name="userName">
|
||||
<a-input
|
||||
v-model:value="queryParams.userName"
|
||||
allow-clear
|
||||
:maxlength="30"
|
||||
placeholder="请输入登录账号"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item label="手机号码" name="phonenumber">
|
||||
<a-input
|
||||
v-model:value="queryParams.phonenumber"
|
||||
allow-clear
|
||||
:maxlength="11"
|
||||
placeholder="请输入手机号码"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item label="用户状态" name="status">
|
||||
<a-select
|
||||
v-model:value="queryParams.status"
|
||||
allow-clear
|
||||
placeholder="请选择用户状态"
|
||||
:options="dict.sysNormalDisable"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
搜索</a-button
|
||||
>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
重置</a-button
|
||||
>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="userId"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:scroll="{ scrollToFirstRowOnChange: true, y: 400, x: true }"
|
||||
:pagination="tablePagination"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<DictTag :options="dict.sysNormalDisable" :value="record.status" />
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
1281
src/views/system/role/index.vue
Normal file
1281
src/views/system/role/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
185
src/views/system/user/components/UploadXlsxImport.vue
Normal file
185
src/views/system/user/components/UploadXlsxImport.vue
Normal file
@@ -0,0 +1,185 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue';
|
||||
import { message } from 'ant-design-vue/lib';
|
||||
import { FileType } from 'ant-design-vue/lib/upload/interface';
|
||||
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
|
||||
import { ResultType } from '@/plugins/http-fetch';
|
||||
const emit = defineEmits(['close', 'update:visible']);
|
||||
const props = defineProps({
|
||||
/**窗口标题 */
|
||||
title: {
|
||||
type: String,
|
||||
default: '标题',
|
||||
},
|
||||
/**是否弹出显示,必传 */
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
/**文件上传函数方法,必传 */
|
||||
uploadFileMethod: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
/**下载模板函数方法 */
|
||||
downloadTemplateMethod: {
|
||||
type: Function,
|
||||
default: undefined,
|
||||
},
|
||||
/**显示更新已存在数据勾选项 */
|
||||
showUpdateSupport: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/**允许上传的文件拓展类型,默认 xls、xlsx */
|
||||
fileExt: {
|
||||
type: Array<string>,
|
||||
default: ['xls', 'xlsx'],
|
||||
},
|
||||
/**上传文件大小(单位MB),默认 10 */
|
||||
fileSize: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
});
|
||||
|
||||
/**上传状态 */
|
||||
let updateState = reactive({
|
||||
/**是否更新已经存在的数据 */
|
||||
updateSupport: false,
|
||||
/**是否上传中 */
|
||||
loading: false,
|
||||
/**是否已上传文件 */
|
||||
isUpload: false,
|
||||
/**上传结果信息 */
|
||||
msg: '',
|
||||
});
|
||||
|
||||
/**重置上传状态 */
|
||||
function fnResetUpdateState() {
|
||||
updateState = Object.assign(updateState, {
|
||||
updateSupport: false,
|
||||
loading: false,
|
||||
isUpload: false,
|
||||
msg: '',
|
||||
});
|
||||
}
|
||||
|
||||
/**上传前检查或转换压缩 */
|
||||
function fnBeforeUpload(file: FileType) {
|
||||
if (updateState.loading) return false;
|
||||
const isAllowType = props.fileExt.some(v => file.name.endsWith(v));
|
||||
if (!isAllowType) {
|
||||
message.error(`只支持上传文件格式 ${props.fileExt.join('、')}`, 3);
|
||||
}
|
||||
const isLtM = file.size / 1024 / 1024 < props.fileSize;
|
||||
if (!isLtM) {
|
||||
message.error(`上传文件大小必须小于 ${props.fileSize}MB`, 3);
|
||||
}
|
||||
return isAllowType && isLtM;
|
||||
}
|
||||
|
||||
/**上传请求发出 */
|
||||
function fnUpload(up: UploadRequestOption) {
|
||||
if (typeof props.uploadFileMethod !== 'function') return;
|
||||
const hide = message.loading('正在上传并解析数据...', 0);
|
||||
updateState.loading = true;
|
||||
let formData = new FormData();
|
||||
formData.append('file', up.file);
|
||||
formData.append('updateSupport', `${updateState.updateSupport}`);
|
||||
props
|
||||
.uploadFileMethod(formData)
|
||||
.then((res: ResultType) => {
|
||||
updateState.loading = false;
|
||||
updateState.isUpload = true;
|
||||
updateState.msg = res.msg?.replaceAll(/<br\/>+/g, '\r');
|
||||
})
|
||||
.catch((err: { code: number; msg: string }) => {
|
||||
message.error(`上传失败 ${err.msg}`);
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
});
|
||||
}
|
||||
|
||||
/**弹框确认按钮事件 */
|
||||
function fnModalOk() {
|
||||
emit('update:visible', false);
|
||||
emit('close', updateState.isUpload);
|
||||
fnResetUpdateState();
|
||||
}
|
||||
|
||||
/**弹框取消按钮事件 */
|
||||
function fnModalCancel() {
|
||||
emit('update:visible', false);
|
||||
emit('close', updateState.isUpload);
|
||||
fnResetUpdateState();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-modal
|
||||
width="500px"
|
||||
:title="props.title"
|
||||
:visible="props.visible"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-space :size="8" direction="vertical" style="width: 100%">
|
||||
<a-upload-dragger
|
||||
:disabled="updateState.loading"
|
||||
name="file"
|
||||
:max-count="1"
|
||||
:show-upload-list="false"
|
||||
:before-upload="fnBeforeUpload"
|
||||
:custom-request="fnUpload"
|
||||
>
|
||||
<p class="ant-upload-drag-icon">
|
||||
<inbox-outlined></inbox-outlined>
|
||||
</p>
|
||||
<p class="ant-upload-text">点击选择或将文件拖入边框区域进行上传</p>
|
||||
<p class="ant-upload-hint">
|
||||
仅允许导入
|
||||
{{ props.fileExt.join('、') }}
|
||||
格式文件,上传文件大小
|
||||
{{ props.fileSize }}
|
||||
MB。
|
||||
</p>
|
||||
</a-upload-dragger>
|
||||
<a-row :gutter="18" justify="space-between" align="middle">
|
||||
<a-col :span="12">
|
||||
<a-checkbox
|
||||
v-model:checked="updateState.updateSupport"
|
||||
v-if="showUpdateSupport"
|
||||
>
|
||||
是否更新已经存在的数据
|
||||
</a-checkbox>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-button
|
||||
type="link"
|
||||
title="下载模板"
|
||||
@click="downloadTemplateMethod()"
|
||||
v-if="downloadTemplateMethod"
|
||||
>
|
||||
下载模板
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-textarea
|
||||
:disabled="true"
|
||||
:hidden="updateState.msg.length < 1"
|
||||
:value="updateState.msg"
|
||||
:auto-size="{ minRows: 2, maxRows: 8 }"
|
||||
/>
|
||||
</a-space>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
1433
src/views/system/user/index.vue
Normal file
1433
src/views/system/user/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
11
src/views/tool/build/index.vue
Normal file
11
src/views/tool/build/index.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
const msg = ref<string>('愿这世间美好与你环环相扣');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
14
src/views/tool/swagger/index.vue
Normal file
14
src/views/tool/swagger/index.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<script lang="ts" setup>
|
||||
import LinkiFrame from '@/components/LinkiFrame/index.vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const baseUrl = import.meta.env.VITE_API_BASE_URL;
|
||||
const url = ref<string>(`${baseUrl}/swagger-ui/index.html`);
|
||||
url.value = 'https://mask-api-midwayjs.apifox.cn/';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LinkiFrame :src="url"></LinkiFrame>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
236
src/views/tool/upload/index.vue
Normal file
236
src/views/tool/upload/index.vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from 'vue';
|
||||
import { PageContainer } from '@ant-design-vue/pro-layout';
|
||||
import { Modal } from 'ant-design-vue/lib/components';
|
||||
import message from 'ant-design-vue/lib/message';
|
||||
import { FileType, UploadFile } from 'ant-design-vue/lib/upload/interface';
|
||||
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
|
||||
import saveAs from 'file-saver';
|
||||
import {
|
||||
downloadFile,
|
||||
downloadFileChunk,
|
||||
uploadFile,
|
||||
uploadFileChunk,
|
||||
} from '@/api/tool/file';
|
||||
|
||||
let state = reactive<{
|
||||
/**上传状态 */
|
||||
loading: boolean;
|
||||
uploadFilePath: string;
|
||||
downloadFilePath: string;
|
||||
/*文件列表 */
|
||||
fileList: UploadFile<any>[];
|
||||
}>({
|
||||
loading: false,
|
||||
uploadFilePath: '',
|
||||
downloadFilePath: '',
|
||||
fileList: [
|
||||
// {
|
||||
// uid: '1',
|
||||
// percent: 100,
|
||||
// status: 'success',
|
||||
// name: 'xxx.png',
|
||||
// url: '/upload/default/2023/06/xxx.png',
|
||||
// thumbUrl: '/upload/default/2023/06/xxx.png',
|
||||
// },
|
||||
],
|
||||
});
|
||||
|
||||
/**下载文件 */
|
||||
function fnDownload() {
|
||||
const key = 'downloadFile';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
const filePath = state.downloadFilePath;
|
||||
if (!filePath) return;
|
||||
downloadFile(filePath).then(res => {
|
||||
if (res.code === 200) {
|
||||
message.success({
|
||||
content: `已完成下载`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
const fileName = filePath.substring(filePath.lastIndexOf('/') + 1);
|
||||
saveAs(res.data, fileName);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**下载切片文件 */
|
||||
function fnDownloadChunk() {
|
||||
const key = 'downloadFileChunk';
|
||||
message.loading({ content: '请稍等...', key });
|
||||
const filePath = state.downloadFilePath;
|
||||
downloadFileChunk(filePath, 5).then(blob => {
|
||||
console.log(blob);
|
||||
if (blob.size === 0) {
|
||||
message.error({
|
||||
content: `文件读取失败`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
} else {
|
||||
message.success({
|
||||
content: `已完成下载`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
const fileName = filePath.substring(filePath.lastIndexOf('/') + 1);
|
||||
saveAs(blob, fileName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**上传前检查或转换压缩 */
|
||||
function fnBeforeUpload(file: FileType) {
|
||||
if (state.loading) return false;
|
||||
const isJpgOrPng = ['image/jpeg', 'image/png'].includes(file.type);
|
||||
if (!isJpgOrPng) {
|
||||
message.error('只支持上传图片格式(jpg、png)', 3);
|
||||
}
|
||||
const isLt2M = file.size / 1024 / 1024 < 2;
|
||||
if (!isLt2M) {
|
||||
message.error('图片文件大小必须小于 2MB', 3);
|
||||
}
|
||||
return isJpgOrPng && isLt2M;
|
||||
}
|
||||
|
||||
/**上传文件 */
|
||||
function fnUpload(up: UploadRequestOption) {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认要上传文件吗?`,
|
||||
onOk() {
|
||||
// 发送请求
|
||||
const hide = message.loading('请稍等...', 0);
|
||||
state.loading = true;
|
||||
let formData = new FormData();
|
||||
formData.append('file', up.file);
|
||||
formData.append('subPath', 'default');
|
||||
uploadFile(formData).then(res => {
|
||||
state.loading = false;
|
||||
hide();
|
||||
if (res.code === 200) {
|
||||
message.success('文件上传成功', 3);
|
||||
state.uploadFilePath = res.data.url;
|
||||
state.downloadFilePath = res.data.fileName;
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**上传分片 */
|
||||
function fnUploadChunk(up: UploadRequestOption) {
|
||||
const fileData = up.file as File;
|
||||
const item = state.fileList.find(f => f.name === fileData.name);
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `确认要上传文件吗?`,
|
||||
onOk() {
|
||||
// 发送请求
|
||||
const hide = message.loading('请稍等...', 0);
|
||||
uploadFileChunk(fileData, 4, 'default').then(res => {
|
||||
hide();
|
||||
if (res.code === 200) {
|
||||
message.success('文件上传成功', 3);
|
||||
if (item) {
|
||||
item.url = res.data.url;
|
||||
item.name = res.data.newFileName;
|
||||
item.percent = 100;
|
||||
item.status = 'done';
|
||||
}
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
state.fileList.splice(state.fileList.length - 1, 1);
|
||||
}
|
||||
});
|
||||
},
|
||||
onCancel() {
|
||||
if (item) {
|
||||
state.fileList.splice(state.fileList.length - 1, 1);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer title="上传示例">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-card title="普通文件" style="margin-bottom: 16px">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="24" style="margin-bottom: 16px">
|
||||
<a-input
|
||||
style="margin-bottom: 16px"
|
||||
type="text"
|
||||
placeholder="输入资源文件地址"
|
||||
v-model:value="state.downloadFilePath"
|
||||
>
|
||||
<template #suffix>
|
||||
<a-button type="primary" @click="fnDownload">
|
||||
普通下载
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
<a-input
|
||||
type="text"
|
||||
placeholder="输入资源文件地址"
|
||||
v-model:value="state.downloadFilePath"
|
||||
>
|
||||
<template #suffix>
|
||||
<a-button type="primary" @click="fnDownloadChunk">
|
||||
分片下载
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-space direction="vertical" :size="16">
|
||||
<a-upload
|
||||
name="file"
|
||||
list-type="picture"
|
||||
:max-count="1"
|
||||
:show-upload-list="false"
|
||||
:before-upload="fnBeforeUpload"
|
||||
:custom-request="fnUpload"
|
||||
>
|
||||
<a-button type="default" :loading="state.loading">
|
||||
选择文件
|
||||
</a-button>
|
||||
</a-upload>
|
||||
<a-image
|
||||
:width="128"
|
||||
:height="128"
|
||||
:src="state.uploadFilePath"
|
||||
/>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-card title="大文件分片上传" style="margin-bottom: 16px">
|
||||
<a-upload
|
||||
v-model:file-list="state.fileList"
|
||||
name="file"
|
||||
list-type="picture"
|
||||
:custom-request="fnUploadChunk"
|
||||
>
|
||||
<a-button> 选择文件 </a-button>
|
||||
</a-upload>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
Reference in New Issue
Block a user