Merge remote-tracking branch 'yudao/dev'

This commit is contained in:
puhui999 2023-03-22 23:54:24 +08:00
commit 5057e755e2
100 changed files with 5766 additions and 4082 deletions
.gitignore
build/vite
src
api
bpm/userGroup
infra
apiErrorLog
config
dataSourceConfig
file
fileConfig
system
components
ContentWrap/src
Form/src
Pagination
Search/src
Table/src
hooks/web
locales
router/modules
store/modules
types
utils
views

2
.gitignore vendored
View File

@ -5,4 +5,4 @@ dist-ssr
*.local
/dist*
*-lock.*
pnpm-debug
pnpm-debug

View File

@ -39,11 +39,14 @@ export function createVitePlugins(VITE_APP_TITLE: string) {
imports: [
'vue',
'vue-router',
// 可额外添加需要 autoImport 的组件
{
'@/hooks/web/useI18n': ['useI18n'],
'@/hooks/web/useXTable': ['useXTable'],
'@/hooks/web/useMessage': ['useMessage'],
'@/hooks/web/useXTable': ['useXTable'],
'@/hooks/web/useVxeCrudSchemas': ['useVxeCrudSchemas'],
'@/hooks/web/useTable': ['useTable'],
'@/hooks/web/useCrudSchemas': ['useCrudSchemas'],
'@/utils/formRules': ['required'],
'@/utils/dict': ['DICT_TYPE']
}

View File

@ -11,7 +11,7 @@ export type UserGroupVO = {
}
// 创建用户组
export const createUserGroupApi = async (data: UserGroupVO) => {
export const createUserGroup = async (data: UserGroupVO) => {
return await request.post({
url: '/bpm/user-group/create',
data: data
@ -19,7 +19,7 @@ export const createUserGroupApi = async (data: UserGroupVO) => {
}
// 更新用户组
export const updateUserGroupApi = async (data: UserGroupVO) => {
export const updateUserGroup = async (data: UserGroupVO) => {
return await request.put({
url: '/bpm/user-group/update',
data: data
@ -27,21 +27,21 @@ export const updateUserGroupApi = async (data: UserGroupVO) => {
}
// 删除用户组
export const deleteUserGroupApi = async (id: number) => {
export const deleteUserGroup = async (id: number) => {
return await request.delete({ url: '/bpm/user-group/delete?id=' + id })
}
// 获得用户组
export const getUserGroupApi = async (id: number) => {
export const getUserGroup = async (id: number) => {
return await request.get({ url: '/bpm/user-group/get?id=' + id })
}
// 获得用户组分页
export const getUserGroupPageApi = async (params) => {
export const getUserGroupPage = async (params) => {
return await request.get({ url: '/bpm/user-group/page', params })
}
// 获取用户组精简信息列表
export const listSimpleUserGroupsApi = async () => {
export const listSimpleUserGroup = async () => {
return await request.get({ url: '/bpm/user-group/list-all-simple' })
}

View File

@ -27,38 +27,20 @@ export interface ApiErrorLogVO {
createTime: Date
}
export interface ApiErrorLogPageReqVO extends PageParam {
userId?: number
userType?: number
applicationName?: string
requestUrl?: string
exceptionTime?: Date[]
processStatus: number
}
export interface ApiErrorLogExportReqVO {
userId?: number
userType?: number
applicationName?: string
requestUrl?: string
exceptionTime?: Date[]
processStatus: number
}
// 查询列表API 访问日志
export const getApiErrorLogPageApi = (params: ApiErrorLogPageReqVO) => {
export const getApiErrorLogPage = (params: PageParam) => {
return request.get({ url: '/infra/api-error-log/page', params })
}
// 更新 API 错误日志的处理状态
export const updateApiErrorLogPageApi = (id: number, processStatus: number) => {
export const updateApiErrorLogPage = (id: number, processStatus: number) => {
return request.put({
url: '/infra/api-error-log/update-status?id=' + id + '&processStatus=' + processStatus
})
}
// 导出API 访问日志
export const exportApiErrorLogApi = (params: ApiErrorLogExportReqVO) => {
export const exportApiErrorLog = (params) => {
return request.download({
url: '/infra/api-error-log/export-excel',
params

View File

@ -12,13 +12,6 @@ export interface ConfigVO {
createTime: Date
}
export interface ConfigExportReqVO {
name?: string
key?: string
type?: number
createTime?: Date[]
}
// 查询参数列表
export const getConfigPage = (params: PageParam) => {
return request.get({ url: '/infra/config/page', params })
@ -50,6 +43,6 @@ export const deleteConfig = (id: number) => {
}
// 导出参数
export const exportConfigApi = (params: ConfigExportReqVO) => {
export const exportConfigApi = (params) => {
return request.download({ url: '/infra/config/export', params })
}

View File

@ -1,35 +1,35 @@
import request from '@/config/axios'
export interface DataSourceConfigVO {
id: number
id: number | undefined
name: string
url: string
username: string
password: string
createTime: Date
}
// 查询数据源配置列表
export const getDataSourceConfigListApi = () => {
return request.get({ url: '/infra/data-source-config/list' })
}
// 查询数据源配置详情
export const getDataSourceConfigApi = (id: number) => {
return request.get({ url: '/infra/data-source-config/get?id=' + id })
createTime?: Date
}
// 新增数据源配置
export const createDataSourceConfigApi = (data: DataSourceConfigVO) => {
export const createDataSourceConfig = (data: DataSourceConfigVO) => {
return request.post({ url: '/infra/data-source-config/create', data })
}
// 修改数据源配置
export const updateDataSourceConfigApi = (data: DataSourceConfigVO) => {
export const updateDataSourceConfig = (data: DataSourceConfigVO) => {
return request.put({ url: '/infra/data-source-config/update', data })
}
// 删除数据源配置
export const deleteDataSourceConfigApi = (id: number) => {
export const deleteDataSourceConfig = (id: number) => {
return request.delete({ url: '/infra/data-source-config/delete?id=' + id })
}
// 查询数据源配置详情
export const getDataSourceConfig = (id: number) => {
return request.get({ url: '/infra/data-source-config/get?id=' + id })
}
// 查询数据源配置列表
export const getDataSourceConfigList = () => {
return request.get({ url: '/infra/data-source-config/list' })
}

View File

@ -1,16 +1,5 @@
import request from '@/config/axios'
export interface FileVO {
id: number
configId: number
path: string
name: string
url: string
size: string
type: string
createTime: Date
}
export interface FilePageReqVO extends PageParam {
path?: string
type?: string
@ -18,11 +7,11 @@ export interface FilePageReqVO extends PageParam {
}
// 查询文件列表
export const getFilePageApi = (params: FilePageReqVO) => {
export const getFilePage = (params: FilePageReqVO) => {
return request.get({ url: '/infra/file/page', params })
}
// 删除文件
export const deleteFileApi = (id: number) => {
export const deleteFile = (id: number) => {
return request.delete({ url: '/infra/file/delete?id=' + id })
}

View File

@ -13,6 +13,7 @@ export interface FileClientConfig {
accessSecret?: string
domain: string
}
export interface FileConfigVO {
id: number
name: string
@ -24,43 +25,37 @@ export interface FileConfigVO {
createTime: Date
}
export interface FileConfigPageReqVO extends PageParam {
name?: string
storage?: number
createTime?: Date[]
}
// 查询文件配置列表
export const getFileConfigPageApi = (params: FileConfigPageReqVO) => {
export const getFileConfigPage = (params: PageParam) => {
return request.get({ url: '/infra/file-config/page', params })
}
// 查询文件配置详情
export const getFileConfigApi = (id: number) => {
export const getFileConfig = (id: number) => {
return request.get({ url: '/infra/file-config/get?id=' + id })
}
// 更新文件配置为主配置
export const updateFileConfigMasterApi = (id: number) => {
export const updateFileConfigMaster = (id: number) => {
return request.put({ url: '/infra/file-config/update-master?id=' + id })
}
// 新增文件配置
export const createFileConfigApi = (data: FileConfigVO) => {
export const createFileConfig = (data: FileConfigVO) => {
return request.post({ url: '/infra/file-config/create', data })
}
// 修改文件配置
export const updateFileConfigApi = (data: FileConfigVO) => {
export const updateFileConfig = (data: FileConfigVO) => {
return request.put({ url: '/infra/file-config/update', data })
}
// 删除文件配置
export const deleteFileConfigApi = (id: number) => {
export const deleteFileConfig = (id: number) => {
return request.delete({ url: '/infra/file-config/delete?id=' + id })
}
// 测试文件配置
export const testFileConfigApi = (id: number) => {
export const testFileConfig = (id: number) => {
return request.get({ url: '/infra/file-config/test?id=' + id })
}

View File

@ -0,0 +1,11 @@
import request from '@/config/axios'
// 获得地区树
export const getAreaTree = async () => {
return await request.get({ url: '/system/area/tree' })
}
// 获得 IP 对应的地区名
export const getAreaByIp = async (ip: string) => {
return await request.get({ url: '/system/area/get-by-ip?ip=' + ip })
}

View File

@ -1,36 +1,49 @@
import request from '@/config/axios'
import type { DictDataVO, DictDataPageReqVO, DictDataExportReqVO } from './types'
export type DictDataVO = {
id: number | undefined
sort: number | undefined
label: string
value: string
dictType: string
status: number
colorType: string
cssClass: string
remark: string
createTime: Date
}
// 查询字典数据(精简)列表
export const listSimpleDictDataApi = () => {
export const listSimpleDictData = () => {
return request.get({ url: '/system/dict-data/list-all-simple' })
}
// 查询字典数据列表
export const getDictDataPageApi = (params: DictDataPageReqVO) => {
export const getDictDataPage = (params: PageParam) => {
return request.get({ url: '/system/dict-data/page', params })
}
// 查询字典数据详情
export const getDictDataApi = (id: number) => {
export const getDictData = (id: number) => {
return request.get({ url: '/system/dict-data/get?id=' + id })
}
// 新增字典数据
export const createDictDataApi = (data: DictDataVO) => {
export const createDictData = (data: DictDataVO) => {
return request.post({ url: '/system/dict-data/create', data })
}
// 修改字典数据
export const updateDictDataApi = (data: DictDataVO) => {
export const updateDictData = (data: DictDataVO) => {
return request.put({ url: '/system/dict-data/update', data })
}
// 删除字典数据
export const deleteDictDataApi = (id: number) => {
export const deleteDictData = (id: number) => {
return request.delete({ url: '/system/dict-data/delete?id=' + id })
}
// 导出字典类型数据
export const exportDictDataApi = (params: DictDataExportReqVO) => {
export const exportDictDataApi = (params) => {
return request.get({ url: '/system/dict-data/export', params })
}

View File

@ -1,36 +1,44 @@
import request from '@/config/axios'
import type { DictTypeVO, DictTypePageReqVO, DictTypeExportReqVO } from './types'
export type DictTypeVO = {
id: number | undefined
name: string
type: string
status: number
remark: string
createTime: Date
}
// 查询字典(精简)列表
export const listSimpleDictTypeApi = () => {
export const listSimpleDictType = () => {
return request.get({ url: '/system/dict-type/list-all-simple' })
}
// 查询字典列表
export const getDictTypePageApi = (params: DictTypePageReqVO) => {
export const getDictTypePage = (params: PageParam) => {
return request.get({ url: '/system/dict-type/page', params })
}
// 查询字典详情
export const getDictTypeApi = (id: number) => {
export const getDictType = (id: number) => {
return request.get({ url: '/system/dict-type/get?id=' + id })
}
// 新增字典
export const createDictTypeApi = (data: DictTypeVO) => {
export const createDictType = (data: DictTypeVO) => {
return request.post({ url: '/system/dict-type/create', data })
}
// 修改字典
export const updateDictTypeApi = (data: DictTypeVO) => {
export const updateDictType = (data: DictTypeVO) => {
return request.put({ url: '/system/dict-type/update', data })
}
// 删除字典
export const deleteDictTypeApi = (id: number) => {
export const deleteDictType = (id: number) => {
return request.delete({ url: '/system/dict-type/delete?id=' + id })
}
// 导出字典类型
export const exportDictTypeApi = (params: DictTypeExportReqVO) => {
export const exportDictType = (params) => {
return request.get({ url: '/system/dict-type/export', params })
}

View File

@ -1,46 +0,0 @@
export type DictTypeVO = {
id: number
name: string
type: string
status: number
remark: string
createTime: Date
}
export type DictTypePageReqVO = {
name: string
type: string
status: number
createTime: Date[]
}
export type DictTypeExportReqVO = {
name: string
type: string
status: number
createTime: Date[]
}
export type DictDataVO = {
id: number
sort: number
label: string
value: string
dictType: string
status: number
colorType: string
cssClass: string
remark: string
createTime: Date
}
export type DictDataPageReqVO = {
label: string
dictType: string
status: number
}
export type DictDataExportReqVO = {
label: string
dictType: string
status: number
}

View File

@ -1,10 +1,10 @@
import request from '@/config/axios'
export interface ErrorCodeVO {
id: number
id: number | undefined
type: number
applicationName: string
code: number
code: number | undefined
message: string
memo: string
createTime: Date

View File

@ -10,37 +10,32 @@ export interface MailAccountVO {
sslEnable: boolean
}
export interface MailAccountPageReqVO extends PageParam {
mail?: string
username?: string
}
// 查询邮箱账号列表
export const getMailAccountPageApi = async (params: MailAccountPageReqVO) => {
export const getMailAccountPage = async (params: PageParam) => {
return await request.get({ url: '/system/mail-account/page', params })
}
// 查询邮箱账号详情
export const getMailAccountApi = async (id: number) => {
export const getMailAccount = async (id: number) => {
return await request.get({ url: '/system/mail-account/get?id=' + id })
}
// 新增邮箱账号
export const createMailAccountApi = async (data: MailAccountVO) => {
export const createMailAccount = async (data: MailAccountVO) => {
return await request.post({ url: '/system/mail-account/create', data })
}
// 修改邮箱账号
export const updateMailAccountApi = async (data: MailAccountVO) => {
export const updateMailAccount = async (data: MailAccountVO) => {
return await request.put({ url: '/system/mail-account/update', data })
}
// 删除邮箱账号
export const deleteMailAccountApi = async (id: number) => {
export const deleteMailAccount = async (id: number) => {
return await request.delete({ url: '/system/mail-account/delete?id=' + id })
}
// 获得邮箱账号精简列表
export const getSimpleMailAccounts = async () => {
export const getSimpleMailAccountList = async () => {
return request.get({ url: '/system/mail-account/list-all-simple' })
}

View File

@ -19,22 +19,12 @@ export interface MailLogVO {
sendException: string
}
export interface MailLogPageReqVO extends PageParam {
userId?: number
userType?: number
toMail?: string
accountId?: number
templateId?: number
sendStatus?: number
sendTime?: Date[]
}
// 查询邮件日志列表
export const getMailLogPageApi = async (params: MailLogPageReqVO) => {
export const getMailLogPage = async (params: PageParam) => {
return await request.get({ url: '/system/mail-log/page', params })
}
// 查询邮件日志详情
export const getMailLogApi = async (id: number) => {
export const getMailLog = async (id: number) => {
return await request.get({ url: '/system/mail-log/get?id=' + id })
}

View File

@ -13,14 +13,6 @@ export interface MailTemplateVO {
remark: string
}
export interface MailTemplatePageReqVO extends PageParam {
name?: string
code?: string
accountId?: number
status?: number
createTime?: Date[]
}
export interface MailSendReqVO {
mail: string
templateCode: string
@ -28,31 +20,31 @@ export interface MailSendReqVO {
}
// 查询邮件模版列表
export const getMailTemplatePageApi = async (params: MailTemplatePageReqVO) => {
export const getMailTemplatePage = async (params: PageParam) => {
return await request.get({ url: '/system/mail-template/page', params })
}
// 查询邮件模版详情
export const getMailTemplateApi = async (id: number) => {
export const getMailTemplate = async (id: number) => {
return await request.get({ url: '/system/mail-template/get?id=' + id })
}
// 新增邮件模版
export const createMailTemplateApi = async (data: MailTemplateVO) => {
export const createMailTemplate = async (data: MailTemplateVO) => {
return await request.post({ url: '/system/mail-template/create', data })
}
// 修改邮件模版
export const updateMailTemplateApi = async (data: MailTemplateVO) => {
export const updateMailTemplate = async (data: MailTemplateVO) => {
return await request.put({ url: '/system/mail-template/update', data })
}
// 删除邮件模版
export const deleteMailTemplateApi = async (id: number) => {
export const deleteMailTemplate = async (id: number) => {
return await request.delete({ url: '/system/mail-template/delete?id=' + id })
}
// 发送邮件
export const sendMailApi = (data: MailSendReqVO) => {
export const sendMail = (data: MailSendReqVO) => {
return request.post({ url: '/system/mail-template/send-mail', data })
}

View File

@ -11,18 +11,12 @@ export interface OAuth2TokenVO {
expiresTime: Date
}
export interface OAuth2TokenPageReqVO extends PageParam {
userId?: number
userType?: number
clientId?: string
}
// 查询 token列表
export const getAccessTokenPageApi = (params: OAuth2TokenPageReqVO) => {
export const getAccessTokenPage = (params: PageParam) => {
return request.get({ url: '/system/oauth2-token/page', params })
}
// 删除 token
export const deleteAccessTokenApi = (accessToken: number) => {
export const deleteAccessToken = (accessToken: number) => {
return request.delete({ url: '/system/oauth2-token/delete?accessToken=' + accessToken })
}

View File

@ -23,19 +23,11 @@ export type OperateLogVO = {
resultData: string
}
export interface OperateLogPageReqVO extends PageParam {
module?: string
userNickname?: string
type?: number
success?: boolean
startTime?: Date[]
}
// 查询操作日志列表
export const getOperateLogPageApi = (params: OperateLogPageReqVO) => {
export const getOperateLogPage = (params: PageParam) => {
return request.get({ url: '/system/operate-log/page', params })
}
// 导出操作日志
export const exportOperateLogApi = (params: OperateLogPageReqVO) => {
export const exportOperateLog = (params) => {
return request.download({ url: '/system/operate-log/export', params })
}

View File

@ -10,49 +10,37 @@ export interface PostVO {
createTime?: Date
}
export interface PostPageReqVO extends PageParam {
code?: string
name?: string
status?: number
}
export interface PostExportReqVO {
code?: string
name?: string
status?: number
}
// 查询岗位列表
export const getPostPageApi = async (params: PostPageReqVO) => {
export const getPostPage = async (params: PageParam) => {
return await request.get({ url: '/system/post/page', params })
}
// 获取岗位精简信息列表
export const listSimplePostsApi = async () => {
export const getSimplePostList = async () => {
return await request.get({ url: '/system/post/list-all-simple' })
}
// 查询岗位详情
export const getPostApi = async (id: number) => {
export const getPost = async (id: number) => {
return await request.get({ url: '/system/post/get?id=' + id })
}
// 新增岗位
export const createPostApi = async (data: PostVO) => {
export const createPost = async (data: PostVO) => {
return await request.post({ url: '/system/post/create', data })
}
// 修改岗位
export const updatePostApi = async (data: PostVO) => {
export const updatePost = async (data: PostVO) => {
return await request.put({ url: '/system/post/update', data })
}
// 删除岗位
export const deletePostApi = async (id: number) => {
export const deletePost = async (id: number) => {
return await request.delete({ url: '/system/post/delete?id=' + id })
}
// 导出岗位
export const exportPostApi = async (params: PostExportReqVO) => {
export const exportPost = async (params) => {
return await request.download({ url: '/system/post/export', params })
}

View File

@ -86,6 +86,6 @@ export const updateUserStatusApi = (id: number, status: number) => {
}
// 获取用户精简信息列表
export const getListSimpleUsersApi = () => {
export const getSimpleUserList = () => {
return request.get({ url: '/system/user/list-all-simple' })
}

View File

@ -13,7 +13,7 @@ defineProps({
</script>
<template>
<ElCard :class="[prefixCls, 'mb-20px']" shadow="never">
<ElCard :class="[prefixCls, 'mb-15px']" shadow="never">
<template v-if="title" #header>
<div class="flex items-center">
<span class="text-16px font-700">{{ title }}</span>

View File

@ -35,7 +35,8 @@ export default defineComponent({
default: () => []
},
//
isCol: propTypes.bool.def(true),
// update by true false
isCol: propTypes.bool.def(false),
//
model: {
type: Object as PropType<Recordable>,
@ -46,7 +47,9 @@ export default defineComponent({
//
isCustom: propTypes.bool.def(false),
// label
labelWidth: propTypes.oneOfType([String, Number]).def('auto')
labelWidth: propTypes.oneOfType([String, Number]).def('auto'),
// loading add by
vLoading: propTypes.bool.def(false)
},
emits: ['register'],
setup(props, { slots, expose, emit }) {
@ -280,6 +283,7 @@ export default defineComponent({
{...getFormBindValue()}
model={props.isCustom ? props.model : formModel}
class={prefixCls}
v-loading={props.vLoading}
>
{{
//

View File

@ -5,7 +5,7 @@
class="float-right mt-15px mb-15px"
:background="true"
layout="total, sizes, prev, pager, next, jumper"
:page-sizes="[10, 20, 30, 50]"
:page-sizes="[10, 20, 30, 50, 100]"
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:pager-count="pagerCount"

View File

@ -98,6 +98,7 @@ const setVisible = () => {
</script>
<template>
<!-- update by 芋艿class="-mb-15px" 用于降低和 ContentWrap 组件的底部距离避免空隙过大 -->
<Form
:is-custom="false"
:label-width="labelWidth"
@ -106,21 +107,26 @@ const setVisible = () => {
:is-col="isCol"
:schema="newSchema"
@register="register"
class="-mb-15px"
>
<template #action>
<div v-if="layout === 'inline'">
<ElButton v-if="showSearch" type="primary" @click="search">
<!-- update by 芋艿去除搜索的 type="primary"颜色变淡一点 -->
<ElButton v-if="showSearch" @click="search">
<Icon icon="ep:search" class="mr-5px" />
{{ t('common.query') }}
</ElButton>
<!-- update by 芋艿 icon="ep:refresh-right" 修改成 icon="ep:refresh" ruoyi-vue 搜索保持一致 -->
<ElButton v-if="showReset" @click="reset">
<Icon icon="ep:refresh-right" class="mr-5px" />
<Icon icon="ep:refresh" class="mr-5px" />
{{ t('common.reset') }}
</ElButton>
<ElButton v-if="expand" text @click="setVisible">
{{ t(visible ? 'common.shrink' : 'common.expand') }}
<Icon :icon="visible ? 'ep:arrow-up' : 'ep:arrow-down'" />
</ElButton>
<!-- add by 芋艿补充在搜索后的按钮 -->
<slot name="actionMore"></slot>
</div>
</template>
<template #[name] v-for="name in Object.keys($slots)" :key="name"
@ -142,6 +148,8 @@ const setVisible = () => {
{{ t(visible ? 'common.shrink' : 'common.expand') }}
<Icon :icon="visible ? 'ep:arrow-up' : 'ep:arrow-down'" />
</ElButton>
<!-- add by 芋艿补充在搜索后的按钮 -->
<slot name="actionMore"></slot>
</div>
</template>
</template>

View File

@ -104,11 +104,12 @@ export default defineComponent({
})
const pagination = computed(() => {
// update by Pagination
return Object.assign(
{
small: false,
background: true,
pagerCount: 5,
pagerCount: document.body.clientWidth < 992 ? 5 : 7,
layout: 'total, sizes, prev, pager, next, jumper',
pageSizes: [10, 20, 30, 50, 100],
disabled: false,
@ -283,10 +284,11 @@ export default defineComponent({
}}
</ElTable>
{unref(getProps).pagination ? (
// update by Pagination
<ElPagination
v-model:pageSize={pageSizeRef.value}
v-model:currentPage={currentPageRef.value}
class="mt-10px"
class="float-right mt-15px mb-15px"
{...unref(pagination)}
></ElPagination>
) : undefined}

View File

@ -8,6 +8,7 @@ import { FormSchema } from '@/types/form'
import { TableColumn } from '@/types/table'
import { DescriptionsSchema } from '@/types/descriptions'
import { ComponentOptions, ComponentProps } from '@/types/components'
import { DictTag } from '@/components/DictTag'
export type CrudSchema = Omit<TableColumn, 'children'> & {
isSearch?: boolean // 是否在查询显示
@ -151,6 +152,15 @@ const filterTableSchema = (crudSchema: CrudSchema[]): TableColumn[] => {
const tableColumns = treeMap<CrudSchema>(crudSchema, {
conversion: (schema: CrudSchema) => {
if (schema?.isTable !== false && schema?.table?.show !== false) {
// add by 芋艿:增加对 dict 字典数据的支持
if (!schema.formatter && schema.dictType) {
schema.formatter = (_: Recordable, __: TableColumn, cellValue: any) => {
return h(DictTag, {
type: schema.dictType!, // ! 表示一定不为空
value: cellValue
})
}
}
return {
...schema.table,
...schema

View File

@ -218,6 +218,8 @@ export const useTable = <T = any>(config?: UseTableConfig<T>) => {
register,
elTableRef,
tableObject,
methods
methods,
// add by 芋艿:返回 tableMethods 属性,和 tableObject 更统一
tableMethods: methods
}
}

View File

@ -297,7 +297,8 @@ export default {
typeCreate: 'Dict Type Create',
typeUpdate: 'Dict Type Eidt',
dataCreate: 'Dict Data Create',
dataUpdate: 'Dict Data Eidt'
dataUpdate: 'Dict Data Eidt',
fileUpload: 'File Upload'
},
dialog: {
dialog: 'Dialog',

View File

@ -297,7 +297,8 @@ export default {
typeCreate: '字典类型新增',
typeUpdate: '字典类型编辑',
dataCreate: '字典数据新增',
dataUpdate: '字典数据编辑'
dataUpdate: '字典数据编辑',
fileUpload: '上传文件'
},
dialog: {
dialog: '弹窗',

View File

@ -104,6 +104,31 @@ const remainingRouter: AppRouteRecordRaw[] = [
}
]
},
{
path: '/dict',
component: Layout,
name: 'dict',
meta: {
hidden: true
},
children: [
{
path: 'type/data/:dictType',
component: () => import('@/views/system/dict/data.vue'),
name: 'data',
meta: {
title: '字典数据',
noCache: true,
hidden: true,
canTo: true,
icon: '',
activeMenu: 'system/dict/data'
}
}
]
},
{
path: '/codegen',
component: Layout,

View File

@ -3,7 +3,7 @@ import { store } from '../index'
import { DictDataVO } from '@/api/system/dict/types'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache('sessionStorage')
import { listSimpleDictDataApi } from '@/api/system/dict/dict.data'
import { listSimpleDictData } from '@/api/system/dict/dict.data'
export interface DictValueType {
value: any
@ -44,7 +44,7 @@ export const useDictStore = defineStore('dict', {
this.dictMap = dictMap
this.isSetDict = true
} else {
const res = await listSimpleDictDataApi()
const res = await listSimpleDictData()
// 设置数据
const dictDataMap = new Map<string, any>()
res.forEach((dictData: DictDataVO) => {
@ -74,7 +74,7 @@ export const useDictStore = defineStore('dict', {
},
async resetDict() {
wsCache.delete(CACHE_KEY.DICT_CACHE)
const res = await listSimpleDictDataApi()
const res = await listSimpleDictData()
// 设置数据
const dictDataMap = new Map<string, any>()
res.forEach((dictData: DictDataVO) => {

View File

@ -26,6 +26,7 @@ declare module '@vue/runtime-core' {
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
ElCard: typeof import('element-plus/es')['ElCard']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
@ -67,6 +68,7 @@ declare module '@vue/runtime-core' {
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTableV2: typeof import('element-plus/es')['ElTableV2']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']

View File

@ -52,6 +52,7 @@ declare global {
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useCrudSchemas: typeof import('@/hooks/web/useCrudSchemas')['useCrudSchemas']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useI18n: typeof import('@/hooks/web/useI18n')['useI18n']
@ -60,6 +61,7 @@ declare global {
const useRoute: typeof import('vue-router')['useRoute']
const useRouter: typeof import('vue-router')['useRouter']
const useSlots: typeof import('vue')['useSlots']
const useTable: typeof import('@/hooks/web/useTable')['useTable']
const useVxeCrudSchemas: typeof import('@/hooks/web/useVxeCrudSchemas')['useVxeCrudSchemas']
const useXTable: typeof import('@/hooks/web/useXTable')['useXTable']
const watch: typeof import('vue')['watch']

View File

@ -8,6 +8,6 @@ export interface DescriptionsSchema {
labelAlign?: 'left' | 'center' | 'right'
className?: string
labelClassName?: string
dateFormat?: string
dictType?: string
dateFormat?: string // add by 星语:支持时间的格式化
dictType?: string // add by 星语:支持 dict 字典数据
}

View File

@ -12,57 +12,7 @@ import dayjs from 'dayjs'
* @returns
*/
export function formatDate(date: Date, format: string): string {
const we = date.getDay() // 星期
const z = getWeek(date) // 周
const qut = Math.floor((date.getMonth() + 3) / 3).toString() // 季度
const opt: { [key: string]: string } = {
'Y+': date.getFullYear().toString(), // 年
'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始要+1)
'd+': date.getDate().toString(), // 日
'H+': date.getHours().toString(), // 时
'M+': date.getMinutes().toString(), // 分
'S+': date.getSeconds().toString(), // 秒
'q+': qut // 季度
}
// 中文数字 (星期)
const week: { [key: string]: string } = {
'0': '日',
'1': '一',
'2': '二',
'3': '三',
'4': '四',
'5': '五',
'6': '六'
}
// 中文数字(季度)
const quarter: { [key: string]: string } = {
'1': '一',
'2': '二',
'3': '三',
'4': '四'
}
if (/(W+)/.test(format))
format = format.replace(
RegExp.$1,
RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we]
)
if (/(Q+)/.test(format))
format = format.replace(
RegExp.$1,
RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut]
)
if (/(Z+)/.test(format))
format = format.replace(RegExp.$1, RegExp.$1.length == 3 ? '第' + z + '周' : z + '')
for (const k in opt) {
const r = new RegExp('(' + k + ')').exec(format)
// 若输入的长度不为1则前面补零
if (r)
format = format.replace(
r[1],
RegExp.$1.length == 1 ? opt[k] : opt[k].padStart(RegExp.$1.length, '0')
)
}
return format
return dayjs(date).format(format)
}
// 日期格式化
export function parseTime(time: any, pattern?: string) {

View File

@ -137,3 +137,21 @@ export const generateUUID = () => {
return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16)
})
}
/**
* element plus Formatter
*
* @param row
* @param column
* @param cellValue
*/
// @ts-ignore
export const fileSizeFormatter = (row, column, cellValue) => {
const fileSize = cellValue
const unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const srcSize = parseFloat(fileSize)
const index = Math.floor(Math.log(srcSize) / Math.log(1024))
const size = srcSize / Math.pow(1024, index)
const sizeStr = size.toFixed(2) //保留的小数位数
return sizeStr + ' ' + unitArr[index]
}

View File

@ -0,0 +1,132 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="组名" prop="name">
<el-input v-model="formData.name" placeholder="请输入组名" />
</el-form-item>
<el-form-item label="描述">
<el-input type="textarea" v-model="formData.name" placeholder="请输入描述" />
</el-form-item>
<el-form-item label="成员" prop="memberUserIds">
<el-select v-model="formData.memberUserIds" multiple placeholder="请选择成员">
<el-option
v-for="user in userList"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { CommonStatusEnum } from '@/utils/constants'
import * as UserGroupApi from '@/api/bpm/userGroup'
import * as UserApi from '@/api/system/user'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
name: undefined,
description: undefined,
memberUserIds: undefined,
status: CommonStatusEnum.ENABLE
})
const formRules = reactive({
name: [{ required: true, message: '组名不能为空', trigger: 'blur' }],
description: [{ required: true, message: '描述不能为空', trigger: 'blur' }],
memberUserIds: [{ required: true, message: '成员不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
const userList = ref([]) //
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
modelVisible.value = true
modelTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await UserGroupApi.getUserGroup(id)
} finally {
formLoading.value = false
}
}
//
userList.value = await UserApi.getSimpleUserList()
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as unknown as UserGroupApi.UserGroupVO
if (formType.value === 'create') {
await UserGroupApi.createUserGroup(data)
message.success(t('common.createSuccess'))
} else {
await UserGroupApi.updateUserGroup(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: undefined,
description: undefined,
memberUserIds: undefined,
status: CommonStatusEnum.ENABLE
}
formRef.value?.resetFields()
}
</script>

View File

@ -1,64 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
name: [required],
description: [required],
memberUserIds: [required],
status: [required]
})
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: 'id',
primaryTitle: '编号',
action: true,
searchSpan: 8,
columns: [
{
title: '组名',
field: 'name',
isSearch: true
},
{
title: '成员',
field: 'memberUserIds',
table: {
slots: {
default: 'memberUserIds_default'
}
}
},
{
title: '描述',
field: 'description'
},
{
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,
isSearch: true,
search: {
show: true,
itemRender: {
name: 'XDataTimePicker'
}
},
table: {
width: 180
}
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -1,182 +1,184 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<template #toolbar_buttons>
<!-- 操作新增 -->
<XButton
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="组名" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入组名"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="yyyy-MM-dd HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
preIcon="ep:zoom-in"
:title="t('action.add')"
@click="openForm('create')"
v-hasPermi="['bpm:user-group:create']"
@click="handleCreate()"
/>
</template>
<template #memberUserIds_default="{ row }">
<span v-for="userId in row.memberUserIds" :key="userId">
{{ getUserNickname(userId) }} &nbsp;
</span>
</template>
<template #actionbtns_default="{ row }">
<!-- 操作修改 -->
<XTextButton
preIcon="ep:edit"
:title="t('action.edit')"
v-hasPermi="['bpm:user-group:update']"
@click="handleUpdate(row.id)"
/>
<!-- 操作详情 -->
<XTextButton
preIcon="ep:view"
:title="t('action.detail')"
v-hasPermi="['bpm:user-group:query']"
@click="handleDetail(row.id)"
/>
<!-- 操作删除 -->
<XTextButton
preIcon="ep:delete"
:title="t('action.del')"
v-hasPermi="['bpm:user-group:delete']"
@click="deleteData(row.id)"
/>
</template>
</XTable>
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<XModal v-model="dialogVisible" :title="dialogTitle" :mask-closable="false">
<!-- 对话框(添加 / 修改) -->
<Form
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
ref="formRef"
>
<template #memberUserIds="form">
<el-select v-model="form.memberUserIds" multiple>
<el-option v-for="item in users" :key="item.id" :label="item.nickname" :value="item.id" />
</el-select>
</template>
</Form>
<!-- 对话框(详情) -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailData"
>
<template #memberUserIds="{ row }">
<span v-for="userId in row.memberUserIds" :key="userId">
{{ getUserNickname(userId) }} &nbsp;
</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<!-- 按钮保存 -->
<XButton
v-if="['create', 'update'].includes(actionType)"
type="primary"
:title="t('action.save')"
:loading="actionLoading"
@click="submitForm"
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="组名" align="center" prop="name" />
<el-table-column label="描述" align="center" prop="description" />
<el-table-column label="成员" align="center">
<template #default="scope">
<span v-for="userId in scope.row.memberUserIds" :key="userId" class="pr-5px">
{{ userList.find((user) => user.id === userId)?.nickname }}
</span>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
/>
<!-- 按钮关闭 -->
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
</template>
</XModal>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['bpm:user-group:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['bpm:user-group:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<UserGroupForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
// import
<script setup lang="ts" name="UserGroup">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as UserGroupApi from '@/api/bpm/userGroup'
import { getListSimpleUsersApi, UserVO } from '@/api/system/user'
import { allSchemas, rules } from './group.data'
import { FormExpose } from '@/components/Form'
const { t } = useI18n() //
import * as UserApi from '@/api/system/user'
import UserGroupForm from './UserGroupForm.vue'
const message = useMessage() //
//
const [registerTable, { reload, deleteData }] = useXTable({
allSchemas: allSchemas,
getListApi: UserGroupApi.getUserGroupPageApi,
deleteApi: UserGroupApi.deleteUserGroupApi
const { t } = useI18n() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: null,
status: null,
createTime: []
})
//
const users = ref<UserVO[]>([])
const queryFormRef = ref() //
const userList = ref([]) //
const getUserNickname = (userId) => {
for (const user of users.value) {
if (user.id === userId) {
return user.nickname
}
/** 查询参数列表 */
const getList = async () => {
loading.value = true
try {
const data = await UserGroupApi.getUserGroupPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
return '未知(' + userId + ')'
}
// ========== 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 handleQuery = () => {
queryParams.pageNo = 1
getList()
}
//
const handleCreate = () => {
setDialogTile('create')
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
//
const handleUpdate = async (rowId: number) => {
setDialogTile('update')
//
const res = await UserGroupApi.getUserGroupApi(rowId)
unref(formRef)?.setValues(res)
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
//
const handleDetail = async (rowId: number) => {
setDialogTile('detail')
detailData.value = await UserGroupApi.getUserGroupApi(rowId)
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await UserGroupApi.deleteUserGroup(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
//
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 UserGroupApi.UserGroupVO
if (actionType.value === 'create') {
await UserGroupApi.createUserGroupApi(data)
message.success(t('common.createSuccess'))
} else {
await UserGroupApi.updateUserGroupApi(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
} finally {
actionLoading.value = false
//
await reload()
}
}
})
}
// ========== ==========
onMounted(() => {
getListSimpleUsersApi().then((data) => {
users.value = data
})
/** 初始化 **/
onMounted(async () => {
await getList()
//
userList.value = await UserApi.getSimpleUserList()
})
</script>

View File

@ -378,7 +378,7 @@ onMounted(() => {
//
getDetail()
//
UserApi.getListSimpleUsersApi().then((data) => {
UserApi.getSimpleUserList().then((data) => {
userOptions.value.push(...data)
})
})

View File

@ -139,9 +139,9 @@ import { FormInstance } from 'element-plus'
// import
import * as TaskAssignRuleApi from '@/api/bpm/taskAssignRule'
import { listSimpleRolesApi } from '@/api/system/role'
import { listSimplePostsApi } from '@/api/system/post'
import { getListSimpleUsersApi } from '@/api/system/user'
import { listSimpleUserGroupsApi } from '@/api/bpm/userGroup'
import { getSimplePostList } from '@/api/system/post'
import { getSimpleUserList } from '@/api/system/user'
import { listSimpleUserGroup } from '@/api/bpm/userGroup'
import { listSimpleDeptApi } from '@/api/system/dept'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import { handleTree, defaultProps } from '@/utils/tree'
@ -336,17 +336,17 @@ onMounted(() => {
})
//
postOptions.value = []
listSimplePostsApi().then((data) => {
getSimplePostList().then((data) => {
postOptions.value.push(...data)
})
//
userOptions.value = []
getListSimpleUsersApi().then((data) => {
getSimpleUserList().then((data) => {
userOptions.value.push(...data)
})
//
userGroupOptions.value = []
listSimpleUserGroupsApi().then((data) => {
listSimpleUserGroup().then((data) => {
userGroupOptions.value.push(...data)
})
if (!isShow) {

View File

@ -0,0 +1,79 @@
<template>
<Dialog title="详情" v-model="modelVisible" :scroll="true" :max-height="500" width="800">
<el-descriptions border :column="1">
<el-descriptions-item label="日志主键" min-width="120">
{{ detailData.id }}
</el-descriptions-item>
<el-descriptions-item label="链路追踪">
{{ detailData.traceId }}
</el-descriptions-item>
<el-descriptions-item label="应用名">
{{ detailData.applicationName }}
</el-descriptions-item>
<el-descriptions-item label="用户编号">
{{ detailData.userId }}
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="detailData.userType" />
</el-descriptions-item>
<el-descriptions-item label="用户 IP">
{{ detailData.userIp }}
</el-descriptions-item>
<el-descriptions-item label="用户 UA">
{{ detailData.userAgent }}
</el-descriptions-item>
<el-descriptions-item label="请求信息">
{{ detailData.requestMethod }} {{ detailData.requestUrl }}
</el-descriptions-item>
<el-descriptions-item label="请求参数">
{{ detailData.requestParams }}
</el-descriptions-item>
<el-descriptions-item label="异常时间">
{{ formatDate(detailData.exceptionTime, 'YYYY-MM-DD HH:mm:ss') }}
</el-descriptions-item>
<el-descriptions-item label="异常名">
{{ detailData.exceptionName }}
</el-descriptions-item>
<el-descriptions-item label="异常堆栈" v-if="detailData.exceptionStackTrace">
<el-input
type="textarea"
:readonly="true"
:autosize="{ maxRows: 20 }"
v-model="detailData.exceptionStackTrace"
/>
</el-descriptions-item>
<el-descriptions-item label="处理状态">
<dict-tag
:type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS"
:value="detailData.processStatus"
/>
</el-descriptions-item>
<el-descriptions-item label="处理人" v-if="detailData.processUserId">
{{ detailData.processUserId }}
</el-descriptions-item>
<el-descriptions-item label="处理时间" v-if="detailData.processTime">
{{ formatDate(detailData.processTime, 'YYYY-MM-DD HH:mm:ss') }}
</el-descriptions-item>
</el-descriptions>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE } from '@/utils/dict'
import { formatDate } from '@/utils/formatTime'
import * as ApiErrorLog from '@/api/infra/apiErrorLog'
const modelVisible = ref(false) //
const detailLoading = ref(false) //
const detailData = ref() //
/** 打开弹窗 */
const open = async (data: ApiErrorLog.ApiErrorLogVO) => {
modelVisible.value = true
//
detailLoading.value = true
try {
detailData.value = data
} finally {
detailLoading.value = false
}
}
defineExpose({ open }) // open
</script>

View File

@ -1,76 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: 'id',
primaryTitle: '日志编号',
action: true,
actionWidth: '300',
columns: [
{
title: '链路追踪',
field: 'traceId',
isTable: false
},
{
title: '用户编号',
field: 'userId',
isSearch: true
},
{
title: '用户类型',
field: 'userType',
dictType: DICT_TYPE.USER_TYPE,
isSearch: true
},
{
title: '应用名',
field: 'applicationName',
isSearch: true
},
{
title: '请求方法名',
field: 'requestMethod'
},
{
title: '请求地址',
field: 'requestUrl',
isSearch: true
},
{
title: '异常发生时间',
field: 'exceptionTime',
formatter: 'formatDate',
search: {
show: true,
itemRender: {
name: 'XDataTimePicker'
}
}
},
{
title: '异常名',
field: 'exceptionName'
},
{
title: '处理状态',
field: 'processStatus',
dictType: DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS,
dictClass: 'number',
isSearch: true
},
{
title: '处理人',
field: 'processUserId',
isTable: false
},
{
title: '处理时间',
field: 'processTime',
formatter: 'formatDate',
isTable: false
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -1,99 +1,248 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<!-- 操作导出 -->
<template #toolbar_buttons>
<XButton
type="warning"
preIcon="ep:download"
:title="t('action.export')"
@click="exportList('错误数据.xls')"
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="用户编号" prop="userId">
<el-input
v-model="queryParams.userId"
placeholder="请输入用户编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</template>
<template #duration_default="{ row }">
<span>{{ row.duration + 'ms' }}</span>
</template>
<template #resultCode_default="{ row }">
<span>{{ row.resultCode === 0 ? '成功' : '失败(' + row.resultMsg + ')' }}</span>
</template>
<template #actionbtns_default="{ row }">
<!-- 操作详情 -->
<XTextButton
preIcon="ep:view"
:title="t('action.detail')"
v-hasPermi="['infra:api-access-log:query']"
@click="handleDetail(row)"
</el-form-item>
<el-form-item label="用户类型" prop="userType">
<el-select
v-model="queryParams.userType"
placeholder="请选择用户类型"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.USER_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="应用名" prop="applicationName">
<el-input
v-model="queryParams.applicationName"
placeholder="请输入应用名"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
<XTextButton
preIcon="ep:cpu"
title="已处理"
v-if="row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT"
v-hasPermi="['infra:api-error-log:update-status']"
@click="handleProcessClick(row, InfraApiErrorLogProcessStatusEnum.DONE, '已处理')"
</el-form-item>
<el-form-item label="异常时间" prop="exceptionTime">
<el-date-picker
v-model="queryParams.exceptionTime"
value-format="yyyy-MM-dd HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
<XTextButton
preIcon="ep:mute-notification"
title="已忽略"
v-if="row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT"
v-hasPermi="['infra:api-error-log:update-status']"
@click="handleProcessClick(row, InfraApiErrorLogProcessStatusEnum.IGNORE, '已忽略')"
/>
</template>
</XTable>
</el-form-item>
<el-form-item label="处理状态" prop="processStatus">
<el-select
v-model="queryParams.processStatus"
placeholder="请选择处理状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['infra:api-error-log:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</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>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="日志编号" align="center" prop="id" />
<el-table-column label="用户编号" align="center" prop="userId" />
<el-table-column label="用户类型" align="center" prop="userType">
<template #default="scope">
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" />
</template>
</el-table-column>
<el-table-column label="应用名" align="center" prop="applicationName" width="200" />
<el-table-column label="请求方法" align="center" prop="requestMethod" width="80" />
<el-table-column label="请求地址" align="center" prop="requestUrl" width="180" />
<el-table-column
label="异常发生时间"
align="center"
prop="exceptionTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="异常名" align="center" prop="exceptionName" width="180" />
<el-table-column label="处理状态" align="center" prop="processStatus">
<template #default="scope">
<dict-tag
:type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS"
:value="scope.row.processStatus"
/>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200">
<template #default="scope">
<el-button
link
type="primary"
@click="openDetail(scope.row)"
v-hasPermi="['infra:api-access-log:query']"
>
详细
</el-button>
<el-button
link
type="primary"
v-if="scope.row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT"
@click="handleProcess(scope.row.id, InfraApiErrorLogProcessStatusEnum.DONE)"
v-hasPermi="['infra:api-error-log:update-status']"
>
已处理
</el-button>
<el-button
link
type="primary"
v-if="scope.row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT"
@click="handleProcess(scope.row.id, InfraApiErrorLogProcessStatusEnum.IGNORE)"
v-hasPermi="['infra:api-error-log:update-status']"
>
已忽略
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗详情 -->
<ApiErrorLogDetail ref="detailRef" />
</template>
<script setup lang="ts" name="ApiErrorLog">
import { allSchemas } from './apiErrorLog.data'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as ApiErrorLogApi from '@/api/infra/apiErrorLog'
import ApiErrorLogDetail from './ApiErrorLogDetail.vue'
import { InfraApiErrorLogProcessStatusEnum } from '@/utils/constants'
const { t } = useI18n() //
const message = useMessage()
const message = useMessage() //
// ========== ==========
const [registerTable, { reload, exportList }] = useXTable({
allSchemas: allSchemas,
getListApi: ApiErrorLogApi.getApiErrorLogPageApi,
exportListApi: ApiErrorLogApi.exportApiErrorLogApi
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
userId: null,
userType: null,
applicationName: null,
requestUrl: null,
processStatus: null,
exceptionTime: []
})
// ========== ==========
const detailData = ref() // Ref
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const queryFormRef = ref() //
const exportLoading = ref(false) //
//
const handleDetail = (row: ApiErrorLogApi.ApiErrorLogVO) => {
//
detailData.value = row
dialogTitle.value = t('action.detail')
dialogVisible.value = true
/** 查询参数列表 */
const getList = async () => {
loading.value = true
try {
const data = await ApiErrorLogApi.getApiErrorLogPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
//
const handleProcessClick = (
row: ApiErrorLogApi.ApiErrorLogVO,
processSttatus: number,
type: string
) => {
message
.confirm('确认标记为' + type + '?', t('common.reminder'))
.then(async () => {
await ApiErrorLogApi.updateApiErrorLogPageApi(row.id, processSttatus)
message.success(t('common.updateSuccess'))
})
.finally(async () => {
//
await reload()
})
.catch(() => {})
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 详情操作 */
const detailRef = ref()
const openDetail = (data: ApiErrorLogApi.ApiErrorLogVO) => {
detailRef.value.open(data)
}
/** 处理已处理 / 已忽略的操作 **/
const handleProcess = async (id: number, processStatus: number) => {
try {
//
const type = processStatus === InfraApiErrorLogProcessStatusEnum.DONE ? '已处理' : '已忽略'
await message.confirm('确认标记为' + type + '?')
//
await ApiErrorLogApi.updateApiErrorLogPage(id, processStatus)
await message.success(type)
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await ApiErrorLogApi.exportApiErrorLog(queryParams)
download.excel(data, '异常日志.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -114,7 +114,7 @@
import { PropType } from 'vue'
import { DictTypeVO } from '@/api/system/dict/types'
import { CodegenColumnVO } from '@/api/infra/codegen/types'
import { listSimpleDictTypeApi } from '@/api/system/dict/dict.type'
import { listSimpleDictType } from '@/api/system/dict/dict.type'
const props = defineProps({
info: {
@ -125,7 +125,7 @@ const props = defineProps({
/** 查询字典下拉列表 */
const dictOptions = ref<DictTypeVO[]>()
const getDictOptions = async () => {
const res = await listSimpleDictTypeApi()
const res = await listSimpleDictType()
dictOptions.value = res
}
onMounted(async () => {

View File

@ -1,13 +1,20 @@
<template>
<!-- 搜索 -->
<content-wrap>
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="参数名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入参数名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="参数键名" prop="key">
@ -16,10 +23,16 @@
placeholder="请输入参数键名"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="系统内置" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择系统内置" clearable>
<el-select
v-model="queryParams.type"
placeholder="请选择系统内置"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.INFRA_CONFIG_TYPE)"
:key="parseInt(dict.value)"
@ -33,10 +46,10 @@
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
@ -56,9 +69,11 @@
</el-button>
</el-form-item>
</el-form>
</content-wrap>
<!-- 列表 -->
<el-table v-loading="loading" :data="list" align="center">
<!-- 列表 -->
<content-wrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="参数主键" align="center" prop="id" />
<el-table-column label="参数分类" align="center" prop="category" />
<el-table-column label="参数名称" align="center" prop="name" :show-overflow-tooltip="true" />

View File

@ -1,52 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
// 国际化
const { t } = useI18n()
// 表单校验
export const rules = reactive({
name: [required],
url: [required],
username: [required],
password: [required]
})
// 新增 + 修改
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: 'seq',
action: true,
columns: [
{
title: '数据源名称',
field: 'name'
},
{
title: '数据源连接',
field: 'url',
form: {
component: 'Input',
componentProps: {
type: 'textarea',
rows: 4
},
colProps: {
span: 24
}
}
},
{
title: '用户名',
field: 'username'
},
{
title: '密码',
field: 'password',
isTable: false
},
{
title: t('common.createTime'),
field: 'createTime',
formatter: 'formatDate',
isForm: false
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -0,0 +1,111 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="数据源名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入参数名称" />
</el-form-item>
<el-form-item label="数据源连接" prop="url">
<el-input v-model="formData.url" placeholder="请输入数据源连接" />
</el-form-item>
<el-form-item label="用户名" prop="username">
<el-input v-model="formData.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="formData.password" placeholder="请输入密码" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref<DataSourceConfigApi.DataSourceConfigVO>({
id: undefined,
name: '',
url: '',
username: '',
password: ''
})
const formRules = reactive({
name: [{ required: true, message: '数据源名称不能为空', trigger: 'blur' }],
url: [{ required: true, message: '数据源连接不能为空', trigger: 'blur' }],
username: [{ required: true, message: '用户名不能为空', trigger: 'blur' }],
password: [{ required: true, message: '密码不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
/** 打开弹窗 */
const openModal = async (type: string, id?: number) => {
modelVisible.value = true
modelTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await DataSourceConfigApi.getDataSourceConfig(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ openModal }) // openModal
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as DataSourceConfigApi.DataSourceConfigVO
if (formType.value === 'create') {
await DataSourceConfigApi.createDataSourceConfig(data)
message.success(t('common.createSuccess'))
} else {
await DataSourceConfigApi.updateDataSourceConfig(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: '',
url: '',
username: '',
password: ''
}
formRef.value?.resetFields()
}
</script>

View File

@ -1,145 +1,102 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<template #toolbar_buttons>
<XButton
<content-wrap>
<!-- 搜索工作栏 -->
<el-form class="-mb-15px" :inline="true">
<el-form-item>
<el-button
type="primary"
preIcon="ep:zoom-in"
:title="t('action.add')"
@click="openModal('create')"
v-hasPermi="['infra:data-source-config:create']"
@click="handleCreate()"
/>
</template>
<template #actionbtns_default="{ row }">
<!-- 操作修改 -->
<XTextButton
preIcon="ep:edit"
:title="t('action.edit')"
v-hasPermi="['infra:data-source-config:update']"
@click="handleUpdate(row.id)"
/>
<!-- 操作详情 -->
<XTextButton
preIcon="ep:view"
:title="t('action.detail')"
v-hasPermi="['infra:data-source-config:query']"
@click="handleDetail(row.id)"
/>
<!-- 操作删除 -->
<XTextButton
preIcon="ep:delete"
:title="t('action.del')"
v-hasPermi="['infra:data-source-config: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="loading"
@click="submitForm()"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</el-form-item>
</el-form>
</content-wrap>
<!-- 列表 -->
<content-wrap>
<el-table v-loading="loading" :data="list" align="center">
<el-table-column label="主键编号" align="center" prop="id" />
<el-table-column label="数据源名称" align="center" prop="name" />
<el-table-column label="数据源连接" align="center" prop="url" :show-overflow-tooltip="true" />
<el-table-column label="用户名" align="center" prop="username" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<!-- 按钮关闭 -->
<XButton :loading="loading" :title="t('dialog.close')" @click="dialogVisible = false" />
</template>
</XModal>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openModal('update', scope.row.id)"
v-hasPermi="['infra:data-source-config:update']"
:disabled="scope.row.id === 0"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['infra:data-source-config:delete']"
:disabled="scope.row.id === 0"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</content-wrap>
<!-- 表单弹窗添加/修改 -->
<data-source-config-form ref="modalRef" @success="getList" />
</template>
<script setup lang="ts" name="DataSourceConfig">
import type { FormExpose } from '@/components/Form'
// import
import * as DataSourceConfiggApi from '@/api/infra/dataSourceConfig'
import { rules, allSchemas } from './dataSourceConfig.data'
const { t } = useI18n() //
import { dateFormatter } from '@/utils/formatTime'
import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig'
import DataSourceConfigForm from './form.vue'
const message = useMessage() //
//
const [registerTable, { reload, deleteData }] = useXTable({
allSchemas: allSchemas,
isList: true,
getListApi: DataSourceConfiggApi.getDataSourceConfigListApi,
deleteApi: DataSourceConfiggApi.deleteDataSourceConfigApi
const { t } = useI18n() //
const loading = ref(true) //
const list = ref([]) //
/** 查询参数列表 */
const getList = async () => {
loading.value = true
try {
list.value = await DataSourceConfigApi.getDataSourceConfigList()
} finally {
loading.value = false
}
}
/** 添加/修改操作 */
const modalRef = ref()
const openModal = (type: string, id?: number) => {
modalRef.value.openModal(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await DataSourceConfigApi.deleteDataSourceConfig(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 初始化 **/
onMounted(() => {
getList()
})
// ========== CRUD ==========
const loading = 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 DataSourceConfiggApi.getDataSourceConfigApi(rowId)
unref(formRef)?.setValues(res)
}
//
const handleDetail = async (rowId: number) => {
//
const res = await DataSourceConfiggApi.getDataSourceConfigApi(rowId)
detailData.value = res
setDialogTile('detail')
}
//
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 DataSourceConfiggApi.DataSourceConfigVO
if (actionType.value === 'create') {
await DataSourceConfiggApi.createDataSourceConfigApi(data)
message.success(t('common.createSuccess'))
} else {
await DataSourceConfiggApi.updateDataSourceConfigApi(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
} finally {
loading.value = false
//
await reload()
}
}
})
}
</script>

View File

@ -0,0 +1,83 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible">
<el-upload
ref="uploadRef"
:limit="1"
accept=".jpg, .png, .gif"
:auto-upload="false"
drag
:headers="headers"
:action="url"
:data="data"
:disabled="formLoading"
:on-change="handleFileChange"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text"> 将文件拖到此处 <em>点击上传</em> </div>
<template #tip>
<div class="el-upload__tip" style="color: red">
提示仅允许导入 jpgpnggif 格式文件
</div>
</template>
</el-upload>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitFileForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { Dialog } from '@/components/Dialog'
import { getAccessToken } from '@/utils/auth'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const url = import.meta.env.VITE_UPLOAD_URL
const headers = { Authorization: 'Bearer ' + getAccessToken() }
const data = ref({ path: '' })
const uploadRef = ref()
/** 打开弹窗 */
const openModal = async () => {
modelVisible.value = true
modelTitle.value = t('action.fileUpload')
}
defineExpose({ openModal }) // openModal
/** 提交表单 */
const emit = defineEmits(['success']) // success
/** 处理上传的文件发生变化 */
const handleFileChange = (file) => {
data.value.path = file.name
}
/** 处理文件上传中 */
const handleFileUploadProgress = () => {
formLoading.value = true //
}
/** 发起文件上传 */
const submitFileForm = () => {
unref(uploadRef)?.submit()
}
/** 文件上传成功处理 */
const handleFileSuccess = () => {
//
modelVisible.value = false
formLoading.value = false
unref(uploadRef)?.clearFiles()
//
message.success(t('common.createSuccess'))
emit('success')
}
</script>

View File

@ -0,0 +1,154 @@
<template>
<!-- 搜索 -->
<content-wrap>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
<el-form-item label="文件路径" prop="path">
<el-input
v-model="queryParams.path"
placeholder="请输入文件路径"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="文件类型" prop="type" width="80">
<el-input
v-model="queryParams.type"
placeholder="请输入文件类型"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button type="primary" @click="openModal">
<Icon icon="ep:upload" class="mr-5px" /> 上传文件
</el-button>
</el-form-item>
</el-form>
</content-wrap>
<!-- 列表 -->
<content-wrap>
<el-table v-loading="loading" :data="list" align="center">
<el-table-column label="文件名" align="center" prop="name" :show-overflow-tooltip="true" />
<el-table-column label="文件路径" align="center" prop="path" :show-overflow-tooltip="true" />
<el-table-column label="URL" align="center" prop="url" :show-overflow-tooltip="true" />
<el-table-column
label="文件大小"
align="center"
prop="size"
width="120"
:formatter="fileSizeFormatter"
/>
<el-table-column label="文件类型" align="center" prop="type" width="180px" />
<el-table-column
label="上传时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['infra:config:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</content-wrap>
<!-- 表单弹窗添加/修改 -->
<file-upload-form ref="modalRef" @success="getList" />
</template>
<script setup lang="ts" name="Config">
import { fileSizeFormatter } from '@/utils'
import { dateFormatter } from '@/utils/formatTime'
import * as FileApi from '@/api/infra/file'
import FileUploadForm from './form.vue'
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
type: undefined,
createTime: []
})
const queryFormRef = ref() //
/** 查询参数列表 */
const getList = async () => {
loading.value = true
try {
const data = await FileApi.getFilePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const modalRef = ref()
const openModal = () => {
modalRef.value.openModal()
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await FileApi.deleteFile(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -1,77 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
name: [required],
storage: [required],
config: {
basePath: [required],
host: [required],
port: [required],
username: [required],
password: [required],
mode: [required],
endpoint: [required],
bucket: [required],
accessKey: [required],
accessSecret: [required],
domain: [required]
}
})
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: 'id',
primaryTitle: '配置编号',
action: true,
actionWidth: '400px',
columns: [
{
title: '配置名',
field: 'name',
isSearch: true
},
{
title: '存储器',
field: 'storage',
dictType: DICT_TYPE.INFRA_FILE_STORAGE,
dictClass: 'number',
isSearch: true
},
{
title: '主配置',
field: 'master',
dictType: DICT_TYPE.INFRA_BOOLEAN_STRING,
dictClass: 'boolean'
},
{
title: t('form.remark'),
field: 'remark',
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)

View File

@ -0,0 +1,195 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
v-loading="formLoading"
>
<el-form-item label="配置名" prop="name">
<el-input v-model="formData.name" placeholder="请输入配置名" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="存储器" prop="storage">
<el-select
v-model="formData.storage"
placeholder="请选择存储器"
:disabled="formData.id !== undefined"
>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)"
:key="dict.value"
:label="dict.label"
:value="parseInt(dict.value)"
/>
</el-select>
</el-form-item>
<!-- DB -->
<!-- Local / FTP / SFTP -->
<el-form-item
v-if="formData.storage >= 10 && formData.storage <= 12"
label="基础路径"
prop="config.basePath"
>
<el-input v-model="formData.config.basePath" placeholder="请输入基础路径" />
</el-form-item>
<el-form-item
v-if="formData.storage >= 11 && formData.storage <= 12"
label="主机地址"
prop="config.host"
>
<el-input v-model="formData.config.host" placeholder="请输入主机地址" />
</el-form-item>
<el-form-item
v-if="formData.storage >= 11 && formData.storage <= 12"
label="主机端口"
prop="config.port"
>
<el-input-number :min="0" v-model="formData.config.port" placeholder="请输入主机端口" />
</el-form-item>
<el-form-item
v-if="formData.storage >= 11 && formData.storage <= 12"
label="用户名"
prop="config.username"
>
<el-input v-model="formData.config.username" placeholder="请输入密码" />
</el-form-item>
<el-form-item
v-if="formData.storage >= 11 && formData.storage <= 12"
label="密码"
prop="config.password"
>
<el-input v-model="formData.config.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item v-if="formData.storage === 11" label="连接模式" prop="config.mode">
<el-radio-group v-model="formData.config.mode">
<el-radio key="Active" label="Active">主动模式</el-radio>
<el-radio key="Passive" label="Passive">主动模式</el-radio>
</el-radio-group>
</el-form-item>
<!-- S3 -->
<el-form-item v-if="formData.storage === 20" label="节点地址" prop="config.endpoint">
<el-input v-model="formData.config.endpoint" placeholder="请输入节点地址" />
</el-form-item>
<el-form-item v-if="formData.storage === 20" label="存储 bucket" prop="config.bucket">
<el-input v-model="formData.config.bucket" placeholder="请输入 bucket" />
</el-form-item>
<el-form-item v-if="formData.storage === 20" label="accessKey" prop="config.accessKey">
<el-input v-model="formData.config.accessKey" placeholder="请输入 accessKey" />
</el-form-item>
<el-form-item v-if="formData.storage === 20" label="accessSecret" prop="config.accessSecret">
<el-input v-model="formData.config.accessSecret" placeholder="请输入 accessSecret" />
</el-form-item>
<!-- 通用 -->
<el-form-item v-if="formData.storage === 20" label="自定义域名">
<!-- 无需参数校验所以去掉 prop -->
<el-input v-model="formData.config.domain" placeholder="请输入自定义域名" />
</el-form-item>
<el-form-item v-else-if="formData.storage" label="自定义域名" prop="config.domain">
<el-input v-model="formData.config.domain" placeholder="请输入自定义域名" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import * as FileConfigApi from '@/api/infra/fileConfig'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
name: '',
storage: '',
remark: '',
config: {}
})
const formRules = reactive({
name: [{ required: true, message: '配置名不能为空', trigger: 'blur' }],
storage: [{ required: true, message: '存储器不能为空', trigger: 'change' }],
config: {
basePath: [{ required: true, message: '基础路径不能为空', trigger: 'blur' }],
host: [{ required: true, message: '主机地址不能为空', trigger: 'blur' }],
port: [{ required: true, message: '主机端口不能为空', trigger: 'blur' }],
username: [{ required: true, message: '用户名不能为空', trigger: 'blur' }],
password: [{ required: true, message: '密码不能为空', trigger: 'blur' }],
mode: [{ required: true, message: '连接模式不能为空', trigger: 'change' }],
endpoint: [{ required: true, message: '节点地址不能为空', trigger: 'blur' }],
bucket: [{ required: true, message: '存储 bucket 不能为空', trigger: 'blur' }],
accessKey: [{ required: true, message: 'accessKey 不能为空', trigger: 'blur' }],
accessSecret: [{ required: true, message: 'accessSecret 不能为空', trigger: 'blur' }],
domain: [{ required: true, message: '自定义域名不能为空', trigger: 'blur' }]
}
})
const formRef = ref() // Ref
/** 打开弹窗 */
const openModal = async (type: string, id?: number) => {
modelVisible.value = true
modelTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await FileConfigApi.getFileConfig(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ openModal }) // openModal
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as unknown as FileConfigApi.FileConfigVO
if (formType.value === 'create') {
await FileConfigApi.createFileConfig(data)
message.success(t('common.createSuccess'))
} else {
await FileConfigApi.updateFileConfig(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: '',
storage: '',
remark: '',
config: {}
}
formRef.value?.resetFields()
}
</script>

View File

@ -1,294 +1,198 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<template #toolbar_buttons>
<!-- 操作新增 -->
<XButton
type="primary"
preIcon="ep:zoom-in"
:title="t('action.add')"
v-hasPermi="['infra:file-config:create']"
@click="handleCreate(formRef)"
/>
</template>
<template #actionbtns_default="{ row }">
<!-- 操作编辑 -->
<XTextButton
preIcon="ep:edit"
:title="t('action.edit')"
v-hasPermi="['infra:file-config:update']"
@click="handleUpdate(row.id)"
/>
<!-- 操作详情 -->
<XTextButton
preIcon="ep:view"
:title="t('action.detail')"
v-hasPermi="['infra:file-config:query']"
@click="handleDetail(row.id)"
/>
<!-- 操作主配置 -->
<XTextButton
preIcon="ep:flag"
title="主配置"
v-hasPermi="['infra:file-config:update']"
@click="handleMaster(row)"
/>
<!-- 操作测试 -->
<XTextButton preIcon="ep:share" :title="t('action.test')" @click="handleTest(row.id)" />
<!-- 操作删除 -->
<XTextButton
preIcon="ep:delete"
:title="t('action.del')"
v-hasPermi="['infra:file-config:delete']"
@click="deleteData(row.id)"
/>
</template>
</XTable>
</ContentWrap>
<XModal v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(添加 / 修改) -->
<el-form
ref="formRef"
v-if="['create', 'update'].includes(actionType)"
:model="form"
:rules="rules"
label-width="120px"
>
<!-- 搜索 -->
<content-wrap>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
<el-form-item label="配置名" prop="name">
<el-input v-model="form.name" placeholder="请输入配置名" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
<el-input
v-model="queryParams.name"
placeholder="请输入配置名"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="存储器" prop="storage">
<el-select v-model="form.storage" placeholder="请选择存储器" :disabled="form.id !== 0">
<el-select v-model="queryParams.storage" placeholder="请选择存储器" clearable>
<el-option
v-for="(dict, index) in getIntDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)"
:key="index"
v-for="dict in getDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)"
:key="parseInt(dict.value)"
:label="dict.label"
:value="dict.value"
:value="parseInt(dict.value)"
/>
</el-select>
</el-form-item>
<!-- DB -->
<!-- Local / FTP / SFTP -->
<el-form-item
v-if="form.storage >= 10 && form.storage <= 12"
label="基础路径"
prop="config.basePath"
>
<el-input v-model="form.config.basePath" placeholder="请输入基础路径" />
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
/>
</el-form-item>
<el-form-item
v-if="form.storage >= 11 && form.storage <= 12"
label="主机地址"
prop="config.host"
>
<el-input v-model="form.config.host" placeholder="请输入主机地址" />
</el-form-item>
<el-form-item
v-if="form.storage >= 11 && form.storage <= 12"
label="主机端口"
prop="config.port"
>
<el-input-number :min="0" v-model="form.config.port" placeholder="请输入主机端口" />
</el-form-item>
<el-form-item
v-if="form.storage >= 11 && form.storage <= 12"
label="用户名"
prop="config.username"
>
<el-input v-model="form.config.username" placeholder="请输入密码" />
</el-form-item>
<el-form-item
v-if="form.storage >= 11 && form.storage <= 12"
label="密码"
prop="config.password"
>
<el-input v-model="form.config.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item v-if="form.storage === 11" label="连接模式" prop="config.mode">
<el-radio-group v-model="form.config.mode">
<el-radio key="Active" label="Active">主动模式</el-radio>
<el-radio key="Passive" label="Passive">主动模式</el-radio>
</el-radio-group>
</el-form-item>
<!-- S3 -->
<el-form-item v-if="form.storage === 20" label="节点地址" prop="config.endpoint">
<el-input v-model="form.config.endpoint" placeholder="请输入节点地址" />
</el-form-item>
<el-form-item v-if="form.storage === 20" label="存储 bucket" prop="config.bucket">
<el-input v-model="form.config.bucket" placeholder="请输入 bucket" />
</el-form-item>
<el-form-item v-if="form.storage === 20" label="accessKey" prop="config.accessKey">
<el-input v-model="form.config.accessKey" placeholder="请输入 accessKey" />
</el-form-item>
<el-form-item v-if="form.storage === 20" label="accessSecret" prop="config.accessSecret">
<el-input v-model="form.config.accessSecret" placeholder="请输入 accessSecret" />
</el-form-item>
<!-- 通用 -->
<el-form-item v-if="form.storage === 20" label="自定义域名">
<!-- 无需参数校验所以去掉 prop -->
<el-input v-model="form.config.domain" placeholder="请输入自定义域名" />
</el-form-item>
<el-form-item v-else-if="form.storage" label="自定义域名" prop="config.domain">
<el-input v-model="form.config.domain" placeholder="请输入自定义域名" />
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
@click="openModal('create')"
v-hasPermi="['infra:file-config:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</el-form-item>
</el-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(formRef)"
</content-wrap>
<!-- 列表 -->
<content-wrap>
<el-table v-loading="loading" :data="list" align="center">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="配置名" align="center" prop="name" />
<el-table-column label="存储器" align="center" prop="storage">
<template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_FILE_STORAGE" :value="scope.row.storage" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="主配置" align="center" prop="primary">
<template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" />
</template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<!-- 按钮关闭 -->
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
</template>
</XModal>
<el-table-column label="操作" align="center" width="240px">
<template #default="scope">
<el-button
link
type="primary"
@click="openModal('update', scope.row.id)"
v-hasPermi="['infra:file-config:update']"
>
编辑
</el-button>
<el-button
link
type="primary"
:disabled="scope.row.master"
@click="handleMaster(scope.row.id)"
v-hasPermi="['infra:file-config:update']"
>
主配置
</el-button>
<el-button link type="primary" @click="handleTest(scope.row.id)"> 测试 </el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['infra:config:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</content-wrap>
<!-- 表单弹窗添加/修改 -->
<file-config-form ref="modalRef" @success="getList" />
</template>
<script setup lang="ts" name="FileConfig">
import type { FormInstance } from 'element-plus'
// import
<script setup lang="ts" name="Config">
import * as FileConfigApi from '@/api/infra/fileConfig'
import { rules, allSchemas } from './fileConfig.data'
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
const { t } = useI18n() //
import FileConfigForm from './form.vue'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
const message = useMessage() //
//
const [registerTable, { reload, deleteData }] = useXTable({
allSchemas: allSchemas,
getListApi: FileConfigApi.getFileConfigPageApi,
deleteApi: FileConfigApi.deleteFileConfigApi
})
const { t } = useI18n() //
// ========== CRUD ==========
const actionLoading = ref(false) //
const actionType = ref('') //
const dialogVisible = ref(false) //
const dialogTitle = ref('edit') //
const formRef = ref<FormInstance>() // Ref
const detailData = ref() // Ref
const form = ref<FileConfigApi.FileConfigVO>({
id: 0,
name: '',
storage: 0,
master: false,
visible: false,
config: {
basePath: '',
host: '',
port: 0,
username: '',
password: '',
mode: '',
endpoint: '',
bucket: '',
accessKey: '',
accessSecret: '',
domain: ''
},
remark: '',
createTime: new Date()
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
storage: undefined,
createTime: []
})
//
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
const queryFormRef = ref() //
//
const handleCreate = (formEl: FormInstance | undefined) => {
setDialogTile('create')
formEl?.resetFields()
form.value = {
id: 0,
name: '',
storage: 0,
master: false,
visible: false,
config: {
basePath: '',
host: '',
port: 0,
username: '',
password: '',
mode: '',
endpoint: '',
bucket: '',
accessKey: '',
accessSecret: '',
domain: ''
},
remark: '',
createTime: new Date()
/** 查询参数列表 */
const getList = async () => {
loading.value = true
try {
const data = await FileConfigApi.getFileConfigPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
//
const handleUpdate = async (rowId: number) => {
//
const res = await FileConfigApi.getFileConfigApi(rowId)
form.value = res
setDialogTile('update')
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
//
const handleDetail = async (rowId: number) => {
setDialogTile('detail')
//
const res = await FileConfigApi.getFileConfigApi(rowId)
detailData.value = res
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
//
const handleMaster = (row: FileConfigApi.FileConfigVO) => {
message
.confirm('是否确认修改配置【 ' + row.name + ' 】为主配置?', t('common.reminder'))
.then(async () => {
await FileConfigApi.updateFileConfigMasterApi(row.id)
await reload()
})
/** 添加/修改操作 */
const modalRef = ref()
const openModal = (type: string, id?: number) => {
modalRef.value.openModal(type, id)
}
const handleTest = async (rowId: number) => {
const res = await FileConfigApi.testFileConfigApi(rowId)
message.alert('测试通过,上传文件成功!访问地址:' + res)
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await FileConfigApi.deleteFileConfig(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
//
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate(async (valid) => {
if (valid) {
actionLoading.value = true
//
try {
if (actionType.value === 'create') {
await FileConfigApi.createFileConfigApi(form.value)
message.success(t('common.createSuccess'))
} else {
await FileConfigApi.updateFileConfigApi(form.value)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
} finally {
actionLoading.value = false
await reload()
}
}
})
/** 主配置按钮操作 */
const handleMaster = async (id) => {
try {
await message.confirm('是否确认修改配置编号为"' + id + '"的数据项为主配置?')
await FileConfigApi.updateFileConfigMaster(id)
message.success(t('common.updateSuccess'))
await getList()
} catch {}
}
/** 测试按钮操作 */
const handleTest = async (id) => {
try {
const response = await FileConfigApi.testFileConfig(id)
message.alert('测试通过,上传文件成功!访问地址:' + response)
} catch {}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -1,52 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
const { t } = useI18n() // 国际化
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: 'seq',
action: true,
columns: [
{
title: '文件名',
field: 'name'
},
{
title: '文件路径',
field: 'path',
isSearch: true
},
{
title: 'URL',
field: 'url',
table: {
cellRender: {
name: 'XPreview'
}
}
},
{
title: '文件大小',
field: 'size',
formatter: 'formatSize'
},
{
title: '文件类型',
field: 'type',
isSearch: true
},
{
title: t('common.createTime'),
field: 'createTime',
formatter: 'formatDate',
isForm: false,
search: {
show: true,
itemRender: {
name: 'XDataTimePicker'
}
}
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -1,173 +0,0 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<template #toolbar_buttons>
<XButton
type="primary"
preIcon="ep:upload"
title="上传文件"
@click="uploadDialogVisible = true"
/>
</template>
<template #actionbtns_default="{ row }">
<XTextButton
preIcon="ep:copy-document"
:title="t('common.copy')"
@click="handleCopy(row.url)"
/>
<XTextButton preIcon="ep:view" :title="t('action.detail')" @click="handleDetail(row)" />
<XTextButton
preIcon="ep:delete"
:title="t('action.del')"
v-hasPermi="['infra:file:delete']"
@click="deleteData(row.id)"
/>
</template>
</XTable>
</ContentWrap>
<XModal v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(详情) -->
<Descriptions :schema="allSchemas.detailSchema" :data="detailData">
<template #url="{ row }">
<el-image
v-if="row.type === 'jpg' || 'png' || 'gif'"
style="width: 100px; height: 100px"
:src="row.url"
:key="row.url"
lazy
/>
<span>{{ row.url }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<XButton :title="t('dialog.close')" @click="dialogVisible = false" />
</template>
</XModal>
<XModal v-model="uploadDialogVisible" :title="uploadDialogTitle">
<el-upload
ref="uploadRef"
:action="updateUrl + '?updateSupport=' + updateSupport"
:headers="uploadHeaders"
:drag="true"
:limit="1"
:multiple="true"
:show-file-list="true"
:disabled="uploadDisabled"
:before-upload="beforeUpload"
:on-exceed="handleExceed"
:on-success="handleFileSuccess"
:on-error="excelUploadError"
:auto-upload="false"
accept=".jpg, .png, .gif"
>
<Icon icon="ep:upload-filled" />
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip">请上传 .jpg, .png, .gif 标准格式文件</div>
</template>
</el-upload>
<template #footer>
<!-- 按钮保存 -->
<XButton
type="primary"
preIcon="ep:upload-filled"
:title="t('action.save')"
@click="submitFileForm()"
/>
<!-- 按钮关闭 -->
<XButton :title="t('dialog.close')" @click="uploadDialogVisible = false" />
</template>
</XModal>
</template>
<script setup lang="ts" name="FileList">
import type { UploadInstance, UploadRawFile } from 'element-plus'
// import
import { allSchemas } from './fileList.data'
import * as FileApi from '@/api/infra/fileList'
import { getAccessToken, getTenantId } from '@/utils/auth'
import { useClipboard } from '@vueuse/core'
const { t } = useI18n() //
const message = useMessage() //
//
const [registerTable, { reload, deleteData }] = useXTable({
allSchemas: allSchemas,
getListApi: FileApi.getFilePageApi,
deleteApi: FileApi.deleteFileApi
})
const detailData = ref() // Ref
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const uploadDialogVisible = ref(false)
const uploadDialogTitle = ref('上传')
const updateSupport = ref(0)
const uploadDisabled = ref(false)
const uploadRef = ref<UploadInstance>()
let updateUrl = import.meta.env.VITE_UPLOAD_URL
const uploadHeaders = ref()
//
const beforeUpload = (file: UploadRawFile) => {
const isImg = file.type === 'image/jpeg' || 'image/gif' || 'image/png'
const isLt5M = file.size / 1024 / 1024 < 5
if (!isImg) message.error('上传文件只能是 jpeg / gif / png 格式!')
if (!isLt5M) message.error('上传文件大小不能超过 5MB!')
return isImg && isLt5M
}
//
// const handleFileChange = (uploadFile: UploadFile): void => {
// uploadRef.value.data.path = uploadFile.name
// }
//
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
}
message.success('上传成功')
uploadDialogVisible.value = false
uploadDisabled.value = false
await reload()
}
//
const handleExceed = (): void => {
message.error('最多只能上传一个文件!')
}
//
const excelUploadError = (): void => {
message.error('导入数据失败,请您重新上传!')
}
//
const handleDetail = (row: FileApi.FileVO) => {
//
detailData.value = row
dialogTitle.value = t('action.detail')
dialogVisible.value = true
}
// ========== ==========
const handleCopy = async (text: string) => {
const { copy, copied, isSupported } = useClipboard({ source: text })
if (!isSupported) {
message.error(t('common.copyError'))
} else {
await copy()
if (unref(copied)) {
message.success(t('common.copySuccess'))
}
}
}
</script>

View File

@ -0,0 +1,71 @@
<template>
<Dialog title="IP 查询" v-model="modelVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="80px"
v-loading="formLoading"
>
<el-form-item label="IP" prop="ip">
<el-input v-model="formData.ip" placeholder="请输入 IP 地址" />
</el-form-item>
<el-form-item label="地址" prop="result">
<el-input v-model="formData.result" readonly placeholder="展示查询 IP 结果" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import * as AreaApi from '@/api/system/area'
const message = useMessage() //
const modelVisible = ref(false) //
const formLoading = ref(false) //
const formData = ref({
ip: '',
result: undefined
})
const formRules = reactive({
ip: [{ required: true, message: 'IP 地址不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
/** 打开弹窗 */
const openModal = async () => {
modelVisible.value = true
resetForm()
}
defineExpose({ openModal }) // openModal
/** 提交表单 */
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
formData.value.result = await AreaApi.getAreaByIp(formData.value.ip!.trim())
message.success('查询成功')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
ip: '',
result: undefined
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,71 @@
<template>
<!-- 操作栏 -->
<content-wrap>
<el-button type="primary" @click="openModal()">
<Icon icon="ep:plus" class="mr-5px" /> IP 查询
</el-button>
</content-wrap>
<!-- 列表 -->
<content-wrap>
<div style="width: 100%; height: 700px">
<!-- AutoResizer 自动调节大小 -->
<el-auto-resizer>
<template #default="{ height, width }">
<!-- Virtualized Table 虚拟化表格高性能解决表格在大数据量下的卡顿问题 -->
<el-table-v2
:columns="columns"
:data="list"
:width="width"
:height="height"
expand-column-key="id"
/>
</template>
</el-auto-resizer>
</div>
</content-wrap>
<!-- 表单弹窗添加/修改 -->
<area-form ref="modalRef" />
</template>
<script setup lang="tsx" name="Area">
import type { Column } from 'element-plus'
import AreaForm from './form.vue'
import * as AreaApi from '@/api/system/area'
// column
const columns: Column[] = [
{
dataKey: 'id', // {id:9527, name:'Mike'} id
title: '编号', //
width: 400, //
fixed: true, //
key: 'id' // key
},
{
dataKey: 'name',
title: '地名',
width: 200
}
]
//
const list = ref([])
/**
* 获得数据列表
*/
const getList = async () => {
list.value = await AreaApi.getAreaTree()
}
/** 添加/修改操作 */
const modalRef = ref()
const openModal = () => {
modalRef.value.openModal()
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -1,84 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
name: [required],
sort: [required],
// email: [required],
email: [
{ required: true, message: t('profile.rules.mail'), trigger: 'blur' },
{
type: 'email',
message: t('profile.rules.truemail'),
trigger: ['blur', 'change']
}
],
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)

View File

@ -0,0 +1,190 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible" width="800">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
<el-row>
<el-col :span="24" v-if="formData.parentId !== 0">
<el-form-item label="上级部门" prop="parentId">
<el-tree-select
v-model="formData.parentId"
:data="deptOptions"
:props="{ value: 'id', label: 'name', children: 'children' }"
value-key="deptId"
placeholder="选择上级部门"
check-strictly
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="部门名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入部门名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="显示排序" prop="sort">
<el-input-number v-model="formData.sort" controls-position="right" :min="0" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="负责人" prop="leaderUserId">
<el-select
v-model="formData.leaderUserId"
placeholder="请输入负责人"
clearable
style="width: 100%"
>
<el-option
v-for="item in userList"
:key="item.id"
:label="item.nickname"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系电话" prop="phone">
<el-input v-model="formData.phone" placeholder="请输入联系电话" maxlength="11" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="邮箱" prop="email">
<el-input v-model="formData.email" placeholder="请输入邮箱" maxlength="50" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-select v-model="formData.status" placeholder="请选择状态" clearable>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import * as DeptApi from '@/api/system/dept'
import { UserVO } from '@/api/system/user'
import { handleTree } from '@/utils/tree'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formRef = ref() // Ref
const deptOptions = ref() //
const userList = ref() //
const formData = ref({
id: undefined,
title: '',
parentId: undefined,
name: undefined,
sort: undefined,
leaderUserId: undefined,
phone: undefined,
email: undefined,
status: undefined
})
const formRules = reactive({
parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }],
name: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
order: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }],
email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
phone: [
{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
]
})
/** 打开弹窗 */
const openModal = async (type: string, id?: number, userOption?: UserVO[]) => {
userList.value = userOption
modelVisible.value = true
modelTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await DeptApi.getDeptApi(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ openModal }) // openModal
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as unknown as DeptApi.DeptVO
if (formType.value === 'create') {
await DeptApi.createDeptApi(data)
message.success(t('common.createSuccess'))
} else {
await DeptApi.updateDeptApi(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
title: '',
parentId: undefined,
name: undefined,
sort: undefined,
leaderUserId: undefined,
phone: undefined,
email: undefined,
status: undefined
}
formRef.value?.resetFields()
}
// []
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)
}
// ========== ==========
onMounted(async () => {
await getTree()
})
</script>

View File

@ -1,174 +1,177 @@
<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-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
<el-form-item label="部门名称" prop="title">
<el-input v-model="queryParams.name" placeholder="请输入部门名称" clearable />
</el-form-item>
<el-form-item label="部门状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择" clearable>
<el-option
v-for="item in userOption"
:key="item.id"
:label="item.nickname"
:value="item.id"
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
</el-select>
</template>
</Form>
<template #footer>
<!-- 按钮保存 -->
<XButton
v-if="['create', 'update'].includes(actionType)"
type="primary"
:loading="actionLoading"
@click="submitForm()"
:title="t('action.save')"
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
@click="openModal('create')"
v-hasPermi="['system:dept:create']"
><Icon icon="ep:plus" class="mr-5px" /> 新增</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain @click="toggleExpandAll"
><Icon icon="ep:sort" class="mr-5px" /> 展开/折叠</el-button
>
</el-col>
</el-row>
<!-- 列表 -->
<el-table
v-if="refreshTable"
v-loading="loading"
:data="deptDatas"
row-key="id"
:default-expand-all="isExpandAll"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column prop="name" label="部门名称" width="260" />
<el-table-column prop="leader" label="负责人" :formatter="userNicknameFormat" width="120" />
<el-table-column prop="sort" label="排序" width="200" />
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<!-- 按钮关闭 -->
<XButton :loading="actionLoading" @click="dialogVisible = false" :title="t('dialog.close')" />
</template>
</XModal>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button
link
type="primary"
icon="el-icon-edit"
@click="openModal('update', scope.row.id)"
v-hasPermi="['system:dept:update']"
>修改</el-button
>
<el-button
v-if="scope.row.parentId !== 0"
link
type="danger"
icon="el-icon-delete"
@click="handleDelete(scope.row.id)"
v-hasPermi="['system:dept:delete']"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
</ContentWrap>
<!-- 添加或修改部门对话框 -->
<dept-form ref="modalRef" @success="getList" />
</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 { handleTree } from '@/utils/tree'
import * as DeptApi from '@/api/system/dept'
import { getListSimpleUsersApi, UserVO } from '@/api/system/user'
const { t } = useI18n() //
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import DeptForm from './form.vue'
import { dateFormatter } from '@/utils/formatTime'
import { getSimpleUserList, UserVO } from '@/api/system/user'
const message = useMessage() //
//
const xGrid = ref<any>() // Grid Ref
const treeConfig = {
transform: true,
rowField: 'id',
parentField: 'parentId',
expandAll: true
}
const { t } = useI18n() //
//
const queryParams = reactive({
title: '',
name: undefined,
status: undefined,
pageNo: 1,
pageSize: 100
})
//
const dialogVisible = ref(false) //
const dialogTitle = ref('edit') //
const actionType = ref('') //
const actionLoading = ref(false) //
const formRef = ref<FormExpose>() // Ref
const deptOptions = ref() //
const queryFormRef = ref() //
const deptDatas = ref() //
const userOption = ref<UserVO[]>([])
const isExpandAll = ref(true) //
const refreshTable = ref(true) //
const loading = ref(true) //
//
const getUserList = async () => {
const res = await getListSimpleUsersApi()
const res = await getSimpleUserList()
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 toggleExpandAll = () => {
refreshTable.value = false
isExpandAll.value = !isExpandAll.value
console.log(isExpandAll.value)
nextTick(() => {
refreshTable.value = true
})
}
/** 搜索按钮操作 */
const handleQuery = () => {
getList()
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await DeptApi.deleteDeptApi(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 查询部门列表 */
const getList = async () => {
loading.value = true
try {
const res = await DeptApi.getDeptPageApi(queryParams)
deptDatas.value = handleTree(res)
} finally {
loading.value = false
}
}
/** 重置按钮操作 */
const resetQuery = () => {
queryParams.pageNo = 1
queryParams.name = undefined
queryParams.status = undefined
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const modalRef = ref()
const openModal = (type: string, id?: number) => {
modalRef.value.openModal(type, id, userOption.value)
}
const userNicknameFormat = (row) => {
if (!row || !row.leaderUserId) {
return '未设置'
@ -184,6 +187,6 @@ const userNicknameFormat = (row) => {
// ========== ==========
onMounted(async () => {
await getUserList()
await getTree()
await getList()
})
</script>

View File

@ -0,0 +1,177 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="80px"
v-loading="formLoading"
>
<el-form-item label="字典类型" prop="type">
<el-input
:disabled="typeof formData.id !== 'undefined'"
v-model="formData.dictType"
placeholder="请输入参数名称"
/>
</el-form-item>
<el-form-item label="数据标签" prop="label">
<el-input v-model="formData.label" placeholder="请输入数据标签" />
</el-form-item>
<el-form-item label="数据键值" prop="value">
<el-input v-model="formData.value" placeholder="请输入数据键值" />
</el-form-item>
<el-form-item label="显示排序" prop="sort">
<el-input-number v-model="formData.sort" controls-position="right" :min="0" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="parseInt(dict.value)"
:label="parseInt(dict.value)"
>{{ dict.label }}</el-radio
>
</el-radio-group>
</el-form-item>
<el-form-item label="颜色类型" prop="colorType">
<el-select v-model="formData.colorType">
<el-option
v-for="item in colorTypeOptions"
:key="item.value"
:label="item.label + '(' + item.value + ')'"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="CSS Class" prop="cssClass">
<el-input v-model="formData.cssClass" placeholder="请输入 CSS Class" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import * as DictDataApi from '@/api/system/dict/dict.data'
import { CommonStatusEnum } from '@/utils/constants'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
sort: undefined,
label: '',
value: '',
dictType: '',
status: CommonStatusEnum.ENABLE,
colorType: '',
cssClass: '',
remark: ''
})
const formRules = reactive({
label: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],
value: [{ required: true, message: '数据键值不能为空', trigger: 'blur' }],
sort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
//
const colorTypeOptions = readonly([
{
value: 'default',
label: '默认'
},
{
value: 'primary',
label: '主要'
},
{
value: 'success',
label: '成功'
},
{
value: 'info',
label: '信息'
},
{
value: 'warning',
label: '警告'
},
{
value: 'danger',
label: '危险'
}
])
/** 打开弹窗 */
const openModal = async (type: string, id?: number) => {
modelVisible.value = true
modelTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await DictDataApi.getDictData(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ openModal }) // openModal
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as DictDataApi.DictDataVO
if (formType.value === 'create') {
await DictDataApi.createDictData(data)
message.success(t('common.createSuccess'))
} else {
await DictDataApi.updateDictData(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
sort: undefined,
label: '',
value: '',
dictType: '',
status: CommonStatusEnum.ENABLE,
colorType: '',
cssClass: '',
remark: ''
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,202 @@
<template>
<content-wrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="字典名称" prop="dictType">
<el-select v-model="queryParams.dictType" class="!w-240px">
<el-option v-for="item in dicts" :key="item.type" :label="item.name" :value="item.type" />
</el-select>
</el-form-item>
<el-form-item label="字典标签" prop="label">
<el-input
v-model="queryParams.label"
placeholder="请输入字典标签"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="数据状态" clearable class="!w-240px">
<el-option
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button type="primary" @click="openModal('create')" v-hasPermi="['system:dict:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['system:dict:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</content-wrap>
<!-- 列表 -->
<content-wrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="字典编码" align="center" prop="id" />
<el-table-column label="字典标签" align="center" prop="label" />
<el-table-column label="字典键值" align="center" prop="value" />
<el-table-column label="字典排序" align="center" prop="sort" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="颜色类型" align="center" prop="colorType" />
<el-table-column label="CSS Class" align="center" prop="cssClass" />
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openModal('update', scope.row.id)"
v-hasPermi="['system:dict:update']"
>
修改
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['system:dict:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</content-wrap>
<!-- 表单弹窗添加/修改 -->
<data-form ref="modalRef" @success="getList" />
</template>
<script setup lang="ts" name="Data">
import * as DictDataApi from '@/api/system/dict/dict.data'
import * as DictTypeApi from '@/api/system/dict/dict.type'
import { getDictOptions, DICT_TYPE } from '@/utils/dict'
import download from '@/utils/download'
import { dateFormatter } from '@/utils/formatTime'
import DataForm from './data.form.vue'
const message = useMessage() //
const { t } = useI18n() //
const route = useRoute() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
label: '',
status: undefined,
dictType: route.params.dictType
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
const dicts = ref<DictTypeApi.DictTypeVO[]>() //
/** 查询参数列表 */
const getList = async () => {
loading.value = true
try {
const data = await DictDataApi.getDictDataPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const modalRef = ref()
const openModal = (type: string, id?: number) => {
modalRef.value.openModal(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await DictDataApi.deleteDictData(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await DictDataApi.exportDictDataApi(queryParams)
download.excel(data, '字典数据.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 查询字典(精简)列表 */
const getDictList = async () => {
dicts.value = await DictTypeApi.listSimpleDictType()
}
/** 初始化 **/
onMounted(() => {
getList()
// )
getDictList()
})
</script>

View File

@ -1,104 +0,0 @@
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)

View File

@ -1,65 +0,0 @@
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)

View File

@ -0,0 +1,123 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="80px"
v-loading="formLoading"
>
<el-form-item label="字典名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入字典名称" />
</el-form-item>
<el-form-item label="字典类型" prop="type">
<el-input
:disabled="typeof formData.id !== 'undefined'"
v-model="formData.type"
placeholder="请输入参数名称"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="parseInt(dict.value)"
:label="parseInt(dict.value)"
>{{ dict.label }}</el-radio
>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import * as DictTypeApi from '@/api/system/dict/dict.type'
import { CommonStatusEnum } from '@/utils/constants'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
name: '',
type: '',
status: CommonStatusEnum.ENABLE,
remark: ''
})
const formRules = reactive({
name: [{ required: true, message: '字典名称不能为空', trigger: 'blur' }],
type: [{ required: true, message: '字典类型不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
/** 打开弹窗 */
const openModal = async (type: string, id?: number) => {
modelVisible.value = true
modelTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await DictTypeApi.getDictType(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ openModal }) // openModal
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as DictTypeApi.DictTypeVO
if (formType.value === 'create') {
await DictTypeApi.createDictType(data)
message.success(t('common.createSuccess'))
} else {
await DictTypeApi.updateDictType(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
type: '',
name: '',
status: CommonStatusEnum.ENABLE,
remark: ''
}
formRef.value?.resetFields()
}
</script>

View File

@ -1,257 +1,211 @@
<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()"
<!-- 搜索工作栏 -->
<content-wrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="字典名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入字典名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="字典类型" prop="type">
<el-input
v-model="queryParams.type"
placeholder="请输入字典类型"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="字典状态" clearable class="!w-240px">
<el-option
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="parseInt(dict.value)"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="yyyy-MM-dd HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button type="primary" @click="openModal('create')" v-hasPermi="['system:dict:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['system:dict:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</content-wrap>
<!-- 列表 -->
<content-wrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="字典编号" align="center" prop="id" />
<el-table-column label="字典名称" align="center" prop="name" show-overflow-tooltip />
<el-table-column label="字典类型" align="center" prop="type" width="300" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</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"
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column
label="创建时间"
:formatter="dateFormatter"
align="center"
prop="createTime"
width="180"
/>
<!-- 操作按钮 -->
<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>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openModal('update', scope.row.id)"
v-hasPermi="['system:dict:update']"
>
修改
</el-button>
<router-link :to="'/dict/type/data/' + scope.row.type">
<el-button link type="primary">数据</el-button>
</router-link>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['system:dict:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</content-wrap>
<!-- 表单弹窗添加/修改 -->
<dict-type-form ref="modalRef" @success="getList" />
</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 { getDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
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() //
import DictTypeForm from './form.vue'
import download from '@/utils/download'
const message = useMessage() //
const { t } = useI18n() //
const [registerType, { reload: typeGetList, deleteData: typeDeleteData }] = useXTable({
allSchemas: DictTypeSchemas.allSchemas,
getListApi: DictTypeApi.getDictTypePageApi,
deleteApi: DictTypeApi.deleteDictTypeApi
})
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
dictType: null
pageNo: 1,
pageSize: 10,
name: '',
type: '',
status: undefined,
createTime: []
})
const [registerData, { reload: dataGetList, deleteData: dataDeleteData }] = useXTable({
allSchemas: DictDataSchemas.allSchemas,
params: queryParams,
getListApi: DictDataApi.getDictDataPageApi,
deleteApi: DictDataApi.deleteDictDataApi
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询字典类型列表 */
const getList = async () => {
loading.value = true
try {
const data = await DictTypeApi.getDictTypePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const modalRef = ref()
const openModal = (type: string, id?: number) => {
modalRef.value.openModal(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await DictTypeApi.deleteDictType(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await DictTypeApi.exportDictType(queryParams)
download.excel(data, '字典类型.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
// ========== ==========
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 nextTick()
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
}
// dictTypeValueform
watch(dictTypeValue, (val) => {
unref(typeFormRef)?.setValues({ type: val })
})
//
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>

View File

@ -1,54 +0,0 @@
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: 'id',
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)

View File

@ -0,0 +1,112 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="应用名" prop="applicationName">
<el-input v-model="formData.applicationName" placeholder="请输入应用名" clearable />
</el-form-item>
<el-form-item label="错误码编码" prop="code">
<el-input v-model="formData.code" placeholder="请输入错误码编码" clearable />
</el-form-item>
<el-form-item label="错误码提示" prop="message">
<el-input v-model="formData.message" placeholder="请输入错误码提示" clearable />
</el-form-item>
<el-form-item label="备注" prop="memo">
<el-input v-model="formData.memo" placeholder="请输入备注" clearable />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import * as ErrorCodeApi from '@/api/system/errorCode'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
//
const formData = ref({
id: undefined,
code: undefined,
applicationName: '',
message: '',
memo: ''
})
//
const formRules = reactive({
applicationName: [{ required: true, message: '应用名不能为空', trigger: 'blur' }],
code: [{ required: true, message: '错误码编码不能为空', trigger: 'blur' }],
message: [{ required: true, message: '错误码提示不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
/** 打开弹窗 */
const openModal = async (type: string, id?: number) => {
modelVisible.value = true
modelTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await ErrorCodeApi.getErrorCodeApi(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ openModal }) // openModal
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as ErrorCodeApi.ErrorCodeVO
if (formType.value === 'create') {
await ErrorCodeApi.createErrorCodeApi(data)
message.success(t('common.createSuccess'))
} else {
await ErrorCodeApi.updateErrorCodeApi(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 表单重置 */
const resetForm = () => {
formData.value = {
id: undefined,
applicationName: '',
code: undefined,
message: '',
memo: ''
}
formRef.value?.resetFields()
}
</script>

View File

@ -1,145 +1,229 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<!-- 操作新增 -->
<template #toolbar_buttons>
<XButton
<!-- 搜索工作栏 -->
<content-wrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="90px"
>
<el-form-item label="错误码类型" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择错误码类型" clearable>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_ERROR_CODE_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
class="!w-240px"
/>
</el-select>
</el-form-item>
<el-form-item label="应用名" prop="applicationName">
<el-input
v-model="queryParams.applicationName"
placeholder="请输入应用名"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="错误码编码" prop="code">
<el-input
v-model="queryParams.code"
placeholder="请输入错误码编码"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="错误码提示" prop="message">
<el-input
v-model="queryParams.message"
placeholder="请输入错误码提示"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
preIcon="ep:zoom-in"
:title="t('action.add')"
@click="openModal('create')"
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()"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['system:error-code:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</content-wrap>
<!-- 列表 -->
<content-wrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="类型" align="center" prop="type" width="80">
<template #default="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_ERROR_CODE_TYPE" :value="scope.row.type" />
</template>
</el-table-column>
<el-table-column label="应用名" align="center" prop="applicationName" width="200" />
<el-table-column label="错误码编码" align="center" prop="code" width="120" />
<el-table-column label="错误码提示" align="center" prop="message" width="300" />
<el-table-column label="备注" align="center" prop="memo" width="200" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<!-- 按钮关闭 -->
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
</template>
</XModal>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button
link
type="primary"
@click="openModal('update', scope.row.id)"
v-hasPermi="['system:error-code:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['system:error-code:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</content-wrap>
<!-- 表单弹窗添加/修改 -->
<error-code-form ref="modalRef" @success="getList" />
</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() //
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import ErrorCodeForm from './form.vue'
import download from '@/utils/download'
const message = useMessage() //
//
const [registerTable, { reload, deleteData }] = useXTable({
allSchemas: allSchemas,
getListApi: ErrorCodeApi.getErrorCodePageApi,
deleteApi: ErrorCodeApi.deleteErrorCodeApi
const { t } = useI18n() //
//
const loading = ref(true)
//
const exportLoading = ref(false)
//
const total = ref(0)
//
const list = ref([])
//
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
type: undefined,
applicationName: undefined,
code: undefined,
message: undefined,
createTime: []
})
//
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 queryFormRef = ref()
//
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
/** 查询列表 */
const getList = async () => {
loading.value = true
//
try {
const data = await ErrorCodeApi.getErrorCodePageApi(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
//
const handleCreate = () => {
setDialogTile('create')
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
//
const handleUpdate = async (rowId: number) => {
setDialogTile('update')
//
const res = await ErrorCodeApi.getErrorCodeApi(rowId)
unref(formRef)?.setValues(res)
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
//
const handleDetail = async (rowId: number) => {
setDialogTile('detail')
//
const res = await ErrorCodeApi.getErrorCodeApi(rowId)
detailData.value = res
/** 添加/修改操作 */
const modalRef = ref()
const openModal = (type: string, id?: number) => {
modalRef.value.openModal(type, id)
}
// /
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()
}
}
})
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
await ErrorCodeApi.deleteErrorCodeApi(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await ErrorCodeApi.excelErrorCodeApi(queryParams)
download.excel(data, '错误码.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -1,10 +1,10 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
import { dateFormatter } from '@/utils/formatTime'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
// mail: [required],
mail: [
{ required: true, message: t('profile.rules.mail'), trigger: 'blur' },
{
@ -20,56 +20,54 @@ export const rules = reactive({
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
}
// CrudSchemahttps://kailong110120130.gitee.io/vue-element-plus-admin-doc/hooks/useCrudSchemas.html
const crudSchemas = reactive<CrudSchema[]>([
{
label: '邮箱',
field: 'mail',
isSearch: true
},
{
label: '用户名',
field: 'username',
isSearch: true
},
{
label: '密码',
field: 'password',
isTable: false
},
{
label: 'SMTP 服务器域名',
field: 'host'
},
{
label: 'SMTP 服务器端口',
field: 'port',
form: {
component: 'InputNumber',
value: 465
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
},
{
label: '是否开启 SSL',
field: 'sslEnable',
dictType: DICT_TYPE.INFRA_BOOLEAN_STRING,
dictClass: 'boolean',
form: {
component: 'Radio'
}
},
{
label: '创建时间',
field: 'createTime',
isForm: false,
formatter: dateFormatter
},
{
label: '操作',
field: 'action',
isForm: false
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@ -0,0 +1,66 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible">
<Form ref="formRef" :schema="allSchemas.formSchema" :rules="rules" v-loading="formLoading" />
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import * as MailAccountApi from '@/api/system/mail/account'
import { rules, allSchemas } from './account.data'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formRef = ref() // Ref
/** 打开弹窗 */
const openModal = async (type: string, id?: number) => {
modelVisible.value = true
modelTitle.value = t('action.' + type)
formType.value = type
//
if (id) {
formLoading.value = true
try {
const data = await MailAccountApi.getMailAccount(id)
formRef.value.setValues(data)
} finally {
formLoading.value = false
}
}
}
defineExpose({ openModal }) // openModal
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.getElFormRef().validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formRef.value.formModel as MailAccountApi.MailAccountVO
if (formType.value === 'create') {
await MailAccountApi.createMailAccount(data)
message.success(t('common.createSuccess'))
} else {
await MailAccountApi.updateMailAccount(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
</script>

View File

@ -1,151 +1,84 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<template #toolbar_buttons>
<!-- 操作新增 -->
<XButton
<!-- 搜索工作栏 -->
<content-wrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams">
<!-- 新增等操作按钮 -->
<template #actionMore>
<el-button
type="primary"
preIcon="ep:zoom-in"
:title="t('action.add')"
@click="openModal('create')"
v-hasPermi="['system:mail-account:create']"
@click="handleCreate()"
/>
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</template>
<template #actionbtns_default="{ row }">
<!-- 操作修改 -->
<XTextButton
preIcon="ep:edit"
:title="t('action.edit')"
</Search>
</content-wrap>
<!-- 列表 -->
<content-wrap>
<Table
:columns="allSchemas.tableColumns"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
>
<template #action="{ row }">
<el-button
link
type="primary"
@click="openModal('update', row.id)"
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')"
>
编辑
</el-button>
<el-button
link
type="danger"
v-hasPermi="['system:mail-account:delete']"
@click="deleteData(row.id)"
/>
@click="handleDelete(row.id)"
>
删除
</el-button>
</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>
</Table>
</content-wrap>
<!-- 表单弹窗添加/修改 -->
<mail-account-form ref="modalRef" @success="getList" />
</template>
<script setup lang="ts" name="MailAccount">
import { FormExpose } from '@/components/Form'
// import
import { rules, allSchemas } from './account.data'
import { allSchemas } from './account.data'
import * as MailAccountApi from '@/api/system/mail/account'
import MailAccountForm from './form.vue'
const { t } = useI18n() //
const message = useMessage() //
//
const [registerTable, { reload, deleteData }] = useXTable({
allSchemas: allSchemas,
getListApi: MailAccountApi.getMailAccountPageApi,
deleteApi: MailAccountApi.deleteMailAccountApi
// tableObject
// tableMethods
// https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/table.html#usetable
const { tableObject, tableMethods } = useTable({
getListApi: MailAccountApi.getMailAccountPage, //
delListApi: MailAccountApi.deleteMailAccount //
})
//
const { getList, setSearchParams } = tableMethods
//
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 modalRef = ref()
const openModal = (type: string, id?: number) => {
modalRef.value.openModal(type, id)
}
//
const handleCreate = () => {
setDialogTile('create')
modelLoading.value = false
/** 删除按钮操作 */
const handleDelete = (id: number) => {
tableMethods.delList(id, 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()
}
}
})
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -0,0 +1,31 @@
<template>
<Dialog title="详情" v-model="modelVisible" :scroll="true" :max-height="500">
<Descriptions :schema="allSchemas.detailSchema" :data="detailData">
<!-- 展示 HTML 内容 -->
<template #templateContent="{ row }">
<div v-html="row.templateContent"></div>
</template>
</Descriptions>
</Dialog>
</template>
<script setup lang="ts">
import * as MailLogApi from '@/api/system/mail/log'
import { allSchemas } from './log.data'
const modelVisible = ref(false) //
const detailLoading = ref(false) //
const detailData = ref() //
/** 打开弹窗 */
const openModal = async (id: number) => {
modelVisible.value = true
//
detailLoading.value = true
try {
detailData.value = await MailLogApi.getMailLog(id)
} finally {
detailLoading.value = false
}
}
defineExpose({ openModal }) // openModal
</script>

View File

@ -1,98 +1,59 @@
<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')"
<!-- 搜索工作栏 -->
<content-wrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
</content-wrap>
<!-- 列表 -->
<content-wrap>
<Table
:columns="allSchemas.tableColumns"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
>
<template #action="{ row }">
<el-button
link
type="primary"
@click="openModal(row.id)"
v-hasPermi="['system:mail-log:query']"
@click="handleDetail(row.id)"
/>
>
详情
</el-button>
</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>
</Table>
</content-wrap>
<!-- 表单弹窗详情 -->
<mail-log-detail ref="modalRef" />
</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'
import MailLogDetail from './detail.vue'
const { t } = useI18n() //
//
const queryParams = reactive({
accountId: null
// tableObject
// tableMethods
// https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/table.html#usetable
const { tableObject, tableMethods } = useTable({
getListApi: MailLogApi.getMailLogPage //
})
const [registerTable] = useXTable({
allSchemas: allSchemas,
topActionSlots: false,
params: queryParams,
getListApi: MailLogApi.getMailLogPageApi
})
const accountOptions = ref<any[]>([]) //
//
const { getList, setSearchParams } = tableMethods
//
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 modalRef = ref()
const openModal = (id: number) => {
modalRef.value.openModal(id)
}
//
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
})
getList()
})
</script>

View File

@ -1,121 +1,133 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
import { dateFormatter } from '@/utils/formatTime'
import * as MailAccountApi from '@/api/system/mail/account'
// 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'
}
// 邮箱账号的列表
const accounts = await MailAccountApi.getSimpleMailAccountList()
// CrudSchemahttps://kailong110120130.gitee.io/vue-element-plus-admin-doc/hooks/useCrudSchemas.html
const crudSchemas = reactive<CrudSchema[]>([
{
label: '编号',
field: 'id'
},
{
label: '发送时间',
field: 'sendTime',
formatter: dateFormatter,
search: {
show: true,
component: 'DatePicker',
componentProps: {
valueFormat: 'YYYY-MM-DD HH:mm:ss',
type: 'daterange',
defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')]
}
},
{
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
detail: {
dateFormat: 'YYYY-MM-DD HH:mm:ss'
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
},
{
label: '接收邮箱',
field: 'toMail'
},
{
label: '用户编号',
field: 'userId',
isSearch: true,
isTable: false
},
{
label: '用户类型',
field: 'userType',
dictType: DICT_TYPE.USER_TYPE,
dictClass: 'number',
isSearch: true,
isTable: false
},
{
label: '邮件标题',
field: 'templateTitle'
},
{
label: '邮件内容',
field: 'templateContent',
isTable: false
},
{
label: '邮箱参数',
field: 'templateParams',
isTable: false
},
{
label: '发送状态',
field: 'sendStatus',
dictType: DICT_TYPE.SYSTEM_MAIL_SEND_STATUS,
dictClass: 'string',
isSearch: true
},
{
label: '邮箱账号',
field: 'accountId',
isTable: false,
search: {
show: true,
component: 'Select',
api: () => accounts,
componentProps: {
optionsAlias: {
labelField: 'mail',
valueField: 'id'
}
}
}
},
{
label: '发送邮箱地址',
field: 'fromMail',
table: {
label: '邮箱账号'
}
},
{
label: '模板编号',
field: 'templateId',
isSearch: true
},
{
label: '模板编码',
field: 'templateCode',
isTable: false
},
{
label: '模版发送人名称',
field: 'templateNickname',
isTable: false
},
{
label: '发送返回的消息编号',
field: 'sendMessageId',
isTable: false
},
{
label: '发送异常',
field: 'sendException',
isTable: false
},
{
label: '创建时间',
field: 'createTime',
isTable: false,
formatter: dateFormatter,
detail: {
dateFormat: 'YYYY-MM-DD HH:mm:ss'
}
},
{
label: '操作',
field: 'action',
isDetail: false
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@ -0,0 +1,66 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible" :scroll="true" :width="800" :max-height="500">
<Form ref="formRef" :schema="allSchemas.formSchema" :rules="rules" v-loading="formLoading" />
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import * as MailTemplateApi from '@/api/system/mail/template'
import { rules, allSchemas } from './template.data'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formRef = ref() // Ref
/** 打开弹窗 */
const openModal = async (type: string, id?: number) => {
modelVisible.value = true
modelTitle.value = t('action.' + type)
formType.value = type
//
if (id) {
formLoading.value = true
try {
const data = await MailTemplateApi.getMailTemplate(id)
formRef.value.setValues(data)
} finally {
formLoading.value = false
}
}
}
defineExpose({ openModal }) // openModal
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.getElFormRef().validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formRef.value.formModel as MailTemplateApi.MailTemplateVO
if (formType.value === 'create') {
await MailTemplateApi.createMailTemplate(data)
message.success(t('common.createSuccess'))
} else {
await MailTemplateApi.updateMailTemplate(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
</script>

View File

@ -1,273 +1,102 @@
<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
<!-- 搜索工作栏 -->
<content-wrap>
<Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams">
<!-- 新增等操作按钮 -->
<template #actionMore>
<el-button
type="primary"
preIcon="ep:zoom-in"
:title="t('action.add')"
v-hasPermi="['system:mail-template:create']"
@click="handleCreate()"
/>
@click="openModal('create')"
v-hasPermi="['system:mail-account:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</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>
</Search>
</content-wrap>
<!-- 添加/修改/详情的弹窗 -->
<XModal id="mailTemplateModel" :loading="modelLoading" v-model="modelVisible" :title="modelTitle">
<!-- 表单添加/修改 -->
<Form
ref="formRef"
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
<!-- 列表 -->
<content-wrap>
<Table
:columns="allSchemas.tableColumns"
:data="tableObject.tableList"
:loading="tableObject.loading"
:pagination="{
total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
>
<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 #action="{ row }">
<el-button
link
type="primary"
@click="openSend(row.id)"
v-hasPermi="['system:mail-template:send-mail']"
>
测试
</el-button>
<el-button
link
type="primary"
@click="openModal('update', row.id)"
v-hasPermi="['system:mail-template:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
v-hasPermi="['system:mail-template:delete']"
@click="handleDelete(row.id)"
>
删除
</el-button>
</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>
</Table>
</content-wrap>
<!-- 测试邮件的弹窗 -->
<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>
<!-- 表单弹窗添加/修改 -->
<mail-template-form ref="modalRef" @success="getList" />
<!-- 表单弹窗发送测试 -->
<mail-template-send ref="sendRef" />
</template>
<script setup lang="ts" name="MailTemplate">
import { FormExpose } from '@/components/Form'
// import
import { rules, allSchemas } from './template.data'
import { allSchemas } from './template.data'
import * as MailTemplateApi from '@/api/system/mail/template'
import * as MailAccountApi from '@/api/system/mail/account'
import MailTemplateForm from './form.vue'
import MailTemplateSend from './send.vue'
const { t } = useI18n() //
const message = useMessage() //
//
const queryParams = reactive({
accountId: null
// tableObject
// tableMethods
// https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/table.html#usetable
const { tableObject, tableMethods } = useTable({
getListApi: MailTemplateApi.getMailTemplatePage, //
delListApi: MailTemplateApi.deleteMailTemplate //
})
const [registerTable, { reload, deleteData }] = useXTable({
allSchemas: allSchemas,
params: queryParams,
getListApi: MailTemplateApi.getMailTemplatePageApi,
deleteApi: MailTemplateApi.deleteMailTemplateApi
})
const accountOptions = ref<any[]>([]) //
//
const { getList, setSearchParams } = tableMethods
//
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 modalRef = ref()
const openModal = (type: string, id?: number) => {
modalRef.value.openModal(type, id)
}
//
const handleCreate = () => {
setDialogTile('create')
modelLoading.value = false
/** 删除按钮操作 */
const handleDelete = (id: number) => {
tableMethods.delList(id, false)
}
//
const handleUpdate = async (rowId: number) => {
setDialogTile('update')
//
const res = await MailTemplateApi.getMailTemplateApi(rowId)
unref(formRef)?.setValues(res)
modelLoading.value = false
/** 发送测试操作 */
const sendRef = ref()
const openSend = (id: number) => {
sendRef.value.openModal(id)
}
//
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
})
getList()
})
</script>

View File

@ -0,0 +1,115 @@
<template>
<Dialog title="测试" v-model="modelVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
v-loading="formLoading"
>
<el-form-item label="模板内容" prop="content">
<Editor :model-value="formData.content" readonly height="150px" />
</el-form-item>
<el-form-item label="收件邮箱" prop="mail">
<el-input v-model="formData.mail" placeholder="请输入收件邮箱" />
</el-form-item>
<el-form-item
v-for="param in formData.params"
:key="param"
:label="'参数 {' + param + '}'"
:prop="'templateParams.' + param"
>
<el-input
v-model="formData.templateParams[param]"
:placeholder="'请输入 ' + param + ' 参数'"
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import * as MailTemplateApi from '@/api/system/mail/template'
const message = useMessage() //
const modelVisible = ref(false) //
const formLoading = ref(false) // 12
const formData = ref({
content: '',
params: {},
mail: '',
templateCode: '',
templateParams: new Map()
})
const formRules = reactive({
mail: [{ required: true, message: '邮箱不能为空', trigger: 'blur' }],
templateCode: [{ required: true, message: '模版编号不能为空', trigger: 'blur' }],
templateParams: {}
})
const formRef = ref() // Ref
/** 打开弹窗 */
const openModal = async (id: number) => {
modelVisible.value = true
resetForm()
//
formLoading.value = true
try {
const data = await MailTemplateApi.getMailTemplate(id)
//
formData.value.content = data.content
formData.value.params = data.params
formData.value.templateCode = data.code
formData.value.templateParams = data.params.reduce((obj, item) => {
obj[item] = '' //
return obj
}, {})
formRules.templateParams = data.params.reduce((obj, item) => {
obj[item] = { required: true, message: '参数 ' + item + ' 不能为空', trigger: 'blur' }
return obj
}, {})
} finally {
formLoading.value = false
}
}
defineExpose({ openModal }) // openModal
/** 提交表单 */
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as MailTemplateApi.MailSendReqVO
const logId = await MailTemplateApi.sendMail(data)
if (logId) {
message.success('提交发送成功!发送结果,见发送日志编号:' + logId)
}
modelVisible.value = false
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
content: '',
params: {},
mail: '',
templateCode: '',
templateParams: new Map()
}
formRules.templateParams = {}
formRef.value?.resetFields()
}
</script>

View File

@ -1,98 +1,113 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
import { dateFormatter } from '@/utils/formatTime'
import { TableColumn } from '@/types/table'
import * as MailAccountApi from '@/api/system/mail/account'
// 邮箱账号的列表
const accounts = await MailAccountApi.getSimpleMailAccountList()
// 表单校验
export const rules = reactive({
name: [required],
code: [required],
accountId: [required],
title: [required],
label: [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
// CrudSchemahttps://kailong110120130.gitee.io/vue-element-plus-admin-doc/hooks/useCrudSchemas.html
const crudSchemas = reactive<CrudSchema[]>([
{
label: '模板编码',
field: 'code',
isSearch: true
},
{
label: '模板名称',
field: 'name',
isSearch: true
},
{
label: '模板标题',
field: 'title'
},
{
label: '模板内容',
field: 'content',
form: {
component: 'Editor',
componentProps: {
valueHtml: '',
height: 200
}
}
},
{
label: '邮箱账号',
field: 'accountId',
width: '200px',
formatter: (_: Recordable, __: TableColumn, cellValue: number) => {
return accounts.find((account) => account.id === cellValue)?.mail
},
{
title: '模板名称',
field: 'name',
isSearch: true
},
{
title: '模板标题',
field: 'title'
},
{
title: '模板内容',
field: 'content',
form: {
component: 'Editor',
colProps: {
span: 24
},
componentProps: {
valueHtml: ''
search: {
show: true,
component: 'Select',
api: () => accounts,
componentProps: {
optionsAlias: {
labelField: 'mail',
valueField: 'id'
}
}
},
{
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'
form: {
component: 'Select',
api: () => accounts,
componentProps: {
optionsAlias: {
labelField: 'mail',
valueField: 'id'
}
}
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
},
{
label: '发送人名称',
field: 'nickname'
},
{
label: '开启状态',
field: 'status',
isSearch: true,
dictType: DICT_TYPE.COMMON_STATUS,
dictClass: 'number'
},
{
label: '备注',
field: 'remark',
isTable: false
},
{
label: '创建时间',
field: 'createTime',
isForm: false,
formatter: dateFormatter,
search: {
show: true,
component: 'DatePicker',
componentProps: {
valueFormat: 'YYYY-MM-DD HH:mm:ss',
type: 'daterange',
defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')]
}
}
},
{
label: '操作',
field: 'action',
isForm: false
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

@ -1,17 +1,29 @@
<template>
<content-wrap>
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="公告标题" prop="title">
<el-input
v-model="queryParams.title"
placeholder="请输入公告标题"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="公告状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择公告状态" clearable>
<el-select
v-model="queryParams.status"
placeholder="请选择公告状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="parseInt(dict.value)"
@ -32,9 +44,11 @@
</el-button>
</el-form-item>
</el-form>
</content-wrap>
<!-- 列表 -->
<el-table v-loading="loading" :data="list" align="center">
<!-- 列表 -->
<content-wrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="公告编号" align="center" prop="id" />
<el-table-column label="公告标题" align="center" prop="title" />
<el-table-column label="公告类型" align="center" prop="type">

View File

@ -119,7 +119,7 @@ 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'
import { getSimpleUserList, UserVO } from '@/api/system/user'
const { t } = useI18n() //
const message = useMessage() //
@ -244,7 +244,7 @@ const sendTest = async () => {
// ========== ==========
onMounted(() => {
getListSimpleUsersApi().then((data) => {
getSimpleUserList().then((data) => {
userOption.value = data
})
})

View File

@ -1,64 +1,159 @@
<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)"
<content-wrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="90px"
>
<el-form-item label="用户编号" prop="userId">
<el-input
v-model="queryParams.userId"
placeholder="请输入用户编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</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>
</el-form-item>
<el-form-item label="用户类型" prop="userType">
<el-select
v-model="queryParams.userType"
placeholder="请选择用户类型"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.USER_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="客户端编号" prop="clientId">
<el-input
v-model="queryParams.clientId"
placeholder="请输入客户端编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
</el-form-item>
</el-form>
</content-wrap>
<!-- 列表 -->
<content-wrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="访问令牌" align="center" prop="accessToken" width="300" />
<el-table-column label="刷新令牌" align="center" prop="refreshToken" width="300" />
<el-table-column label="用户编号" align="center" prop="userId" />
<el-table-column label="用户类型" align="center" prop="userType">
<template #default="scope">
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" />
</template>
</el-table-column>
<el-table-column
label="过期时间"
align="center"
prop="expiresTime"
:formatter="dateFormatter"
width="180"
/>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="danger"
@click="handleForceLogout(scope.row.id)"
v-hasPermi="['system:oauth2-token:delete']"
>
强退
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</content-wrap>
</template>
<script setup lang="ts" name="Token">
import { allSchemas } from './token.data'
import * as TokenApi from '@/api/system/oauth2/token'
const { t } = useI18n() //
<script setup lang="ts" name="Oauth2AccessToken">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as OAuth2AccessTokenApi from '@/api/system/oauth2/token'
const message = useMessage() //
//
const [registerTable, { reload }] = useXTable({
allSchemas: allSchemas,
topActionSlots: false,
getListApi: TokenApi.getAccessTokenPageApi
const { t } = useI18n() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
userId: null,
userType: null,
clientId: null
})
const queryFormRef = ref() //
// ========== ==========
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 getList = async () => {
loading.value = true
try {
const data = await OAuth2AccessTokenApi.getAccessTokenPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
// 退
const handleForceLogout = (rowId: number) => {
message
.confirm('是否要强制退出用户')
.then(async () => {
await TokenApi.deleteAccessTokenApi(rowId)
message.success(t('common.success'))
})
.finally(async () => {
//
await reload()
})
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 强制退出操作 */
const handleForceLogout = async (id: number) => {
try {
//
await message.confirm('是否要强制退出用户')
//
await OAuth2AccessTokenApi.deleteAccessToken(id)
message.success(t('common.success'))
//
await getList()
} catch {}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -1,48 +0,0 @@
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)

View File

@ -0,0 +1,80 @@
<template>
<Dialog title="详情" v-model="modelVisible" :scroll="true" :max-height="500" width="800">
<el-descriptions border :column="1">
<el-descriptions-item label="日志主键" min-width="120">
{{ detailData.id }}
</el-descriptions-item>
<el-descriptions-item label="链路追踪">
{{ detailData.traceId }}
</el-descriptions-item>
<el-descriptions-item label="操作人编号">
{{ detailData.userId }}
</el-descriptions-item>
<el-descriptions-item label="操作人名字">
{{ detailData.userNickname }}
</el-descriptions-item>
<el-descriptions-item label="操作人 IP">
{{ detailData.userIp }}
</el-descriptions-item>
<el-descriptions-item label="操作人 UA">
{{ detailData.userAgent }}
</el-descriptions-item>
<el-descriptions-item label="操作模块">
{{ detailData.module }}
</el-descriptions-item>
<el-descriptions-item label="操作名">
{{ detailData.name }}
</el-descriptions-item>
<el-descriptions-item label="操作内容" v-if="detailData.content">
{{ detailData.content }}
</el-descriptions-item>
<el-descriptions-item label="操作拓展参数" v-if="detailData.exts">
{{ detailData.exts }}
</el-descriptions-item>
<el-descriptions-item label="请求 URL">
{{ detailData.requestMethod }} {{ detailData.requestUrl }}
</el-descriptions-item>
<el-descriptions-item label="Java 方法名">
{{ detailData.javaMethod }}
</el-descriptions-item>
<el-descriptions-item label="Java 方法参数">
{{ detailData.javaMethodArgs }}
</el-descriptions-item>
<el-descriptions-item label="操作时间">
{{ formatDate(detailData.startTime, 'YYYY-MM-DD HH:mm:ss') }}
</el-descriptions-item>
<el-descriptions-item label="执行时长">{{ detailData.duration }} ms</el-descriptions-item>
<el-descriptions-item label="操作结果">
<div v-if="detailData.resultCode === 0">正常</div>
<div v-else>失败({{ detailData.resultCode }})</div>
</el-descriptions-item>
<el-descriptions-item label="操作结果" v-if="detailData.resultCode === 0">
{{ detailData.resultData }}
</el-descriptions-item>
<el-descriptions-item label="失败提示" v-if="detailData.resultCode > 0">
{{ detailData.resultMsg }}
</el-descriptions-item>
</el-descriptions>
</Dialog>
</template>
<script setup lang="ts">
import { formatDate } from '@/utils/formatTime'
import * as OperateLogApi from '@/api/system/operatelog'
const modelVisible = ref(false) //
const detailLoading = ref(false) //
const detailData = ref() //
/** 打开弹窗 */
const openModal = async (data: OperateLogApi.OperateLogVO) => {
modelVisible.value = true
//
detailLoading.value = true
try {
detailData.value = data
} finally {
detailLoading.value = false
}
}
defineExpose({ openModal }) // openModal
</script>

View File

@ -1,67 +1,208 @@
<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')"
<content-wrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="系统模块" prop="module">
<el-input
v-model="queryParams.module"
placeholder="请输入系统模块"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</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>
</el-form-item>
<el-form-item label="操作人员" prop="userNickname">
<el-input
v-model="queryParams.userNickname"
placeholder="请输入操作人员"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="操作类型" prop="type">
<el-select
v-model="queryParams.type"
placeholder="请选择操作类型"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_OPERATE_TYPE)"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
</el-select>
</el-form-item>
<el-form-item label="操作状态" prop="success">
<el-select
v-model="queryParams.success"
placeholder="请选择操作状态"
clearable
class="!w-240px"
>
<el-option :key="true" label="成功" :value="true" />
<el-option :key="false" label="失败" :value="false" />
</el-select>
</el-form-item>
<el-form-item label="操作时间" prop="startTime">
<el-date-picker
v-model="queryParams.startTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['infra:config:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</content-wrap>
<!-- 列表 -->
<content-wrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="日志编号" align="center" prop="id" />
<el-table-column label="操作模块" align="center" prop="module" width="180" />
<el-table-column label="操作名" align="center" prop="name" width="180" />
<el-table-column label="操作类型" align="center" prop="type">
<template #default="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_OPERATE_TYPE" :value="scope.row.type" />
</template>
</el-table-column>
<el-table-column label="操作人" align="center" prop="userNickname" />
<el-table-column label="操作结果" align="center" prop="status">
<template #default="scope">
<span>{{ scope.row.resultCode === 0 ? '成功' : '失败' }}</span>
</template>
</el-table-column>
<el-table-column
label="操作时间"
align="center"
prop="startTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="执行时长" align="center" prop="startTime">
<template #default="scope">
<span>{{ scope.row.duration }} ms</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openModal(scope.row)"
v-hasPermi="['infra:config:query']"
>
详情
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</content-wrap>
<!-- 表单弹窗详情 -->
<operate-log-detail ref="modalRef" />
</template>
<script setup lang="ts" name="OperateLog">
// import
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as OperateLogApi from '@/api/system/operatelog'
import { allSchemas } from './operatelog.data'
import OperateLogDetail from './detail.vue'
const message = useMessage() //
const { t } = useI18n() //
//
const [registerTable, { exportList }] = useXTable({
allSchemas: allSchemas,
getListApi: OperateLogApi.getOperateLogPageApi,
exportListApi: OperateLogApi.exportOperateLogApi
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
module: undefined,
userNickname: undefined,
type: undefined,
success: undefined,
startTime: []
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
//
const dialogVisible = ref(false) //
const actionLoading = ref(false) // Loading
const detailData = ref() // Ref
//
const handleDetail = (row: OperateLogApi.OperateLogVO) => {
//
detailData.value = row
dialogVisible.value = true
/** 查询参数列表 */
const getList = async () => {
loading.value = true
try {
const data = await OperateLogApi.getOperateLogPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 详情操作 */
const modalRef = ref()
const openModal = (data: OperateLogApi.OperateLogVO) => {
modalRef.value.openModal(data)
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await OperateLogApi.exportOperateLog(queryParams)
download.excel(data, '操作日志.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -1,106 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
const { t } = useI18n() // 国际化
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: 'id',
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)

View File

@ -0,0 +1,122 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible" width="800">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="80px"
v-loading="formLoading"
>
<el-form-item label="岗位标题" prop="name">
<el-input v-model="formData.name" placeholder="请输入岗位标题" />
</el-form-item>
<el-form-item label="岗位编码" prop="code">
<el-input :model-value="formData.code" placeholder="请输入岗位编码" height="150px" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="formData.status" placeholder="请选择状态" clearable>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" placeholder="请输备注" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import { CommonStatusEnum } from '@/utils/constants'
import * as PostApi from '@/api/system/post'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
name: '',
code: '',
sort: undefined,
status: CommonStatusEnum.ENABLE,
remark: ''
})
const formRules = reactive({
name: [{ required: true, message: '岗位标题不能为空', trigger: 'blur' }],
code: [{ required: true, message: '岗位编码不能为空', trigger: 'change' }],
status: [{ required: true, message: '岗位状态不能为空', trigger: 'change' }],
remark: [{ required: false, message: '岗位内容不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
/** 打开弹窗 */
const openModal = async (type: string, id?: number) => {
modelVisible.value = true
modelTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await PostApi.getPost(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ openModal }) // openModal
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as unknown as PostApi.PostVO
if (formType.value === 'create') {
await PostApi.createPost(data)
message.success(t('common.createSuccess'))
} else {
await PostApi.updatePost(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: '',
code: '',
sort: undefined,
status: CommonStatusEnum.ENABLE,
remark: ''
}
formRef.value?.resetFields()
}
</script>

View File

@ -1,91 +0,0 @@
<template>
<!-- 弹窗 -->
<XModal :title="modelTitle" :loading="modelLoading" v-model="modelVisible">
<!-- 表单添加/修改 -->
<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 modelVisible = ref(false) //
const modelTitle = ref('') //
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, id?: number) => {
modelVisible.value = true
modelLoading.value = true
modelTitle.value = t('action.' + type)
actionType.value = type
//
if (id) {
const res = await PostApi.getPostApi(id)
if (type === 'update') {
unref(formRef)?.setValues(res)
} else if (type === 'detail') {
detailData.value = res
}
}
modelLoading.value = false
}
defineExpose({ openModal }) // openModal
// /
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
const elForm = unref(formRef)?.getElFormRef()
if (!elForm) return
const valid = await elForm.validate()
if (!valid) return
//
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
}
}
</script>

View File

@ -1,71 +1,198 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<template #toolbar_buttons>
<!-- 操作新增 -->
<XButton
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="岗位名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入岗位名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="岗位编码" prop="code">
<el-input
v-model="queryParams.code"
placeholder="请输入岗位编码"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
preIcon="ep:zoom-in"
:title="t('action.add')"
v-hasPermi="['system:post:create']"
@click="openModal('create')"
/>
<!-- 操作导出 -->
<XButton
type="primary"
v-hasPermi="['system:notice:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
preIcon="ep:download"
:title="t('action.export')"
v-hasPermi="['system:post:export']"
@click="exportList('岗位列表.xls')"
/>
</template>
<template #actionbtns_default="{ row }">
<!-- 操作修改 -->
<XTextButton
preIcon="ep:edit"
:title="t('action.edit')"
v-hasPermi="['system:post:update']"
@click="openModal('update', row?.id)"
/>
<!-- 操作详情 -->
<XTextButton
preIcon="ep:view"
:title="t('action.detail')"
v-hasPermi="['system:post:query']"
@click="openModal('detail', row?.id)"
/>
<!-- 操作删除 -->
<XTextButton
preIcon="ep:delete"
:title="t('action.delete')"
v-hasPermi="['system:post:delete']"
@click="deleteData(row?.id)"
/>
</template>
</XTable>
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['infra:config:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 表单弹窗添加/修改/详情 -->
<PostForm ref="modalRef" @success="reload()" />
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" align="center">
<el-table-column label="岗位编号" align="center" prop="id" />
<el-table-column label="岗位名称" align="center" prop="name" />
<el-table-column label="岗位编码" align="center" prop="code" />
<el-table-column label="岗位顺序" align="center" prop="sort" />
<el-table-column label="岗位备注" align="center" prop="remark" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openModal('update', scope.row.id)"
v-hasPermi="['system:post:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['system:post:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<PostForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="Post">
<script setup lang="tsx">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as PostApi from '@/api/system/post'
import { allSchemas } from './post.data'
import PostForm from './form.vue'
import PostForm from './PostForm.vue'
const message = useMessage() //
const { t } = useI18n() //
//
const [registerTable, { reload, deleteData, exportList }] = useXTable({
allSchemas: allSchemas, //
getListApi: PostApi.getPostPageApi, // API
deleteApi: PostApi.deletePostApi, // API
exportListApi: PostApi.exportPostApi // API
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
code: '',
name: '',
status: undefined
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
//
const modalRef = ref()
const openModal = (type: string, id?: number) => {
modalRef.value.openModal(type, id)
/** 查询岗位列表 */
const getList = async () => {
loading.value = true
try {
const data = await PostApi.getPostPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openModal = (type: string, id?: number) => {
formRef.value.openModal(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await PostApi.deletePost(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await PostApi.exportPost(queryParams)
download.excel(data, '岗位列表.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -1,58 +0,0 @@
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: 'id',
primaryTitle: '岗位编号',
action: true,
columns: [
{
title: '岗位名称',
field: 'name',
isSearch: true
},
{
title: '岗位编码',
field: 'code',
isSearch: true
},
{
title: '岗位顺序',
field: 'sort',
form: {
component: 'InputNumber'
}
},
{
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,
table: {
width: 180
}
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -0,0 +1,130 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="80px"
v-loading="formLoading"
>
<el-form-item label="敏感词" prop="name">
<el-input v-model="formData.name" placeholder="请输入敏感词" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="description">
<el-input v-model="formData.description" placeholder="请输入内容" />
</el-form-item>
<el-form-item label="标签" prop="tags">
<el-select
v-model="formData.tags"
multiple
filterable
allow-create
placeholder="请选择文章标签"
style="width: 380px"
>
<el-option v-for="tag in tags" :key="tag" :label="tag" :value="tag" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as SensitiveWordApi from '@/api/system/sensitiveWord'
import { CommonStatusEnum } from '@/utils/constants'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
name: '',
status: CommonStatusEnum.ENABLE,
description: '',
tags: []
})
const formRules = reactive({
name: [{ required: true, message: '敏感词不能为空', trigger: 'blur' }],
tags: [{ required: true, message: '标签不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
const tags = ref([]) // todo @blue-syd openModal
/** 打开弹窗 */
const openModal = async (type: string, id?: number) => {
modelVisible.value = true
modelTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await SensitiveWordApi.getSensitiveWordApi(id)
console.log(formData.value)
} finally {
formLoading.value = false
}
}
}
defineExpose({ openModal }) // openModal
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as unknown as SensitiveWordApi.SensitiveWordVO
if (formType.value === 'create') {
await SensitiveWordApi.createSensitiveWordApi(data) // TODO @blue-syd API
message.success(t('common.createSuccess'))
} else {
await SensitiveWordApi.updateSensitiveWordApi(data) // TODO @blue-syd API
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: '',
status: CommonStatusEnum.ENABLE,
description: '',
tags: []
}
formRef.value?.resetFields()
}
</script>

View File

@ -1,191 +1,225 @@
<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()"
<!-- 搜索 -->
<content-wrap>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
<el-form-item label="敏感词" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入敏感词"
clearable
@keyup.enter="handleQuery"
/>
<!-- 操作导出 -->
<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"
</el-form-item>
<el-form-item label="标签" prop="tag">
<el-select
v-model="queryParams.tag"
placeholder="请选择标签"
clearable
@keyup.enter="handleQuery"
>
{{ 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-option v-for="tag in tags" :key="tag" :label="tag" :value="tag" />
</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"
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择启用状态" clearable>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
@click="openModal('create')"
v-hasPermi="['system:sensitive-word:create']"
>
{{ tag }}
</el-tag>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<!-- 按钮保存 -->
<XButton
v-if="['create', 'update'].includes(actionType)"
type="primary"
:title="t('action.save')"
:loading="actionLoading"
@click="submitForm()"
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['system:sensitive-word:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</content-wrap>
<!-- 列表 -->
<content-wrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="敏感词" align="center" prop="name" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="描述" align="center" prop="description" />
<el-table-column label="标签" align="center" prop="tags">
<template #default="scope">
<el-tag
:disable-transitions="true"
:key="index"
v-for="(tag, index) in scope.row.tags"
:index="index"
class="mr-5px"
>
{{ tag }}
</el-tag>
&nbsp; &nbsp;
</template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<!-- 按钮关闭 -->
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
</template>
</XModal>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openModal('update', scope.row.id)"
v-hasPermi="['infra:config:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['infra:config:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</content-wrap>
<!-- 表单弹窗添加/修改 -->
<config-form ref="modalRef" @success="getList" />
</template>
<script setup lang="ts" name="SensitiveWord">
import type { FormExpose } from '@/components/Form'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as SensitiveWordApi from '@/api/system/sensitiveWord'
import { rules, allSchemas } from './sensitiveWord.data'
const { t } = useI18n() //
import ConfigForm from './form.vue' // TODO @blue-syd
const message = useMessage() //
//
const [registerTable, { reload, deleteData, exportList }] = useXTable({
allSchemas: allSchemas,
getListApi: SensitiveWordApi.getSensitiveWordPageApi,
deleteApi: SensitiveWordApi.deleteSensitiveWordApi,
exportListApi: SensitiveWordApi.exportSensitiveWordApi
const { t } = useI18n() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
tag: undefined,
status: undefined,
createTime: []
})
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 queryFormRef = ref() //
const exportLoading = ref(false) //
const tags = ref([])
//
const tagsOptions = ref()
/** 查询参数列表 */
const getList = async () => {
loading.value = true
try {
const data = await SensitiveWordApi.getSensitiveWordPageApi(queryParams) // TODO @blue-syd API
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const modalRef = ref()
const openModal = (type: string, id?: number) => {
modalRef.value.openModal(type, id)
}
// TODO @blue-syd http://dashboard.yudao.iocoder.cn/system/sensitive-word
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await SensitiveWordApi.deleteSensitiveWordApi(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await SensitiveWordApi.exportSensitiveWordApi(queryParams) // TODO @blue-syd API
download.excel(data, '敏感词.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 获得 Tag 标签列表 */
const getTags = async () => {
const res = await SensitiveWordApi.getSensitiveWordTagsApi()
tagsOptions.value = res
tags.value = await SensitiveWordApi.getSensitiveWordTagsApi() // TODO @blue-syd API
}
//
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()
/** 初始化 **/
onMounted(() => {
getTags()
getList()
})
</script>

View File

@ -1,74 +0,0 @@
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: 'id',
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)

View File

@ -0,0 +1,137 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible">
<el-form ref="formRef" :model="form" :rules="rules" label-width="130px" v-loading="formLoading">
<el-form-item label="短信签名" prop="signature">
<el-input v-model="form.signature" placeholder="请输入短信签名" />
</el-form-item>
<el-form-item label="渠道编码" prop="code">
<el-select v-model="form.code" placeholder="请选择渠道编码" clearable>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="启用状态">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="parseInt(dict.value)"
>{{ dict.label }}</el-radio
>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="短信 API 的账号" prop="apiKey">
<el-input v-model="form.apiKey" placeholder="请输入短信 API 的账号" />
</el-form-item>
<el-form-item label="短信 API 的密钥" prop="apiSecret">
<el-input v-model="form.apiSecret" placeholder="请输入短信 API 的密钥" />
</el-form-item>
<el-form-item label="短信发送回调 URL" prop="callbackUrl">
<el-input v-model="form.callbackUrl" placeholder="请输入短信发送回调 URL" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import * as SmsChannelApi from '@/api/system/sms/smsChannel'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const form = ref({
id: undefined,
signature: '',
code: '',
status: '',
remark: '',
apiKey: '',
apiSecret: '',
callbackUrl: ''
})
const rules = reactive({
signature: [{ required: true, message: '短信签名不能为空', trigger: 'blur' }],
code: [{ required: true, message: '渠道编码不能为空', trigger: 'blur' }],
status: [{ required: true, message: '启用状态不能为空', trigger: 'blur' }],
apiKey: [{ required: true, message: '短信 API 的账号不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
/** 打开弹窗 */
const openModal = async (type: string, id?: number) => {
modelVisible.value = true
modelTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
form.value = await SmsChannelApi.getSmsChannelApi(id)
console.log(form)
} finally {
formLoading.value = false
}
}
}
defineExpose({ openModal }) // openModal
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = unref(formRef)?.formModel as SmsChannelApi.SmsChannelVO
if (formType.value === 'create') {
await SmsChannelApi.createSmsChannelApi(data)
message.success(t('common.createSuccess'))
} else {
await SmsChannelApi.updateSmsChannelApi(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
form.value = {
id: undefined,
signature: '',
code: '',
status: '',
remark: '',
apiKey: '',
apiSecret: '',
callbackUrl: ''
}
formRef.value?.resetFields()
}
</script>

View File

@ -1,147 +1,225 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<!-- 操作新增 -->
<template #toolbar_buttons>
<XButton
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
<el-form-item label="短信签名" prop="signature">
<el-input
v-model="queryParams.signature"
placeholder="请输入短信签名"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="启用状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择启用状态" clearable>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
preIcon="ep:zoom-in"
:title="t('action.add')"
@click="openModal('create')"
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>
>
<Icon icon="ep:plus" class="mr-5px" /> 新增</el-button
>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['system:sms-channel:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出</el-button
>
</el-form-item>
</el-form>
<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()"
<!-- 列表 -->
<el-table v-loading="loading" :data="list" align="center">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="短信签名" align="center" prop="signature" />
<el-table-column label="渠道编码" align="center" prop="code">
<template #default="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="scope.row.code" />
</template>
</el-table-column>
<el-table-column label="启用状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column
label="短信 API 的账号"
align="center"
prop="apiKey"
:show-overflow-tooltip="true"
/>
<!-- 按钮关闭 -->
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
</template>
</XModal>
<el-table-column
label="短信 API 的密钥"
align="center"
prop="apiSecret"
:show-overflow-tooltip="true"
/>
<el-table-column
label="短信发送回调 URL"
align="center"
prop="callbackUrl"
:show-overflow-tooltip="true"
/>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openModal('update', scope.row.id)"
v-hasPermi="['system:sms-channel:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['system:sms-channel:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<SmsChannelForm ref="modalRef" @success="getList" />
</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'
//
import { dateFormatter } from '@/utils/formatTime'
//
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
///
import SmsChannelForm from './form.vue'
//
// import download from '@/utils/download'
const { t } = useI18n() //
const message = useMessage() //
//
const [registerTable, { reload, deleteData }] = useXTable({
allSchemas: allSchemas,
getListApi: SmsChannelApi.getSmsChannelPageApi,
deleteApi: SmsChannelApi.deleteSmsChannelApi
//
const loading = ref(true)
//
const queryFormRef = ref()
//
const total = ref(0)
//
const list = ref([])
//
const exportLoading = ref(false)
//
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
signature: undefined,
status: undefined,
createTime: []
})
//
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 getList = async () => {
loading.value = true
//
try {
const data = await SmsChannelApi.getSmsChannelPageApi(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
//
const handleCreate = () => {
setDialogTile('create')
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
//
const handleUpdate = async (rowId: number) => {
setDialogTile('update')
//
const res = await SmsChannelApi.getSmsChannelApi(rowId)
unref(formRef)?.setValues(res)
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
//
const handleDetail = async (rowId: number) => {
setDialogTile('detail')
const res = await SmsChannelApi.getSmsChannelApi(rowId)
detailData.value = res
/** 添加/修改操作 */
const modalRef = ref()
const openModal = (type: string, id?: number) => {
modalRef.value.openModal(type, id)
}
//
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()
}
}
})
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
await message.info('该功能目前不支持')
//
// const data = await SmsChannelApi.exportSmsChanelApi(queryParams)
// download.excel(data, '.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await SmsChannelApi.deleteSmsChannelApi(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -1,76 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
const { t } = useI18n() // 国际化
const authorizedGrantOptions = getStrDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE)
// 表单校验
export const rules = reactive({
signature: [required],
code: [required],
apiKey: [required],
status: [required]
})
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: 'id',
primaryTitle: '渠道编号',
action: true,
columns: [
{
title: '短信签名',
field: 'signature',
isSearch: true
},
{
title: '渠道编码',
field: 'code',
// dictType: DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE,
// dictClass: 'string',
isSearch: true,
form: {
component: 'Select',
componentProps: {
options: authorizedGrantOptions,
multiple: false,
filterable: 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)

View File

@ -0,0 +1,204 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible" width="50%">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="80px"
v-loading="formLoading"
>
<el-row>
<el-col :span="10">
<el-form-item label="租户名" prop="name">
<el-input v-model="formData.name" placeholder="请输入租户名" />
</el-form-item>
</el-col>
<el-col :span="10" :offset="2">
<el-form-item label="租户套餐" prop="packageId">
<el-select v-model="formData.packageId" placeholder="请选择租户套餐" clearable>
<el-option
v-for="item in packageList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="10">
<el-form-item label="联系人" prop="contactName">
<el-input v-model="formData.contactName" placeholder="请输入联系人" />
</el-form-item>
</el-col>
<el-col :span="10" :offset="2">
<el-form-item label="联系手机" prop="contactMobile">
<el-input v-model="formData.contactMobile" placeholder="请输入联系手机" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="10">
<el-form-item v-if="formData.id === undefined" label="用户名称" prop="username">
<el-input v-model="formData.username" placeholder="请输入用户名称" />
</el-form-item>
</el-col>
<el-col :span="10" :offset="2">
<el-form-item v-if="formData.id === undefined" label="用户密码" prop="password">
<el-input
v-model="formData.password"
placeholder="请输入用户密码"
type="password"
show-password
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="10">
<el-form-item label="账号额度" prop="accountCount">
<el-input-number
v-model="formData.accountCount"
placeholder="请输入账号额度"
controls-position="right"
:min="0"
/>
</el-form-item>
</el-col>
<el-col :span="10" :offset="2">
<el-form-item label="过期时间" prop="expireTime">
<el-date-picker
clearable
v-model="formData.expireTime"
type="date"
value-format="x"
placeholder="请选择过期时间"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="10">
<el-form-item label="绑定域名" prop="domain">
<el-input v-model="formData.domain" placeholder="请输入绑定域名" />
</el-form-item>
</el-col>
<el-col :span="10" :offset="2">
<el-form-item label="租户状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.value"
>{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as TenantApi from '@/api/system/tenant'
import { CommonStatusEnum } from '@/utils/constants'
import { getTenantPackageList as getTenantPackageListApi } from '@/api/system/tenantPackage'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
name: undefined,
packageId: undefined,
contactName: undefined,
contactMobile: undefined,
accountCount: undefined,
expireTime: undefined,
domain: undefined,
status: CommonStatusEnum.ENABLE
})
const formRules = reactive({
name: [{ required: true, message: '租户名不能为空', trigger: 'blur' }],
packageId: [{ required: true, message: '租户套餐不能为空', trigger: 'blur' }],
contactName: [{ required: true, message: '联系人不能为空', trigger: 'blur' }],
status: [{ required: true, message: '租户状态不能为空', trigger: 'blur' }],
accountCount: [{ required: true, message: '账号额度不能为空', trigger: 'blur' }],
expireTime: [{ required: true, message: '过期时间不能为空', trigger: 'blur' }],
domain: [{ required: true, message: '绑定域名不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
const packageList = ref([]) //
/** 打开弹窗 */
const openModal = async (type: string, id?: number) => {
modelVisible.value = true
modelTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await TenantApi.getTenantApi(id)
} finally {
formLoading.value = false
}
}
packageList.value = await getTenantPackageListApi()
}
defineExpose({ openModal }) // openModal
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as unknown as TenantApi.TenantVO
if (formType.value === 'create') {
await TenantApi.createTenantApi(data)
message.success(t('common.createSuccess'))
} else {
await TenantApi.updateTenantApi(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: undefined,
packageId: undefined,
contactName: undefined,
contactMobile: undefined,
accountCount: undefined,
expireTime: undefined,
domain: undefined,
status: CommonStatusEnum.ENABLE
}
formRef.value?.resetFields()
}
</script>

View File

@ -1,197 +1,255 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<template #toolbar_buttons>
<!-- 操作新增 -->
<XButton
<!-- 搜索 -->
<content-wrap>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
<el-form-item label="租户名" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入租户名"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="联系人" prop="contactName">
<el-input
v-model="queryParams.contactName"
placeholder="请输入联系人"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="联系手机" prop="contactMobile">
<el-input
v-model="queryParams.contactMobile"
placeholder="请输入联系手机"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="租户状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择租户状态" clearable>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" />
搜索
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" />
重置
</el-button>
<el-button
type="primary"
preIcon="ep:zoom-in"
:title="t('action.add')"
@click="openModal('create')"
v-hasPermi="['system:tenant:create']"
@click="handleCreate()"
/>
<XButton
type="warning"
preIcon="ep:download"
:title="t('action.export')"
>
<Icon icon="ep:plus" class="mr-5px" />
新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
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()"
>
<Icon icon="ep:download" class="mr-5px" />
导出
</el-button>
</el-form-item>
</el-form>
</content-wrap>
<!-- 列表 -->
<content-wrap>
<el-table v-loading="loading" :data="list" align="center">
<el-table-column label="租户编号" align="center" prop="id" />
<el-table-column label="租户名" align="center" prop="name" />
<el-table-column label="租户套餐" align="center" prop="packageId">
<template #default="scope">
<el-tag v-if="scope.row.packageId === 0" type="danger">系统租户</el-tag>
<template v-else v-for="item in packageList">
<el-tag type="success" :key="item.id" v-if="item.id === scope.row.packageId"
>{{ item.name }}
</el-tag>
</template>
</template>
</el-table-column>
<el-table-column label="联系人" align="center" prop="contactName" />
<el-table-column label="联系手机" align="center" prop="contactMobile" />
<el-table-column label="账号额度" align="center" prop="accountCount">
<template #default="scope">
<el-tag>{{ scope.row.accountCount }}</el-tag>
</template>
</el-table-column>
<el-table-column
label="过期时间"
align="center"
prop="expireTime"
width="180"
:formatter="dateFormatter"
/>
<!-- 按钮关闭 -->
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
</template>
</XModal>
<el-table-column label="绑定域名" align="center" prop="domain" width="180" />
<el-table-column label="租户状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center" min-width="110" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openModal('update', scope.row.id)"
v-hasPermi="['system:tenant:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['system:tenant:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</content-wrap>
<!-- 表单弹窗添加/修改 -->
<tenant-form ref="modalRef" @success="getList" />
</template>
<script setup lang="ts" name="Tenant">
import type { FormExpose } from '@/components/Form'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as TenantApi from '@/api/system/tenant'
import { rules, allSchemas, tenantPackageOption } from './tenant.data'
import { getTenantPackageList as getTenantPackageListApi } from '@/api/system/tenantPackage'
import TenantForm from './form.vue'
import ContentWrap from '@/components/ContentWrap/src/ContentWrap.vue'
import DictTag from '@/components/DictTag/src/DictTag.vue'
const { t } = useI18n() //
const message = useMessage() //
//
const [registerTable, { reload, deleteData, exportList }] = useXTable({
allSchemas: allSchemas,
getListApi: TenantApi.getTenantPageApi,
deleteApi: TenantApi.deleteTenantApi,
exportListApi: TenantApi.exportTenantApi
const { t } = useI18n() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const packageList = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
contactName: undefined,
contactMobile: undefined,
status: undefined,
createTime: []
}) //
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询参数列表 */
const getList = async () => {
loading.value = true
try {
const data = await TenantApi.getTenantPageApi(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const modalRef = ref()
const openModal = (type: string, id?: number) => {
modalRef.value.openModal(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await TenantApi.deleteTenantApi(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await TenantApi.exportTenantApi(queryParams)
download.excel(data, '参数配置.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/**获取租户套餐**/
const getTenantPackageList = async () => {
const data = await getTenantPackageListApi()
packageList.value = data
}
/** 初始化 **/
onMounted(() => {
getList()
getTenantPackageList()
})
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 = async () => {
//
setDialogTile('create')
await nextTick()
console.log(allSchemas.formSchema, 'allSchemas.formSchema')
if (allSchemas.formSchema[4].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 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>

View File

@ -273,7 +273,7 @@ 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 { getSimplePostList, PostVO } from '@/api/system/post'
import {
aassignUserRoleApi,
listUserRolesApi,
@ -329,7 +329,7 @@ const postOptions = ref<PostVO[]>([]) //岗位列表
//
const getPostOptions = async () => {
const res = await listSimplePostsApi()
const res = await getSimplePostList()
postOptions.value.push(...res)
}
const dataFormater = (val) => {