# Conflicts:
#	src/views/system/tenant/index.vue
#	src/views/system/tenant/tenant.data.ts
pull/35/head
东方白 2023-03-19 11:09:04 +08:00
commit d5d000341e
75 changed files with 2644 additions and 2301 deletions

View File

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

View File

@ -1,16 +1,5 @@
import request from '@/config/axios' 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 { export interface FilePageReqVO extends PageParam {
path?: string path?: string
type?: 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 }) 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 }) return request.delete({ url: '/infra/file/delete?id=' + id })
} }

View File

@ -13,6 +13,7 @@ export interface FileClientConfig {
accessSecret?: string accessSecret?: string
domain: string domain: string
} }
export interface FileConfigVO { export interface FileConfigVO {
id: number id: number
name: string name: string
@ -24,43 +25,37 @@ export interface FileConfigVO {
createTime: Date 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 }) 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 }) 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 }) 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 }) 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 }) 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 }) 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 }) return request.get({ url: '/infra/file-config/test?id=' + id })
} }

View File

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

View File

@ -10,37 +10,32 @@ export interface MailAccountVO {
sslEnable: boolean 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 }) 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 }) 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 }) 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 }) 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 }) 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' }) return request.get({ url: '/system/mail-account/list-all-simple' })
} }

View File

@ -19,22 +19,12 @@ export interface MailLogVO {
sendException: string 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 }) 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 }) return await request.get({ url: '/system/mail-log/get?id=' + id })
} }

View File

@ -13,14 +13,6 @@ export interface MailTemplateVO {
remark: string remark: string
} }
export interface MailTemplatePageReqVO extends PageParam {
name?: string
code?: string
accountId?: number
status?: number
createTime?: Date[]
}
export interface MailSendReqVO { export interface MailSendReqVO {
mail: string mail: string
templateCode: 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 }) 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 }) 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 }) 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 }) 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 }) 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 }) return request.post({ url: '/system/mail-template/send-mail', data })
} }

View File

@ -1,7 +1,7 @@
import request from '@/config/axios' import request from '@/config/axios'
export interface NoticeVO { export interface NoticeVO {
id: number id: number | undefined
title: string title: string
type: number type: number
content: string content: string
@ -11,32 +11,27 @@ export interface NoticeVO {
createTime: Date createTime: Date
} }
export interface NoticePageReqVO extends PageParam {
title?: string
status?: number
}
// 查询公告列表 // 查询公告列表
export const getNoticePageApi = (params: NoticePageReqVO) => { export const getNoticePage = (params: PageParam) => {
return request.get({ url: '/system/notice/page', params }) return request.get({ url: '/system/notice/page', params })
} }
// 查询公告详情 // 查询公告详情
export const getNoticeApi = (id: number) => { export const getNotice = (id: number) => {
return request.get({ url: '/system/notice/get?id=' + id }) return request.get({ url: '/system/notice/get?id=' + id })
} }
// 新增公告 // 新增公告
export const createNoticeApi = (data: NoticeVO) => { export const createNotice = (data: NoticeVO) => {
return request.post({ url: '/system/notice/create', data }) return request.post({ url: '/system/notice/create', data })
} }
// 修改公告 // 修改公告
export const updateNoticeApi = (data: NoticeVO) => { export const updateNotice = (data: NoticeVO) => {
return request.put({ url: '/system/notice/update', data }) return request.put({ url: '/system/notice/update', data })
} }
// 删除公告 // 删除公告
export const deleteNoticeApi = (id: number) => { export const deleteNotice = (id: number) => {
return request.delete({ url: '/system/notice/delete?id=' + id }) return request.delete({ url: '/system/notice/delete?id=' + id })
} }

View File

@ -23,19 +23,11 @@ export type OperateLogVO = {
resultData: string 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 }) 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 }) return request.download({ url: '/system/operate-log/export', params })
} }

View File

@ -53,5 +53,5 @@ export const getSmsLogPageApi = (params: SmsLogPageReqVO) => {
// 导出短信日志 // 导出短信日志
export const exportSmsLogApi = (params: SmsLogExportReqVO) => { export const exportSmsLogApi = (params: SmsLogExportReqVO) => {
return request.download({ url: '/system/sms-log/export', params }) return request.download({ url: '/system/sms-log/export-excel', params })
} }

View File

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

View File

@ -45,17 +45,22 @@ const tabsList = [
const pageList = computed(() => { const pageList = computed(() => {
if (currentPage.value === 1) { if (currentPage.value === 1) {
return copyIconList[currentActiveType.value] return copyIconList[currentActiveType.value]
.filter((v) => v.includes(filterValue.value)) ?.filter((v) => v.includes(filterValue.value))
.slice(currentPage.value - 1, pageSize.value) .slice(currentPage.value - 1, pageSize.value)
} else { } else {
return copyIconList[currentActiveType.value] return copyIconList[currentActiveType.value]
.filter((v) => v.includes(filterValue.value)) ?.filter((v) => v.includes(filterValue.value))
.slice( .slice(
pageSize.value * (currentPage.value - 1), pageSize.value * (currentPage.value - 1),
pageSize.value * (currentPage.value - 1) + pageSize.value pageSize.value * (currentPage.value - 1) + pageSize.value
) )
} }
}) })
const iconCount = computed(() => {
return copyIconList[currentActiveType.value] == undefined
? 0
: copyIconList[currentActiveType.value].length
})
const iconItemStyle = computed((): ParameterCSSProperties => { const iconItemStyle = computed((): ParameterCSSProperties => {
return (item) => { return (item) => {
@ -159,7 +164,7 @@ watch(
<ElPagination <ElPagination
small small
:total="copyIconList[currentActiveType].length as unknown as number" :total="iconCount"
:page-size="pageSize" :page-size="pageSize"
:current-page="currentPage" :current-page="currentPage"
background background

View File

@ -5,7 +5,7 @@
class="float-right mt-15px mb-15px" class="float-right mt-15px mb-15px"
:background="true" :background="true"
layout="total, sizes, prev, pager, next, jumper" 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:current-page="currentPage"
v-model:page-size="pageSize" v-model:page-size="pageSize"
:pager-count="pagerCount" :pager-count="pagerCount"

View File

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

View File

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

View File

@ -128,7 +128,7 @@ const getColumnsConfig = (options: XTableProps) => {
proxyForm = true proxyForm = true
options.formConfig = { options.formConfig = {
enabled: true, enabled: true,
titleWidth: 100, titleWidth: 180,
titleAlign: 'right', titleAlign: 'right',
items: allSchemas.searchSchema items: allSchemas.searchSchema
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="my-process-designer"> <div class="my-process-designer">
<div class="my-process-designer__header"> <div class="my-process-designer__header" style="display: table-row-group; z-index: 999">
<slot name="control-header"></slot> <slot name="control-header"></slot>
<template v-if="!$slots['control-header']"> <template v-if="!$slots['control-header']">
<ElButtonGroup key="file-control"> <ElButtonGroup key="file-control">

View File

@ -3,10 +3,8 @@
<el-form label-width="90px"> <el-form label-width="90px">
<el-form-item label="回路特性"> <el-form-item label="回路特性">
<el-select v-model="loopCharacteristics" @change="changeLoopCharacteristicsType"> <el-select v-model="loopCharacteristics" @change="changeLoopCharacteristicsType">
<!--bpmn:MultiInstanceLoopCharacteristics-->
<el-option label="并行多重事件" value="ParallelMultiInstance" /> <el-option label="并行多重事件" value="ParallelMultiInstance" />
<el-option label="时序多重事件" value="SequentialMultiInstance" /> <el-option label="时序多重事件" value="SequentialMultiInstance" />
<!--bpmn:StandardLoopCharacteristics-->
<el-option label="循环事件" value="StandardLoop" /> <el-option label="循环事件" value="StandardLoop" />
<el-option label="无" value="Null" /> <el-option label="无" value="Null" />
</el-select> </el-select>

View File

@ -8,6 +8,7 @@ import { FormSchema } from '@/types/form'
import { TableColumn } from '@/types/table' import { TableColumn } from '@/types/table'
import { DescriptionsSchema } from '@/types/descriptions' import { DescriptionsSchema } from '@/types/descriptions'
import { ComponentOptions, ComponentProps } from '@/types/components' import { ComponentOptions, ComponentProps } from '@/types/components'
import { DictTag } from '@/components/DictTag'
export type CrudSchema = Omit<TableColumn, 'children'> & { export type CrudSchema = Omit<TableColumn, 'children'> & {
isSearch?: boolean // 是否在查询显示 isSearch?: boolean // 是否在查询显示
@ -151,6 +152,15 @@ const filterTableSchema = (crudSchema: CrudSchema[]): TableColumn[] => {
const tableColumns = treeMap<CrudSchema>(crudSchema, { const tableColumns = treeMap<CrudSchema>(crudSchema, {
conversion: (schema: CrudSchema) => { conversion: (schema: CrudSchema) => {
if (schema?.isTable !== false && schema?.table?.show !== false) { 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 { return {
...schema.table, ...schema.table,
...schema ...schema

View File

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

View File

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

View File

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

View File

@ -35,7 +35,7 @@ router.beforeEach(async (to, from, next) => {
const userStore = useUserStoreWithOut() const userStore = useUserStoreWithOut()
const permissionStore = usePermissionStoreWithOut() const permissionStore = usePermissionStoreWithOut()
if (!dictStore.getIsSetDict) { if (!dictStore.getIsSetDict) {
dictStore.setDictMap() await dictStore.setDictMap()
} }
if (!userStore.getIsSetUser) { if (!userStore.getIsSetUser) {
isRelogin.show = true isRelogin.show = true

View File

@ -23,8 +23,6 @@ declare module '@vue/runtime-core' {
DictTag: typeof import('./../components/DictTag/src/DictTag.vue')['default'] DictTag: typeof import('./../components/DictTag/src/DictTag.vue')['default']
Echart: typeof import('./../components/Echart/src/Echart.vue')['default'] Echart: typeof import('./../components/Echart/src/Echart.vue')['default']
Editor: typeof import('./../components/Editor/src/Editor.vue')['default'] Editor: typeof import('./../components/Editor/src/Editor.vue')['default']
ElAutoResizer: typeof import('element-plus/es')['ElAutoResizer']
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBadge: typeof import('element-plus/es')['ElBadge'] ElBadge: typeof import('element-plus/es')['ElBadge']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup'] ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
@ -54,7 +52,6 @@ declare module '@vue/runtime-core' {
ElForm: typeof import('element-plus/es')['ElForm'] ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon'] ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
@ -63,25 +60,18 @@ declare module '@vue/runtime-core' {
ElPagination: typeof import('element-plus/es')['ElPagination'] ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover'] ElPopover: typeof import('element-plus/es')['ElPopover']
ElRadio: typeof import('element-plus/es')['ElRadio'] ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow'] ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect'] ElSelect: typeof import('element-plus/es')['ElSelect']
ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
ElSpace: typeof import('element-plus/es')['ElSpace']
ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable'] ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs'] ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag'] ElTag: typeof import('element-plus/es')['ElTag']
ElTimeline: typeof import('element-plus/es')['ElTimeline']
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTransfer: typeof import('element-plus/es')['ElTransfer']
ElTree: typeof import('element-plus/es')['ElTree']
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
ElUpload: typeof import('element-plus/es')['ElUpload'] ElUpload: typeof import('element-plus/es')['ElUpload']
Error: typeof import('./../components/Error/src/Error.vue')['default'] Error: typeof import('./../components/Error/src/Error.vue')['default']
FlowCondition: typeof import('./../components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue')['default'] FlowCondition: typeof import('./../components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue')['default']

View File

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

View File

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

View File

@ -12,57 +12,7 @@ import dayjs from 'dayjs'
* @returns * @returns
*/ */
export function formatDate(date: Date, format: string): string { export function formatDate(date: Date, format: string): string {
const we = date.getDay() // 星期 return dayjs(date).format(format)
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
} }
/** /**

View File

@ -137,3 +137,21 @@ export const generateUUID = () => {
return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16) 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]
}

45
src/utils/permission.ts Normal file
View File

@ -0,0 +1,45 @@
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
const { t } = useI18n() // 国际化
/**
*
* @param {Array} value
* @returns {Boolean}
*/
export function checkPermi(value: string[]) {
if (value && value instanceof Array && value.length > 0) {
const { wsCache } = useCache()
const permissionDatas = value
const all_permission = '*:*:*'
const permissions = wsCache.get(CACHE_KEY.USER).permissions
const hasPermission = permissions.some((permission) => {
return all_permission === permission || permissionDatas.includes(permission)
})
return !!hasPermission
} else {
console.error(t('permission.hasPermission'))
return false
}
}
/**
*
* @param {string[]} value
* @returns {Boolean}
*/
export function checkRole(value: string[]) {
if (value && value instanceof Array && value.length > 0) {
const { wsCache } = useCache()
const permissionRoles = value
const super_admin = 'admin'
const roles = wsCache.get(CACHE_KEY.USER).roles
const hasRole = roles.some((role) => {
return super_admin === role || permissionRoles.includes(role)
})
return !!hasRole
} else {
console.error(t('permission.hasRole'))
return false
}
}

View File

@ -10,7 +10,7 @@ export const rules = reactive({
// CrudSchema // CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({ const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id', primaryKey: 'id',
primaryType: 'seq', primaryType: 'id',
primaryTitle: '表单编号', primaryTitle: '表单编号',
action: true, action: true,
columns: [ columns: [

View File

@ -250,7 +250,7 @@
name="bpmnFile" name="bpmnFile"
:data="importForm" :data="importForm"
> >
<el-icon class="el-icon--upload"><upload-filled /></el-icon> <Icon class="el-icon--upload" icon="ep:upload-filled" />
<div class="el-upload__text"> 将文件拖到此处 <em>点击上传</em> </div> <div class="el-upload__text"> 将文件拖到此处 <em>点击上传</em> </div>
<template #tip> <template #tip>
<div class="el-upload__tip" style="color: red"> <div class="el-upload__tip" style="color: red">

View File

@ -3,14 +3,15 @@ import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
// CrudSchema // CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({ const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id', primaryKey: 'id',
primaryType: 'seq', primaryType: 'id',
primaryTitle: '日志编号', primaryTitle: '日志编号',
action: true, action: true,
actionWidth: '80px', actionWidth: '80px',
columns: [ columns: [
{ {
title: '链路追踪', title: '链路追踪',
field: 'traceId' field: 'traceId',
isTable: false
}, },
{ {
title: '用户编号', title: '用户编号',

View File

@ -3,7 +3,7 @@ import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
// CrudSchema // CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({ const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id', primaryKey: 'id',
primaryType: 'seq', primaryType: 'id',
primaryTitle: '日志编号', primaryTitle: '日志编号',
action: true, action: true,
actionWidth: '300', actionWidth: '300',

View File

@ -8,7 +8,7 @@
<el-button size="small" type="primary" @click="showJson">JSON</el-button> <el-button size="small" type="primary" @click="showJson">JSON</el-button>
<el-button size="small" type="success" @click="showOption">Options</el-button> <el-button size="small" type="success" @click="showOption">Options</el-button>
<el-button size="small" type="danger" @click="showTemplate"></el-button> <el-button size="small" type="danger" @click="showTemplate"></el-button>
<el-button size="small" @click="changeLocale"></el-button> <!-- <el-button size="small" @click="changeLocale"></el-button> -->
</div> </div>
</el-col> </el-col>
<el-col> <el-col>
@ -19,9 +19,11 @@
<div ref="editor" v-if="dialogVisible"> <div ref="editor" v-if="dialogVisible">
<XTextButton style="float: right" :title="t('common.copy')" @click="copy(formValue)" /> <XTextButton style="float: right" :title="t('common.copy')" @click="copy(formValue)" />
<el-scrollbar height="580"> <el-scrollbar height="580">
<pre> <div v-highlight>
{{ formValue }} <code class="hljs">
</pre> {{ formValue }}
</code>
</div>
</el-scrollbar> </el-scrollbar>
</div> </div>
<span style="color: red" v-if="err">!</span> <span style="color: red" v-if="err">!</span>
@ -69,9 +71,9 @@ const showTemplate = () => {
type.value = 2 type.value = 2
formValue.value = makeTemplate() formValue.value = makeTemplate()
} }
const changeLocale = () => { // const changeLocale = () => {
console.info('changeLocale') // console.info('changeLocale')
} // }
/** 复制 **/ /** 复制 **/
const copy = async (text: string) => { const copy = async (text: string) => {

View File

@ -33,7 +33,6 @@
v-model="queryParams.createTime" v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
type="daterange" type="daterange"
range-separator="-"
start-placeholder="开始日期" start-placeholder="开始日期"
end-placeholder="结束日期" end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
@ -60,7 +59,7 @@
<!-- 列表 --> <!-- 列表 -->
<content-wrap> <content-wrap>
<el-table v-loading="loading" :data="list" align="center"> <el-table v-loading="loading" :data="list">
<el-table-column label="参数主键" align="center" prop="id" /> <el-table-column label="参数主键" align="center" prop="id" />
<el-table-column label="参数分类" align="center" prop="category" /> <el-table-column label="参数分类" align="center" prop="category" />
<el-table-column label="参数名称" align="center" prop="name" :show-overflow-tooltip="true" /> <el-table-column label="参数名称" align="center" prop="name" :show-overflow-tooltip="true" />

View File

@ -30,7 +30,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig' import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig'
import { DataSourceConfigVO } from '@/api/infra/dataSourceConfig'
const { t } = useI18n() // const { t } = useI18n() //
const message = useMessage() // const message = useMessage() //
@ -39,7 +38,7 @@ const modelVisible = ref(false) // 弹窗的是否展示
const modelTitle = ref('') // const modelTitle = ref('') //
const formLoading = ref(false) // 12 const formLoading = ref(false) // 12
const formType = ref('') // create - update - const formType = ref('') // create - update -
const formData = ref<DataSourceConfigVO>({ const formData = ref<DataSourceConfigApi.DataSourceConfigVO>({
id: undefined, id: undefined,
name: '', name: '',
url: '', url: '',

View File

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

View File

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

View File

@ -1,77 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
name: [required],
storage: [required],
config: {
basePath: [required],
host: [required],
port: [required],
username: [required],
password: [required],
mode: [required],
endpoint: [required],
bucket: [required],
accessKey: [required],
accessSecret: [required],
domain: [required]
}
})
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: 'seq',
primaryTitle: '配置编号',
action: true,
actionWidth: '400px',
columns: [
{
title: '配置名',
field: 'name',
isSearch: true
},
{
title: '存储器',
field: 'storage',
dictType: DICT_TYPE.INFRA_FILE_STORAGE,
dictClass: 'number',
isSearch: true
},
{
title: '主配置',
field: 'master',
dictType: DICT_TYPE.INFRA_BOOLEAN_STRING,
dictClass: 'boolean'
},
{
title: t('form.remark'),
field: 'remark',
form: {
component: 'Input',
componentProps: {
type: 'textarea',
rows: 4
},
colProps: {
span: 24
}
}
},
{
title: t('common.createTime'),
field: 'createTime',
formatter: 'formatDate',
isForm: false,
search: {
show: true,
itemRender: {
name: 'XDataTimePicker'
}
}
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

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

View File

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

View File

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

View File

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

View File

@ -70,7 +70,7 @@ const detailData = ref() // 详情 Ref
// //
const handleDetail = async (row: JobLogApi.JobLogVO) => { const handleDetail = async (row: JobLogApi.JobLogVO) => {
// //
const res = JobLogApi.getJobLogApi(row.id) const res = await JobLogApi.getJobLogApi(row.id)
detailData.value = res detailData.value = res
dialogTitle.value = t('action.detail') dialogTitle.value = t('action.detail')
dialogVisible.value = true dialogVisible.value = true

View File

@ -11,7 +11,7 @@ export const rules = reactive({
// CrudSchema // CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({ const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id', primaryKey: 'id',
primaryType: 'seq', primaryType: 'id',
primaryTitle: '任务编号', primaryTitle: '任务编号',
action: true, action: true,
actionWidth: '280px', actionWidth: '280px',

View File

@ -4,7 +4,7 @@ const { t } = useI18n()
// CrudSchema // CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({ const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id', primaryKey: 'id',
primaryType: 'seq', primaryType: 'id',
primaryTitle: '日志编号', primaryTitle: '日志编号',
action: true, action: true,
columns: [ columns: [

View File

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

View File

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

View File

@ -1,10 +1,10 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
import { dateFormatter } from '@/utils/formatTime'
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
// 表单校验 // 表单校验
export const rules = reactive({ export const rules = reactive({
// mail: [required],
mail: [ mail: [
{ required: true, message: t('profile.rules.mail'), trigger: 'blur' }, { required: true, message: t('profile.rules.mail'), trigger: 'blur' },
{ {
@ -20,56 +20,54 @@ export const rules = reactive({
sslEnable: [required] sslEnable: [required]
}) })
// CrudSchema // CrudSchemahttps://kailong110120130.gitee.io/vue-element-plus-admin-doc/hooks/useCrudSchemas.html
const crudSchemas = reactive<VxeCrudSchema>({ const crudSchemas = reactive<CrudSchema[]>([
primaryKey: 'id', // 默认的主键 ID {
primaryTitle: '编号', label: '邮箱',
primaryType: 'id', field: 'mail',
action: true, isSearch: true
actionWidth: '200', // 3 个按钮默认 200如有删减对应增减即可 },
columns: [ {
{ label: '用户名',
title: '邮箱', field: 'username',
field: 'mail', isSearch: true
isSearch: true },
}, {
{ label: '密码',
title: '用户名', field: 'password',
field: 'username', isTable: false
isSearch: true },
}, {
{ label: 'SMTP 服务器域名',
title: '密码', field: 'host'
field: 'password', },
isTable: false {
}, label: 'SMTP 服务器端口',
{ field: 'port',
title: 'SMTP 服务器域名', form: {
field: 'host' component: 'InputNumber',
}, value: 465
{
title: 'SMTP 服务器端口',
field: 'port',
form: {
component: 'InputNumber',
value: 465
}
},
{
title: '是否开启 SSL',
field: 'sslEnable',
dictType: DICT_TYPE.INFRA_BOOLEAN_STRING,
dictClass: 'boolean'
},
{
title: '创建时间',
field: 'createTime',
isForm: false,
formatter: 'formatDate',
table: {
width: 180
}
} }
] },
}) {
export const { allSchemas } = useVxeCrudSchemas(crudSchemas) label: '是否开启 SSL',
field: 'sslEnable',
dictType: DICT_TYPE.INFRA_BOOLEAN_STRING,
dictClass: 'boolean',
form: {
component: 'Radio'
}
},
{
label: '创建时间',
field: 'createTime',
isForm: false,
formatter: dateFormatter
},
{
label: '操作',
field: 'action',
isForm: false
}
])
export const { allSchemas } = useCrudSchemas(crudSchemas)

View File

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

View File

@ -1,151 +1,84 @@
<template> <template>
<ContentWrap> <!-- 搜索工作栏 -->
<!-- 列表 --> <content-wrap>
<XTable @register="registerTable"> <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams">
<template #toolbar_buttons> <!-- 新增等操作按钮 -->
<!-- 操作新增 --> <template #actionMore>
<XButton <el-button
type="primary" type="primary"
preIcon="ep:zoom-in" @click="openModal('create')"
:title="t('action.add')"
v-hasPermi="['system:mail-account:create']" v-hasPermi="['system:mail-account:create']"
@click="handleCreate()" >
/> <Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</template> </template>
<template #actionbtns_default="{ row }"> </Search>
<!-- 操作修改 --> </content-wrap>
<XTextButton
preIcon="ep:edit" <!-- 列表 -->
:title="t('action.edit')" <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']" v-hasPermi="['system:mail-account:update']"
@click="handleUpdate(row.id)" >
/> 编辑
<!-- 操作详情 --> </el-button>
<XTextButton <el-button
preIcon="ep:view" link
:title="t('action.detail')" type="danger"
v-hasPermi="['system:mail-account:query']"
@click="handleDetail(row.id)"
/>
<!-- 操作删除 -->
<XTextButton
preIcon="ep:delete"
:title="t('action.del')"
v-hasPermi="['system:mail-account:delete']" v-hasPermi="['system:mail-account:delete']"
@click="deleteData(row.id)" @click="handleDelete(row.id)"
/> >
删除
</el-button>
</template> </template>
</XTable> </Table>
</ContentWrap> </content-wrap>
<!-- 弹窗 -->
<XModal id="mailAccountModel" :loading="modelLoading" v-model="modelVisible" :title="modelTitle"> <!-- 表单弹窗添加/修改 -->
<!-- 表单添加/修改 --> <mail-account-form ref="modalRef" @success="getList" />
<Form
ref="formRef"
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
/>
<!-- 表单详情 -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailData"
/>
<template #footer>
<!-- 按钮保存 -->
<XButton
v-if="['create', 'update'].includes(actionType)"
type="primary"
:title="t('action.save')"
:loading="actionLoading"
@click="submitForm()"
/>
<!-- 按钮关闭 -->
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" />
</template>
</XModal>
</template> </template>
<script setup lang="ts" name="MailAccount"> <script setup lang="ts" name="MailAccount">
import { FormExpose } from '@/components/Form' import { allSchemas } from './account.data'
// import
import { rules, allSchemas } from './account.data'
import * as MailAccountApi from '@/api/system/mail/account' import * as MailAccountApi from '@/api/system/mail/account'
import MailAccountForm from './form.vue'
const { t } = useI18n() // // tableObject
const message = useMessage() // // tableMethods
// https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/table.html#usetable
// const { tableObject, tableMethods } = useTable({
const [registerTable, { reload, deleteData }] = useXTable({ getListApi: MailAccountApi.getMailAccountPage, //
allSchemas: allSchemas, delListApi: MailAccountApi.deleteMailAccount //
getListApi: MailAccountApi.getMailAccountPageApi,
deleteApi: MailAccountApi.deleteMailAccountApi
}) })
//
const { getList, setSearchParams } = tableMethods
// /** 添加/修改操作 */
const modelVisible = ref(false) // const modalRef = ref()
const modelTitle = ref('edit') // const openModal = (type: string, id?: number) => {
const modelLoading = ref(false) // loading modalRef.value.openModal(type, id)
const actionType = ref('') //
const actionLoading = ref(false) // Loading
const formRef = ref<FormExpose>() // Ref
const detailData = ref() // Ref
//
const setDialogTile = (type: string) => {
modelLoading.value = true
modelTitle.value = t('action.' + type)
actionType.value = type
modelVisible.value = true
} }
// /** 删除按钮操作 */
const handleCreate = () => { const handleDelete = (id: number) => {
setDialogTile('create') tableMethods.delList(id, false)
modelLoading.value = false
} }
// /** 初始化 **/
const handleUpdate = async (rowId: number) => { onMounted(() => {
setDialogTile('update') getList()
// })
const res = await MailAccountApi.getMailAccountApi(rowId)
unref(formRef)?.setValues(res)
modelLoading.value = false
}
//
const handleDetail = async (rowId: number) => {
setDialogTile('detail')
const res = await MailAccountApi.getMailAccountApi(rowId)
detailData.value = res
modelLoading.value = false
}
//
const submitForm = async () => {
const elForm = unref(formRef)?.getElFormRef()
if (!elForm) return
elForm.validate(async (valid) => {
if (valid) {
actionLoading.value = true
//
try {
const data = unref(formRef)?.formModel as MailAccountApi.MailAccountVO
if (actionType.value === 'create') {
await MailAccountApi.createMailAccountApi(data)
message.success(t('common.createSuccess'))
} else {
await MailAccountApi.updateMailAccountApi(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
} finally {
actionLoading.value = false
//
await reload()
}
}
})
}
</script> </script>

View File

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

View File

@ -1,98 +1,59 @@
<template> <template>
<ContentWrap> <!-- 搜索工作栏 -->
<!-- 列表 --> <content-wrap>
<XTable @register="registerTable"> <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
<template #accountId_search> </content-wrap>
<el-select v-model="queryParams.accountId">
<el-option :key="undefined" label="全部" :value="undefined" /> <!-- 列表 -->
<el-option <content-wrap>
v-for="item in accountOptions" <Table
:key="item.id" :columns="allSchemas.tableColumns"
:label="item.mail" :data="tableObject.tableList"
:value="item.id" :loading="tableObject.loading"
/> :pagination="{
</el-select> total: tableObject.total
</template> }"
<template #toMail_default="{ row }"> v-model:pageSize="tableObject.pageSize"
<div>{{ row.toMail }}</div> v-model:currentPage="tableObject.currentPage"
<div v-if="row.userType && row.userId"> >
<DictTag :type="DICT_TYPE.USER_TYPE" :value="row.userType" />{{ '(' + row.userId + ')' }} <template #action="{ row }">
</div> <el-button
</template> link
<template #actionbtns_default="{ row }"> type="primary"
<!-- 操作详情 --> @click="openModal(row.id)"
<XTextButton
preIcon="ep:view"
:title="t('action.detail')"
v-hasPermi="['system:mail-log:query']" v-hasPermi="['system:mail-log:query']"
@click="handleDetail(row.id)" >
/> 详情
</el-button>
</template> </template>
</XTable> </Table>
</ContentWrap> </content-wrap>
<!-- 弹窗 -->
<XModal id="mailLogModel" :loading="modelLoading" v-model="modelVisible" :title="modelTitle"> <!-- 表单弹窗详情 -->
<!-- 表单详情 --> <mail-log-detail ref="modalRef" />
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailData"
/>
<template #footer>
<!-- 按钮关闭 -->
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" />
</template>
</XModal>
</template> </template>
<script setup lang="ts" name="MailLog"> <script setup lang="ts" name="MailLog">
// import
import { DICT_TYPE } from '@/utils/dict'
import { allSchemas } from './log.data' import { allSchemas } from './log.data'
import * as MailLogApi from '@/api/system/mail/log' import * as MailLogApi from '@/api/system/mail/log'
import * as MailAccountApi from '@/api/system/mail/account' import MailLogDetail from './detail.vue'
const { t } = useI18n() // // tableObject
// tableMethods
// // https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/table.html#usetable
const queryParams = reactive({ const { tableObject, tableMethods } = useTable({
accountId: null getListApi: MailLogApi.getMailLogPage //
}) })
const [registerTable] = useXTable({ //
allSchemas: allSchemas, const { getList, setSearchParams } = tableMethods
topActionSlots: false,
params: queryParams,
getListApi: MailLogApi.getMailLogPageApi
})
const accountOptions = ref<any[]>([]) //
// /** 详情操作 */
const modelVisible = ref(false) // const modalRef = ref()
const modelTitle = ref('edit') // const openModal = (id: number) => {
const modelLoading = ref(false) // loading modalRef.value.openModal(id)
const actionType = ref('') //
const actionLoading = ref(false) // Loading
const detailData = ref() // Ref
//
const setDialogTile = (type: string) => {
modelLoading.value = true
modelTitle.value = t('action.' + type)
actionType.value = type
modelVisible.value = true
} }
// /** 初始化 **/
const handleDetail = async (rowId: number) => {
setDialogTile('detail')
const res = await MailLogApi.getMailLogApi(rowId)
detailData.value = res
modelLoading.value = false
}
// ========== ==========
onMounted(() => { onMounted(() => {
MailAccountApi.getSimpleMailAccounts().then((data) => { getList()
accountOptions.value = data
})
}) })
</script> </script>

View File

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

View File

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

View File

@ -1,273 +1,102 @@
<template> <template>
<ContentWrap> <!-- 搜索工作栏 -->
<!-- 列表 --> <content-wrap>
<XTable @register="registerTable"> <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams">
<template #accountId_search> <!-- 新增等操作按钮 -->
<el-select v-model="queryParams.accountId"> <template #actionMore>
<el-option :key="undefined" label="全部" :value="undefined" /> <el-button
<el-option
v-for="item in accountOptions"
:key="item.id"
:label="item.mail"
:value="item.id"
/>
</el-select>
</template>
<template #toolbar_buttons>
<!-- 操作新增 -->
<XButton
type="primary" type="primary"
preIcon="ep:zoom-in" @click="openModal('create')"
:title="t('action.add')" v-hasPermi="['system:mail-account:create']"
v-hasPermi="['system:mail-template:create']" >
@click="handleCreate()" <Icon icon="ep:plus" class="mr-5px" /> 新增
/> </el-button>
</template> </template>
<template #accountId_default="{ row }"> </Search>
<span>{{ accountOptions.find((account) => account.id === row.accountId)?.mail }}</span> </content-wrap>
</template>
<template #actionbtns_default="{ row }">
<!-- 操作测试短信 -->
<XTextButton
preIcon="ep:cpu"
:title="t('action.test')"
v-hasPermi="['system:mail-template:send-mail']"
@click="handleSendMail(row)"
/>
<!-- 操作修改 -->
<XTextButton
preIcon="ep:edit"
:title="t('action.edit')"
v-hasPermi="['system:mail-template:update']"
@click="handleUpdate(row.id)"
/>
<!-- 操作详情 -->
<XTextButton
preIcon="ep:view"
:title="t('action.detail')"
v-hasPermi="['system:mail-template:query']"
@click="handleDetail(row.id)"
/>
<!-- 操作删除 -->
<XTextButton
preIcon="ep:delete"
:title="t('action.del')"
v-hasPermi="['system:mail-template:delete']"
@click="deleteData(row.id)"
/>
</template>
</XTable>
</ContentWrap>
<!-- 添加/修改/详情的弹窗 --> <!-- 列表 -->
<XModal id="mailTemplateModel" :loading="modelLoading" v-model="modelVisible" :title="modelTitle"> <content-wrap>
<!-- 表单添加/修改 --> <Table
<Form :columns="allSchemas.tableColumns"
ref="formRef" :data="tableObject.tableList"
v-if="['create', 'update'].includes(actionType)" :loading="tableObject.loading"
:schema="allSchemas.formSchema" :pagination="{
:rules="rules" total: tableObject.total
}"
v-model:pageSize="tableObject.pageSize"
v-model:currentPage="tableObject.currentPage"
> >
<template #accountId="form"> <template #action="{ row }">
<el-select v-model="form.accountId"> <el-button
<el-option link
v-for="item in accountOptions" type="primary"
:key="item.id" @click="openSend(row.id)"
:label="item.mail" v-hasPermi="['system:mail-template:send-mail']"
:value="item.id" >
/> 测试
</el-select> </el-button>
<el-button
link
type="primary"
@click="openModal('update', row.id)"
v-hasPermi="['system:mail-template:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
v-hasPermi="['system:mail-template:delete']"
@click="handleDelete(row.id)"
>
删除
</el-button>
</template> </template>
</Form> </Table>
<!-- 表单详情 --> </content-wrap>
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailData"
/>
<template #footer>
<!-- 按钮保存 -->
<XButton
v-if="['create', 'update'].includes(actionType)"
type="primary"
:title="t('action.save')"
:loading="actionLoading"
@click="submitForm()"
/>
<!-- 按钮关闭 -->
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" />
</template>
</XModal>
<!-- 测试邮件的弹窗 --> <!-- 表单弹窗添加/修改 -->
<XModal id="sendTest" v-model="sendVisible" title="测试"> <mail-template-form ref="modalRef" @success="getList" />
<el-form :model="sendForm" :rules="sendRules" label-width="200px" label-position="top">
<el-form-item label="模板内容" prop="content"> <!-- 表单弹窗发送测试 -->
<Editor :model-value="sendForm.content" readonly height="150px" /> <mail-template-send ref="sendRef" />
</el-form-item>
<el-form-item label="收件邮箱" prop="mail">
<el-input v-model="sendForm.mail" placeholder="请输入收件邮箱" />
</el-form-item>
<el-form-item
v-for="param in sendForm.params"
:key="param"
:label="'参数 {' + param + '}'"
:prop="'templateParams.' + param"
>
<el-input
v-model="sendForm.templateParams[param]"
:placeholder="'请输入 ' + param + ' 参数'"
/>
</el-form-item>
</el-form>
<!-- 操作按钮 -->
<template #footer>
<XButton
type="primary"
:title="t('action.test')"
:loading="actionLoading"
@click="sendTest()"
/>
<XButton :title="t('dialog.close')" @click="sendVisible = false" />
</template>
</XModal>
</template> </template>
<script setup lang="ts" name="MailTemplate"> <script setup lang="ts" name="MailTemplate">
import { FormExpose } from '@/components/Form' import { allSchemas } from './template.data'
// import
import { rules, allSchemas } from './template.data'
import * as MailTemplateApi from '@/api/system/mail/template' import * as MailTemplateApi from '@/api/system/mail/template'
import * as MailAccountApi from '@/api/system/mail/account' import MailTemplateForm from './form.vue'
import MailTemplateSend from './send.vue'
const { t } = useI18n() // // tableObject
const message = useMessage() // // tableMethods
// https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/table.html#usetable
// const { tableObject, tableMethods } = useTable({
const queryParams = reactive({ getListApi: MailTemplateApi.getMailTemplatePage, //
accountId: null delListApi: MailTemplateApi.deleteMailTemplate //
}) })
const [registerTable, { reload, deleteData }] = useXTable({ //
allSchemas: allSchemas, const { getList, setSearchParams } = tableMethods
params: queryParams,
getListApi: MailTemplateApi.getMailTemplatePageApi,
deleteApi: MailTemplateApi.deleteMailTemplateApi
})
const accountOptions = ref<any[]>([]) //
// /** 添加/修改操作 */
const modelVisible = ref(false) // const modalRef = ref()
const modelTitle = ref('edit') // const openModal = (type: string, id?: number) => {
const modelLoading = ref(false) // loading modalRef.value.openModal(type, id)
const actionType = ref('') //
const actionLoading = ref(false) // Loading
const formRef = ref<FormExpose>() // Ref
const detailData = ref() // Ref
//
const setDialogTile = (type: string) => {
modelLoading.value = true
modelTitle.value = t('action.' + type)
actionType.value = type
modelVisible.value = true
} }
// /** 删除按钮操作 */
const handleCreate = () => { const handleDelete = (id: number) => {
setDialogTile('create') tableMethods.delList(id, false)
modelLoading.value = false
} }
// /** 发送测试操作 */
const handleUpdate = async (rowId: number) => { const sendRef = ref()
setDialogTile('update') const openSend = (id: number) => {
// sendRef.value.openModal(id)
const res = await MailTemplateApi.getMailTemplateApi(rowId)
unref(formRef)?.setValues(res)
modelLoading.value = false
} }
// /** 初始化 **/
const handleDetail = async (rowId: number) => {
setDialogTile('detail')
const res = await MailTemplateApi.getMailTemplateApi(rowId)
detailData.value = res
modelLoading.value = false
}
//
const submitForm = async () => {
const elForm = unref(formRef)?.getElFormRef()
if (!elForm) return
elForm.validate(async (valid) => {
if (valid) {
actionLoading.value = true
//
try {
const data = unref(formRef)?.formModel as MailTemplateApi.MailTemplateVO
if (actionType.value === 'create') {
await MailTemplateApi.createMailTemplateApi(data)
message.success(t('common.createSuccess'))
} else {
await MailTemplateApi.updateMailTemplateApi(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
} finally {
actionLoading.value = false
//
await reload()
}
}
})
}
// ========== ==========
const sendForm = ref({
content: '',
params: {},
mail: '',
templateCode: '',
templateParams: {}
})
const sendRules = ref({
mail: [{ required: true, message: '邮箱不能为空', trigger: 'blur' }],
templateCode: [{ required: true, message: '模版编号不能为空', trigger: 'blur' }],
templateParams: {}
})
const sendVisible = ref(false)
const handleSendMail = (row: any) => {
sendForm.value.content = row.content
sendForm.value.params = row.params
sendForm.value.templateCode = row.code
sendForm.value.templateParams = row.params.reduce(function (obj, item) {
obj[item] = undefined
return obj
}, {})
sendRules.value.templateParams = row.params.reduce(function (obj, item) {
obj[item] = { required: true, message: '参数 ' + item + ' 不能为空', trigger: 'change' }
return obj
}, {})
sendVisible.value = true
}
const sendTest = async () => {
const data: MailTemplateApi.MailSendReqVO = {
mail: sendForm.value.mail,
templateCode: sendForm.value.templateCode,
templateParams: sendForm.value.templateParams as unknown as Map<string, Object>
}
const res = await MailTemplateApi.sendMailApi(data)
if (res) {
message.success('提交发送成功!发送结果,见发送日志编号:' + res)
}
sendVisible.value = false
}
// ========== ==========
onMounted(() => { onMounted(() => {
MailAccountApi.getSimpleMailAccounts().then((data) => { getList()
accountOptions.value = data
})
}) })
</script> </script>

View File

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

View File

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

View File

@ -0,0 +1,131 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible" width="800">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="80px"
v-loading="formLoading"
>
<el-form-item label="公告标题" prop="title">
<el-input v-model="formData.title" placeholder="请输入公告标题" />
</el-form-item>
<el-form-item label="公告内容" prop="content">
<Editor :model-value="formData.content" height="150px" />
</el-form-item>
<el-form-item label="公告类型" prop="type">
<el-select v-model="formData.type" placeholder="请选择公告类型" clearable>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_NOTICE_TYPE)"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="formData.status" placeholder="请选择状态" clearable>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" placeholder="请输备注" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import * as NoticeApi from '@/api/system/notice'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
title: '',
type: undefined,
content: '',
status: undefined,
remark: ''
})
const formRules = reactive({
title: [{ required: true, message: '公告标题不能为空', trigger: 'blur' }],
type: [{ required: true, message: '公告类型不能为空', trigger: 'change' }],
status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
content: [{ 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 NoticeApi.getNotice(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 NoticeApi.NoticeVO
if (formType.value === 'create') {
await NoticeApi.createNotice(data)
message.success(t('common.createSuccess'))
} else {
await NoticeApi.updateNotice(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
title: '',
type: undefined,
content: '',
status: undefined,
remark: ''
}
formRef.value?.resetFields()
}
</script>

View File

@ -1,150 +1,161 @@
<template> <template>
<ContentWrap> <content-wrap>
<el-form ref="searchForm" :model="queryParms" :inline="true"> <!-- 搜索工作栏 -->
<el-form-item label="公告标题"> <el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
<el-input v-model="queryParms.title" /> <el-form-item label="公告标题" prop="title">
<el-input
v-model="queryParams.title"
placeholder="请输入公告标题"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item> </el-form-item>
<el-form-item label="状态"> <el-form-item label="公告状态" prop="status">
<el-select v-model="queryParms.status"> <el-select v-model="queryParams.status" placeholder="请选择公告状态" clearable>
<el-option label="全部" value="" /> <el-option
<el-option label="开启" :value="1" /> v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
<el-option label="关闭" :value="0" /> :key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="getList">Query</el-button> <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:notice:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div style="width: 100%; height: 600px"> </content-wrap>
<el-auto-resizer>
<template #default="{ height, width }"> <!-- 列表 -->
<el-table-v2 <content-wrap>
:columns="columns" <el-table v-loading="loading" :data="list">
:data="tableData" <el-table-column label="公告编号" align="center" prop="id" />
:width="width" <el-table-column label="公告标题" align="center" prop="title" />
:height="height - 50" <el-table-column label="公告类型" align="center" prop="type">
fixed <template #default="scope">
/> <dict-tag :type="DICT_TYPE.SYSTEM_NOTICE_TYPE" :value="scope.row.type" />
</template> </template>
</el-auto-resizer> </el-table-column>
</div> <el-table-column label="状态" align="center" prop="status">
<div class="mt-2"> <template #default="scope">
<el-pagination <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
:current-page="queryParms.pageNo" </template>
:page-size="queryParms.pageSize" </el-table-column>
:page-sizes="[10, 20, 30, 50, 100]" <el-table-column
layout="total, sizes, prev, pager, next, jumper" label="创建时间"
:total="tableTotal" align="center"
@size-change="getList" prop="createTime"
@current-change="getList" width="180"
:formatter="dateFormatter"
/> />
</div> <el-table-column label="操作" align="center">
</ContentWrap> <template #default="scope">
<el-button
link
type="primary"
@click="openModal('update', scope.row.id)"
v-hasPermi="['system:notice:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['system:notice: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>
<!-- 表单弹窗添加/修改 -->
<notice-form ref="modalRef" @success="getList" />
</template> </template>
<script setup lang="tsx"> <script setup lang="tsx">
import dayjs from 'dayjs' import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import { Column, ElPagination, ElTableV2, TableV2FixedDir } from 'element-plus' import { dateFormatter } from '@/utils/formatTime'
import * as NoticeApi from '@/api/system/notice' import * as NoticeApi from '@/api/system/notice'
import { XTextButton } from '@/components/XButton'
import { DictTag } from '@/components/DictTag' import { DictTag } from '@/components/DictTag'
import NoticeForm from './form.vue'
const message = useMessage() //
const { t } = useI18n() // const { t } = useI18n() //
const columns: Column<any>[] = [ const loading = ref(true) //
{ const total = ref(0) //
key: 'id', const list = ref([]) //
dataKey: 'id', //{id:9527,name:'Mike'}id const queryParams = reactive({
title: 'id', //
width: 80, //
fixed: true //
},
{
key: 'title',
dataKey: 'title',
title: '公告标题',
width: 180
},
{
key: 'type',
dataKey: 'type',
title: '公告类型',
width: 180,
cellRenderer: ({ cellData: type }) => (
<DictTag type={DICT_TYPE.SYSTEM_NOTICE_TYPE} value={type}></DictTag>
)
},
{
key: 'status',
dataKey: 'status',
title: t('common.status'),
width: 180,
cellRenderer: ({ cellData: status }) => (
<DictTag type={DICT_TYPE.COMMON_STATUS} value={status}></DictTag>
)
},
{
key: 'content',
dataKey: 'content',
title: '公告内容',
width: 400,
cellRenderer: ({ cellData: content }) => <span v-html={content}></span>
},
{
key: 'createTime',
dataKey: 'createTime',
title: t('common.createTime'),
width: 180,
cellRenderer: ({ cellData: createTime }) => (
<>{dayjs(createTime).format('YYYY-MM-DD HH:mm:ss')}</>
)
},
{
key: 'actionbtns',
dataKey: 'actionbtns', //{id:9527,name:'Mike'}id
title: '操作', //
width: 160, //
fixed: TableV2FixedDir.RIGHT, //
align: 'center',
cellRenderer: ({ cellData: id }) => (
<>
<XTextButton
preIcon="ep:delete"
title={t('action.edit')}
onClick={handleUpdate.bind(this, id)}
></XTextButton>
<XTextButton
preIcon="ep:delete"
title={t('action.del')}
onClick={handleDelete.bind(this, id)}
></XTextButton>
</>
)
}
]
const tableData = ref([])
const tableTotal = ref(0)
const queryParms = reactive({
title: '', title: '',
type: undefined,
status: undefined, status: undefined,
pageNo: 1, pageNo: 1,
pageSize: 100 pageSize: 100
}) })
const queryFormRef = ref() //
/** 查询公告列表 */
const getList = async () => { const getList = async () => {
const res = await NoticeApi.getNoticePageApi(queryParms) loading.value = true
tableData.value = res.list try {
tableTotal.value = res.total const data = await NoticeApi.getNoticePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
} }
const handleUpdate = (id) => { /** 搜索按钮操作 */
console.info(id) const handleQuery = () => {
queryParams.pageNo = 1
getList()
} }
const handleDelete = (id) => { /** 重置按钮操作 */
console.info(id) const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
} }
getList() /** 添加/修改操作 */
const modalRef = ref()
const openModal = (type: string, id?: number) => {
modalRef.value.openModal(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await NoticeApi.deleteNotice(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script> </script>

View File

@ -1,148 +0,0 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<!-- 操作新增 -->
<template #toolbar_buttons>
<XButton
type="primary"
preIcon="ep:zoom-in"
:title="t('action.add')"
v-hasPermi="['system:notice:create']"
@click="handleCreate()"
/>
</template>
<template #actionbtns_default="{ row }">
<!-- 操作修改 -->
<XTextButton
preIcon="ep:edit"
:title="t('action.edit')"
v-hasPermi="['system:notice:update']"
@click="handleUpdate(row.id)"
/>
<!-- 操作详情 -->
<XTextButton
preIcon="ep:view"
:title="t('action.detail')"
v-hasPermi="['system:notice:query']"
@click="handleDetail(row.id)"
/>
<!-- 操作删除 -->
<XTextButton
preIcon="ep:delete"
:title="t('action.del')"
v-hasPermi="['system:notice:delete']"
@click="deleteData(row.id)"
/>
</template>
</XTable>
</ContentWrap>
<!-- 弹窗 -->
<XModal id="noticeModel" v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(添加 / 修改) -->
<Form
ref="formRef"
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
/>
<!-- 对话框(详情) -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailData"
>
<template #content="{ row }">
<Editor :model-value="row.content" :readonly="true" />
</template>
</Descriptions>
<template #footer>
<!-- 按钮保存 -->
<XButton
v-if="['create', 'update'].includes(actionType)"
type="primary"
:title="t('action.save')"
:loading="actionLoading"
@click="submitForm()"
/>
<!-- 按钮关闭 -->
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
</template>
</XModal>
</template>
<script setup lang="ts" name="Notice">
import type { FormExpose } from '@/components/Form'
// import
import * as NoticeApi from '@/api/system/notice'
import { rules, allSchemas } from './notice.data'
const { t } = useI18n() //
const message = useMessage() //
//
const [registerTable, { reload, deleteData }] = useXTable({
allSchemas: allSchemas,
getListApi: NoticeApi.getNoticePageApi,
deleteApi: NoticeApi.deleteNoticeApi
})
//
const dialogVisible = ref(false) //
const dialogTitle = ref('edit') //
const actionType = ref('') //
const actionLoading = ref(false) // Loading
const formRef = ref<FormExpose>() // Ref
const detailData = ref() // Ref
//
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
//
const handleCreate = () => {
setDialogTile('create')
}
//
const handleUpdate = async (rowId: number) => {
setDialogTile('update')
//
const res = await NoticeApi.getNoticeApi(rowId)
unref(formRef)?.setValues(res)
}
//
const handleDetail = async (rowId: number) => {
setDialogTile('detail')
//
const res = await NoticeApi.getNoticeApi(rowId)
detailData.value = res
}
// /
const submitForm = async () => {
const elForm = unref(formRef)?.getElFormRef()
if (!elForm) return
elForm.validate(async (valid) => {
if (valid) {
actionLoading.value = true
//
try {
const data = unref(formRef)?.formModel as NoticeApi.NoticeVO
if (actionType.value === 'create') {
await NoticeApi.createNoticeApi(data)
message.success(t('common.createSuccess'))
} else {
await NoticeApi.updateNoticeApi(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
} finally {
actionLoading.value = false
await reload()
}
}
})
}
</script>

View File

@ -1,103 +0,0 @@
<template>
<ContentWrap>
<div style="width: 100%; height: 500px">
<el-auto-resizer>
<template #default="{ height, width }">
<el-table-v2 :columns="columns" :data="tableData" :width="width" :height="height" fixed />
</template>
</el-auto-resizer>
<el-pagination
:current-page="queryParms.pageNo"
:page-size="queryParms.pageSize"
layout="total, prev, pager, next"
:total="tableTotal"
@size-change="getList"
@current-change="getList"
/>
</div>
</ContentWrap>
</template>
<script setup lang="ts">
import { Column, TableV2FixedDir } from 'element-plus'
import * as NoticeApi from '@/api/system/notice'
import { XTextButton } from '@/components/XButton'
const { t } = useI18n() //
const columns: Column<any>[] = [
{
key: 'id',
dataKey: 'id', //{id:9527,name:'Mike'}id
title: 'id', //
width: 80, //
fixed: true //
},
{
key: 'title',
dataKey: 'title',
title: '公告标题',
width: 180
},
{
key: 'type',
dataKey: 'type',
title: '公告类型',
width: 180
},
{
key: 'status',
dataKey: 'status',
title: t('common.status'),
width: 180
},
{
key: 'content',
dataKey: 'content',
title: '公告内容',
width: 180
},
{
key: 'createTime',
dataKey: 'createTime',
title: t('common.createTime'),
width: 180
},
{
key: 'actionbtns',
dataKey: 'actionbtns', //{id:9527,name:'Mike'}id
title: '操作', //
width: 80, //
fixed: TableV2FixedDir.RIGHT, //
align: 'center',
cellRenderer: (date) =>
h(XTextButton, {
onClick: () => handleDelete(date.rowData),
type: 'danger',
preIcon: 'ep:delete',
title: t('action.del')
})
}
]
const tableData = ref([])
const tableTotal = ref(0)
const queryParms = reactive({
title: '',
status: undefined,
pageNo: 1,
pageSize: 10
})
const getList = async () => {
const res = await NoticeApi.getNoticePageApi(queryParms)
tableData.value = res.list
tableTotal.value = res.total
}
const handleDelete = (row) => {
console.info(row.id)
}
getList()
</script>

View File

@ -1,59 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
title: [required],
type: [required]
})
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: 'seq',
action: true,
columns: [
{
title: '公告标题',
field: 'title',
isSearch: true
},
{
title: '公告类型',
field: 'type',
dictType: DICT_TYPE.SYSTEM_NOTICE_TYPE,
dictClass: 'number'
},
{
title: t('common.status'),
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
dictClass: 'number',
isSearch: true
},
{
title: '公告内容',
field: 'content',
table: {
type: 'html'
},
form: {
component: 'Editor',
colProps: {
span: 24
},
componentProps: {
valueHtml: ''
}
},
isTable: false
},
{
title: t('common.createTime'),
field: 'createTime',
formatter: 'formatDate',
isForm: false
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -22,6 +22,10 @@ const crudSchemas = reactive<VxeCrudSchema>({
primaryType: null, primaryType: null,
action: true, action: true,
columns: [ columns: [
{
title: '客户端端号',
field: 'clientId'
},
{ {
title: '客户端密钥', title: '客户端密钥',
field: 'secret' field: 'secret'

View File

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

View File

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

View File

@ -1,106 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
const { t } = useI18n() // 国际化
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: 'id',
primaryTitle: '日志编号',
action: true,
actionWidth: '80px',
columns: [
{
title: '操作模块',
field: 'module',
isSearch: true
},
{
title: '操作名',
field: 'name'
},
{
title: '操作类型',
field: 'type',
dictType: DICT_TYPE.SYSTEM_OPERATE_TYPE,
dictClass: 'number',
isSearch: true
},
{
title: '请求方法名',
field: 'requestMethod',
isTable: false
},
{
title: '请求地址',
field: 'requestUrl',
isTable: false
},
{
title: '操作人员',
field: 'userNickname',
isSearch: true
},
{
title: '操作明细',
field: 'content',
isTable: false
},
{
title: '用户 IP',
field: 'userIp',
isTable: false
},
{
title: 'userAgent',
field: 'userAgent'
},
{
title: '操作结果',
field: 'resultCode',
table: {
slots: {
default: 'resultCode'
}
}
},
{
title: '操作结果',
field: 'success',
isTable: false,
isDetail: false,
search: {
show: true,
itemRender: {
name: '$select',
props: { placeholder: t('common.selectText') },
options: [
{ label: '成功', value: 'true' },
{ label: '失败', value: 'false' }
]
}
}
},
{
title: '操作日期',
field: 'startTime',
formatter: 'formatDate',
isForm: false,
search: {
show: true,
itemRender: {
name: 'XDataTimePicker'
}
}
},
{
title: '执行时长',
field: 'duration',
table: {
slots: {
default: 'duration'
}
}
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

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

View File

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

View File

@ -1,63 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
signature: [required],
code: [required],
apiKey: [required],
status: [required]
})
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: 'id',
primaryTitle: '渠道编号',
action: true,
columns: [
{
title: '短信签名',
field: 'signature',
isSearch: true
},
{
title: '渠道编码',
field: 'code',
dictType: DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE,
isSearch: true
},
{
title: t('common.status'),
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
dictClass: 'number',
isSearch: true
},
{
title: '短信 API 的账号',
field: 'apiKey'
},
{
title: '短信 API 的密钥',
field: 'apiSecret'
},
{
title: '短信发送回调 URL',
field: 'callbackUrl'
},
{
title: t('common.createTime'),
field: 'createTime',
formatter: 'formatDate',
isForm: false,
search: {
show: true,
itemRender: {
name: 'XDataTimePicker'
}
}
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -44,12 +44,13 @@ const [registerTable, { exportList }] = useXTable({
// //
const dialogVisible = ref(false) // const dialogVisible = ref(false) //
const dialogTitle = ref('edit') // const dialogTitle = ref(t('action.detail')) //
const actionType = ref('') // const actionType = ref('') //
// ========== ========== // ========== ==========
const detailData = ref() // Ref const detailData = ref() // Ref
const handleDetail = (row: SmsLoglApi.SmsLogVO) => { const handleDetail = (row: SmsLoglApi.SmsLogVO) => {
// //
actionType.value = 'detail'
detailData.value = row detailData.value = row
dialogVisible.value = true dialogVisible.value = true
} }

View File

@ -1,6 +1,9 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
const authorizedGrantOptions = getStrDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE)
// CrudSchema // CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({ const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id', primaryKey: 'id',
@ -25,9 +28,17 @@ const crudSchemas = reactive<VxeCrudSchema>({
{ {
title: '短信渠道', title: '短信渠道',
field: 'channelId', field: 'channelId',
dictType: DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, // dictType: DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE,
dictClass: 'number', // dictClass: 'number',
isSearch: true isSearch: true,
// table: {
// component: 'Select',
componentProps: {
options: authorizedGrantOptions
// multiple: false,
// filterable: true
}
// }
}, },
{ {
title: '发送状态', title: '发送状态',

View File

@ -1,6 +1,19 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
import * as smsApi from '@/api/system/sms/smsChannel'
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
const tenantPackageOption = []
const getTenantPackageOptions = async () => {
const res = await smsApi.getSimpleSmsChannels()
console.log(res, 'resresres')
res.forEach((tenantPackage: TenantPackageVO) => {
tenantPackageOption.push({
key: tenantPackage.id,
value: tenantPackage.id,
label: tenantPackage.signature
})
})
}
getTenantPackageOptions()
// 表单校验 // 表单校验
export const rules = reactive({ export const rules = reactive({
type: [required], type: [required],
@ -20,6 +33,19 @@ const crudSchemas = reactive<VxeCrudSchema>({
action: true, action: true,
actionWidth: '280', actionWidth: '280',
columns: [ columns: [
{
title: '短信渠道编码',
field: 'channelId',
isSearch: false,
isForm: true,
isTable: false,
form: {
component: 'Select',
componentProps: {
options: tenantPackageOption
}
}
},
{ {
title: '模板编码', title: '模板编码',
field: 'code', field: 'code',

View File

@ -15,54 +15,54 @@
<XTextButton preIcon="ep:delete" :title="t('action.del')" @click="deleteData(row.id)" /> <XTextButton preIcon="ep:delete" :title="t('action.del')" @click="deleteData(row.id)" />
</template> </template>
</XTable> </XTable>
</ContentWrap> <XModal v-model="dialogVisible" :title="dialogTitle">
<XModal v-model="dialogVisible" :title="dialogTitle"> <!-- 对话框(添加 / 修改) -->
<!-- 对话框(添加 / 修改) --> <Form
<Form
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
ref="formRef"
>
<template #menuIds>
<el-card>
<template #header>
<div class="card-header">
全选/全不选:
<el-switch
v-model="treeNodeAll"
inline-prompt
active-text="是"
inactive-text="否"
@change="handleCheckedTreeNodeAll()"
/>
</div>
</template>
<el-tree
ref="treeRef"
node-key="id"
show-checkbox
:props="defaultProps"
:data="menuOptions"
empty-text="加载中,请稍后"
/>
</el-card>
</template>
</Form>
<!-- 操作按钮 -->
<template #footer>
<!-- 按钮保存 -->
<XButton
v-if="['create', 'update'].includes(actionType)" v-if="['create', 'update'].includes(actionType)"
type="primary" :schema="allSchemas.formSchema"
:title="t('action.save')" :rules="rules"
:loading="loading" ref="formRef"
@click="submitForm()" >
/> <template #menuIds>
<!-- 按钮关闭 --> <el-card class="cardHeight">
<XButton :loading="loading" :title="t('dialog.close')" @click="dialogVisible = false" /> <template #header>
</template> <div class="card-header">
</XModal> 全选/全不选:
<el-switch
v-model="treeNodeAll"
inline-prompt
active-text="是"
inactive-text="否"
@change="handleCheckedTreeNodeAll()"
/>
</div>
</template>
<el-tree
ref="treeRef"
node-key="id"
show-checkbox
:props="defaultProps"
:data="menuOptions"
empty-text="加载中,请稍候"
/>
</el-card>
</template>
</Form>
<!-- 操作按钮 -->
<template #footer>
<!-- 按钮保存 -->
<XButton
v-if="['create', 'update'].includes(actionType)"
type="primary"
:title="t('action.save')"
:loading="loading"
@click="submitForm()"
/>
<!-- 按钮关闭 -->
<XButton :loading="loading" :title="t('dialog.close')" @click="dialogVisible = false" />
</template>
</XModal>
</ContentWrap>
</template> </template>
<script setup lang="ts" name="TenantPackage"> <script setup lang="ts" name="TenantPackage">
import { handleTree, defaultProps } from '@/utils/tree' import { handleTree, defaultProps } from '@/utils/tree'
@ -179,7 +179,7 @@ onMounted(async () => {
// getList() // getList()
</script> </script>
<style scoped> <style scoped>
.el-card { .cardHeight {
width: 100%; width: 100%;
max-height: 400px; max-height: 400px;
overflow-y: scroll; overflow-y: scroll;

View File

@ -1,6 +1,18 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
// 国际化 // 国际化
const { t } = useI18n() const { t } = useI18n()
const validateMobile = (rule: any, value: any, callback: any) => {
const reg = /^1(3[0-9]|4[01456879]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\d{8}$/
if (value === '') {
callback(new Error('请输入联系手机'))
} else {
if (!reg.test(value)) {
callback(new Error('请输入正确的手机号'))
} else {
callback()
}
}
}
// 表单校验 // 表单校验
export const rules = reactive({ export const rules = reactive({
username: [required], username: [required],
@ -17,12 +29,13 @@ export const rules = reactive({
], ],
status: [required], status: [required],
mobile: [ mobile: [
required,
{ {
required: true,
len: 11, len: 11,
trigger: 'blur', trigger: 'blur',
message: '请输入正确的手机号码' message: '请输入正确的手机号码'
} },
{ validator: validateMobile, trigger: 'blur' }
] ]
}) })
// crudSchemas // crudSchemas
@ -47,6 +60,13 @@ const crudSchemas = reactive<VxeCrudSchema>({
component: 'InputPassword' component: 'InputPassword'
} }
}, },
{
title: '用户' + t('profile.user.sex'),
field: 'sex',
dictType: DICT_TYPE.SYSTEM_USER_SEX,
dictClass: 'number',
table: { show: false }
},
{ {
title: '用户昵称', title: '用户昵称',
field: 'nickname' field: 'nickname'