Commit 5057e755 by puhui999

Merge remote-tracking branch 'yudao/dev'

parents 42128709 3db7b26e
......@@ -5,4 +5,4 @@ dist-ssr
*.local
/dist*
*-lock.*
pnpm-debug
pnpm-debug
\ No newline at end of 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']
}
......
......@@ -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' })
}
......@@ -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
......
......@@ -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 })
}
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' })
}
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 })
}
......@@ -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 })
}
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 })
}
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 })
}
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 })
}
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
}
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
......
......@@ -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' })
}
......@@ -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 })
}
......@@ -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 })
}
......@@ -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 })
}
......@@ -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 })
}
......@@ -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 })
}
......@@ -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' })
}
......@@ -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>
......
......@@ -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}
>
{{
// 如果需要自定义,就什么都不渲染,而是提供默认插槽
......
......@@ -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"
......
......@@ -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>
......@@ -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}
......
......@@ -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
......
......@@ -218,6 +218,8 @@ export const useTable = <T = any>(config?: UseTableConfig<T>) => {
register,
elTableRef,
tableObject,
methods
methods,
// add by 芋艿:返回 tableMethods 属性,和 tableObject 更统一
tableMethods: methods
}
}
......@@ -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',
......
......@@ -297,7 +297,8 @@ export default {
typeCreate: '字典类型新增',
typeUpdate: '字典类型编辑',
dataCreate: '字典数据新增',
dataUpdate: '字典数据编辑'
dataUpdate: '字典数据编辑',
fileUpload: '上传文件'
},
dialog: {
dialog: '弹窗',
......
......@@ -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,
......
......@@ -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) => {
......
......@@ -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']
......
......@@ -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']
......
......@@ -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 字典数据
}
......@@ -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) {
......
......@@ -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]
}
<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) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
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>
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)
......@@ -378,7 +378,7 @@ onMounted(() => {
// 加载详情
getDetail()
// 加载用户的列表
UserApi.getListSimpleUsersApi().then((data) => {
UserApi.getSimpleUserList().then((data) => {
userOptions.value.push(...data)
})
})
......
......@@ -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) {
......
<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>
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)
......@@ -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 () => {
......
<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" />
......
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)
<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) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
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>
<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
})
// ========== 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 { t } = useI18n() // 国际化
// 设置标题
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
const loading = ref(true) // 列表的加载中
const list = ref([]) // 列表的数据
// 新增操作
const handleCreate = () => {
setDialogTile('create')
/** 查询参数列表 */
const getList = async () => {
loading.value = true
try {
list.value = await DataSourceConfigApi.getDataSourceConfigList()
} finally {
loading.value = false
}
}
// 修改操作
const handleUpdate = async (rowId: number) => {
setDialogTile('update')
// 设置数据
const res = await DataSourceConfiggApi.getDataSourceConfigApi(rowId)
unref(formRef)?.setValues(res)
/** 添加/修改操作 */
const modalRef = ref()
const openModal = (type: string, id?: number) => {
modalRef.value.openModal(type, id)
}
// 详情操作
const handleDetail = async (rowId: number) => {
// 设置数据
const res = await DataSourceConfiggApi.getDataSourceConfigApi(rowId)
detailData.value = res
setDialogTile('detail')
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await DataSourceConfigApi.deleteDataSourceConfig(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) {
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()
}
}
})
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>
<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">
提示:仅允许导入 jpg、png、gif 格式文件!
</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) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
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>
<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>
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)
<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) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
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>
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)
<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>
<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>
<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>
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)
<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) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
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>
<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) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
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>
<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>
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)
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)
<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) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
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>
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)
<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) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
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>
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
}
// CrudSchema:https://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)
<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) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
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>
<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>
<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>
<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'
const { t } = useI18n() // 国际化
import MailLogDetail from './detail.vue'
// 列表相关的变量
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 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 { getList, setSearchParams } = tableMethods
// 详情操作
const handleDetail = async (rowId: number) => {
setDialogTile('detail')
const res = await MailLogApi.getMailLogApi(rowId)
detailData.value = res
modelLoading.value = false
/** 详情操作 */
const modalRef = ref()
const openModal = (id: number) => {
modalRef.value.openModal(id)
}
// ========== 初始化 ==========
/** 初始化 **/
onMounted(() => {
MailAccountApi.getSimpleMailAccounts().then((data) => {
accountOptions.value = data
})
getList()
})
</script>
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'
}
}
},
{
title: '接收邮箱',
field: 'toMail',
isSearch: true,
table: {
width: 180,
slots: {
default: 'toMail_default'
}
// 邮箱账号的列表
const accounts = await MailAccountApi.getSimpleMailAccountList()
// CrudSchema:https://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: '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'
detail: {
dateFormat: 'YYYY-MM-DD HH:mm:ss'
}
},
{
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'
}
}
},
{
title: '发送邮箱地址',
field: 'fromMail',
table: {
title: '邮箱账号'
}
},
{
title: '模板编号',
field: 'templateId',
isSearch: true
},
{
title: '模板编码',
field: 'templateCode',
isTable: false
},
{
title: '模版发送人名称',
field: 'templateNickname',
isTable: false
},
{
title: '发送返回的消息编号',
field: 'sendMessageId',
isTable: false
},
{
title: '发送异常',
field: 'sendException',
isTable: false
},
{
title: '创建时间',
field: 'createTime',
isTable: false
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
},
{
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)
<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) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
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>
<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) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
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>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment