初始化项目,自 v1.7.1 版本开始
This commit is contained in:
76
src/views/system/dept/dept.data.ts
Normal file
76
src/views/system/dept/dept.data.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
sort: [required],
|
||||
email: [required],
|
||||
phone: [
|
||||
{
|
||||
len: 11,
|
||||
trigger: 'blur',
|
||||
message: '请输入正确的手机号码'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: null,
|
||||
action: true,
|
||||
columns: [
|
||||
{
|
||||
title: '上级部门',
|
||||
field: 'parentId',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: '部门名称',
|
||||
field: 'name',
|
||||
isSearch: true,
|
||||
table: {
|
||||
treeNode: true,
|
||||
align: 'left'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '负责人',
|
||||
field: 'leaderUserId',
|
||||
table: {
|
||||
slots: {
|
||||
default: 'leaderUserId_default'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '联系电话',
|
||||
field: 'phone'
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
field: 'email',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: '显示排序',
|
||||
field: 'sort'
|
||||
},
|
||||
{
|
||||
title: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
isForm: false
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
189
src/views/system/dept/index.vue
Normal file
189
src/views/system/dept/index.vue
Normal file
@@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable ref="xGrid" @register="registerTable" show-overflow>
|
||||
<template #toolbar_buttons>
|
||||
<!-- 操作:新增 -->
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:zoom-in"
|
||||
:title="t('action.add')"
|
||||
v-hasPermi="['system:dept:create']"
|
||||
@click="handleCreate()"
|
||||
/>
|
||||
<XButton title="展开所有" @click="xGrid?.Ref.setAllTreeExpand(true)" />
|
||||
<XButton title="关闭所有" @click="xGrid?.Ref.clearTreeExpand()" />
|
||||
</template>
|
||||
<template #leaderUserId_default="{ row }">
|
||||
<span>{{ userNicknameFormat(row) }}</span>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:修改 -->
|
||||
<XTextButton
|
||||
preIcon="ep:edit"
|
||||
:title="t('action.edit')"
|
||||
v-hasPermi="['system:dept:update']"
|
||||
@click="handleUpdate(row.id)"
|
||||
/>
|
||||
<!-- 操作:删除 -->
|
||||
<XTextButton
|
||||
preIcon="ep:delete"
|
||||
:title="t('action.del')"
|
||||
v-hasPermi="['system:dept:delete']"
|
||||
@click="deleteData(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
<!-- 添加或修改菜单对话框 -->
|
||||
<XModal id="deptModel" v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form ref="formRef" :schema="allSchemas.formSchema" :rules="rules">
|
||||
<template #parentId="form">
|
||||
<el-tree-select
|
||||
node-key="id"
|
||||
v-model="form['parentId']"
|
||||
:props="defaultProps"
|
||||
:data="deptOptions"
|
||||
:default-expanded-keys="[100]"
|
||||
check-strictly
|
||||
/>
|
||||
</template>
|
||||
<template #leaderUserId="form">
|
||||
<el-select v-model="form['leaderUserId']">
|
||||
<el-option
|
||||
v-for="item in userOption"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</Form>
|
||||
<template #footer>
|
||||
<!-- 按钮:保存 -->
|
||||
<XButton
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm()"
|
||||
:title="t('action.save')"
|
||||
/>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="actionLoading" @click="dialogVisible = false" :title="t('dialog.close')" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="Dept">
|
||||
import { handleTree, defaultProps } from '@/utils/tree'
|
||||
import type { FormExpose } from '@/components/Form'
|
||||
import { allSchemas, rules } from './dept.data'
|
||||
import * as DeptApi from '@/api/system/dept'
|
||||
import { getListSimpleUsersApi, UserVO } from '@/api/system/user'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
// 列表相关的变量
|
||||
const xGrid = ref<any>() // 列表 Grid Ref
|
||||
const treeConfig = {
|
||||
transform: true,
|
||||
rowField: 'id',
|
||||
parentField: 'parentId',
|
||||
expandAll: true
|
||||
}
|
||||
|
||||
// 弹窗相关的变量
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const actionLoading = ref(false) // 遮罩层
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const deptOptions = ref() // 树形结构
|
||||
const userOption = ref<UserVO[]>([])
|
||||
|
||||
const getUserList = async () => {
|
||||
const res = await getListSimpleUsersApi()
|
||||
userOption.value = res
|
||||
}
|
||||
// 获取下拉框[上级]的数据
|
||||
const getTree = async () => {
|
||||
deptOptions.value = []
|
||||
const res = await DeptApi.listSimpleDeptApi()
|
||||
let dept: Tree = { id: 0, name: '顶级部门', children: [] }
|
||||
dept.children = handleTree(res)
|
||||
deptOptions.value.push(dept)
|
||||
}
|
||||
const [registerTable, { reload, deleteData }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
treeConfig: treeConfig,
|
||||
getListApi: DeptApi.getDeptPageApi,
|
||||
deleteApi: DeptApi.deleteDeptApi
|
||||
})
|
||||
// ========== 新增/修改 ==========
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = async () => {
|
||||
setDialogTile('create')
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (rowId: number) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await DeptApi.getDeptApi(rowId)
|
||||
await nextTick()
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 提交新增/修改的表单
|
||||
const submitForm = async () => {
|
||||
const elForm = unref(formRef)?.getElFormRef()
|
||||
if (!elForm) return
|
||||
elForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as DeptApi.DeptVO
|
||||
if (actionType.value === 'create') {
|
||||
await DeptApi.createDeptApi(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else if (actionType.value === 'update') {
|
||||
await DeptApi.updateDeptApi(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
await getTree()
|
||||
await reload()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const userNicknameFormat = (row) => {
|
||||
if (!row || !row.leaderUserId) {
|
||||
return '未设置'
|
||||
}
|
||||
for (const user of userOption.value) {
|
||||
if (row.leaderUserId === user.id) {
|
||||
return user.nickname
|
||||
}
|
||||
}
|
||||
return '未知【' + row.leaderUserId + '】'
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
onMounted(async () => {
|
||||
await getUserList()
|
||||
await getTree()
|
||||
})
|
||||
</script>
|
||||
104
src/views/system/dict/dict.data.ts
Normal file
104
src/views/system/dict/dict.data.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
// 表单校验
|
||||
export const dictDataRules = reactive({
|
||||
label: [required],
|
||||
value: [required],
|
||||
sort: [required]
|
||||
})
|
||||
// crudSchemas
|
||||
export const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: null,
|
||||
action: true,
|
||||
actionWidth: '140px',
|
||||
searchSpan: 12,
|
||||
columns: [
|
||||
{
|
||||
title: '字典类型',
|
||||
field: 'dictType',
|
||||
isTable: false,
|
||||
isForm: false
|
||||
},
|
||||
{
|
||||
title: '数据标签',
|
||||
field: 'label',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '数据键值',
|
||||
field: 'value'
|
||||
},
|
||||
// {
|
||||
// title: '标签类型',
|
||||
// field: 'colorType',
|
||||
// form: {
|
||||
// component: 'Select',
|
||||
// componentProps: {
|
||||
// options: [
|
||||
// {
|
||||
// label: 'default',
|
||||
// value: ''
|
||||
// },
|
||||
// {
|
||||
// label: 'success',
|
||||
// value: 'success'
|
||||
// },
|
||||
// {
|
||||
// label: 'info',
|
||||
// value: 'info'
|
||||
// },
|
||||
// {
|
||||
// label: 'warning',
|
||||
// value: 'warning'
|
||||
// },
|
||||
// {
|
||||
// label: 'danger',
|
||||
// value: 'danger'
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// },
|
||||
// isTable: false
|
||||
// },
|
||||
{
|
||||
title: '颜色',
|
||||
field: 'cssClass',
|
||||
isTable: false,
|
||||
form: {
|
||||
component: 'ColorPicker',
|
||||
componentProps: {
|
||||
predefine: ['#ffffff', '#409eff', '#67c23a', '#e6a23c', '#f56c6c', '#909399', '#c71585']
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '显示排序',
|
||||
field: 'sort',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number'
|
||||
},
|
||||
{
|
||||
title: t('form.remark'),
|
||||
field: 'remark',
|
||||
form: {
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 4
|
||||
},
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
isTable: false
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
65
src/views/system/dict/dict.type.ts
Normal file
65
src/views/system/dict/dict.type.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const dictTypeRules = reactive({
|
||||
name: [required],
|
||||
type: [required]
|
||||
})
|
||||
// 新增 + 修改
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: null,
|
||||
action: true,
|
||||
actionWidth: '140px',
|
||||
searchSpan: 12,
|
||||
columns: [
|
||||
{
|
||||
title: '字典名称',
|
||||
field: 'name',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '字典类型',
|
||||
field: 'type',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number',
|
||||
table: {
|
||||
width: 70
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
isForm: false,
|
||||
isTable: false,
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('form.remark'),
|
||||
field: 'remark',
|
||||
isTable: false,
|
||||
form: {
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 4
|
||||
},
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
251
src/views/system/dict/index.vue
Normal file
251
src/views/system/dict/index.vue
Normal file
@@ -0,0 +1,251 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<!-- ====== 字典分类 ====== -->
|
||||
<el-card class="w-1/2 dict" :gutter="12" shadow="always">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>字典分类</span>
|
||||
</div>
|
||||
</template>
|
||||
<XTable @register="registerType" @cell-click="cellClickEvent">
|
||||
<!-- 操作:新增类型 -->
|
||||
<template #toolbar_buttons>
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:zoom-in"
|
||||
:title="t('action.add')"
|
||||
v-hasPermi="['system:dict:create']"
|
||||
@click="handleTypeCreate()"
|
||||
/>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:编辑类型 -->
|
||||
<XTextButton
|
||||
preIcon="ep:edit"
|
||||
:title="t('action.edit')"
|
||||
v-hasPermi="['system:dict:update']"
|
||||
@click="handleTypeUpdate(row.id)"
|
||||
/>
|
||||
<!-- 操作:删除类型 -->
|
||||
<XTextButton
|
||||
preIcon="ep:delete"
|
||||
:title="t('action.del')"
|
||||
v-hasPermi="['system:dict:delete']"
|
||||
@click="typeDeleteData(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
<!-- @星语:分页和列表重叠在一起了 -->
|
||||
</el-card>
|
||||
<!-- ====== 字典数据 ====== -->
|
||||
<el-card class="w-1/2 dict ml-3" :gutter="12" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>字典数据</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 列表 -->
|
||||
<div v-if="!tableTypeSelect">
|
||||
<span>请从左侧选择</span>
|
||||
</div>
|
||||
<div v-if="tableTypeSelect">
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerData">
|
||||
<!-- 操作:新增数据 -->
|
||||
<template #toolbar_buttons>
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:zoom-in"
|
||||
:title="t('action.add')"
|
||||
v-hasPermi="['system:dict:create']"
|
||||
@click="handleDataCreate()"
|
||||
/>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:修改数据 -->
|
||||
<XTextButton
|
||||
v-hasPermi="['system:dict:update']"
|
||||
preIcon="ep:edit"
|
||||
:title="t('action.edit')"
|
||||
@click="handleDataUpdate(row.id)"
|
||||
/>
|
||||
<!-- 操作:删除数据 -->
|
||||
<XTextButton
|
||||
v-hasPermi="['system:dict:delete']"
|
||||
preIcon="ep:delete"
|
||||
:title="t('action.del')"
|
||||
@click="dataDeleteData(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</div>
|
||||
</el-card>
|
||||
<XModal id="dictModel" v-model="dialogVisible" :title="dialogTitle">
|
||||
<Form
|
||||
v-if="['typeCreate', 'typeUpdate'].includes(actionType)"
|
||||
:schema="DictTypeSchemas.allSchemas.formSchema"
|
||||
:rules="DictTypeSchemas.dictTypeRules"
|
||||
ref="typeFormRef"
|
||||
>
|
||||
<template #type>
|
||||
<template v-if="actionType == 'typeUpdate'">
|
||||
<el-tag>{{ dictTypeValue }}</el-tag>
|
||||
</template>
|
||||
<template v-else><el-input v-model="dictTypeValue" /> </template>
|
||||
</template>
|
||||
</Form>
|
||||
<Form
|
||||
v-if="['dataCreate', 'dataUpdate'].includes(actionType)"
|
||||
:schema="DictDataSchemas.allSchemas.formSchema"
|
||||
:rules="DictDataSchemas.dictDataRules"
|
||||
ref="dataFormRef"
|
||||
/>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<XButton
|
||||
v-if="['typeCreate', 'typeUpdate'].includes(actionType)"
|
||||
type="primary"
|
||||
:title="t('action.save')"
|
||||
:loading="actionLoading"
|
||||
@click="submitTypeForm"
|
||||
/>
|
||||
<XButton
|
||||
v-if="['dataCreate', 'dataUpdate'].includes(actionType)"
|
||||
type="primary"
|
||||
:title="t('action.save')"
|
||||
:loading="actionLoading"
|
||||
@click="submitDataForm"
|
||||
/>
|
||||
<XButton :title="t('dialog.close')" @click="dialogVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" name="Dict">
|
||||
import { VxeTableEvents } from 'vxe-table'
|
||||
import type { FormExpose } from '@/components/Form'
|
||||
import * as DictTypeSchemas from './dict.type'
|
||||
import * as DictDataSchemas from './dict.data'
|
||||
import * as DictTypeApi from '@/api/system/dict/dict.type'
|
||||
import * as DictDataApi from '@/api/system/dict/dict.data'
|
||||
import { DictDataVO, DictTypeVO } from '@/api/system/dict/types'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const [registerType, { reload: typeGetList, deleteData: typeDeleteData }] = useXTable({
|
||||
allSchemas: DictTypeSchemas.allSchemas,
|
||||
getListApi: DictTypeApi.getDictTypePageApi,
|
||||
deleteApi: DictTypeApi.deleteDictTypeApi
|
||||
})
|
||||
|
||||
const queryParams = reactive({
|
||||
dictType: null
|
||||
})
|
||||
const [registerData, { reload: dataGetList, deleteData: dataDeleteData }] = useXTable({
|
||||
allSchemas: DictDataSchemas.allSchemas,
|
||||
params: queryParams,
|
||||
getListApi: DictDataApi.getDictDataPageApi,
|
||||
deleteApi: DictDataApi.deleteDictDataApi
|
||||
})
|
||||
// ========== 字典分类列表相关 ==========
|
||||
const dictTypeValue = ref('')
|
||||
// 字典分类修改操作
|
||||
const handleTypeCreate = () => {
|
||||
dictTypeValue.value = ''
|
||||
setDialogTile('typeCreate')
|
||||
}
|
||||
const handleTypeUpdate = async (rowId: number) => {
|
||||
setDialogTile('typeUpdate')
|
||||
// 设置数据
|
||||
const res = await DictTypeApi.getDictTypeApi(rowId)
|
||||
dictTypeValue.value = res.type
|
||||
unref(typeFormRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 字典数据修改操作
|
||||
const handleDataCreate = () => {
|
||||
setDialogTile('dataCreate')
|
||||
}
|
||||
const handleDataUpdate = async (rowId: number) => {
|
||||
setDialogTile('dataUpdate')
|
||||
// 设置数据
|
||||
const res = await DictDataApi.getDictDataApi(rowId)
|
||||
unref(dataFormRef)?.setValues(res)
|
||||
}
|
||||
// 字典分类点击行事件
|
||||
const parentType = ref('')
|
||||
const tableTypeSelect = ref(false)
|
||||
const cellClickEvent: VxeTableEvents.CellClick = async ({ row }) => {
|
||||
tableTypeSelect.value = true
|
||||
queryParams.dictType = row['type']
|
||||
await dataGetList()
|
||||
parentType.value = row['type']
|
||||
}
|
||||
// 弹出框
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const actionLoading = ref(false) // 遮罩层
|
||||
const typeFormRef = ref<FormExpose>() // 分类表单 Ref
|
||||
const dataFormRef = ref<FormExpose>() // 数据表单 Ref
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitTypeForm = async () => {
|
||||
const elForm = unref(typeFormRef)?.getElFormRef()
|
||||
if (!elForm) return
|
||||
elForm.validate(async (valid) => {
|
||||
if (valid && dictTypeValue.value != '') {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(typeFormRef)?.formModel as DictTypeVO
|
||||
if (actionType.value === 'typeCreate') {
|
||||
data.type = dictTypeValue.value
|
||||
await DictTypeApi.createDictTypeApi(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else if (actionType.value === 'typeUpdate') {
|
||||
await DictTypeApi.updateDictTypeApi(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
typeGetList()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
const submitDataForm = async () => {
|
||||
const elForm = unref(dataFormRef)?.getElFormRef()
|
||||
if (!elForm) return
|
||||
elForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(dataFormRef)?.formModel as DictDataVO
|
||||
if (actionType.value === 'dataCreate') {
|
||||
data.dictType = parentType.value
|
||||
await DictDataApi.createDictDataApi(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else if (actionType.value === 'dataUpdate') {
|
||||
await DictDataApi.updateDictDataApi(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
dataGetList()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
54
src/views/system/errorCode/errorCode.data.ts
Normal file
54
src/views/system/errorCode/errorCode.data.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
applicationName: [required],
|
||||
code: [required],
|
||||
message: [required]
|
||||
})
|
||||
|
||||
// 新增 + 修改
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: 'seq',
|
||||
primaryTitle: '编号',
|
||||
action: true,
|
||||
columns: [
|
||||
{
|
||||
title: '错误码类型',
|
||||
field: 'type',
|
||||
dictType: DICT_TYPE.SYSTEM_ERROR_CODE_TYPE,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '应用名',
|
||||
field: 'applicationName',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '错误码编码',
|
||||
field: 'code',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '错误码错误提示',
|
||||
field: 'message',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
isForm: false,
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
145
src/views/system/errorCode/index.vue
Normal file
145
src/views/system/errorCode/index.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<!-- 操作:新增 -->
|
||||
<template #toolbar_buttons>
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:zoom-in"
|
||||
:title="t('action.add')"
|
||||
v-hasPermi="['system:error-code:create']"
|
||||
@click="handleCreate()"
|
||||
/>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:修改 -->
|
||||
<XTextButton
|
||||
preIcon="ep:edit"
|
||||
:title="t('action.edit')"
|
||||
v-hasPermi="['system:error-code:update']"
|
||||
@click="handleUpdate(row.id)"
|
||||
/>
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton
|
||||
preIcon="ep:view"
|
||||
:title="t('action.detail')"
|
||||
v-hasPermi="['system:error-code:query']"
|
||||
@click="handleDetail(row.id)"
|
||||
/>
|
||||
<!-- 操作:删除 -->
|
||||
<XTextButton
|
||||
preIcon="ep:delete"
|
||||
:title="t('action.del')"
|
||||
v-hasPermi="['system:error-code:delete']"
|
||||
@click="deleteData(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
<!-- 弹窗 -->
|
||||
<XModal id="errorCodeModel" v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
/>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailData"
|
||||
/>
|
||||
<template #footer>
|
||||
<!-- 按钮:保存 -->
|
||||
<XButton
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:title="t('action.save')"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm()"
|
||||
/>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="ErrorCode">
|
||||
import type { FormExpose } from '@/components/Form'
|
||||
// 业务相关的 import
|
||||
import { rules, allSchemas } from './errorCode.data'
|
||||
import * as ErrorCodeApi from '@/api/system/errorCode'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
// 列表相关的变量
|
||||
const [registerTable, { reload, deleteData }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: ErrorCodeApi.getErrorCodePageApi,
|
||||
deleteApi: ErrorCodeApi.deleteErrorCodeApi
|
||||
})
|
||||
// 弹窗相关的变量
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const actionLoading = ref(false) // 按钮 Loading
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const detailData = ref() // 详情 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (rowId: number) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await ErrorCodeApi.getErrorCodeApi(rowId)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (rowId: number) => {
|
||||
setDialogTile('detail')
|
||||
// 设置数据
|
||||
const res = await ErrorCodeApi.getErrorCodeApi(rowId)
|
||||
detailData.value = res
|
||||
}
|
||||
|
||||
// 提交新增/修改的表单
|
||||
const submitForm = async () => {
|
||||
const elForm = unref(formRef)?.getElFormRef()
|
||||
if (!elForm) return
|
||||
elForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as ErrorCodeApi.ErrorCodeVO
|
||||
if (actionType.value === 'create') {
|
||||
await ErrorCodeApi.createErrorCodeApi(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ErrorCodeApi.updateErrorCodeApi(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
// 刷新列表
|
||||
await reload()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
53
src/views/system/loginlog/index.vue
Normal file
53
src/views/system/loginlog/index.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<!-- 操作:导出 -->
|
||||
<template #toolbar_buttons>
|
||||
<XButton
|
||||
type="warning"
|
||||
preIcon="ep:download"
|
||||
:title="t('action.export')"
|
||||
@click="exportList('登录列表.xls')"
|
||||
/>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton preIcon="ep:view" :title="t('action.detail')" @click="handleDetail(row)" />
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
<!-- 弹窗 -->
|
||||
<XModal id="postModel" v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 表单:详情 -->
|
||||
<Descriptions :schema="allSchemas.detailSchema" :data="detailData" />
|
||||
<template #footer>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :title="t('dialog.close')" @click="dialogVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="Loginlog">
|
||||
// 业务相关的 import
|
||||
import { allSchemas } from './loginLog.data'
|
||||
import { getLoginLogPageApi, exportLoginLogApi, LoginLogVO } from '@/api/system/loginLog'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
// 列表相关的变量
|
||||
const [registerTable, { exportList }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: getLoginLogPageApi,
|
||||
exportListApi: exportLoginLogApi
|
||||
})
|
||||
|
||||
// 详情操作
|
||||
const detailData = ref() // 详情 Ref
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref(t('action.detail')) // 弹出层标题
|
||||
// 详情
|
||||
const handleDetail = async (row: LoginLogVO) => {
|
||||
// 设置数据
|
||||
detailData.value = row
|
||||
dialogVisible.value = true
|
||||
}
|
||||
</script>
|
||||
53
src/views/system/loginlog/loginLog.data.ts
Normal file
53
src/views/system/loginlog/loginLog.data.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: 'seq',
|
||||
primaryTitle: '日志编号',
|
||||
action: true,
|
||||
actionWidth: '100px',
|
||||
columns: [
|
||||
{
|
||||
title: '日志类型',
|
||||
field: 'logType',
|
||||
dictType: DICT_TYPE.SYSTEM_LOGIN_TYPE,
|
||||
dictClass: 'number'
|
||||
},
|
||||
{
|
||||
title: '用户名称',
|
||||
field: 'username',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '登录地址',
|
||||
field: 'userIp',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '浏览器',
|
||||
field: 'userAgent'
|
||||
},
|
||||
{
|
||||
title: '登陆结果',
|
||||
field: 'result',
|
||||
dictType: DICT_TYPE.SYSTEM_LOGIN_RESULT,
|
||||
dictClass: 'number'
|
||||
},
|
||||
{
|
||||
title: '登录日期',
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
table: {
|
||||
width: 150
|
||||
},
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
65
src/views/system/mail/account/account.data.ts
Normal file
65
src/views/system/mail/account/account.data.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
mail: [required],
|
||||
username: [required],
|
||||
password: [required],
|
||||
host: [required],
|
||||
port: [required],
|
||||
sslEnable: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id', // 默认的主键 ID
|
||||
primaryTitle: '编号',
|
||||
primaryType: 'id',
|
||||
action: true,
|
||||
actionWidth: '200', // 3 个按钮默认 200,如有删减对应增减即可
|
||||
columns: [
|
||||
{
|
||||
title: '邮箱',
|
||||
field: 'mail',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
field: 'username',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '密码',
|
||||
field: 'password',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: 'SMTP 服务器域名',
|
||||
field: 'host'
|
||||
},
|
||||
{
|
||||
title: 'SMTP 服务器端口',
|
||||
field: 'port',
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 465
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '是否开启 SSL',
|
||||
field: 'sslEnable',
|
||||
dictType: DICT_TYPE.INFRA_BOOLEAN_STRING,
|
||||
dictClass: 'boolean'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
field: 'createTime',
|
||||
isForm: false,
|
||||
formatter: 'formatDate',
|
||||
table: {
|
||||
width: 180
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
151
src/views/system/mail/account/index.vue
Normal file
151
src/views/system/mail/account/index.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<template #toolbar_buttons>
|
||||
<!-- 操作:新增 -->
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:zoom-in"
|
||||
:title="t('action.add')"
|
||||
v-hasPermi="['system:mail-account:create']"
|
||||
@click="handleCreate()"
|
||||
/>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:修改 -->
|
||||
<XTextButton
|
||||
preIcon="ep:edit"
|
||||
:title="t('action.edit')"
|
||||
v-hasPermi="['system:mail-account:update']"
|
||||
@click="handleUpdate(row.id)"
|
||||
/>
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton
|
||||
preIcon="ep:view"
|
||||
:title="t('action.detail')"
|
||||
v-hasPermi="['system:mail-account:query']"
|
||||
@click="handleDetail(row.id)"
|
||||
/>
|
||||
<!-- 操作:删除 -->
|
||||
<XTextButton
|
||||
preIcon="ep:delete"
|
||||
:title="t('action.del')"
|
||||
v-hasPermi="['system:mail-account:delete']"
|
||||
@click="deleteData(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
<!-- 弹窗 -->
|
||||
<XModal id="mailAccountModel" :loading="modelLoading" v-model="modelVisible" :title="modelTitle">
|
||||
<!-- 表单:添加/修改 -->
|
||||
<Form
|
||||
ref="formRef"
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
/>
|
||||
<!-- 表单:详情 -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailData"
|
||||
/>
|
||||
<template #footer>
|
||||
<!-- 按钮:保存 -->
|
||||
<XButton
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:title="t('action.save')"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm()"
|
||||
/>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="MailAccount">
|
||||
import { FormExpose } from '@/components/Form'
|
||||
// 业务相关的 import
|
||||
import { rules, allSchemas } from './account.data'
|
||||
import * as MailAccountApi from '@/api/system/mail/account'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
// 列表相关的变量
|
||||
const [registerTable, { reload, deleteData }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: MailAccountApi.getMailAccountPageApi,
|
||||
deleteApi: MailAccountApi.deleteMailAccountApi
|
||||
})
|
||||
|
||||
// 弹窗相关的变量
|
||||
const modelVisible = ref(false) // 是否显示弹出层
|
||||
const modelTitle = ref('edit') // 弹出层标题
|
||||
const modelLoading = ref(false) // 弹出层loading
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const actionLoading = ref(false) // 按钮 Loading
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const detailData = ref() // 详情 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
modelLoading.value = true
|
||||
modelTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
modelVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
modelLoading.value = false
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (rowId: number) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await MailAccountApi.getMailAccountApi(rowId)
|
||||
unref(formRef)?.setValues(res)
|
||||
modelLoading.value = false
|
||||
}
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (rowId: number) => {
|
||||
setDialogTile('detail')
|
||||
const res = await MailAccountApi.getMailAccountApi(rowId)
|
||||
detailData.value = res
|
||||
modelLoading.value = false
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
const elForm = unref(formRef)?.getElFormRef()
|
||||
if (!elForm) return
|
||||
elForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as MailAccountApi.MailAccountVO
|
||||
if (actionType.value === 'create') {
|
||||
await MailAccountApi.createMailAccountApi(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await MailAccountApi.updateMailAccountApi(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
modelVisible.value = false
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
// 刷新列表
|
||||
await reload()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
97
src/views/system/mail/log/index.vue
Normal file
97
src/views/system/mail/log/index.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<template #accountId_search>
|
||||
<el-select v-model="queryParams.accountId">
|
||||
<el-option :key="undefined" label="全部" :value="undefined" />
|
||||
<el-option
|
||||
v-for="item in accountOptions"
|
||||
:key="item.id"
|
||||
:label="item.mail"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
<template #toMail_default="{ row }">
|
||||
<div>{{ row.toMail }}</div>
|
||||
<div v-if="row.userType && row.userId">
|
||||
<DictTag :type="DICT_TYPE.USER_TYPE" :value="row.userType" />{{ '(' + row.userId + ')' }}
|
||||
</div>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton
|
||||
preIcon="ep:view"
|
||||
:title="t('action.detail')"
|
||||
v-hasPermi="['system:mail-log:query']"
|
||||
@click="handleDetail(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
<!-- 弹窗 -->
|
||||
<XModal id="mailLogModel" :loading="modelLoading" v-model="modelVisible" :title="modelTitle">
|
||||
<!-- 表单:详情 -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailData"
|
||||
/>
|
||||
<template #footer>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="MailLog">
|
||||
// 业务相关的 import
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { allSchemas } from './log.data'
|
||||
import * as MailLogApi from '@/api/system/mail/log'
|
||||
import * as MailAccountApi from '@/api/system/mail/account'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 列表相关的变量
|
||||
const queryParams = reactive({
|
||||
accountId: null
|
||||
})
|
||||
const [registerTable] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
params: queryParams,
|
||||
getListApi: MailLogApi.getMailLogPageApi
|
||||
})
|
||||
const accountOptions = ref<any[]>([]) // 账号下拉选项
|
||||
|
||||
// 弹窗相关的变量
|
||||
const modelVisible = ref(false) // 是否显示弹出层
|
||||
const modelTitle = ref('edit') // 弹出层标题
|
||||
const modelLoading = ref(false) // 弹出层loading
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const actionLoading = ref(false) // 按钮 Loading
|
||||
const detailData = ref() // 详情 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
modelLoading.value = true
|
||||
modelTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
modelVisible.value = true
|
||||
}
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (rowId: number) => {
|
||||
setDialogTile('detail')
|
||||
const res = await MailLogApi.getMailLogApi(rowId)
|
||||
detailData.value = res
|
||||
modelLoading.value = false
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
onMounted(() => {
|
||||
MailAccountApi.getSimpleMailAccounts().then((data) => {
|
||||
accountOptions.value = data
|
||||
})
|
||||
})
|
||||
</script>
|
||||
121
src/views/system/mail/log/log.data.ts
Normal file
121
src/views/system/mail/log/log.data.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryTitle: '编号',
|
||||
primaryType: 'id',
|
||||
action: true,
|
||||
actionWidth: '70',
|
||||
columns: [
|
||||
{
|
||||
title: '发送时间',
|
||||
field: 'sendTime',
|
||||
table: {
|
||||
width: 180
|
||||
},
|
||||
formatter: 'formatDate',
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '接收邮箱',
|
||||
field: 'toMail',
|
||||
isSearch: true,
|
||||
table: {
|
||||
width: 180,
|
||||
slots: {
|
||||
default: 'toMail_default'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '用户编号',
|
||||
field: 'userId',
|
||||
isSearch: true,
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: '用户类型',
|
||||
field: 'userType',
|
||||
dictType: DICT_TYPE.USER_TYPE,
|
||||
dictClass: 'number',
|
||||
isSearch: true,
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: '邮件标题',
|
||||
field: 'templateTitle'
|
||||
},
|
||||
{
|
||||
title: '邮件内容',
|
||||
field: 'templateContent',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: '邮箱参数',
|
||||
field: 'templateParams',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: '发送状态',
|
||||
field: 'sendStatus',
|
||||
dictType: DICT_TYPE.SYSTEM_MAIL_SEND_STATUS,
|
||||
dictClass: 'string',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '邮箱账号',
|
||||
field: 'accountId',
|
||||
isSearch: true,
|
||||
isTable: false,
|
||||
search: {
|
||||
slots: {
|
||||
default: 'accountId_search'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '发送邮箱地址',
|
||||
field: 'fromMail',
|
||||
table: {
|
||||
title: '邮箱账号'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '模板编号',
|
||||
field: 'templateId',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '模板编码',
|
||||
field: 'templateCode',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: '模版发送人名称',
|
||||
field: 'templateNickname',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: '发送返回的消息编号',
|
||||
field: 'sendMessageId',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: '发送异常',
|
||||
field: 'sendException',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
field: 'createTime',
|
||||
isTable: false
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
273
src/views/system/mail/template/index.vue
Normal file
273
src/views/system/mail/template/index.vue
Normal file
@@ -0,0 +1,273 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<template #accountId_search>
|
||||
<el-select v-model="queryParams.accountId">
|
||||
<el-option :key="undefined" label="全部" :value="undefined" />
|
||||
<el-option
|
||||
v-for="item in accountOptions"
|
||||
:key="item.id"
|
||||
:label="item.mail"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
<template #toolbar_buttons>
|
||||
<!-- 操作:新增 -->
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:zoom-in"
|
||||
:title="t('action.add')"
|
||||
v-hasPermi="['system:mail-template:create']"
|
||||
@click="handleCreate()"
|
||||
/>
|
||||
</template>
|
||||
<template #accountId_default="{ row }">
|
||||
<span>{{ accountOptions.find((account) => account.id === row.accountId)?.mail }}</span>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:测试短信 -->
|
||||
<XTextButton
|
||||
preIcon="ep:cpu"
|
||||
:title="t('action.test')"
|
||||
v-hasPermi="['system:mail-template:send-mail']"
|
||||
@click="handleSendMail(row)"
|
||||
/>
|
||||
<!-- 操作:修改 -->
|
||||
<XTextButton
|
||||
preIcon="ep:edit"
|
||||
:title="t('action.edit')"
|
||||
v-hasPermi="['system:mail-template:update']"
|
||||
@click="handleUpdate(row.id)"
|
||||
/>
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton
|
||||
preIcon="ep:view"
|
||||
:title="t('action.detail')"
|
||||
v-hasPermi="['system:mail-template:query']"
|
||||
@click="handleDetail(row.id)"
|
||||
/>
|
||||
<!-- 操作:删除 -->
|
||||
<XTextButton
|
||||
preIcon="ep:delete"
|
||||
:title="t('action.del')"
|
||||
v-hasPermi="['system:mail-template:delete']"
|
||||
@click="deleteData(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 添加/修改/详情的弹窗 -->
|
||||
<XModal id="mailTemplateModel" :loading="modelLoading" v-model="modelVisible" :title="modelTitle">
|
||||
<!-- 表单:添加/修改 -->
|
||||
<Form
|
||||
ref="formRef"
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
>
|
||||
<template #accountId="form">
|
||||
<el-select v-model="form.accountId">
|
||||
<el-option
|
||||
v-for="item in accountOptions"
|
||||
:key="item.id"
|
||||
:label="item.mail"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</Form>
|
||||
<!-- 表单:详情 -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailData"
|
||||
/>
|
||||
<template #footer>
|
||||
<!-- 按钮:保存 -->
|
||||
<XButton
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:title="t('action.save')"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm()"
|
||||
/>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
|
||||
<!-- 测试邮件的弹窗 -->
|
||||
<XModal id="sendTest" v-model="sendVisible" title="测试">
|
||||
<el-form :model="sendForm" :rules="sendRules" label-width="200px" label-position="top">
|
||||
<el-form-item label="模板内容" prop="content">
|
||||
<Editor :model-value="sendForm.content" readonly height="150px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="收件邮箱" prop="mail">
|
||||
<el-input v-model="sendForm.mail" placeholder="请输入收件邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-for="param in sendForm.params"
|
||||
:key="param"
|
||||
:label="'参数 {' + param + '}'"
|
||||
:prop="'templateParams.' + param"
|
||||
>
|
||||
<el-input
|
||||
v-model="sendForm.templateParams[param]"
|
||||
:placeholder="'请输入 ' + param + ' 参数'"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<XButton
|
||||
type="primary"
|
||||
:title="t('action.test')"
|
||||
:loading="actionLoading"
|
||||
@click="sendTest()"
|
||||
/>
|
||||
<XButton :title="t('dialog.close')" @click="sendVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="MailTemplate">
|
||||
import { FormExpose } from '@/components/Form'
|
||||
// 业务相关的 import
|
||||
import { rules, allSchemas } from './template.data'
|
||||
import * as MailTemplateApi from '@/api/system/mail/template'
|
||||
import * as MailAccountApi from '@/api/system/mail/account'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
// 列表相关的变量
|
||||
const queryParams = reactive({
|
||||
accountId: null
|
||||
})
|
||||
const [registerTable, { reload, deleteData }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
params: queryParams,
|
||||
getListApi: MailTemplateApi.getMailTemplatePageApi,
|
||||
deleteApi: MailTemplateApi.deleteMailTemplateApi
|
||||
})
|
||||
const accountOptions = ref<any[]>([]) // 账号下拉选项
|
||||
|
||||
// 弹窗相关的变量
|
||||
const modelVisible = ref(false) // 是否显示弹出层
|
||||
const modelTitle = ref('edit') // 弹出层标题
|
||||
const modelLoading = ref(false) // 弹出层loading
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const actionLoading = ref(false) // 按钮 Loading
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const detailData = ref() // 详情 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
modelLoading.value = true
|
||||
modelTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
modelVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
modelLoading.value = false
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (rowId: number) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await MailTemplateApi.getMailTemplateApi(rowId)
|
||||
unref(formRef)?.setValues(res)
|
||||
modelLoading.value = false
|
||||
}
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (rowId: number) => {
|
||||
setDialogTile('detail')
|
||||
const res = await MailTemplateApi.getMailTemplateApi(rowId)
|
||||
detailData.value = res
|
||||
modelLoading.value = false
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
const elForm = unref(formRef)?.getElFormRef()
|
||||
if (!elForm) return
|
||||
elForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as MailTemplateApi.MailTemplateVO
|
||||
if (actionType.value === 'create') {
|
||||
await MailTemplateApi.createMailTemplateApi(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await MailTemplateApi.updateMailTemplateApi(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
modelVisible.value = false
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
// 刷新列表
|
||||
await reload()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ========== 测试相关 ==========
|
||||
const sendForm = ref({
|
||||
content: '',
|
||||
params: {},
|
||||
mail: '',
|
||||
templateCode: '',
|
||||
templateParams: {}
|
||||
})
|
||||
const sendRules = ref({
|
||||
mail: [{ required: true, message: '邮箱不能为空', trigger: 'blur' }],
|
||||
templateCode: [{ required: true, message: '模版编号不能为空', trigger: 'blur' }],
|
||||
templateParams: {}
|
||||
})
|
||||
const sendVisible = ref(false)
|
||||
|
||||
const handleSendMail = (row: any) => {
|
||||
sendForm.value.content = row.content
|
||||
sendForm.value.params = row.params
|
||||
sendForm.value.templateCode = row.code
|
||||
sendForm.value.templateParams = row.params.reduce(function (obj, item) {
|
||||
obj[item] = undefined
|
||||
return obj
|
||||
}, {})
|
||||
sendRules.value.templateParams = row.params.reduce(function (obj, item) {
|
||||
obj[item] = { required: true, message: '参数 ' + item + ' 不能为空', trigger: 'change' }
|
||||
return obj
|
||||
}, {})
|
||||
sendVisible.value = true
|
||||
}
|
||||
|
||||
const sendTest = async () => {
|
||||
const data: MailTemplateApi.MailSendReqVO = {
|
||||
mail: sendForm.value.mail,
|
||||
templateCode: sendForm.value.templateCode,
|
||||
templateParams: sendForm.value.templateParams as unknown as Map<string, Object>
|
||||
}
|
||||
const res = await MailTemplateApi.sendMailApi(data)
|
||||
if (res) {
|
||||
message.success('提交发送成功!发送结果,见发送日志编号:' + res)
|
||||
}
|
||||
sendVisible.value = false
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
onMounted(() => {
|
||||
MailAccountApi.getSimpleMailAccounts().then((data) => {
|
||||
accountOptions.value = data
|
||||
})
|
||||
})
|
||||
</script>
|
||||
98
src/views/system/mail/template/template.data.ts
Normal file
98
src/views/system/mail/template/template.data.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
code: [required],
|
||||
accountId: [required],
|
||||
title: [required],
|
||||
content: [required],
|
||||
params: [required],
|
||||
status: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id', // 默认的主键ID
|
||||
primaryTitle: '编号', // 默认显示的值
|
||||
primaryType: null,
|
||||
action: true,
|
||||
actionWidth: '260',
|
||||
columns: [
|
||||
{
|
||||
title: '模板编码',
|
||||
field: 'code',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '模板名称',
|
||||
field: 'name',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '模板标题',
|
||||
field: 'title'
|
||||
},
|
||||
{
|
||||
title: '模板内容',
|
||||
field: 'content',
|
||||
form: {
|
||||
component: 'Editor',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
componentProps: {
|
||||
valueHtml: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '邮箱账号',
|
||||
field: 'accountId',
|
||||
isSearch: true,
|
||||
table: {
|
||||
width: 200,
|
||||
slots: {
|
||||
default: 'accountId_default'
|
||||
}
|
||||
},
|
||||
search: {
|
||||
slots: {
|
||||
default: 'accountId_search'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '发送人名称',
|
||||
field: 'nickname'
|
||||
},
|
||||
{
|
||||
title: '开启状态',
|
||||
field: 'status',
|
||||
isSearch: true,
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number'
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
field: 'remark',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
field: 'createTime',
|
||||
isForm: false,
|
||||
formatter: 'formatDate',
|
||||
table: {
|
||||
width: 180
|
||||
},
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
351
src/views/system/menu/index.vue
Normal file
351
src/views/system/menu/index.vue
Normal file
@@ -0,0 +1,351 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable ref="xGrid" @register="registerTable" show-overflow>
|
||||
<template #toolbar_buttons>
|
||||
<!-- 操作:新增 -->
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:zoom-in"
|
||||
:title="t('action.add')"
|
||||
v-hasPermi="['system:menu:create']"
|
||||
@click="handleCreate()"
|
||||
/>
|
||||
<XButton title="展开所有" @click="xGrid?.Ref.setAllTreeExpand(true)" />
|
||||
<XButton title="关闭所有" @click="xGrid?.Ref.clearTreeExpand()" />
|
||||
</template>
|
||||
<template #name_default="{ row }">
|
||||
<Icon :icon="row.icon" />
|
||||
<span class="ml-3">{{ row.name }}</span>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:修改 -->
|
||||
<XTextButton
|
||||
preIcon="ep:edit"
|
||||
:title="t('action.edit')"
|
||||
v-hasPermi="['system:menu:update']"
|
||||
@click="handleUpdate(row.id)"
|
||||
/>
|
||||
<!-- 操作:删除 -->
|
||||
<XTextButton
|
||||
preIcon="ep:delete"
|
||||
:title="t('action.del')"
|
||||
v-hasPermi="['system:menu:delete']"
|
||||
@click="deleteData(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
<!-- 添加或修改菜单对话框 -->
|
||||
<XModal id="menuModel" v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="menuForm"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
label-position="right"
|
||||
>
|
||||
<el-form-item label="上级菜单">
|
||||
<el-tree-select
|
||||
node-key="id"
|
||||
v-model="menuForm.parentId"
|
||||
:props="defaultProps"
|
||||
:data="menuOptions"
|
||||
:default-expanded-keys="[0]"
|
||||
check-strictly
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-col :span="16">
|
||||
<el-form-item label="菜单名称" prop="name">
|
||||
<el-input v-model="menuForm.name" placeholder="请输入菜单名称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-form-item label="菜单类型" prop="type">
|
||||
<el-radio-group v-model="menuForm.type">
|
||||
<el-radio-button
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_MENU_TYPE)"
|
||||
:key="dict.label"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<template v-if="menuForm.type !== 3">
|
||||
<el-form-item label="菜单图标">
|
||||
<IconSelect v-model="menuForm.icon" clearable />
|
||||
</el-form-item>
|
||||
<el-col :span="16">
|
||||
<el-form-item label="路由地址" prop="path">
|
||||
<template #label>
|
||||
<Tooltip
|
||||
titel="路由地址"
|
||||
message="访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头"
|
||||
/>
|
||||
</template>
|
||||
<el-input v-model="menuForm.path" placeholder="请输入路由地址" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
<template v-if="menuForm.type === 2">
|
||||
<el-col :span="16">
|
||||
<el-form-item label="组件地址" prop="component">
|
||||
<el-input
|
||||
v-model="menuForm.component"
|
||||
placeholder="例如说:system/user/index"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<el-form-item label="组件名字" prop="componentName">
|
||||
<el-input v-model="menuForm.componentName" placeholder="例如说:SystemUser" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
<template v-if="menuForm.type !== 1">
|
||||
<el-col :span="16">
|
||||
<el-form-item label="权限标识" prop="permission">
|
||||
<template #label>
|
||||
<Tooltip
|
||||
titel="权限标识"
|
||||
message="Controller 方法上的权限字符,如:@PreAuthorize(`@ss.hasPermission('system:user:list')`)"
|
||||
/>
|
||||
</template>
|
||||
<el-input v-model="menuForm.permission" placeholder="请输入权限标识" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
<el-col :span="16">
|
||||
<el-form-item label="显示排序" prop="sort">
|
||||
<el-input-number v-model="menuForm.sort" controls-position="right" :min="0" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<el-form-item label="菜单状态" prop="status">
|
||||
<el-radio-group v-model="menuForm.status">
|
||||
<el-radio
|
||||
border
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.label"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<template v-if="menuForm.type !== 3">
|
||||
<el-col :span="16">
|
||||
<el-form-item label="显示状态" prop="visible">
|
||||
<template #label>
|
||||
<Tooltip
|
||||
titel="显示状态"
|
||||
message="选择隐藏时,路由将不会出现在侧边栏,但仍然可以访问"
|
||||
/>
|
||||
</template>
|
||||
<el-radio-group v-model="menuForm.visible">
|
||||
<el-radio border key="true" :label="true">显示</el-radio>
|
||||
<el-radio border key="false" :label="false">隐藏</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
<template v-if="menuForm.type !== 3">
|
||||
<el-col :span="16">
|
||||
<el-form-item label="总是显示" prop="alwaysShow">
|
||||
<template #label>
|
||||
<Tooltip
|
||||
titel="总是显示"
|
||||
message="选择不是时,当该菜单只有一个子菜单时,不展示自己,直接展示子菜单"
|
||||
/>
|
||||
</template>
|
||||
<el-radio-group v-model="menuForm.alwaysShow">
|
||||
<el-radio border key="true" :label="true">总是</el-radio>
|
||||
<el-radio border key="false" :label="false">不是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
<template v-if="menuForm.type === 2">
|
||||
<el-col :span="16">
|
||||
<el-form-item label="缓存状态" prop="keepAlive">
|
||||
<template #label>
|
||||
<Tooltip
|
||||
titel="缓存状态"
|
||||
message="选择缓存时,则会被 `keep-alive` 缓存,必须填写「组件名称」字段"
|
||||
/>
|
||||
</template>
|
||||
<el-radio-group v-model="menuForm.keepAlive">
|
||||
<el-radio border key="true" :label="true">缓存</el-radio>
|
||||
<el-radio border key="false" :label="false">不缓存</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<!-- 按钮:保存 -->
|
||||
<XButton
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm()"
|
||||
:title="t('action.save')"
|
||||
/>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="actionLoading" @click="dialogVisible = false" :title="t('dialog.close')" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="Menu">
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
import { FormInstance } from 'element-plus'
|
||||
// 业务相关的 import
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants'
|
||||
import { handleTree, defaultProps } from '@/utils/tree'
|
||||
import * as MenuApi from '@/api/system/menu'
|
||||
import { allSchemas, rules } from './menu.data'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { wsCache } = useCache()
|
||||
|
||||
const xGrid = ref<any>(null)
|
||||
|
||||
// 列表相关的变量
|
||||
const treeConfig = {
|
||||
transform: true,
|
||||
rowField: 'id',
|
||||
parentField: 'parentId',
|
||||
expandAll: false
|
||||
}
|
||||
const [registerTable, { reload, deleteData }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
treeConfig: treeConfig,
|
||||
getListApi: MenuApi.getMenuListApi,
|
||||
deleteApi: MenuApi.deleteMenuApi
|
||||
})
|
||||
// 弹窗相关的变量
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const actionLoading = ref(false) // 遮罩层
|
||||
// 新增和修改的表单值
|
||||
const formRef = ref<FormInstance>()
|
||||
const menuForm = ref<MenuApi.MenuVO>({
|
||||
id: 0,
|
||||
name: '',
|
||||
permission: '',
|
||||
type: SystemMenuTypeEnum.DIR,
|
||||
sort: 1,
|
||||
parentId: 0,
|
||||
path: '',
|
||||
icon: '',
|
||||
component: '',
|
||||
componentName: '',
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
visible: true,
|
||||
keepAlive: true,
|
||||
alwaysShow: true,
|
||||
createTime: new Date()
|
||||
})
|
||||
|
||||
// ========== 下拉框[上级菜单] ==========
|
||||
const menuOptions = ref<any[]>([]) // 树形结构
|
||||
// 获取下拉框[上级菜单]的数据
|
||||
const getTree = async () => {
|
||||
menuOptions.value = []
|
||||
const res = await MenuApi.listSimpleMenusApi()
|
||||
let menu: Tree = { id: 0, name: '主类目', children: [] }
|
||||
menu.children = handleTree(res)
|
||||
menuOptions.value.push(menu)
|
||||
}
|
||||
|
||||
// ========== 新增/修改 ==========
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = async (type: string) => {
|
||||
await getTree()
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
// 重置表单
|
||||
formRef.value?.resetFields()
|
||||
menuForm.value = {
|
||||
id: 0,
|
||||
name: '',
|
||||
permission: '',
|
||||
type: SystemMenuTypeEnum.DIR,
|
||||
sort: 1,
|
||||
parentId: 0,
|
||||
path: '',
|
||||
icon: '',
|
||||
component: '',
|
||||
componentName: '',
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
visible: true,
|
||||
keepAlive: true,
|
||||
alwaysShow: true,
|
||||
createTime: new Date()
|
||||
}
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (rowId: number) => {
|
||||
await setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await MenuApi.getMenuApi(rowId)
|
||||
menuForm.value = res
|
||||
// TODO 芋艿:这块要优化下,部分字段未重置,无法修改
|
||||
menuForm.value.componentName = res.componentName || ''
|
||||
menuForm.value.alwaysShow = res.alwaysShow !== undefined ? res.alwaysShow : true
|
||||
}
|
||||
|
||||
// 提交新增/修改的表单
|
||||
const submitForm = async () => {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
if (
|
||||
menuForm.value.type === SystemMenuTypeEnum.DIR ||
|
||||
menuForm.value.type === SystemMenuTypeEnum.MENU
|
||||
) {
|
||||
if (!isExternal(menuForm.value.path)) {
|
||||
if (menuForm.value.parentId === 0 && menuForm.value.path.charAt(0) !== '/') {
|
||||
message.error('路径必须以 / 开头')
|
||||
return
|
||||
} else if (menuForm.value.parentId !== 0 && menuForm.value.path.charAt(0) === '/') {
|
||||
message.error('路径不能以 / 开头')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if (actionType.value === 'create') {
|
||||
await MenuApi.createMenuApi(menuForm.value)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await MenuApi.updateMenuApi(menuForm.value)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
} finally {
|
||||
dialogVisible.value = false
|
||||
actionLoading.value = false
|
||||
wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
|
||||
// 操作成功,重新加载列表
|
||||
await reload()
|
||||
}
|
||||
}
|
||||
|
||||
// 判断 path 是不是外部的 HTTP 等链接
|
||||
const isExternal = (path: string) => {
|
||||
return /^(https?:|mailto:|tel:)/.test(path)
|
||||
}
|
||||
</script>
|
||||
76
src/views/system/menu/menu.data.ts
Normal file
76
src/views/system/menu/menu.data.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 新增和修改的表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
sort: [required],
|
||||
path: [required],
|
||||
status: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: null,
|
||||
action: true,
|
||||
columns: [
|
||||
{
|
||||
title: '上级菜单',
|
||||
field: 'parentId',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: '菜单名称',
|
||||
field: 'name',
|
||||
isSearch: true,
|
||||
table: {
|
||||
treeNode: true,
|
||||
align: 'left',
|
||||
width: '200px',
|
||||
slots: {
|
||||
default: 'name_default'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '菜单类型',
|
||||
field: 'type',
|
||||
dictType: DICT_TYPE.SYSTEM_MENU_TYPE
|
||||
},
|
||||
{
|
||||
title: '路由地址',
|
||||
field: 'path'
|
||||
},
|
||||
{
|
||||
title: '组件路径',
|
||||
field: 'component'
|
||||
},
|
||||
{
|
||||
title: '组件名字',
|
||||
field: 'componentName'
|
||||
},
|
||||
{
|
||||
title: '权限标识',
|
||||
field: 'permission'
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
field: 'sort'
|
||||
},
|
||||
{
|
||||
title: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
isTable: false
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
148
src/views/system/notice/index.vue
Normal file
148
src/views/system/notice/index.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<!-- 操作:新增 -->
|
||||
<template #toolbar_buttons>
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:zoom-in"
|
||||
:title="t('action.add')"
|
||||
v-hasPermi="['system:notice:create']"
|
||||
@click="handleCreate()"
|
||||
/>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:修改 -->
|
||||
<XTextButton
|
||||
preIcon="ep:edit"
|
||||
:title="t('action.edit')"
|
||||
v-hasPermi="['system:notice:update']"
|
||||
@click="handleUpdate(row.id)"
|
||||
/>
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton
|
||||
preIcon="ep:view"
|
||||
:title="t('action.detail')"
|
||||
v-hasPermi="['system:notice:query']"
|
||||
@click="handleDetail(row.id)"
|
||||
/>
|
||||
<!-- 操作:删除 -->
|
||||
<XTextButton
|
||||
preIcon="ep:delete"
|
||||
:title="t('action.del')"
|
||||
v-hasPermi="['system:notice:delete']"
|
||||
@click="deleteData(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
<!-- 弹窗 -->
|
||||
<XModal id="noticeModel" v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
ref="formRef"
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
/>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailData"
|
||||
>
|
||||
<template #content="{ row }">
|
||||
<Editor :model-value="row.content" :readonly="true" />
|
||||
</template>
|
||||
</Descriptions>
|
||||
<template #footer>
|
||||
<!-- 按钮:保存 -->
|
||||
<XButton
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:title="t('action.save')"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm()"
|
||||
/>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="Notice">
|
||||
import type { FormExpose } from '@/components/Form'
|
||||
// 业务相关的 import
|
||||
import * as NoticeApi from '@/api/system/notice'
|
||||
import { rules, allSchemas } from './notice.data'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
// 列表相关的变量
|
||||
const [registerTable, { reload, deleteData }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: NoticeApi.getNoticePageApi,
|
||||
deleteApi: NoticeApi.deleteNoticeApi
|
||||
})
|
||||
// 弹窗相关的变量
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const actionLoading = ref(false) // 按钮 Loading
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const detailData = ref() // 详情 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (rowId: number) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await NoticeApi.getNoticeApi(rowId)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (rowId: number) => {
|
||||
setDialogTile('detail')
|
||||
// 设置数据
|
||||
const res = await NoticeApi.getNoticeApi(rowId)
|
||||
detailData.value = res
|
||||
}
|
||||
|
||||
// 提交新增/修改的表单
|
||||
const submitForm = async () => {
|
||||
const elForm = unref(formRef)?.getElFormRef()
|
||||
if (!elForm) return
|
||||
elForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as NoticeApi.NoticeVO
|
||||
if (actionType.value === 'create') {
|
||||
await NoticeApi.createNoticeApi(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await NoticeApi.updateNoticeApi(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
await reload()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
59
src/views/system/notice/notice.data.ts
Normal file
59
src/views/system/notice/notice.data.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
title: [required],
|
||||
type: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: 'seq',
|
||||
action: true,
|
||||
columns: [
|
||||
{
|
||||
title: '公告标题',
|
||||
field: 'title',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '公告类型',
|
||||
field: 'type',
|
||||
dictType: DICT_TYPE.SYSTEM_NOTICE_TYPE,
|
||||
dictClass: 'number'
|
||||
},
|
||||
{
|
||||
title: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '公告内容',
|
||||
field: 'content',
|
||||
table: {
|
||||
type: 'html'
|
||||
},
|
||||
form: {
|
||||
component: 'Editor',
|
||||
colProps: {
|
||||
span: 24
|
||||
},
|
||||
componentProps: {
|
||||
valueHtml: ''
|
||||
}
|
||||
},
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
isForm: false
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
66
src/views/system/notify/message/index.vue
Normal file
66
src/views/system/notify/message/index.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton
|
||||
preIcon="ep:view"
|
||||
:title="t('action.detail')"
|
||||
v-hasPermi="['system:notify-message:query']"
|
||||
@click="handleDetail(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
<!-- 弹窗 -->
|
||||
<XModal id="messageModel" :loading="modelLoading" v-model="modelVisible" :title="modelTitle">
|
||||
<!-- 表单:详情 -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailData"
|
||||
/>
|
||||
<template #footer>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="NotifyMessage">
|
||||
// 业务相关的 import
|
||||
import { allSchemas } from './message.data'
|
||||
import * as NotifyMessageApi from '@/api/system/notify/message'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 列表相关的变量
|
||||
const [registerTable] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: NotifyMessageApi.getNotifyMessagePageApi
|
||||
})
|
||||
|
||||
// 弹窗相关的变量
|
||||
const modelVisible = ref(false) // 是否显示弹出层
|
||||
const modelTitle = ref('edit') // 弹出层标题
|
||||
const modelLoading = ref(false) // 弹出层loading
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const actionLoading = ref(false) // 按钮 Loading
|
||||
const detailData = ref() // 详情 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
modelLoading.value = true
|
||||
modelTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
modelVisible.value = true
|
||||
}
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (rowId: number) => {
|
||||
setDialogTile('detail')
|
||||
const res = await NotifyMessageApi.getNotifyMessageApi(rowId)
|
||||
detailData.value = res
|
||||
modelLoading.value = false
|
||||
}
|
||||
</script>
|
||||
101
src/views/system/notify/message/message.data.ts
Normal file
101
src/views/system/notify/message/message.data.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id', // 默认的主键ID
|
||||
primaryTitle: '编号', // 默认显示的值
|
||||
primaryType: 'seq', // 默认为seq,序号模式
|
||||
action: true,
|
||||
actionWidth: '200', // 3个按钮默认200,如有删减对应增减即可
|
||||
columns: [
|
||||
{
|
||||
title: '用户编号',
|
||||
field: 'userId',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '用户类型',
|
||||
field: 'userType',
|
||||
dictType: DICT_TYPE.USER_TYPE,
|
||||
dictClass: 'string',
|
||||
isSearch: true,
|
||||
table: {
|
||||
width: 80
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '模版编号',
|
||||
field: 'templateId'
|
||||
},
|
||||
{
|
||||
title: '模板编码',
|
||||
field: 'templateCode',
|
||||
isSearch: true,
|
||||
table: {
|
||||
width: 80
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '发送人名称',
|
||||
field: 'templateNickname',
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '模版内容',
|
||||
field: 'templateContent',
|
||||
table: {
|
||||
width: 200
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '模版类型',
|
||||
field: 'templateType',
|
||||
dictType: DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE,
|
||||
dictClass: 'number',
|
||||
isSearch: true,
|
||||
table: {
|
||||
width: 80
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '模版参数',
|
||||
field: 'templateParams',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: '是否已读',
|
||||
field: 'readStatus',
|
||||
dictType: DICT_TYPE.INFRA_BOOLEAN_STRING,
|
||||
dictClass: 'boolean',
|
||||
table: {
|
||||
width: 80
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '阅读时间',
|
||||
field: 'readTime',
|
||||
formatter: 'formatDate',
|
||||
table: {
|
||||
width: 180
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
field: 'createTime',
|
||||
isForm: false,
|
||||
formatter: 'formatDate',
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
},
|
||||
table: {
|
||||
width: 180
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
58
src/views/system/notify/my/index.vue
Normal file
58
src/views/system/notify/my/index.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<template #toolbar_buttons>
|
||||
<!-- 操作:标记已读 -->
|
||||
<XButton type="primary" preIcon="ep:zoom-in" title="标记已读" @click="handleUpdateList" />
|
||||
<!-- 操作:全部已读 -->
|
||||
<XButton type="primary" preIcon="ep:zoom-in" title="全部已读" @click="handleUpdateAll" />
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:已读 -->
|
||||
<XTextButton
|
||||
preIcon="ep:view"
|
||||
title="已读"
|
||||
v-hasPermi="['system:notify-message:query']"
|
||||
v-if="!row.readStatus"
|
||||
@click="handleUpdate([row.id])"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts" name="MyNotifyMessage">
|
||||
// 业务相关的 import
|
||||
import { allSchemas } from './my.data'
|
||||
import * as NotifyMessageApi from '@/api/system/notify/message'
|
||||
|
||||
const message = useMessage() // 消息
|
||||
|
||||
// 列表相关的变量
|
||||
const [registerTable, { reload, getCheckboxRecords }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: NotifyMessageApi.getMyNotifyMessagePage
|
||||
})
|
||||
|
||||
const handleUpdateList = async () => {
|
||||
const list = getCheckboxRecords()
|
||||
if (list.length === 0) {
|
||||
return
|
||||
}
|
||||
await handleUpdate(list.map((v) => v.id))
|
||||
}
|
||||
|
||||
// 标记指定 id 已读
|
||||
const handleUpdate = async (ids) => {
|
||||
await NotifyMessageApi.updateNotifyMessageRead(ids)
|
||||
message.success('标记已读成功!')
|
||||
reload()
|
||||
}
|
||||
|
||||
// 标记全部已读
|
||||
const handleUpdateAll = async () => {
|
||||
await NotifyMessageApi.updateAllNotifyMessageRead()
|
||||
message.success('全部已读成功!')
|
||||
reload()
|
||||
}
|
||||
</script>
|
||||
58
src/views/system/notify/my/my.data.ts
Normal file
58
src/views/system/notify/my/my.data.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryTitle: ' ',
|
||||
primaryType: 'checkbox',
|
||||
action: true,
|
||||
actionWidth: '200', // 3个按钮默认200,如有删减对应增减即可
|
||||
columns: [
|
||||
{
|
||||
title: '发送人名称',
|
||||
field: 'templateNickname',
|
||||
table: {
|
||||
width: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '发送时间',
|
||||
field: 'createTime',
|
||||
isForm: false,
|
||||
formatter: 'formatDate',
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
},
|
||||
table: {
|
||||
width: 180
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
field: 'templateType',
|
||||
dictType: DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE,
|
||||
dictClass: 'number',
|
||||
table: {
|
||||
width: 80
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '内容',
|
||||
field: 'templateContent'
|
||||
},
|
||||
{
|
||||
title: '是否已读',
|
||||
field: 'readStatus',
|
||||
dictType: DICT_TYPE.INFRA_BOOLEAN_STRING,
|
||||
dictClass: 'boolean',
|
||||
table: {
|
||||
width: 80
|
||||
},
|
||||
isSearch: true
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
251
src/views/system/notify/template/index.vue
Normal file
251
src/views/system/notify/template/index.vue
Normal file
@@ -0,0 +1,251 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<template #toolbar_buttons>
|
||||
<!-- 操作:新增 -->
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:zoom-in"
|
||||
:title="t('action.add')"
|
||||
v-hasPermi="['system:notify-template:create']"
|
||||
@click="handleCreate()"
|
||||
/>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:测试站内信 -->
|
||||
<XTextButton
|
||||
preIcon="ep:cpu"
|
||||
:title="t('action.test')"
|
||||
v-hasPermi="['system:notify-template:send-notify']"
|
||||
@click="handleSendNotify(row)"
|
||||
/>
|
||||
<!-- 操作:修改 -->
|
||||
<XTextButton
|
||||
preIcon="ep:edit"
|
||||
:title="t('action.edit')"
|
||||
v-hasPermi="['system:notify-template:update']"
|
||||
@click="handleUpdate(row.id)"
|
||||
/>
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton
|
||||
preIcon="ep:view"
|
||||
:title="t('action.detail')"
|
||||
v-hasPermi="['system:notify-template:query']"
|
||||
@click="handleDetail(row.id)"
|
||||
/>
|
||||
<!-- 操作:删除 -->
|
||||
<XTextButton
|
||||
preIcon="ep:delete"
|
||||
:title="t('action.del')"
|
||||
v-hasPermi="['system:notify-template:delete']"
|
||||
@click="deleteData(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 添加/修改的弹窗 -->
|
||||
<XModal id="templateModel" :loading="modelLoading" v-model="modelVisible" :title="modelTitle">
|
||||
<!-- 表单:添加/修改 -->
|
||||
<Form
|
||||
ref="formRef"
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
/>
|
||||
<!-- 表单:详情 -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailData"
|
||||
/>
|
||||
<template #footer>
|
||||
<!-- 按钮:保存 -->
|
||||
<XButton
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:title="t('action.save')"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm()"
|
||||
/>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
|
||||
<!-- 测试站内信的弹窗 -->
|
||||
<XModal id="sendTest" v-model="sendVisible" title="测试">
|
||||
<el-form :model="sendForm" :rules="sendRules" label-width="200px" label-position="top">
|
||||
<el-form-item label="模板内容" prop="content">
|
||||
<el-input type="textarea" v-model="sendForm.content" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item label="接收人" prop="userId">
|
||||
<el-select v-model="sendForm.userId" placeholder="请选择接收人">
|
||||
<el-option
|
||||
v-for="item in userOption"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-for="param in sendForm.params"
|
||||
:key="param"
|
||||
:label="'参数 {' + param + '}'"
|
||||
:prop="'templateParams.' + param"
|
||||
>
|
||||
<el-input
|
||||
v-model="sendForm.templateParams[param]"
|
||||
:placeholder="'请输入 ' + param + ' 参数'"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<XButton
|
||||
type="primary"
|
||||
:title="t('action.test')"
|
||||
:loading="actionLoading"
|
||||
@click="sendTest()"
|
||||
/>
|
||||
<XButton :title="t('dialog.close')" @click="sendVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="NotifyTemplate">
|
||||
import { FormExpose } from '@/components/Form'
|
||||
// 业务相关的 import
|
||||
import { rules, allSchemas } from './template.data'
|
||||
import * as NotifyTemplateApi from '@/api/system/notify/template'
|
||||
import { getListSimpleUsersApi, UserVO } from '@/api/system/user'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
// 列表相关的变量
|
||||
const [registerTable, { reload, deleteData }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: NotifyTemplateApi.getNotifyTemplatePageApi,
|
||||
deleteApi: NotifyTemplateApi.deleteNotifyTemplateApi
|
||||
})
|
||||
|
||||
// 弹窗相关的变量
|
||||
const modelVisible = ref(false) // 是否显示弹出层
|
||||
const modelTitle = ref('edit') // 弹出层标题
|
||||
const modelLoading = ref(false) // 弹出层loading
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const actionLoading = ref(false) // 按钮 Loading
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const detailData = ref() // 详情 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
modelLoading.value = true
|
||||
modelTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
modelVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
modelLoading.value = false
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (rowId: number) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await NotifyTemplateApi.getNotifyTemplateApi(rowId)
|
||||
unref(formRef)?.setValues(res)
|
||||
modelLoading.value = false
|
||||
}
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (rowId: number) => {
|
||||
setDialogTile('detail')
|
||||
const res = await NotifyTemplateApi.getNotifyTemplateApi(rowId)
|
||||
detailData.value = res
|
||||
modelLoading.value = false
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
const elForm = unref(formRef)?.getElFormRef()
|
||||
if (!elForm) return
|
||||
elForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as NotifyTemplateApi.NotifyTemplateVO
|
||||
if (actionType.value === 'create') {
|
||||
await NotifyTemplateApi.createNotifyTemplateApi(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await NotifyTemplateApi.updateNotifyTemplateApi(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
modelVisible.value = false
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
// 刷新列表
|
||||
await reload()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ========== 测试相关 ==========
|
||||
const sendForm = ref({
|
||||
content: '',
|
||||
params: {},
|
||||
userId: 0,
|
||||
templateCode: '',
|
||||
templateParams: {}
|
||||
})
|
||||
const sendRules = ref({
|
||||
userId: [{ required: true, message: '用户编号不能为空', trigger: 'blur' }],
|
||||
templateCode: [{ required: true, message: '模版编号不能为空', trigger: 'blur' }],
|
||||
templateParams: {}
|
||||
})
|
||||
const sendVisible = ref(false)
|
||||
const userOption = ref<UserVO[]>([])
|
||||
|
||||
const handleSendNotify = (row: any) => {
|
||||
sendForm.value.content = row.content
|
||||
sendForm.value.params = row.params
|
||||
sendForm.value.templateCode = row.code
|
||||
sendForm.value.templateParams = row.params.reduce(function (obj, item) {
|
||||
obj[item] = undefined
|
||||
return obj
|
||||
}, {})
|
||||
sendRules.value.templateParams = row.params.reduce(function (obj, item) {
|
||||
obj[item] = { required: true, message: '参数 ' + item + ' 不能为空', trigger: 'change' }
|
||||
return obj
|
||||
}, {})
|
||||
sendVisible.value = true
|
||||
}
|
||||
|
||||
const sendTest = async () => {
|
||||
const data: NotifyTemplateApi.NotifySendReqVO = {
|
||||
userId: sendForm.value.userId,
|
||||
templateCode: sendForm.value.templateCode,
|
||||
templateParams: sendForm.value.templateParams as unknown as Map<string, Object>
|
||||
}
|
||||
const res = await NotifyTemplateApi.sendNotifyApi(data)
|
||||
if (res) {
|
||||
message.success('提交发送成功!发送结果,见发送日志编号:' + res)
|
||||
}
|
||||
sendVisible.value = false
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
onMounted(() => {
|
||||
getListSimpleUsersApi().then((data) => {
|
||||
userOption.value = data
|
||||
})
|
||||
})
|
||||
</script>
|
||||
85
src/views/system/notify/template/template.data.ts
Normal file
85
src/views/system/notify/template/template.data.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
code: [required],
|
||||
content: [required],
|
||||
type: [required],
|
||||
status: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryTitle: '编号',
|
||||
primaryType: null,
|
||||
action: true,
|
||||
actionWidth: '260', // 3个按钮默认200,如有删减对应增减即可
|
||||
columns: [
|
||||
{
|
||||
title: '模版编码',
|
||||
field: 'code',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '模板名称',
|
||||
field: 'name',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '发件人名称',
|
||||
field: 'nickname'
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
field: 'type',
|
||||
dictType: DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE,
|
||||
dictClass: 'number'
|
||||
},
|
||||
{
|
||||
title: '模版内容',
|
||||
field: 'content',
|
||||
table: {
|
||||
width: 300
|
||||
},
|
||||
form: {
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 4
|
||||
},
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
field: 'remark'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
field: 'createTime',
|
||||
isForm: false,
|
||||
formatter: 'formatDate',
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
},
|
||||
table: {
|
||||
width: 180
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
193
src/views/system/oauth2/client/client.data.ts
Normal file
193
src/views/system/oauth2/client/client.data.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const authorizedGrantOptions = getStrDictOptions(DICT_TYPE.SYSTEM_OAUTH2_GRANT_TYPE)
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
clientId: [required],
|
||||
secret: [required],
|
||||
name: [required],
|
||||
status: [required],
|
||||
accessTokenValiditySeconds: [required],
|
||||
refreshTokenValiditySeconds: [required],
|
||||
redirectUris: [required],
|
||||
authorizedGrantTypes: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'clientId',
|
||||
primaryType: null,
|
||||
action: true,
|
||||
columns: [
|
||||
{
|
||||
title: '客户端密钥',
|
||||
field: 'secret'
|
||||
},
|
||||
{
|
||||
title: '应用名',
|
||||
field: 'name',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '应用图标',
|
||||
field: 'logo',
|
||||
table: {
|
||||
cellRender: {
|
||||
name: 'XImg'
|
||||
}
|
||||
},
|
||||
form: {
|
||||
component: 'UploadImg'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '访问令牌的有效期',
|
||||
field: 'accessTokenValiditySeconds',
|
||||
form: {
|
||||
component: 'InputNumber'
|
||||
},
|
||||
table: {
|
||||
slots: {
|
||||
default: 'accessTokenValiditySeconds_default'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '刷新令牌的有效期',
|
||||
field: 'refreshTokenValiditySeconds',
|
||||
form: {
|
||||
component: 'InputNumber'
|
||||
},
|
||||
table: {
|
||||
slots: {
|
||||
default: 'refreshTokenValiditySeconds_default'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '授权类型',
|
||||
field: 'authorizedGrantTypes',
|
||||
table: {
|
||||
width: 400,
|
||||
slots: {
|
||||
default: 'authorizedGrantTypes_default'
|
||||
}
|
||||
},
|
||||
form: {
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: authorizedGrantOptions,
|
||||
multiple: true,
|
||||
filterable: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '授权范围',
|
||||
field: 'scopes',
|
||||
isTable: false,
|
||||
form: {
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: [],
|
||||
multiple: true,
|
||||
filterable: true,
|
||||
allowCreate: true,
|
||||
defaultFirstOption: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '自动授权范围',
|
||||
field: 'autoApproveScopes',
|
||||
isTable: false,
|
||||
form: {
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: [],
|
||||
multiple: true,
|
||||
filterable: true,
|
||||
allowCreate: true,
|
||||
defaultFirstOption: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '可重定向的 URI 地址',
|
||||
field: 'redirectUris',
|
||||
isTable: false,
|
||||
form: {
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: [],
|
||||
multiple: true,
|
||||
filterable: true,
|
||||
allowCreate: true,
|
||||
defaultFirstOption: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '权限',
|
||||
field: 'authorities',
|
||||
isTable: false,
|
||||
form: {
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: [],
|
||||
multiple: true,
|
||||
filterable: true,
|
||||
allowCreate: true,
|
||||
defaultFirstOption: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '资源',
|
||||
field: 'resourceIds',
|
||||
isTable: false,
|
||||
form: {
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: [],
|
||||
multiple: true,
|
||||
filterable: true,
|
||||
allowCreate: true,
|
||||
defaultFirstOption: true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '附加信息',
|
||||
field: 'additionalInformation',
|
||||
isTable: false,
|
||||
form: {
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 4
|
||||
},
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
isForm: false
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
207
src/views/system/oauth2/client/index.vue
Normal file
207
src/views/system/oauth2/client/index.vue
Normal file
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<template #toolbar_buttons>
|
||||
<!-- 操作:新增 -->
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:zoom-in"
|
||||
:title="t('action.add')"
|
||||
v-hasPermi="['system:oauth2-client:create']"
|
||||
@click="handleCreate()"
|
||||
/>
|
||||
</template>
|
||||
<template #accessTokenValiditySeconds_default="{ row }">
|
||||
{{ row.accessTokenValiditySeconds + '秒' }}
|
||||
</template>
|
||||
<template #refreshTokenValiditySeconds_default="{ row }">
|
||||
{{ row.refreshTokenValiditySeconds + '秒' }}
|
||||
</template>
|
||||
<template #authorizedGrantTypes_default="{ row }">
|
||||
<el-tag
|
||||
:disable-transitions="true"
|
||||
:key="index"
|
||||
v-for="(authorizedGrantType, index) in row.authorizedGrantTypes"
|
||||
:index="index"
|
||||
>
|
||||
{{ authorizedGrantType }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:修改 -->
|
||||
<XTextButton
|
||||
preIcon="ep:edit"
|
||||
:title="t('action.edit')"
|
||||
v-hasPermi="['system:oauth2-client:update']"
|
||||
@click="handleUpdate(row.id)"
|
||||
/>
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton
|
||||
preIcon="ep:view"
|
||||
:title="t('action.detail')"
|
||||
v-hasPermi="['system:oauth2-client:query']"
|
||||
@click="handleDetail(row.id)"
|
||||
/>
|
||||
<!-- 操作:删除 -->
|
||||
<XTextButton
|
||||
preIcon="ep:delete"
|
||||
:title="t('action.del')"
|
||||
v-hasPermi="['system:oauth2-client:delete']"
|
||||
@click="deleteData(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
<!-- 弹窗 -->
|
||||
<XModal id="postModel" v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 表单:添加/修改 -->
|
||||
<Form
|
||||
ref="formRef"
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
/>
|
||||
<!-- 表单:详情 -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailData"
|
||||
>
|
||||
<template #accessTokenValiditySeconds="{ row }">
|
||||
{{ row.accessTokenValiditySeconds + '秒' }}
|
||||
</template>
|
||||
<template #refreshTokenValiditySeconds="{ row }">
|
||||
{{ row.refreshTokenValiditySeconds + '秒' }}
|
||||
</template>
|
||||
<template #authorizedGrantTypes="{ row }">
|
||||
<el-tag
|
||||
:disable-transitions="true"
|
||||
:key="index"
|
||||
v-for="(authorizedGrantType, index) in row.authorizedGrantTypes"
|
||||
:index="index"
|
||||
>
|
||||
{{ authorizedGrantType }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #scopes="{ row }">
|
||||
<el-tag
|
||||
:disable-transitions="true"
|
||||
:key="index"
|
||||
v-for="(scopes, index) in row.scopes"
|
||||
:index="index"
|
||||
>
|
||||
{{ scopes }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #autoApproveScopes="{ row }">
|
||||
<el-tag
|
||||
:disable-transitions="true"
|
||||
:key="index"
|
||||
v-for="(autoApproveScopes, index) in row.autoApproveScopes"
|
||||
:index="index"
|
||||
>
|
||||
{{ autoApproveScopes }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #redirectUris="{ row }">
|
||||
<el-tag
|
||||
:disable-transitions="true"
|
||||
:key="index"
|
||||
v-for="(redirectUris, index) in row.redirectUris"
|
||||
:index="index"
|
||||
>
|
||||
{{ redirectUris }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<template #footer>
|
||||
<!-- 按钮:保存 -->
|
||||
<XButton
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:title="t('action.save')"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm()"
|
||||
/>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="Client">
|
||||
import type { FormExpose } from '@/components/Form'
|
||||
// 业务相关的 import
|
||||
import * as ClientApi from '@/api/system/oauth2/client'
|
||||
import { rules, allSchemas } from './client.data'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
// 列表相关的变量
|
||||
const [registerTable, { reload, deleteData }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: ClientApi.getOAuth2ClientPageApi,
|
||||
deleteApi: ClientApi.deleteOAuth2ClientApi
|
||||
})
|
||||
// 弹窗相关的变量
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const actionLoading = ref(false) // 按钮 Loading
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const detailData = ref() // 详情 Ref
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (rowId: number) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await ClientApi.getOAuth2ClientApi(rowId)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (rowId: number) => {
|
||||
setDialogTile('detail')
|
||||
const res = await ClientApi.getOAuth2ClientApi(rowId)
|
||||
detailData.value = res
|
||||
}
|
||||
|
||||
// 提交新增/修改的表单
|
||||
const submitForm = async () => {
|
||||
const elForm = unref(formRef)?.getElFormRef()
|
||||
if (!elForm) return
|
||||
elForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as ClientApi.OAuth2ClientVO
|
||||
if (actionType.value === 'create') {
|
||||
await ClientApi.createOAuth2ClientApi(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ClientApi.updateOAuth2ClientApi(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
// 刷新列表
|
||||
await reload()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
64
src/views/system/oauth2/token/index.vue
Normal file
64
src/views/system/oauth2/token/index.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton preIcon="ep:view" :title="t('action.detail')" @click="handleDetail(row)" />
|
||||
<!-- 操作:登出 -->
|
||||
<XTextButton
|
||||
preIcon="ep:delete"
|
||||
:title="t('action.logout')"
|
||||
v-hasPermi="['system:oauth2-token:delete']"
|
||||
@click="handleForceLogout(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
<XModal v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions :schema="allSchemas.detailSchema" :data="detailData" />
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<XButton :title="t('dialog.close')" @click="dialogVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="Token">
|
||||
import { allSchemas } from './token.data'
|
||||
import * as TokenApi from '@/api/system/oauth2/token'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
// 列表相关的变量
|
||||
const [registerTable, { reload }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
topActionSlots: false,
|
||||
getListApi: TokenApi.getAccessTokenPageApi
|
||||
})
|
||||
|
||||
// ========== 详情相关 ==========
|
||||
const detailData = ref() // 详情 Ref
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref(t('action.detail')) // 弹出层标题
|
||||
// 详情
|
||||
const handleDetail = async (row: TokenApi.OAuth2TokenVO) => {
|
||||
// 设置数据
|
||||
detailData.value = row
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 强退操作
|
||||
const handleForceLogout = (rowId: number) => {
|
||||
message
|
||||
.confirm('是否要强制退出用户')
|
||||
.then(async () => {
|
||||
await TokenApi.deleteAccessTokenApi(rowId)
|
||||
message.success(t('common.success'))
|
||||
})
|
||||
.finally(async () => {
|
||||
// 刷新列表
|
||||
await reload()
|
||||
})
|
||||
}
|
||||
</script>
|
||||
48
src/views/system/oauth2/token/token.data.ts
Normal file
48
src/views/system/oauth2/token/token.data.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
const { t } = useI18n() // 国际化
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: null,
|
||||
action: true,
|
||||
columns: [
|
||||
{
|
||||
title: '用户编号',
|
||||
field: 'userId',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '访问令牌',
|
||||
field: 'accessToken'
|
||||
},
|
||||
{
|
||||
title: '刷新令牌',
|
||||
field: 'refreshToken'
|
||||
},
|
||||
{
|
||||
title: '客户端编号',
|
||||
field: 'clientId',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '用户类型',
|
||||
field: 'userType',
|
||||
dictType: DICT_TYPE.USER_TYPE,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
isForm: false
|
||||
},
|
||||
{
|
||||
title: '过期时间',
|
||||
field: 'expiresTime',
|
||||
formatter: 'formatDate',
|
||||
isForm: false
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
67
src/views/system/operatelog/index.vue
Normal file
67
src/views/system/operatelog/index.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<template #toolbar_buttons>
|
||||
<!-- 操作:新增 -->
|
||||
<XButton
|
||||
type="warning"
|
||||
preIcon="ep:download"
|
||||
:title="t('action.export')"
|
||||
v-hasPermi="['system:operate-log:export']"
|
||||
@click="exportList('操作日志.xls')"
|
||||
/>
|
||||
</template>
|
||||
<template #duration="{ row }">
|
||||
<span>{{ row.duration + 'ms' }}</span>
|
||||
</template>
|
||||
<template #resultCode="{ row }">
|
||||
<span>{{ row.resultCode === 0 ? '成功' : '失败' }}</span>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton preIcon="ep:view" :title="t('action.detail')" @click="handleDetail(row)" />
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
<!-- 弹窗 -->
|
||||
<XModal id="postModel" v-model="dialogVisible" :title="t('action.detail')">
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions :schema="allSchemas.detailSchema" :data="detailData">
|
||||
<template #resultCode="{ row }">
|
||||
<span>{{ row.resultCode === 0 ? '成功' : '失败' }}</span>
|
||||
</template>
|
||||
<template #duration="{ row }">
|
||||
<span>{{ row.duration + 'ms' }}</span>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<template #footer>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="OperateLog">
|
||||
// 业务相关的 import
|
||||
import * as OperateLogApi from '@/api/system/operatelog'
|
||||
import { allSchemas } from './operatelog.data'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
// 列表相关的变量
|
||||
const [registerTable, { exportList }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: OperateLogApi.getOperateLogPageApi,
|
||||
exportListApi: OperateLogApi.exportOperateLogApi
|
||||
})
|
||||
|
||||
// 弹窗相关的变量
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const actionLoading = ref(false) // 按钮 Loading
|
||||
const detailData = ref() // 详情 Ref
|
||||
// 详情
|
||||
const handleDetail = (row: OperateLogApi.OperateLogVO) => {
|
||||
// 设置数据
|
||||
detailData.value = row
|
||||
dialogVisible.value = true
|
||||
}
|
||||
</script>
|
||||
106
src/views/system/operatelog/operatelog.data.ts
Normal file
106
src/views/system/operatelog/operatelog.data.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: 'seq',
|
||||
primaryTitle: '日志编号',
|
||||
action: true,
|
||||
actionWidth: '80px',
|
||||
columns: [
|
||||
{
|
||||
title: '操作模块',
|
||||
field: 'module',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '操作名',
|
||||
field: 'name'
|
||||
},
|
||||
{
|
||||
title: '操作类型',
|
||||
field: 'type',
|
||||
dictType: DICT_TYPE.SYSTEM_OPERATE_TYPE,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '请求方法名',
|
||||
field: 'requestMethod',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: '请求地址',
|
||||
field: 'requestUrl',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: '操作人员',
|
||||
field: 'userNickname',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '操作明细',
|
||||
field: 'content',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: '用户 IP',
|
||||
field: 'userIp',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: 'userAgent',
|
||||
field: 'userAgent'
|
||||
},
|
||||
{
|
||||
title: '操作结果',
|
||||
field: 'resultCode',
|
||||
table: {
|
||||
slots: {
|
||||
default: 'resultCode'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作结果',
|
||||
field: 'success',
|
||||
isTable: false,
|
||||
isDetail: false,
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: '$select',
|
||||
props: { placeholder: t('common.selectText') },
|
||||
options: [
|
||||
{ label: '成功', value: 'true' },
|
||||
{ label: '失败', value: 'false' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作日期',
|
||||
field: 'startTime',
|
||||
formatter: 'formatDate',
|
||||
isForm: false,
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '执行时长',
|
||||
field: 'duration',
|
||||
table: {
|
||||
slots: {
|
||||
default: 'duration'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
92
src/views/system/post/PostForm.vue
Normal file
92
src/views/system/post/PostForm.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<!-- 弹窗 -->
|
||||
<XModal id="PostForm" :loading="modelLoading" v-model="modelVisible" :title="modelTitle">
|
||||
<!-- 表单:添加/修改 -->
|
||||
<Form
|
||||
ref="formRef"
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
/>
|
||||
<!-- 表单:详情 -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailData"
|
||||
/>
|
||||
<template #footer>
|
||||
<!-- 按钮:保存 -->
|
||||
<XButton
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:title="t('action.save')"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm()"
|
||||
/>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import type { FormExpose } from '@/components/Form'
|
||||
import * as PostApi from '@/api/system/post'
|
||||
import { rules, allSchemas } from './post.data'
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
// 弹窗相关的变量
|
||||
const modelVisible = ref(false) // 是否显示弹出层
|
||||
const modelTitle = ref('update') // 弹出层标题
|
||||
const modelLoading = ref(false) // 弹出层loading
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const actionLoading = ref(false) // 按钮 Loading
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const detailData = ref() // 详情 Ref
|
||||
|
||||
const openModal = async (type: string, rowId?: number) => {
|
||||
modelLoading.value = true
|
||||
modelTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
modelVisible.value = true
|
||||
// 设置数据
|
||||
if (rowId) {
|
||||
const res = await PostApi.getPostApi(rowId)
|
||||
if (type === 'update') {
|
||||
unref(formRef)?.setValues(res)
|
||||
} else if (type === 'detail') {
|
||||
detailData.value = res
|
||||
}
|
||||
}
|
||||
modelLoading.value = false
|
||||
}
|
||||
|
||||
// 提交新增/修改的表单
|
||||
const submitForm = async () => {
|
||||
const elForm = unref(formRef)?.getElFormRef()
|
||||
if (!elForm) return
|
||||
const valid = await elForm.validate()
|
||||
if (valid) {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as PostApi.PostVO
|
||||
if (actionType.value === 'create') {
|
||||
await PostApi.createPostApi(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await PostApi.updatePostApi(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
modelVisible.value = false
|
||||
emit('success')
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ openModal: openModal })
|
||||
</script>
|
||||
67
src/views/system/post/index.vue
Normal file
67
src/views/system/post/index.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<template #toolbar_buttons>
|
||||
<!-- 操作:新增 -->
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:zoom-in"
|
||||
:title="t('action.add')"
|
||||
v-hasPermi="['system:post:create']"
|
||||
@click="openModal('create')"
|
||||
/>
|
||||
<!-- 操作:导出 -->
|
||||
<XButton
|
||||
type="warning"
|
||||
preIcon="ep:download"
|
||||
:title="t('action.export')"
|
||||
v-hasPermi="['system:post:export']"
|
||||
@click="exportList('岗位列表.xls')"
|
||||
/>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:修改 -->
|
||||
<XTextButton
|
||||
preIcon="ep:edit"
|
||||
v-hasPermi="['system:post:update']"
|
||||
@click="openModal('update', row.id)"
|
||||
/>
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton
|
||||
preIcon="ep:view"
|
||||
v-hasPermi="['system:post:query']"
|
||||
@click="openModal('detail', row.id)"
|
||||
/>
|
||||
<!-- 操作:删除 -->
|
||||
<XTextButton
|
||||
preIcon="ep:delete"
|
||||
v-hasPermi="['system:post:delete']"
|
||||
@click="deleteData(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
<PostForm ref="modalRef" @success="reload()" />
|
||||
</template>
|
||||
<script setup lang="ts" name="Post">
|
||||
// 业务相关的 import
|
||||
import * as PostApi from '@/api/system/post'
|
||||
import { allSchemas } from './post.data'
|
||||
import PostForm from './PostForm.vue'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 列表相关的变量
|
||||
const [registerTable, { reload, deleteData, exportList }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: PostApi.getPostPageApi,
|
||||
deleteApi: PostApi.deletePostApi,
|
||||
exportListApi: PostApi.exportPostApi
|
||||
})
|
||||
// 表单相关的变量
|
||||
const modalRef = ref()
|
||||
const openModal = (type: string, rowId?: number) => {
|
||||
modalRef.value.openModal(type, rowId)
|
||||
}
|
||||
</script>
|
||||
52
src/views/system/post/post.data.ts
Normal file
52
src/views/system/post/post.data.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
code: [required],
|
||||
sort: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: 'seq',
|
||||
primaryTitle: '岗位编号',
|
||||
action: true,
|
||||
columns: [
|
||||
{
|
||||
title: '岗位名称',
|
||||
field: 'name',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '岗位编码',
|
||||
field: 'code',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '岗位顺序',
|
||||
field: 'sort'
|
||||
},
|
||||
{
|
||||
title: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
field: 'remark',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
isForm: false
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
314
src/views/system/role/index.vue
Normal file
314
src/views/system/role/index.vue
Normal file
@@ -0,0 +1,314 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<!-- 操作:新增 -->
|
||||
<template #toolbar_buttons>
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:zoom-in"
|
||||
:title="t('action.add')"
|
||||
v-hasPermi="['system:role:create']"
|
||||
@click="handleCreate()"
|
||||
/>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:编辑 -->
|
||||
<XTextButton
|
||||
preIcon="ep:edit"
|
||||
:title="t('action.edit')"
|
||||
v-hasPermi="['system:role:update']"
|
||||
@click="handleUpdate(row.id)"
|
||||
/>
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton
|
||||
preIcon="ep:view"
|
||||
:title="t('action.detail')"
|
||||
v-hasPermi="['system:role:query']"
|
||||
@click="handleDetail(row.id)"
|
||||
/>
|
||||
<!-- 操作:菜单权限 -->
|
||||
<XTextButton
|
||||
preIcon="ep:basketball"
|
||||
title="菜单权限"
|
||||
v-hasPermi="['system:permission:assign-role-menu']"
|
||||
@click="handleScope('menu', row)"
|
||||
/>
|
||||
<!-- 操作:数据权限 -->
|
||||
<XTextButton
|
||||
preIcon="ep:coin"
|
||||
title="数据权限"
|
||||
v-hasPermi="['system:permission:assign-role-data-scope']"
|
||||
@click="handleScope('data', row)"
|
||||
/>
|
||||
<!-- 操作:删除 -->
|
||||
<XTextButton
|
||||
preIcon="ep:delete"
|
||||
:title="t('action.del')"
|
||||
v-hasPermi="['system:role:delete']"
|
||||
@click="deleteData(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
|
||||
<XModal v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
/>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailData"
|
||||
/>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<XButton
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:title="t('action.save')"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm()"
|
||||
/>
|
||||
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
|
||||
<XModal v-model="dialogScopeVisible" :title="dialogScopeTitle">
|
||||
<el-form :model="dataScopeForm" label-width="140px" :inline="true">
|
||||
<el-form-item label="角色名称">
|
||||
<el-tag>{{ dataScopeForm.name }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色标识">
|
||||
<el-tag>{{ dataScopeForm.code }}</el-tag>
|
||||
</el-form-item>
|
||||
<!-- 分配角色的数据权限对话框 -->
|
||||
<el-form-item label="权限范围" v-if="actionScopeType === 'data'">
|
||||
<el-select v-model="dataScopeForm.dataScope">
|
||||
<el-option
|
||||
v-for="item in dataScopeDictDatas"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- 分配角色的菜单权限对话框 -->
|
||||
<el-form-item
|
||||
label="权限范围"
|
||||
v-if="
|
||||
actionScopeType === 'menu' || dataScopeForm.dataScope === SystemDataScopeEnum.DEPT_CUSTOM
|
||||
"
|
||||
>
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
父子联动(选中父节点,自动选择子节点):
|
||||
<el-switch v-model="checkStrictly" inline-prompt active-text="是" inactive-text="否" />
|
||||
全选/全不选:
|
||||
<el-switch
|
||||
v-model="treeNodeAll"
|
||||
inline-prompt
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
@change="handleCheckedTreeNodeAll()"
|
||||
/>
|
||||
</template>
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
:default-checked-keys="defaultCheckedKeys"
|
||||
:check-strictly="!checkStrictly"
|
||||
:props="defaultProps"
|
||||
:data="treeOptions"
|
||||
empty-text="加载中,请稍后"
|
||||
/>
|
||||
</el-card>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<XButton
|
||||
type="primary"
|
||||
:title="t('action.save')"
|
||||
:loading="actionLoading"
|
||||
@click="submitScope()"
|
||||
/>
|
||||
<XButton
|
||||
:loading="actionLoading"
|
||||
:title="t('dialog.close')"
|
||||
@click="dialogScopeVisible = false"
|
||||
/>
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="Role">
|
||||
import type { ElTree } from 'element-plus'
|
||||
import type { FormExpose } from '@/components/Form'
|
||||
import { handleTree, defaultProps } from '@/utils/tree'
|
||||
import { SystemDataScopeEnum } from '@/utils/constants'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { rules, allSchemas } from './role.data'
|
||||
import * as RoleApi from '@/api/system/role'
|
||||
import { listSimpleMenusApi } from '@/api/system/menu'
|
||||
import { listSimpleDeptApi } from '@/api/system/dept'
|
||||
import * as PermissionApi from '@/api/system/permission'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
// 列表相关的变量
|
||||
const [registerTable, { reload, deleteData }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: RoleApi.getRolePageApi,
|
||||
deleteApi: RoleApi.deleteRoleApi
|
||||
})
|
||||
|
||||
// ========== CRUD 相关 ==========
|
||||
const actionLoading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const detailData = ref() // 详情 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (rowId: number) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await RoleApi.getRoleApi(rowId)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (rowId: number) => {
|
||||
setDialogTile('detail')
|
||||
// 设置数据
|
||||
const res = await RoleApi.getRoleApi(rowId)
|
||||
detailData.value = res
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
const elForm = unref(formRef)?.getElFormRef()
|
||||
if (!elForm) return
|
||||
elForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as RoleApi.RoleVO
|
||||
if (actionType.value === 'create') {
|
||||
await RoleApi.createRoleApi(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await RoleApi.updateRoleApi(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
// 刷新列表
|
||||
await reload()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ========== 数据权限 ==========
|
||||
const dataScopeForm = reactive({
|
||||
id: 0,
|
||||
name: '',
|
||||
code: '',
|
||||
dataScope: 0,
|
||||
checkList: []
|
||||
})
|
||||
const treeOptions = ref<any[]>([]) // 菜单树形结构
|
||||
const treeRef = ref<InstanceType<typeof ElTree>>()
|
||||
const dialogScopeVisible = ref(false)
|
||||
const dialogScopeTitle = ref('数据权限')
|
||||
const actionScopeType = ref('')
|
||||
const dataScopeDictDatas = ref()
|
||||
const defaultCheckedKeys = ref()
|
||||
// 选项
|
||||
const checkStrictly = ref(true)
|
||||
const treeNodeAll = ref(false)
|
||||
// 全选/全不选
|
||||
const handleCheckedTreeNodeAll = () => {
|
||||
treeRef.value!.setCheckedNodes(treeNodeAll.value ? treeOptions.value : [])
|
||||
}
|
||||
// 权限操作
|
||||
const handleScope = async (type: string, row: RoleApi.RoleVO) => {
|
||||
dataScopeForm.id = row.id
|
||||
dataScopeForm.name = row.name
|
||||
dataScopeForm.code = row.code
|
||||
if (type === 'menu') {
|
||||
const menuRes = await listSimpleMenusApi()
|
||||
treeOptions.value = handleTree(menuRes)
|
||||
const role = await PermissionApi.listRoleMenusApi(row.id)
|
||||
if (role) {
|
||||
// treeRef.value!.setCheckedKeys(role as unknown as Array<number>)
|
||||
defaultCheckedKeys.value = role
|
||||
}
|
||||
} else if (type === 'data') {
|
||||
const deptRes = await listSimpleDeptApi()
|
||||
treeOptions.value = handleTree(deptRes)
|
||||
const role = await RoleApi.getRoleApi(row.id)
|
||||
dataScopeForm.dataScope = role.dataScope
|
||||
if (role.dataScopeDeptIds) {
|
||||
// treeRef.value!.setCheckedKeys(role.dataScopeDeptIds as unknown as Array<number>, false)
|
||||
defaultCheckedKeys.value = role.dataScopeDeptIds
|
||||
}
|
||||
}
|
||||
actionScopeType.value = type
|
||||
dialogScopeVisible.value = true
|
||||
}
|
||||
// 保存权限
|
||||
const submitScope = async () => {
|
||||
if ('data' === actionScopeType.value) {
|
||||
const data = ref<PermissionApi.PermissionAssignRoleDataScopeReqVO>({
|
||||
roleId: dataScopeForm.id,
|
||||
dataScope: dataScopeForm.dataScope,
|
||||
dataScopeDeptIds:
|
||||
dataScopeForm.dataScope !== SystemDataScopeEnum.DEPT_CUSTOM
|
||||
? []
|
||||
: (treeRef.value!.getCheckedKeys(false) as unknown as Array<number>)
|
||||
})
|
||||
await PermissionApi.assignRoleDataScopeApi(data.value)
|
||||
} else if ('menu' === actionScopeType.value) {
|
||||
const data = ref<PermissionApi.PermissionAssignRoleMenuReqVO>({
|
||||
roleId: dataScopeForm.id,
|
||||
menuIds: [
|
||||
...(treeRef.value!.getCheckedKeys(false) as unknown as Array<number>),
|
||||
...(treeRef.value!.getHalfCheckedKeys() as unknown as Array<number>)
|
||||
]
|
||||
})
|
||||
await PermissionApi.assignRoleMenuApi(data.value)
|
||||
}
|
||||
message.success(t('common.updateSuccess'))
|
||||
dialogScopeVisible.value = false
|
||||
}
|
||||
const init = () => {
|
||||
dataScopeDictDatas.value = getIntDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE)
|
||||
}
|
||||
// ========== 初始化 ==========
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
75
src/views/system/role/role.data.ts
Normal file
75
src/views/system/role/role.data.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
code: [required],
|
||||
sort: [required]
|
||||
})
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryTitle: '角色编号',
|
||||
primaryType: 'seq',
|
||||
action: true,
|
||||
actionWidth: '400px',
|
||||
columns: [
|
||||
{
|
||||
title: '角色名称',
|
||||
field: 'name',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '角色类型',
|
||||
field: 'type',
|
||||
dictType: DICT_TYPE.SYSTEM_ROLE_TYPE,
|
||||
dictClass: 'number',
|
||||
isForm: false
|
||||
},
|
||||
{
|
||||
title: '角色标识',
|
||||
field: 'code',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '显示顺序',
|
||||
field: 'sort'
|
||||
},
|
||||
{
|
||||
title: t('form.remark'),
|
||||
field: 'remark',
|
||||
isTable: false,
|
||||
form: {
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 4
|
||||
},
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
isForm: false,
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
191
src/views/system/sensitiveWord/index.vue
Normal file
191
src/views/system/sensitiveWord/index.vue
Normal file
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<template #toolbar_buttons>
|
||||
<!-- 操作:新增 -->
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:zoom-in"
|
||||
:title="t('action.add')"
|
||||
v-hasPermi="['system:sensitive-word:create']"
|
||||
@click="handleCreate()"
|
||||
/>
|
||||
<!-- 操作:导出 -->
|
||||
<XButton
|
||||
type="warning"
|
||||
preIcon="ep:download"
|
||||
:title="t('action.export')"
|
||||
v-hasPermi="['system:sensitive-word:export']"
|
||||
@click="exportList('敏感词数据.xls')"
|
||||
/>
|
||||
</template>
|
||||
<template #tags_default="{ row }">
|
||||
<el-tag
|
||||
:disable-transitions="true"
|
||||
:key="index"
|
||||
v-for="(tag, index) in row.tags"
|
||||
:index="index"
|
||||
>
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:修改 -->
|
||||
<XTextButton
|
||||
preIcon="ep:edit"
|
||||
:title="t('action.edit')"
|
||||
v-hasPermi="['system:sensitive-word:update']"
|
||||
@click="handleUpdate(row.id)"
|
||||
/>
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton
|
||||
preIcon="ep:view"
|
||||
:title="t('action.detail')"
|
||||
v-hasPermi="['system:sensitive-word:update']"
|
||||
@click="handleDetail(row.id)"
|
||||
/>
|
||||
<!-- 操作:删除 -->
|
||||
<XTextButton
|
||||
preIcon="ep:delete"
|
||||
:title="t('action.del')"
|
||||
v-hasPermi="['system:sensitive-word:delete']"
|
||||
@click="deleteData(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
|
||||
<XModal v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
>
|
||||
<template #tags="form">
|
||||
<el-select v-model="form['tags']" multiple placeholder="请选择">
|
||||
<el-option v-for="item in tagsOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</template>
|
||||
</Form>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailData"
|
||||
>
|
||||
<template #tags="{ row }">
|
||||
<el-tag
|
||||
:disable-transitions="true"
|
||||
:key="index"
|
||||
v-for="(tag, index) in row.tags"
|
||||
:index="index"
|
||||
>
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<!-- 按钮:保存 -->
|
||||
<XButton
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:title="t('action.save')"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm()"
|
||||
/>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="SensitiveWord">
|
||||
import type { FormExpose } from '@/components/Form'
|
||||
import * as SensitiveWordApi from '@/api/system/sensitiveWord'
|
||||
import { rules, allSchemas } from './sensitiveWord.data'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
// 列表相关的变量
|
||||
const [registerTable, { reload, deleteData, exportList }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: SensitiveWordApi.getSensitiveWordPageApi,
|
||||
deleteApi: SensitiveWordApi.deleteSensitiveWordApi,
|
||||
exportListApi: SensitiveWordApi.exportSensitiveWordApi
|
||||
})
|
||||
const actionLoading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const detailData = ref() // 详情 Ref
|
||||
|
||||
// 获取标签
|
||||
const tagsOptions = ref()
|
||||
const getTags = async () => {
|
||||
const res = await SensitiveWordApi.getSensitiveWordTagsApi()
|
||||
tagsOptions.value = res
|
||||
}
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (rowId: number) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await SensitiveWordApi.getSensitiveWordApi(rowId)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (rowId: number) => {
|
||||
setDialogTile('detail')
|
||||
const res = await SensitiveWordApi.getSensitiveWordApi(rowId)
|
||||
detailData.value = res
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
const elForm = unref(formRef)?.getElFormRef()
|
||||
if (!elForm) return
|
||||
elForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as SensitiveWordApi.SensitiveWordVO
|
||||
if (actionType.value === 'create') {
|
||||
await SensitiveWordApi.createSensitiveWordApi(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await SensitiveWordApi.updateSensitiveWordApi(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
// 刷新列表
|
||||
await reload()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
onMounted(async () => {
|
||||
await getTags()
|
||||
})
|
||||
</script>
|
||||
74
src/views/system/sensitiveWord/sensitiveWord.data.ts
Normal file
74
src/views/system/sensitiveWord/sensitiveWord.data.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
tags: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: 'seq',
|
||||
primaryTitle: '敏感词编号',
|
||||
action: true,
|
||||
columns: [
|
||||
{
|
||||
title: '敏感词',
|
||||
field: 'name',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '标签',
|
||||
field: 'tag',
|
||||
isTable: false,
|
||||
isForm: false,
|
||||
isDetail: false,
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '标签',
|
||||
field: 'tags',
|
||||
table: {
|
||||
slots: {
|
||||
default: 'tags_default'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
field: 'description',
|
||||
form: {
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 4
|
||||
},
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
isForm: false,
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
147
src/views/system/sms/smsChannel/index.vue
Normal file
147
src/views/system/sms/smsChannel/index.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<!-- 操作:新增 -->
|
||||
<template #toolbar_buttons>
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:zoom-in"
|
||||
:title="t('action.add')"
|
||||
v-hasPermi="['system:sms-channel:create']"
|
||||
@click="handleCreate()"
|
||||
/>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:修改 -->
|
||||
<XTextButton
|
||||
preIcon="ep:edit"
|
||||
:title="t('action.edit')"
|
||||
v-hasPermi="['system:sms-channel:update']"
|
||||
@click="handleUpdate(row.id)"
|
||||
/>
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton
|
||||
preIcon="ep:view"
|
||||
:title="t('action.detail')"
|
||||
v-hasPermi="['system:sms-channel:query']"
|
||||
@click="handleDetail(row.id)"
|
||||
/>
|
||||
<!-- 操作:删除 -->
|
||||
<XTextButton
|
||||
preIcon="ep:delete"
|
||||
:title="t('action.del')"
|
||||
v-hasPermi="['system:sms-channel:delete']"
|
||||
@click="deleteData(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
|
||||
<XModal id="smsChannel" v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
/>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailData"
|
||||
/>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<!-- 按钮:保存 -->
|
||||
<XButton
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:title="t('action.save')"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm()"
|
||||
/>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="SmsChannel">
|
||||
import type { FormExpose } from '@/components/Form'
|
||||
// 业务相关的 import
|
||||
import * as SmsChannelApi from '@/api/system/sms/smsChannel'
|
||||
import { rules, allSchemas } from './sms.channel.data'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
// 列表相关的变量
|
||||
const [registerTable, { reload, deleteData }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: SmsChannelApi.getSmsChannelPageApi,
|
||||
deleteApi: SmsChannelApi.deleteSmsChannelApi
|
||||
})
|
||||
|
||||
// 弹窗相关的变量
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const actionLoading = ref(false) // 按钮 Loading
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const detailData = ref() // 详情 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (rowId: number) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await SmsChannelApi.getSmsChannelApi(rowId)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (rowId: number) => {
|
||||
setDialogTile('detail')
|
||||
const res = await SmsChannelApi.getSmsChannelApi(rowId)
|
||||
detailData.value = res
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
const elForm = unref(formRef)?.getElFormRef()
|
||||
if (!elForm) return
|
||||
elForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as SmsChannelApi.SmsChannelVO
|
||||
if (actionType.value === 'create') {
|
||||
await SmsChannelApi.createSmsChannelApi(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await SmsChannelApi.updateSmsChannelApi(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
// 刷新列表
|
||||
await reload()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
63
src/views/system/sms/smsChannel/sms.channel.data.ts
Normal file
63
src/views/system/sms/smsChannel/sms.channel.data.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
signature: [required],
|
||||
code: [required],
|
||||
apiKey: [required],
|
||||
status: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: 'seq',
|
||||
primaryTitle: '渠道编号',
|
||||
action: true,
|
||||
columns: [
|
||||
{
|
||||
title: '短信签名',
|
||||
field: 'signature',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '渠道编码',
|
||||
field: 'code',
|
||||
dictType: DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE,
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '短信 API 的账号',
|
||||
field: 'apiKey'
|
||||
},
|
||||
{
|
||||
title: '短信 API 的密钥',
|
||||
field: 'apiSecret'
|
||||
},
|
||||
{
|
||||
title: '短信发送回调 URL',
|
||||
field: 'callbackUrl'
|
||||
},
|
||||
{
|
||||
title: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
isForm: false,
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
56
src/views/system/sms/smsLog/index.vue
Normal file
56
src/views/system/sms/smsLog/index.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<!-- 操作:导出 -->
|
||||
<template #toolbar_buttons>
|
||||
<XButton
|
||||
type="warning"
|
||||
preIcon="ep:download"
|
||||
:title="t('action.export')"
|
||||
@click="exportList('短信日志.xls')"
|
||||
/>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<XTextButton preIcon="ep:view" :title="t('action.detail')" @click="handleDetail(row)" />
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
|
||||
<XModal id="smsLog" v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailData"
|
||||
/>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<XButton :title="t('dialog.close')" @click="dialogVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="SmsLog">
|
||||
import { allSchemas } from './sms.log.data'
|
||||
import * as SmsLoglApi from '@/api/system/sms/smsLog'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 列表相关的变量
|
||||
const [registerTable, { exportList }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: SmsLoglApi.getSmsLogPageApi,
|
||||
exportListApi: SmsLoglApi.exportSmsLogApi
|
||||
})
|
||||
|
||||
// 弹窗相关的变量
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
// ========== 详情相关 ==========
|
||||
const detailData = ref() // 详情 Ref
|
||||
const handleDetail = (row: SmsLoglApi.SmsLogVO) => {
|
||||
// 设置数据
|
||||
detailData.value = row
|
||||
dialogVisible.value = true
|
||||
}
|
||||
</script>
|
||||
82
src/views/system/sms/smsLog/sms.log.data.ts
Normal file
82
src/views/system/sms/smsLog/sms.log.data.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: 'seq',
|
||||
primaryTitle: '日志编号',
|
||||
action: true,
|
||||
columns: [
|
||||
{
|
||||
title: '手机号',
|
||||
field: 'mobile',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '短信内容',
|
||||
field: 'templateContent'
|
||||
},
|
||||
{
|
||||
title: '模板编号',
|
||||
field: 'templateId',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '短信渠道',
|
||||
field: 'channelId',
|
||||
dictType: DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '发送状态',
|
||||
field: 'sendStatus',
|
||||
dictType: DICT_TYPE.SYSTEM_SMS_SEND_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '发送时间',
|
||||
field: 'sendTime',
|
||||
formatter: 'formatDate',
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '短信类型',
|
||||
field: 'templateType',
|
||||
dictType: DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '接收状态',
|
||||
field: 'receiveStatus',
|
||||
dictType: DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '接收时间',
|
||||
field: 'receiveTime',
|
||||
formatter: 'formatDate',
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate'
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
232
src/views/system/sms/smsTemplate/index.vue
Normal file
232
src/views/system/sms/smsTemplate/index.vue
Normal file
@@ -0,0 +1,232 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<!-- 操作:新增 -->
|
||||
<template #toolbar_buttons>
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:zoom-in"
|
||||
:title="t('action.add')"
|
||||
v-hasPermi="['system:sms-channel:create']"
|
||||
@click="handleCreate()"
|
||||
/>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<XTextButton
|
||||
preIcon="ep:cpu"
|
||||
:title="t('action.test')"
|
||||
v-hasPermi="['system:sms-template:send-sms']"
|
||||
@click="handleSendSms(row)"
|
||||
/>
|
||||
<!-- 操作:修改 -->
|
||||
<XTextButton
|
||||
preIcon="ep:edit"
|
||||
:title="t('action.edit')"
|
||||
v-hasPermi="['system:sms-template:update']"
|
||||
@click="handleUpdate(row.id)"
|
||||
/>
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton
|
||||
preIcon="ep:view"
|
||||
:title="t('action.detail')"
|
||||
v-hasPermi="['system:sms-template:query']"
|
||||
@click="handleDetail(row.id)"
|
||||
/>
|
||||
<!-- 操作:删除 -->
|
||||
<XTextButton
|
||||
preIcon="ep:delete"
|
||||
:title="t('action.del')"
|
||||
v-hasPermi="['system:sms-template:delete']"
|
||||
@click="deleteData(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
<XModal id="smsTemplate" v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
/>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailData"
|
||||
/>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<!-- 按钮:保存 -->
|
||||
<XButton
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:title="t('action.save')"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm()"
|
||||
/>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
<XModal id="sendTest" v-model="sendVisible" title="测试">
|
||||
<el-form :model="sendSmsForm" :rules="sendSmsRules" label-width="200px" label-position="top">
|
||||
<el-form-item label="模板内容" prop="content">
|
||||
<el-input
|
||||
v-model="sendSmsForm.content"
|
||||
type="textarea"
|
||||
placeholder="请输入模板内容"
|
||||
readonly
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="mobile">
|
||||
<el-input v-model="sendSmsForm.mobile" placeholder="请输入手机号" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-for="param in sendSmsForm.params"
|
||||
:key="param"
|
||||
:label="'参数 {' + param + '}'"
|
||||
:prop="'templateParams.' + param"
|
||||
>
|
||||
<el-input
|
||||
v-model="sendSmsForm.templateParams[param]"
|
||||
:placeholder="'请输入 ' + param + ' 参数'"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<XButton
|
||||
type="primary"
|
||||
:title="t('action.test')"
|
||||
:loading="actionLoading"
|
||||
@click="sendSmsTest()"
|
||||
/>
|
||||
<XButton :title="t('dialog.close')" @click="sendVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="SmsTemplate">
|
||||
import type { FormExpose } from '@/components/Form'
|
||||
// 业务相关的 import
|
||||
import * as SmsTemplateApi from '@/api/system/sms/smsTemplate'
|
||||
import { rules, allSchemas } from './sms.template.data'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
// 列表相关的变量
|
||||
const [registerTable, { reload, deleteData }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: SmsTemplateApi.getSmsTemplatePageApi,
|
||||
deleteApi: SmsTemplateApi.deleteSmsTemplateApi
|
||||
})
|
||||
|
||||
// 弹窗相关的变量
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const actionLoading = ref(false) // 按钮 Loading
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const detailData = ref() // 详情 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (rowId: number) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await SmsTemplateApi.getSmsTemplateApi(rowId)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (rowId: number) => {
|
||||
setDialogTile('detail')
|
||||
// 设置数据
|
||||
const res = await SmsTemplateApi.getSmsTemplateApi(rowId)
|
||||
detailData.value = res
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
const elForm = unref(formRef)?.getElFormRef()
|
||||
if (!elForm) return
|
||||
elForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as SmsTemplateApi.SmsTemplateVO
|
||||
if (actionType.value === 'create') {
|
||||
await SmsTemplateApi.createSmsTemplateApi(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await SmsTemplateApi.updateSmsTemplateApi(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
// 刷新列表
|
||||
await reload()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ========== 测试相关 ==========
|
||||
const sendSmsForm = ref({
|
||||
content: '',
|
||||
params: {},
|
||||
mobile: '',
|
||||
templateCode: '',
|
||||
templateParams: {}
|
||||
})
|
||||
const sendSmsRules = ref({
|
||||
mobile: [{ required: true, message: '手机不能为空', trigger: 'blur' }],
|
||||
templateCode: [{ required: true, message: '模版编号不能为空', trigger: 'blur' }],
|
||||
templateParams: {}
|
||||
})
|
||||
const sendVisible = ref(false)
|
||||
|
||||
const handleSendSms = (row: any) => {
|
||||
sendSmsForm.value.content = row.content
|
||||
sendSmsForm.value.params = row.params
|
||||
sendSmsForm.value.templateCode = row.code
|
||||
sendSmsForm.value.templateParams = row.params.reduce(function (obj, item) {
|
||||
obj[item] = undefined
|
||||
return obj
|
||||
}, {})
|
||||
sendSmsRules.value.templateParams = row.params.reduce(function (obj, item) {
|
||||
obj[item] = { required: true, message: '参数 ' + item + ' 不能为空', trigger: 'change' }
|
||||
return obj
|
||||
}, {})
|
||||
sendVisible.value = true
|
||||
}
|
||||
|
||||
const sendSmsTest = async () => {
|
||||
const data: SmsTemplateApi.SendSmsReqVO = {
|
||||
mobile: sendSmsForm.value.mobile,
|
||||
templateCode: sendSmsForm.value.templateCode,
|
||||
templateParams: sendSmsForm.value.templateParams as unknown as Map<string, Object>
|
||||
}
|
||||
const res = await SmsTemplateApi.sendSmsApi(data)
|
||||
if (res) {
|
||||
message.success('提交发送成功!发送结果,见发送日志编号:' + res)
|
||||
}
|
||||
sendVisible.value = false
|
||||
}
|
||||
</script>
|
||||
81
src/views/system/sms/smsTemplate/sms.template.data.ts
Normal file
81
src/views/system/sms/smsTemplate/sms.template.data.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
type: [required],
|
||||
status: [required],
|
||||
code: [required],
|
||||
name: [required],
|
||||
content: [required],
|
||||
apiTemplateId: [required],
|
||||
channelId: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: 'seq',
|
||||
primaryTitle: '模板编号',
|
||||
action: true,
|
||||
actionWidth: '280',
|
||||
columns: [
|
||||
{
|
||||
title: '模板编码',
|
||||
field: 'code',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '模板名称',
|
||||
field: 'name',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '模板内容',
|
||||
field: 'content'
|
||||
},
|
||||
{
|
||||
title: '短信 API 的模板编号',
|
||||
field: 'apiTemplateId',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '短信类型',
|
||||
field: 'type',
|
||||
dictType: DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE,
|
||||
dictClass: 'number',
|
||||
isSearch: true,
|
||||
table: {
|
||||
width: 80
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true,
|
||||
table: {
|
||||
width: 80
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('form.remark'),
|
||||
field: 'remark',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
isForm: false,
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
174
src/views/system/tenant/index.vue
Normal file
174
src/views/system/tenant/index.vue
Normal file
@@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<template #toolbar_buttons>
|
||||
<!-- 操作:新增 -->
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:zoom-in"
|
||||
:title="t('action.add')"
|
||||
v-hasPermi="['system:tenant:create']"
|
||||
@click="handleCreate()"
|
||||
/>
|
||||
<XButton
|
||||
type="warning"
|
||||
preIcon="ep:download"
|
||||
:title="t('action.export')"
|
||||
v-hasPermi="['system:tenant:export']"
|
||||
@click="exportList('租户列表.xls')"
|
||||
/>
|
||||
</template>
|
||||
<template #accountCount_default="{ row }">
|
||||
<el-tag> {{ row.accountCount }} </el-tag>
|
||||
</template>
|
||||
<template #packageId_default="{ row }">
|
||||
<el-tag v-if="row.packageId === 0" type="danger">系统租户</el-tag>
|
||||
<el-tag v-else type="success"> {{ getPackageName(row.packageId) }} </el-tag>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:修改 -->
|
||||
<XTextButton
|
||||
preIcon="ep:edit"
|
||||
:title="t('action.edit')"
|
||||
v-hasPermi="['system:tenant:update']"
|
||||
@click="handleUpdate(row.id)"
|
||||
/>
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton
|
||||
preIcon="ep:view"
|
||||
:title="t('action.detail')"
|
||||
v-hasPermi="['system:tenant:update']"
|
||||
@click="handleDetail(row.id)"
|
||||
/>
|
||||
<!-- 操作:删除 -->
|
||||
<XTextButton
|
||||
preIcon="ep:delete"
|
||||
:title="t('action.del')"
|
||||
v-hasPermi="['system:tenant:delete']"
|
||||
@click="deleteData(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
<XModal v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
/>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailData"
|
||||
>
|
||||
<template #packageId="{ row }">
|
||||
<el-tag v-if="row.packageId === 0" type="danger">系统租户</el-tag>
|
||||
<el-tag v-else type="success"> {{ getPackageName(row.packageId) }} </el-tag>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<!-- 按钮:保存 -->
|
||||
<XButton
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:title="t('action.save')"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm()"
|
||||
/>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="Tenant">
|
||||
import type { FormExpose } from '@/components/Form'
|
||||
import * as TenantApi from '@/api/system/tenant'
|
||||
import { rules, allSchemas, tenantPackageOption } from './tenant.data'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
// 列表相关的变量
|
||||
const [registerTable, { reload, deleteData, exportList }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: TenantApi.getTenantPageApi,
|
||||
deleteApi: TenantApi.deleteTenantApi,
|
||||
exportListApi: TenantApi.exportTenantApi
|
||||
})
|
||||
|
||||
const actionLoading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const detailData = ref() // 详情 Ref
|
||||
const getPackageName = (packageId: number) => {
|
||||
for (let item of tenantPackageOption) {
|
||||
if (item.value === packageId) {
|
||||
return item.label
|
||||
}
|
||||
}
|
||||
return '未知套餐'
|
||||
}
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
// 重置表单
|
||||
setDialogTile('create')
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (rowId: number) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await TenantApi.getTenantApi(rowId)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (rowId: number) => {
|
||||
// 设置数据
|
||||
const res = await TenantApi.getTenantApi(rowId)
|
||||
detailData.value = res
|
||||
setDialogTile('detail')
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
const elForm = unref(formRef)?.getElFormRef()
|
||||
if (!elForm) return
|
||||
elForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as TenantApi.TenantVO
|
||||
if (actionType.value === 'create') {
|
||||
await TenantApi.createTenantApi(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await TenantApi.updateTenantApi(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
// 操作成功,重新加载列表
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
// 刷新列表
|
||||
await reload()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
135
src/views/system/tenant/tenant.data.ts
Normal file
135
src/views/system/tenant/tenant.data.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
import { getTenantPackageList, TenantPackageVO } from '@/api/system/tenantPackage'
|
||||
import { ComponentOptions } from '@/types/components'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
export const tenantPackageOption: ComponentOptions[] = []
|
||||
const getTenantPackageOptions = async () => {
|
||||
const res = await getTenantPackageList()
|
||||
res.forEach((tenantPackage: TenantPackageVO) => {
|
||||
tenantPackageOption.push({
|
||||
key: tenantPackage.id,
|
||||
value: tenantPackage.id,
|
||||
label: tenantPackage.name
|
||||
})
|
||||
})
|
||||
|
||||
return tenantPackageOption
|
||||
}
|
||||
getTenantPackageOptions()
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
packageId: [required],
|
||||
contactName: [required],
|
||||
contactMobile: [required],
|
||||
accountCount: [required],
|
||||
expireTime: [required],
|
||||
domain: [required],
|
||||
status: [required]
|
||||
})
|
||||
|
||||
// CrudSchema.
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryTitle: '租户编号',
|
||||
primaryType: 'seq',
|
||||
action: true,
|
||||
columns: [
|
||||
{
|
||||
title: '租户名称',
|
||||
field: 'name',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '租户套餐',
|
||||
field: 'packageId',
|
||||
table: {
|
||||
slots: {
|
||||
default: 'packageId_default'
|
||||
}
|
||||
},
|
||||
form: {
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: tenantPackageOption
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '联系人',
|
||||
field: 'contactName',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '联系手机',
|
||||
field: 'contactMobile',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '用户名称',
|
||||
field: 'username',
|
||||
isTable: false,
|
||||
isDetail: false
|
||||
},
|
||||
{
|
||||
title: '用户密码',
|
||||
field: 'password',
|
||||
isTable: false,
|
||||
isDetail: false,
|
||||
form: {
|
||||
component: 'InputPassword'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '账号额度',
|
||||
field: 'accountCount',
|
||||
table: {
|
||||
slots: {
|
||||
default: 'accountCount_default'
|
||||
}
|
||||
},
|
||||
form: {
|
||||
component: 'InputNumber'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '过期时间',
|
||||
field: 'expireTime',
|
||||
formatter: 'formatDate',
|
||||
form: {
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
type: 'datetime',
|
||||
valueFormat: 'x'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '绑定域名',
|
||||
field: 'domain'
|
||||
},
|
||||
{
|
||||
title: '租户状态',
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: t('table.createTime'),
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
isForm: false,
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
168
src/views/system/tenantPackage/index.vue
Normal file
168
src/views/system/tenantPackage/index.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<template #toolbar_buttons>
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:zoom-in"
|
||||
:title="t('action.add')"
|
||||
@click="handleCreate()"
|
||||
/>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<XTextButton preIcon="ep:edit" :title="t('action.edit')" @click="handleUpdate(row.id)" />
|
||||
<XTextButton preIcon="ep:delete" :title="t('action.del')" @click="deleteData(row.id)" />
|
||||
</template>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
<XModal v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
>
|
||||
<template #menuIds>
|
||||
<el-card class="w-120">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
全选/全不选:
|
||||
<el-switch
|
||||
v-model="treeNodeAll"
|
||||
inline-prompt
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
@change="handleCheckedTreeNodeAll()"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
:props="defaultProps"
|
||||
:data="menuOptions"
|
||||
empty-text="加载中,请稍后"
|
||||
/>
|
||||
</el-card>
|
||||
</template>
|
||||
</Form>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<!-- 按钮:保存 -->
|
||||
<XButton
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:title="t('action.save')"
|
||||
:loading="loading"
|
||||
@click="submitForm()"
|
||||
/>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="loading" :title="t('dialog.close')" @click="dialogVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="TenantPackage">
|
||||
import { handleTree, defaultProps } from '@/utils/tree'
|
||||
import type { FormExpose } from '@/components/Form'
|
||||
import type { ElTree } from 'element-plus'
|
||||
// 业务相关的 import
|
||||
import { rules, allSchemas } from './tenantPackage.data'
|
||||
import * as TenantPackageApi from '@/api/system/tenantPackage'
|
||||
import { listSimpleMenusApi } from '@/api/system/menu'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const menuOptions = ref<any[]>([]) // 树形结构
|
||||
const menuExpand = ref(false)
|
||||
const menuNodeAll = ref(false)
|
||||
const treeRef = ref<InstanceType<typeof ElTree>>()
|
||||
const treeNodeAll = ref(false)
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const loading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
|
||||
// 全选/全不选
|
||||
const handleCheckedTreeNodeAll = () => {
|
||||
treeRef.value!.setCheckedNodes(treeNodeAll.value ? menuOptions.value : [])
|
||||
}
|
||||
const getTree = async () => {
|
||||
const res = await listSimpleMenusApi()
|
||||
menuOptions.value = handleTree(res)
|
||||
}
|
||||
|
||||
const [registerTable, { reload, deleteData }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: TenantPackageApi.getTenantPackageTypePageApi,
|
||||
deleteApi: TenantPackageApi.deleteTenantPackageTypeApi
|
||||
})
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
//重置菜单树
|
||||
unref(treeRef)?.setCheckedKeys([])
|
||||
menuExpand.value = false
|
||||
menuNodeAll.value = false
|
||||
setDialogTile('create')
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (rowId: number) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await TenantPackageApi.getTenantPackageApi(rowId)
|
||||
unref(formRef)?.setValues(res)
|
||||
// 设置选中
|
||||
unref(treeRef)?.setCheckedKeys(res.menuIds)
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
const elForm = unref(formRef)?.getElFormRef()
|
||||
if (!elForm) return
|
||||
elForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
loading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as TenantPackageApi.TenantPackageVO
|
||||
data.menuIds = [
|
||||
...(treeRef.value!.getCheckedKeys(false) as unknown as Array<number>),
|
||||
...(treeRef.value!.getHalfCheckedKeys() as unknown as Array<number>)
|
||||
]
|
||||
if (actionType.value === 'create') {
|
||||
await TenantPackageApi.createTenantPackageTypeApi(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await TenantPackageApi.updateTenantPackageTypeApi(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
// 操作成功,重新加载列表
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
loading.value = false
|
||||
// 刷新列表
|
||||
await reload()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
onMounted(async () => {
|
||||
await getTree()
|
||||
})
|
||||
// getList()
|
||||
</script>
|
||||
68
src/views/system/tenantPackage/tenantPackage.data.ts
Normal file
68
src/views/system/tenantPackage/tenantPackage.data.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
id: [required],
|
||||
type: [required],
|
||||
remark: [required],
|
||||
status: [required],
|
||||
menuIds: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: 'seq',
|
||||
primaryTitle: '套餐编号',
|
||||
action: true,
|
||||
columns: [
|
||||
{
|
||||
title: '套餐名称',
|
||||
field: 'name',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '菜单权限',
|
||||
field: 'menuIds',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: t('form.remark'),
|
||||
field: 'remark',
|
||||
isTable: false,
|
||||
isSearch: true,
|
||||
form: {
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 4
|
||||
},
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
isForm: false,
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
547
src/views/system/user/index.vue
Normal file
547
src/views/system/user/index.vue
Normal file
@@ -0,0 +1,547 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<el-card class="w-1/5 user" :gutter="12" shadow="always">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>部门列表</span>
|
||||
<XTextButton title="修改部门" @click="handleDeptEdit()" />
|
||||
</div>
|
||||
</template>
|
||||
<el-input v-model="filterText" placeholder="搜索部门" />
|
||||
<el-scrollbar height="650">
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
node-key="id"
|
||||
default-expand-all
|
||||
:data="deptOptions"
|
||||
:props="defaultProps"
|
||||
:highlight-current="true"
|
||||
:filter-node-method="filterNode"
|
||||
:expand-on-click-node="false"
|
||||
@node-click="handleDeptNodeClick"
|
||||
/>
|
||||
</el-scrollbar>
|
||||
</el-card>
|
||||
<el-card class="w-4/5 user" style="margin-left: 10px" :gutter="12" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ tableTitle }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<template #toolbar_buttons>
|
||||
<!-- 操作:新增 -->
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:zoom-in"
|
||||
:title="t('action.add')"
|
||||
v-hasPermi="['system:user:create']"
|
||||
@click="handleCreate()"
|
||||
/>
|
||||
<!-- 操作:导入用户 -->
|
||||
<XButton
|
||||
type="warning"
|
||||
preIcon="ep:upload"
|
||||
:title="t('action.import')"
|
||||
v-hasPermi="['system:user:import']"
|
||||
@click="importDialogVisible = true"
|
||||
/>
|
||||
<!-- 操作:导出用户 -->
|
||||
<XButton
|
||||
type="warning"
|
||||
preIcon="ep:download"
|
||||
:title="t('action.export')"
|
||||
v-hasPermi="['system:user:export']"
|
||||
@click="exportList('用户数据.xls')"
|
||||
/>
|
||||
</template>
|
||||
<template #status_default="{ row }">
|
||||
<el-switch
|
||||
v-model="row.status"
|
||||
:active-value="0"
|
||||
:inactive-value="1"
|
||||
@change="handleStatusChange(row)"
|
||||
/>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:编辑 -->
|
||||
<XTextButton
|
||||
preIcon="ep:edit"
|
||||
:title="t('action.edit')"
|
||||
v-hasPermi="['system:user:update']"
|
||||
@click="handleUpdate(row.id)"
|
||||
/>
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton
|
||||
preIcon="ep:view"
|
||||
:title="t('action.detail')"
|
||||
v-hasPermi="['system:user:update']"
|
||||
@click="handleDetail(row.id)"
|
||||
/>
|
||||
<el-dropdown
|
||||
class="p-0.5"
|
||||
v-hasPermi="[
|
||||
'system:user:update-password',
|
||||
'system:permission:assign-user-role',
|
||||
'system:user:delete'
|
||||
]"
|
||||
>
|
||||
<XTextButton :title="t('action.more')" postIcon="ep:arrow-down" />
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>
|
||||
<!-- 操作:重置密码 -->
|
||||
<XTextButton
|
||||
preIcon="ep:key"
|
||||
title="重置密码"
|
||||
v-hasPermi="['system:user:update-password']"
|
||||
@click="handleResetPwd(row)"
|
||||
/>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<!-- 操作:分配角色 -->
|
||||
<XTextButton
|
||||
preIcon="ep:key"
|
||||
title="分配角色"
|
||||
v-hasPermi="['system:permission:assign-user-role']"
|
||||
@click="handleRole(row)"
|
||||
/>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<!-- 操作:删除 -->
|
||||
<XTextButton
|
||||
preIcon="ep:delete"
|
||||
:title="t('action.del')"
|
||||
v-hasPermi="['system:user:delete']"
|
||||
@click="deleteData(row.id)"
|
||||
/>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</XTable>
|
||||
</el-card>
|
||||
</div>
|
||||
<XModal v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:rules="rules"
|
||||
:schema="allSchemas.formSchema"
|
||||
ref="formRef"
|
||||
>
|
||||
<template #deptId="form">
|
||||
<el-tree-select
|
||||
node-key="id"
|
||||
v-model="form['deptId']"
|
||||
:props="defaultProps"
|
||||
:data="deptOptions"
|
||||
check-strictly
|
||||
/>
|
||||
</template>
|
||||
<template #postIds="form">
|
||||
<el-select v-model="form['postIds']" multiple :placeholder="t('common.selectText')">
|
||||
<el-option
|
||||
v-for="item in postOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="(item.id as unknown as number)"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</Form>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailData"
|
||||
>
|
||||
<template #deptId="{ row }">
|
||||
<span>{{ row.dept?.name }}</span>
|
||||
</template>
|
||||
<template #postIds="{ row }">
|
||||
<template v-if="row.postIds !== ''">
|
||||
<el-tag v-for="(post, index) in row.postIds" :key="index" index="">
|
||||
<template v-for="postObj in postOptions">
|
||||
{{ post === postObj.id ? postObj.name : '' }}
|
||||
</template>
|
||||
</el-tag>
|
||||
</template>
|
||||
<template v-else> </template>
|
||||
</template>
|
||||
</Descriptions>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<!-- 按钮:保存 -->
|
||||
<XButton
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:title="t('action.save')"
|
||||
:loading="loading"
|
||||
@click="submitForm()"
|
||||
/>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="loading" :title="t('dialog.close')" @click="dialogVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
<!-- 分配用户角色 -->
|
||||
<XModal v-model="roleDialogVisible" title="分配角色">
|
||||
<el-form :model="userRole" label-width="140px" :inline="true">
|
||||
<el-form-item label="用户名称">
|
||||
<el-tag>{{ userRole.username }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户昵称">
|
||||
<el-tag>{{ userRole.nickname }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色">
|
||||
<el-transfer
|
||||
v-model="userRole.roleIds"
|
||||
:titles="['角色列表', '已选择']"
|
||||
:props="{
|
||||
key: 'id',
|
||||
label: 'name'
|
||||
}"
|
||||
:data="roleOptions"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<!-- 按钮:保存 -->
|
||||
<XButton type="primary" :title="t('action.save')" :loading="loading" @click="submitRole()" />
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :title="t('dialog.close')" @click="roleDialogVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
<!-- 导入 -->
|
||||
<XModal v-model="importDialogVisible" :title="importDialogTitle">
|
||||
<el-form class="drawer-multiColumn-form" label-width="150px">
|
||||
<el-form-item label="模板下载 :">
|
||||
<XButton type="primary" prefix="ep:download" title="点击下载" @click="handleImportTemp()" />
|
||||
</el-form-item>
|
||||
<el-form-item label="文件上传 :">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:action="updateUrl + '?updateSupport=' + updateSupport"
|
||||
:headers="uploadHeaders"
|
||||
:drag="true"
|
||||
:limit="1"
|
||||
:multiple="true"
|
||||
:show-file-list="true"
|
||||
:disabled="uploadDisabled"
|
||||
:before-upload="beforeExcelUpload"
|
||||
:on-exceed="handleExceed"
|
||||
:on-success="handleFileSuccess"
|
||||
:on-error="excelUploadError"
|
||||
:auto-upload="false"
|
||||
accept="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
>
|
||||
<Icon icon="ep:upload-filled" />
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">请上传 .xls , .xlsx 标准格式文件</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否更新已经存在的用户数据:">
|
||||
<el-checkbox v-model="updateSupport" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<!-- 按钮:保存 -->
|
||||
<XButton
|
||||
type="warning"
|
||||
preIcon="ep:upload-filled"
|
||||
:title="t('action.save')"
|
||||
@click="submitFileForm()"
|
||||
/>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :title="t('dialog.close')" @click="importDialogVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="User">
|
||||
import type { ElTree, UploadRawFile, UploadInstance } from 'element-plus'
|
||||
import { handleTree, defaultProps } from '@/utils/tree'
|
||||
import download from '@/utils/download'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import { getAccessToken, getTenantId } from '@/utils/auth'
|
||||
import type { FormExpose } from '@/components/Form'
|
||||
import { rules, allSchemas } from './user.data'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import { listSimpleDeptApi } from '@/api/system/dept'
|
||||
import { listSimpleRolesApi } from '@/api/system/role'
|
||||
import { listSimplePostsApi, PostVO } from '@/api/system/post'
|
||||
import {
|
||||
aassignUserRoleApi,
|
||||
listUserRolesApi,
|
||||
PermissionAssignUserRoleReqVO
|
||||
} from '@/api/system/permission'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const queryParams = reactive({
|
||||
deptId: null
|
||||
})
|
||||
// ========== 列表相关 ==========
|
||||
const tableTitle = ref('用户列表')
|
||||
// 列表相关的变量
|
||||
const [registerTable, { reload, deleteData, exportList }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
params: queryParams,
|
||||
getListApi: UserApi.getUserPageApi,
|
||||
deleteApi: UserApi.deleteUserApi,
|
||||
exportListApi: UserApi.exportUserApi
|
||||
})
|
||||
// ========== 创建部门树结构 ==========
|
||||
const filterText = ref('')
|
||||
const deptOptions = ref<Tree[]>([]) // 树形结构
|
||||
const treeRef = ref<InstanceType<typeof ElTree>>()
|
||||
const getTree = async () => {
|
||||
const res = await listSimpleDeptApi()
|
||||
deptOptions.value.push(...handleTree(res))
|
||||
}
|
||||
const filterNode = (value: string, data: Tree) => {
|
||||
if (!value) return true
|
||||
return data.name.includes(value)
|
||||
}
|
||||
const handleDeptNodeClick = async (row: { [key: string]: any }) => {
|
||||
queryParams.deptId = row.id
|
||||
await reload()
|
||||
}
|
||||
const { push } = useRouter()
|
||||
const handleDeptEdit = () => {
|
||||
push('/system/dept')
|
||||
}
|
||||
watch(filterText, (val) => {
|
||||
treeRef.value!.filter(val)
|
||||
})
|
||||
// ========== CRUD 相关 ==========
|
||||
const loading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const postOptions = ref<PostVO[]>([]) //岗位列表
|
||||
|
||||
// 获取岗位列表
|
||||
const getPostOptions = async () => {
|
||||
const res = await listSimplePostsApi()
|
||||
postOptions.value.push(...res)
|
||||
}
|
||||
// 设置标题
|
||||
const setDialogTile = async (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = async () => {
|
||||
setDialogTile('create')
|
||||
// 重置表单
|
||||
await nextTick()
|
||||
if (allSchemas.formSchema[0].field !== 'username') {
|
||||
unref(formRef)?.addSchema(
|
||||
{
|
||||
field: 'username',
|
||||
label: '用户账号',
|
||||
component: 'Input'
|
||||
},
|
||||
0
|
||||
)
|
||||
unref(formRef)?.addSchema(
|
||||
{
|
||||
field: 'password',
|
||||
label: '用户密码',
|
||||
component: 'InputPassword'
|
||||
},
|
||||
1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (rowId: number) => {
|
||||
setDialogTile('update')
|
||||
await nextTick()
|
||||
unref(formRef)?.delSchema('username')
|
||||
unref(formRef)?.delSchema('password')
|
||||
// 设置数据
|
||||
const res = await UserApi.getUserApi(rowId)
|
||||
unref(formRef)?.setValues(res)
|
||||
}
|
||||
const detailData = ref()
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (rowId: number) => {
|
||||
// 设置数据
|
||||
const res = await UserApi.getUserApi(rowId)
|
||||
detailData.value = res
|
||||
await setDialogTile('detail')
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
loading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as UserApi.UserVO
|
||||
if (actionType.value === 'create') {
|
||||
await UserApi.createUserApi(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await UserApi.updateUserApi(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
// unref(formRef)?.setSchema(allSchemas.formSchema)
|
||||
// 刷新列表
|
||||
await reload()
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
// 改变用户状态操作
|
||||
const handleStatusChange = async (row: UserApi.UserVO) => {
|
||||
const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'
|
||||
message
|
||||
.confirm('确认要"' + text + '""' + row.username + '"用户吗?', t('common.reminder'))
|
||||
.then(async () => {
|
||||
row.status =
|
||||
row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.ENABLE : CommonStatusEnum.DISABLE
|
||||
await UserApi.updateUserStatusApi(row.id, row.status)
|
||||
message.success(text + '成功')
|
||||
// 刷新列表
|
||||
await reload()
|
||||
})
|
||||
.catch(() => {
|
||||
row.status =
|
||||
row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
|
||||
})
|
||||
}
|
||||
// 重置密码
|
||||
const handleResetPwd = (row: UserApi.UserVO) => {
|
||||
message.prompt('请输入"' + row.username + '"的新密码', t('common.reminder')).then(({ value }) => {
|
||||
UserApi.resetUserPwdApi(row.id, value).then(() => {
|
||||
message.success('修改成功,新密码是:' + value)
|
||||
})
|
||||
})
|
||||
}
|
||||
// 分配角色
|
||||
const roleDialogVisible = ref(false)
|
||||
const roleOptions = ref()
|
||||
const userRole = reactive({
|
||||
id: 0,
|
||||
username: '',
|
||||
nickname: '',
|
||||
roleIds: []
|
||||
})
|
||||
const handleRole = async (row: UserApi.UserVO) => {
|
||||
userRole.id = row.id
|
||||
userRole.username = row.username
|
||||
userRole.nickname = row.nickname
|
||||
// 获得角色拥有的权限集合
|
||||
const roles = await listUserRolesApi(row.id)
|
||||
userRole.roleIds = roles
|
||||
// 获取角色列表
|
||||
const roleOpt = await listSimpleRolesApi()
|
||||
roleOptions.value = roleOpt
|
||||
roleDialogVisible.value = true
|
||||
}
|
||||
// 提交
|
||||
const submitRole = async () => {
|
||||
const data = ref<PermissionAssignUserRoleReqVO>({
|
||||
userId: userRole.id,
|
||||
roleIds: userRole.roleIds
|
||||
})
|
||||
await aassignUserRoleApi(data.value)
|
||||
message.success(t('common.updateSuccess'))
|
||||
roleDialogVisible.value = false
|
||||
}
|
||||
// ========== 导入相关 ==========
|
||||
// TODO @星语:这个要不要把导入用户,封装成一个小组件?可选哈
|
||||
const importDialogVisible = ref(false)
|
||||
const uploadDisabled = ref(false)
|
||||
const importDialogTitle = ref('用户导入')
|
||||
const updateSupport = ref(0)
|
||||
let updateUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import'
|
||||
const uploadHeaders = ref()
|
||||
// 下载导入模版
|
||||
const handleImportTemp = async () => {
|
||||
const res = await UserApi.importUserTemplateApi()
|
||||
download.excel(res, '用户导入模版.xls')
|
||||
}
|
||||
// 文件上传之前判断
|
||||
const beforeExcelUpload = (file: UploadRawFile) => {
|
||||
const isExcel =
|
||||
file.type === 'application/vnd.ms-excel' ||
|
||||
file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
const isLt5M = file.size / 1024 / 1024 < 5
|
||||
if (!isExcel) message.error('上传文件只能是 xls / xlsx 格式!')
|
||||
if (!isLt5M) message.error('上传文件大小不能超过 5MB!')
|
||||
return isExcel && isLt5M
|
||||
}
|
||||
// 文件上传
|
||||
const uploadRef = ref<UploadInstance>()
|
||||
const submitFileForm = () => {
|
||||
uploadHeaders.value = {
|
||||
Authorization: 'Bearer ' + getAccessToken(),
|
||||
'tenant-id': getTenantId()
|
||||
}
|
||||
uploadDisabled.value = true
|
||||
uploadRef.value!.submit()
|
||||
}
|
||||
// 文件上传成功
|
||||
const handleFileSuccess = async (response: any): Promise<void> => {
|
||||
if (response.code !== 0) {
|
||||
message.error(response.msg)
|
||||
return
|
||||
}
|
||||
importDialogVisible.value = false
|
||||
uploadDisabled.value = false
|
||||
const data = response.data
|
||||
let text = '上传成功数量:' + data.createUsernames.length + ';'
|
||||
for (let username of data.createUsernames) {
|
||||
text += '< ' + username + ' >'
|
||||
}
|
||||
text += '更新成功数量:' + data.updateUsernames.length + ';'
|
||||
for (const username of data.updateUsernames) {
|
||||
text += '< ' + username + ' >'
|
||||
}
|
||||
text += '更新失败数量:' + Object.keys(data.failureUsernames).length + ';'
|
||||
for (const username in data.failureUsernames) {
|
||||
text += '< ' + username + ': ' + data.failureUsernames[username] + ' >'
|
||||
}
|
||||
message.alert(text)
|
||||
await reload()
|
||||
}
|
||||
// 文件数超出提示
|
||||
const handleExceed = (): void => {
|
||||
message.error('最多只能上传一个文件!')
|
||||
}
|
||||
// 上传错误提示
|
||||
const excelUploadError = (): void => {
|
||||
message.error('导入数据失败,请您重新上传!')
|
||||
}
|
||||
// ========== 初始化 ==========
|
||||
onMounted(async () => {
|
||||
await getPostOptions()
|
||||
await getTree()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user {
|
||||
height: 780px;
|
||||
max-height: 800px;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
107
src/views/system/user/user.data.ts
Normal file
107
src/views/system/user/user.data.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
// 国际化
|
||||
const { t } = useI18n()
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
username: [required],
|
||||
nickname: [required],
|
||||
email: [required],
|
||||
status: [required],
|
||||
mobile: [
|
||||
{
|
||||
len: 11,
|
||||
trigger: 'blur',
|
||||
message: '请输入正确的手机号码'
|
||||
}
|
||||
]
|
||||
})
|
||||
// crudSchemas
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: 'seq',
|
||||
primaryTitle: '用户编号',
|
||||
action: true,
|
||||
actionWidth: '200px',
|
||||
columns: [
|
||||
{
|
||||
title: '用户账号',
|
||||
field: 'username',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '用户密码',
|
||||
field: 'password',
|
||||
isDetail: false,
|
||||
isTable: false,
|
||||
form: {
|
||||
component: 'InputPassword'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '用户昵称',
|
||||
field: 'nickname'
|
||||
},
|
||||
{
|
||||
title: '用户邮箱',
|
||||
field: 'email'
|
||||
},
|
||||
{
|
||||
title: '手机号码',
|
||||
field: 'mobile',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '部门',
|
||||
field: 'deptId',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: '岗位',
|
||||
field: 'postIds',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true,
|
||||
table: {
|
||||
slots: {
|
||||
default: 'status_default'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '最后登录时间',
|
||||
field: 'loginDate',
|
||||
formatter: 'formatDate',
|
||||
isForm: false
|
||||
},
|
||||
{
|
||||
title: '最后登录IP',
|
||||
field: 'loginIp',
|
||||
isTable: false,
|
||||
isForm: false
|
||||
},
|
||||
{
|
||||
title: t('form.remark'),
|
||||
field: 'remark',
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
isTable: false,
|
||||
isForm: false,
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
||||
Reference in New Issue
Block a user