!45 重构:短信模板和短信日志vue2改vue3

Merge pull request !45 from puhui999/master
pull/52/head
芋道源码 2023-03-23 15:56:36 +00:00 committed by Gitee
commit 2a3fe7d2bc
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
26 changed files with 1211 additions and 535 deletions

3
.gitignore vendored
View File

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

View File

@ -24,41 +24,41 @@ export interface SensitiveWordExportReqVO {
}
// 查询敏感词列表
export const getSensitiveWordPageApi = (params: SensitiveWordPageReqVO) => {
export const getSensitiveWordPage = (params: SensitiveWordPageReqVO) => {
return request.get({ url: '/system/sensitive-word/page', params })
}
// 查询敏感词详情
export const getSensitiveWordApi = (id: number) => {
export const getSensitiveWord = (id: number) => {
return request.get({ url: '/system/sensitive-word/get?id=' + id })
}
// 新增敏感词
export const createSensitiveWordApi = (data: SensitiveWordVO) => {
export const createSensitiveWord = (data: SensitiveWordVO) => {
return request.post({ url: '/system/sensitive-word/create', data })
}
// 修改敏感词
export const updateSensitiveWordApi = (data: SensitiveWordVO) => {
export const updateSensitiveWord = (data: SensitiveWordVO) => {
return request.put({ url: '/system/sensitive-word/update', data })
}
// 删除敏感词
export const deleteSensitiveWordApi = (id: number) => {
export const deleteSensitiveWord = (id: number) => {
return request.delete({ url: '/system/sensitive-word/delete?id=' + id })
}
// 导出敏感词
export const exportSensitiveWordApi = (params: SensitiveWordExportReqVO) => {
export const exportSensitiveWord = (params: SensitiveWordExportReqVO) => {
return request.download({ url: '/system/sensitive-word/export-excel', params })
}
// 获取所有敏感词的标签数组
export const getSensitiveWordTagsApi = () => {
export const getSensitiveWordTags = () => {
return request.get({ url: '/system/sensitive-word/get-tags' })
}
// 获得文本所包含的不合法的敏感词数组
export const validateTextApi = (id: number) => {
export const validateText = (id: number) => {
return request.get({ url: '/system/sensitive-word/validate-text?' + id })
}

View File

@ -12,6 +12,12 @@ export interface SmsChannelVO {
createTime: Date
}
export interface SmsChannelListVO {
id: number
code: string
signature: string
}
export interface SmsChannelPageReqVO extends PageParam {
signature?: string
code?: string

View File

@ -1,39 +1,40 @@
import request from '@/config/axios'
export interface SmsLogVO {
id: number
channelId: number
id: number | null
channelId: number | null
channelCode: string
templateId: number
templateId: number | null
templateCode: string
templateType: number
templateType: number | null
templateContent: string
templateParams: Map<string, object>
templateParams: Map<string, object> | null
apiTemplateId: string
mobile: string
userId: number
userType: number
sendStatus: number
sendTime: Date
sendCode: number
userId: number | null
userType: number | null
sendStatus: number | null
sendTime: Date | null
sendCode: number | null
sendMsg: string
apiSendCode: string
apiSendMsg: string
apiRequestId: string
apiSerialNo: string
receiveStatus: number
receiveTime: Date
receiveStatus: number | null
receiveTime: Date | null
apiReceiveCode: string
apiReceiveMsg: string
createTime: Date
createTime: Date | null
}
export interface SmsLogPageReqVO extends PageParam {
channelId?: number
templateId?: number
channelId?: number | null
templateId?: number | null
mobile?: string
sendStatus?: number
sendStatus?: number | null
sendTime?: Date[]
receiveStatus?: number
receiveStatus?: number | null
receiveTime?: Date[]
}
export interface SmsLogExportReqVO {

View File

@ -1,18 +1,18 @@
import request from '@/config/axios'
export interface SmsTemplateVO {
id: number
type: number
status: number
id: number | null
type: number | null
status: number | null
code: string
name: string
content: string
remark: string
apiTemplateId: string
channelId: number
channelCode: string
params: string[]
createTime: Date
channelId: number | null
channelCode?: string
params?: string[]
createTime?: Date
}
export interface SendSmsReqVO {
@ -21,13 +21,13 @@ export interface SendSmsReqVO {
templateParams: Map<String, Object>
}
export interface SmsTemplatePageReqVO {
type?: number
status?: number
export interface SmsTemplatePageReqVO extends PageParam {
type?: number | null
status?: number | null
code?: string
content?: string
apiTemplateId?: string
channelId?: number
channelId?: number | null
createTime?: Date[]
}

View File

@ -0,0 +1,9 @@
import RightToolbar from './src/index.vue'
export interface columnsType {
key?: number
label?: string
visible?: boolean
}
export { RightToolbar }

View File

@ -0,0 +1,104 @@
<template>
<div :style="style">
<el-row justify="end">
<el-tooltip
class="item"
effect="dark"
:content="showSearch ? '隐藏搜索' : '显示搜索'"
placement="top"
v-if="search"
>
<el-button circle @click="toggleSearch()">
<Icon icon="ep:search" />
</el-button>
</el-tooltip>
<el-tooltip class="item" effect="dark" content="刷新" placement="top">
<el-button circle @click="refresh()">
<Icon icon="ep:refresh" />
</el-button>
</el-tooltip>
<el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="isColumns">
<el-button circle @click="showColumn()">
<Icon icon="ep:menu" />
</el-button>
</el-tooltip>
</el-row>
<el-dialog :title="title" v-model="open" append-to-body>
<el-transfer
:titles="['显示', '隐藏']"
v-model="value"
:data="columns"
@change="dataChange"
/>
</el-dialog>
</div>
</template>
<script lang="ts" setup name="RightToolbar">
import type { CSSProperties } from 'vue'
import type { columnsType } from '@/components/RightToolbar'
import { propTypes } from '@/utils/propTypes'
//
const value = ref<number[]>([])
//
const title = ref('显示/隐藏')
//
const open = ref(false)
const props = defineProps({
showSearch: propTypes.bool.def(true),
columns: {
type: Array as PropType<columnsType[]>,
default: () => []
},
search: propTypes.bool.def(true),
gutter: propTypes.number.def(10)
})
const isColumns = computed(() => props.columns?.length > 0)
const style = computed((): CSSProperties => {
const ret: CSSProperties = {}
if (props.gutter) {
ret.marginRight = `${props.gutter / 2}px`
}
return ret
})
const emit = defineEmits(['update:showSearch', 'queryTable'])
//
const toggleSearch = () => {
emit('update:showSearch', !props.showSearch)
}
//
const refresh = () => {
emit('queryTable')
}
//
const dataChange = (data: number[]) => {
props.columns.forEach((item) => {
const key: number = item.key!
item.visible = !data.includes(key)
})
}
// dialog
const showColumn = () => {
open.value = true
}
//
const init = () => {
props.columns.forEach((item, index) => {
if (item.visible === false) {
value.value.push(index)
}
})
}
init()
</script>
<style lang="scss" scoped>
:deep(.el-transfer__button) {
border-radius: 50%;
padding: 12px;
display: block;
margin-left: 0px;
}
:deep(.el-transfer__button:first-child) {
margin-bottom: 10px;
}
</style>

View File

@ -9,6 +9,7 @@ import { XButton, XTextButton } from '@/components/XButton'
import { DictTag } from '@/components/DictTag'
import { ContentWrap } from '@/components/ContentWrap'
import { Descriptions } from '@/components/Descriptions'
import { RightToolbar } from '@/components/RightToolbar'
export const setupGlobCom = (app: App<Element>): void => {
app.component('Icon', Icon)
@ -22,4 +23,5 @@ export const setupGlobCom = (app: App<Element>): void => {
app.component('DictTag', DictTag)
app.component('ContentWrap', ContentWrap)
app.component('Descriptions', Descriptions)
app.component('RightToolbar', RightToolbar)
}

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import dayjs from 'dayjs'
import { parseTime } from '@/utils/formatTime'
import * as NotifyMessageApi from '@/api/system/notify/message'
const { push } = useRouter()
@ -57,7 +57,7 @@ onMounted(() => {
{{ item.templateNickname }}{{ item.templateContent }}
</span>
<span class="message-date">
{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }}
{{ parseTime(item.createTime) }}
</span>
</div>
</div>

View File

@ -303,7 +303,14 @@ export default {
dialog: {
dialog: '弹窗',
open: '打开',
close: '关闭'
close: '关闭',
sms: {
template: {
addTitle: '添加短信模板',
updtaeTitle: '修改短信模板',
sendSms: '发送短信'
}
}
},
sys: {
api: {

View File

@ -1,7 +1,5 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
@ -23,7 +21,6 @@ declare module '@vue/runtime-core' {
DictTag: typeof import('./../components/DictTag/src/DictTag.vue')['default']
Echart: typeof import('./../components/Echart/src/Echart.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']
ElButton: typeof import('element-plus/es')['ElButton']
@ -55,7 +52,6 @@ declare module '@vue/runtime-core' {
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
@ -78,8 +74,6 @@ declare module '@vue/runtime-core' {
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
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']
ElTransfer: typeof import('element-plus/es')['ElTransfer']
ElTree: typeof import('element-plus/es')['ElTree']
@ -107,6 +101,7 @@ declare module '@vue/runtime-core' {
ScriptTask: typeof import('./../components/bpmnProcessDesigner/package/penal/task/task-components/ScriptTask.vue')['default']
Search: typeof import('./../components/Search/src/Search.vue')['default']
SignalAndMessage: typeof import('./../components/bpmnProcessDesigner/package/penal/signal-message/SignalAndMessage.vue')['default']
Src: typeof import('./../components/RightToolbar/src/index.vue')['default']
Sticky: typeof import('./../components/Sticky/src/Sticky.vue')['default']
Table: typeof import('./../components/Table/src/Table.vue')['default']
Tooltip: typeof import('./../components/Tooltip/src/Tooltip.vue')['default']

View File

@ -72,5 +72,5 @@ declare global {
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from 'vue'
export type { Component,ComponentPublicInstance,ComputedRef,InjectionKey,PropType,Ref,VNode } from 'vue'
}

View File

@ -69,7 +69,16 @@ export const getDictObj = (dictType: string, value: any) => {
}
})
}
export const getDictLabel = (dictType: string, value: any) => {
const dictOptions: DictDataType[] = getDictOptions(dictType)
const dictLabel = ref('')
dictOptions.forEach((dict: DictDataType) => {
if (dict.value === value) {
dictLabel.value = dict.label
}
})
return dictLabel.value
}
export enum DICT_TYPE {
USER_TYPE = 'user_type',
COMMON_STATUS = 'common_status',

View File

@ -14,6 +14,51 @@ import dayjs from 'dayjs'
export function formatDate(date: Date, format: string): string {
return dayjs(date).format(format)
}
// 日期格式化
export function parseTime(time: any, pattern?: string) {
if (arguments.length === 0 || !time) {
return null
}
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
} else {
if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
time = parseInt(time)
} else if (typeof time === 'string') {
time = time
.replace(new RegExp(/-/gm), '/')
.replace('T', ' ')
.replace(new RegExp(/\.\d{3}/gm), '')
}
if (typeof time === 'number' && time.toString().length === 10) {
time = time * 1000
}
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
let value = formatObj[key]
// Note: getDay() returns 0 on Sunday
if (key === 'a') {
return ['日', '一', '二', '三', '四', '五', '六'][value]
}
if (result.length > 0 && value < 10) {
value = '0' + value
}
return value || 0
})
return time_str
}
/**
*

View File

@ -34,13 +34,13 @@
</li>
<li class="list-group-item">
<Icon icon="ep:calendar" class="mr-5px" />{{ t('profile.user.createTime') }}
<div class="pull-right">{{ dayjs(userInfo?.createTime).format('YYYY-MM-DD') }}</div>
<div class="pull-right">{{ parseTime(userInfo?.createTime) }}</div>
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import dayjs from 'dayjs'
import { parseTime } from '@/utils/formatTime'
import UserAvatar from './UserAvatar.vue'
import { getUserProfileApi, ProfileVO } from '@/api/system/user/profile'

View File

@ -112,13 +112,13 @@
</label>
<label style="font-weight: normal" v-if="item.createTime"></label>
<label style="color: #8a909c; font-weight: normal">
{{ dayjs(item?.createTime).format('YYYY-MM-DD HH:mm:ss') }}
{{ parseTime(item?.createTime) }}
</label>
<label v-if="item.endTime" style="margin-left: 30px; font-weight: normal">
审批时间
</label>
<label v-if="item.endTime" style="color: #8a909c; font-weight: normal">
{{ dayjs(item?.endTime).format('YYYY-MM-DD HH:mm:ss') }}
{{ parseTime(item?.endTime) }}
</label>
<label v-if="item.durationInMillis" style="margin-left: 30px; font-weight: normal">
耗时
@ -192,7 +192,7 @@
</ContentWrap>
</template>
<script setup lang="ts">
import dayjs from 'dayjs'
import { parseTime } from '@/utils/formatTime'
import * as UserApi from '@/api/system/user'
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import * as DefinitionApi from '@/api/bpm/definition'

View File

@ -12,11 +12,7 @@
/>
</template>
<template #beginTime_default="{ row }">
<span>{{
dayjs(row.beginTime).format('YYYY-MM-DD HH:mm:ss') +
' ~ ' +
dayjs(row.endTime).format('YYYY-MM-DD HH:mm:ss')
}}</span>
<span>{{ parseTime(row.beginTime) + ' ~ ' + parseTime(row.endTime) }}</span>
</template>
<template #duration_default="{ row }">
<span>{{ row.duration + ' 毫秒' }}</span>
@ -48,7 +44,7 @@
</XModal>
</template>
<script setup lang="ts" name="JobLog">
import dayjs from 'dayjs'
import { parseTime } from '@/utils/formatTime'
import * as JobLogApi from '@/api/infra/jobLog'
import { allSchemas } from './jobLog.data'

View File

@ -44,7 +44,7 @@
<li v-for="item in getList" class="mt-2" :key="item.time">
<div class="flex items-center">
<span class="mr-2 text-primary font-medium">收到消息:</span>
<span>{{ dayjs(item.time).format('YYYY-MM-DD HH:mm:ss') }}</span>
<span>{{ parseTime(item.time) }}</span>
</div>
<div>
{{ item.res }}
@ -56,7 +56,7 @@
</div>
</template>
<script setup lang="ts">
import dayjs from 'dayjs'
import { parseTime } from '@/utils/formatTime'
import { useUserStore } from '@/store/modules/user'
import { useWebSocket } from '@vueuse/core'

View File

@ -45,7 +45,7 @@
</template>
</Dialog>
</template>
<script setup lang="ts">
<script setup lang="ts" name="SensitiveWordForm">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as SensitiveWordApi from '@/api/system/sensitiveWord'
import { CommonStatusEnum } from '@/utils/constants'
@ -81,7 +81,7 @@ const openModal = async (type: string, id?: number) => {
if (id) {
formLoading.value = true
try {
formData.value = await SensitiveWordApi.getSensitiveWordApi(id)
formData.value = await SensitiveWordApi.getSensitiveWord(id)
console.log(formData.value)
} finally {
formLoading.value = false
@ -102,10 +102,10 @@ const submitForm = async () => {
try {
const data = formData.value as unknown as SensitiveWordApi.SensitiveWordVO
if (formType.value === 'create') {
await SensitiveWordApi.createSensitiveWordApi(data) // TODO @blue-syd API
await SensitiveWordApi.createSensitiveWord(data) // TODO @blue-syd API
message.success(t('common.createSuccess'))
} else {
await SensitiveWordApi.updateSensitiveWordApi(data) // TODO @blue-syd API
await SensitiveWordApi.updateSensitiveWord(data) // TODO @blue-syd API
message.success(t('common.updateSuccess'))
}
modelVisible.value = false

View File

@ -126,14 +126,14 @@
</content-wrap>
<!-- 表单弹窗添加/修改 -->
<config-form ref="modalRef" @success="getList" />
<SensitiveWordForm ref="modalRef" @success="getList" />
</template>
<script setup lang="ts" name="SensitiveWord">
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as SensitiveWordApi from '@/api/system/sensitiveWord'
import ConfigForm from './form.vue' // TODO @blue-syd
import SensitiveWordForm from './form.vue' // TODO @blue-syd
const message = useMessage() //
const { t } = useI18n() //
@ -156,7 +156,7 @@ const tags = ref([])
const getList = async () => {
loading.value = true
try {
const data = await SensitiveWordApi.getSensitiveWordPageApi(queryParams) // TODO @blue-syd API
const data = await SensitiveWordApi.getSensitiveWordPage(queryParams) // TODO @blue-syd API
list.value = data.list
total.value = data.total
} finally {
@ -190,7 +190,7 @@ const handleDelete = async (id: number) => {
//
await message.delConfirm()
//
await SensitiveWordApi.deleteSensitiveWordApi(id)
await SensitiveWordApi.deleteSensitiveWord(id)
message.success(t('common.delSuccess'))
//
await getList()
@ -204,7 +204,7 @@ const handleExport = async () => {
await message.exportConfirm()
//
exportLoading.value = true
const data = await SensitiveWordApi.exportSensitiveWordApi(queryParams) // TODO @blue-syd API
const data = await SensitiveWordApi.exportSensitiveWord(queryParams) // TODO @blue-syd API
download.excel(data, '敏感词.xls')
} catch {
} finally {
@ -214,7 +214,7 @@ const handleExport = async () => {
/** 获得 Tag 标签列表 */
const getTags = async () => {
tags.value = await SensitiveWordApi.getSensitiveWordTagsApi() // TODO @blue-syd API
tags.value = await SensitiveWordApi.getSensitiveWordTags() // TODO @blue-syd API
}
/** 初始化 **/

View File

@ -1,57 +1,382 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<!-- 操作导出 -->
<template #toolbar_buttons>
<XButton
type="warning"
preIcon="ep:download"
:title="t('action.export')"
@click="exportList('短信日志.xls')"
<content-wrap>
<!-- 搜索工作栏 -->
<el-form
:model="queryParams"
ref="queryForm"
:inline="true"
v-show="showSearch"
label-width="100px"
>
<el-form-item label="手机号" prop="mobile">
<el-input
v-model="queryParams.mobile"
placeholder="请输入手机号"
clearable
@keyup.enter="handleQuery"
/>
</template>
<template #actionbtns_default="{ row }">
<XTextButton preIcon="ep:view" :title="t('action.detail')" @click="handleDetail(row)" />
</template>
</XTable>
</ContentWrap>
<XModal id="smsLog" v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(详情) -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailData"
</el-form-item>
<el-form-item label="短信渠道" prop="channelId">
<el-select v-model="queryParams.channelId" placeholder="请选择短信渠道" clearable>
<el-option
v-for="channel in channelOptions"
:key="channel.id"
:value="channel.id"
:label="
channel.signature + optionLabel(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, channel.code)
"
/>
</el-select>
</el-form-item>
<el-form-item label="模板编号" prop="templateId">
<el-input
v-model="queryParams.templateId"
placeholder="请输入模板编号"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="发送状态" prop="sendStatus">
<el-select v-model="queryParams.sendStatus" placeholder="请选择发送状态" clearable>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_SMS_SEND_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="发送时间" prop="sendTime">
<el-date-picker
v-model="queryParams.sendTime"
style="width: 240px"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<el-form-item label="接收状态" prop="receiveStatus">
<el-select v-model="queryParams.receiveStatus" placeholder="请选择接收状态" clearable>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="接收时间" prop="receiveTime">
<el-date-picker
v-model="queryParams.receiveTime"
style="width: 240px"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery"
><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button
>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row class="mb-10px">
<el-col :span="12">
<el-row :gutter="10">
<el-col :span="1.5">
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['system:sms-log:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-col>
</el-row>
</el-col>
<el-col :span="12">
<right-toolbar v-model:showSearch="showSearch" @query-table="getList" />
</el-col>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="smsLoglist">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="手机号" align="center" prop="mobile" width="120">
<template #default="scope">
<div>{{ scope.row.mobile }}</div>
<div v-if="scope.row.userType && scope.row.userId">
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" />{{
'(' + scope.row.userId + ')'
}}
</div>
</template>
</el-table-column>
<el-table-column label="短信内容" align="center" prop="templateContent" width="300" />
<el-table-column label="发送状态" align="center" width="180">
<template #default="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_SMS_SEND_STATUS" :value="scope.row.sendStatus" />
<div>{{ parseTime(scope.row.sendTime) }}</div>
</template>
</el-table-column>
<el-table-column label="接收状态" align="center" width="180">
<template #default="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS" :value="scope.row.receiveStatus" />
<div>{{ parseTime(scope.row.receiveTime) }}</div>
</template>
</el-table-column>
<el-table-column label="短信渠道" align="center" width="120">
<template #default="scope">
<div>{{ formatChannelSignature(scope.row.channelId) }}</div>
<dict-tag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="scope.row.channelCode" />
</template>
</el-table-column>
<el-table-column label="模板编号" align="center" prop="templateId" />
<el-table-column label="短信类型" align="center" prop="templateType">
<template #default="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE" :value="scope.row.templateType" />
</template>
</el-table-column>
<el-table-column
label="操作"
align="center"
fixed="right"
class-name="small-padding fixed-width"
>
<template #default="scope">
<el-button
link
type="primary"
@click="handleView(scope.row)"
v-hasPermi="['system:sms-log:query']"
><Icon icon="ep:view" class="mr-3px" />详情</el-button
>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 操作按钮 -->
<template #footer>
<XButton :title="t('dialog.close')" @click="dialogVisible = false" />
</template>
</XModal>
<!-- 短信日志详细 -->
<Dialog title="短信日志详情" v-model="open">
<el-descriptions border :column="1">
<el-descriptions-item label-align="right" width="50px" label="日志主键:">
{{ formData.id }}
</el-descriptions-item>
<el-descriptions-item label-align="right" width="50px" label="短信渠道:">
{{ formatChannelSignature(formData.channelId) }}
<dict-tag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="formData.channelCode" />
</el-descriptions-item>
<el-descriptions-item label-align="right" width="50px" label="短信模板:">
{{ formData.templateId }} | {{ formData.templateCode }}
<dict-tag :type="DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE" :value="formData.templateType" />
</el-descriptions-item>
<el-descriptions-item label-align="right" width="50px" label="API 的模板编号:">
{{ formData.apiTemplateId }}
</el-descriptions-item>
<el-descriptions-item label-align="right" width="50px" label="用户信息:">
{{ formData.mobile }}
<span v-if="formData.userType && formData.userId">
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="formData.userType" />
({{ formData.userId }})
</span>
</el-descriptions-item>
<el-descriptions-item label-align="right" width="50px" label="短信内容:">
{{ formData.templateContent }}
</el-descriptions-item>
<el-descriptions-item label-align="right" width="50px" label="短信参数:">
{{ formData.templateParams }}
</el-descriptions-item>
<el-descriptions-item label-align="right" width="50px" label="创建时间:">
{{ parseTime(formData.createTime) }}
</el-descriptions-item>
<el-descriptions-item label-align="right" width="50px" label="发送状态:">
<dict-tag :type="DICT_TYPE.SYSTEM_SMS_SEND_STATUS" :value="formData.sendStatus" />
</el-descriptions-item>
<el-descriptions-item label-align="right" width="50px" label="发送时间:">
{{ parseTime(formData.sendTime) }}
</el-descriptions-item>
<el-descriptions-item label-align="right" width="50px" label="发送结果:">
{{ formData.sendCode }} | {{ formData.sendMsg }}
</el-descriptions-item>
<el-descriptions-item label-align="right" width="50px" label="API 发送结果:">
{{ formData.apiSendCode }} | {{ formData.apiSendMsg }}
</el-descriptions-item>
<el-descriptions-item label-align="right" width="50px" label="API 短信编号:">
{{ formData.apiSerialNo }}
</el-descriptions-item>
<el-descriptions-item label-align="right" width="50px" label="API 请求编号:">
{{ formData.apiRequestId }}
</el-descriptions-item>
<el-descriptions-item label-align="right" width="50px" label="接收状态:">
<dict-tag :type="DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS" :value="formData.receiveStatus" />
</el-descriptions-item>
<el-descriptions-item label-align="right" width="50px" label="接收时间:">
{{ parseTime(formData.receiveTime) }}
</el-descriptions-item>
<el-descriptions-item label-align="right" width="50px" label="API 接收结果:">
{{ formData.apiReceiveCode }} | {{ formData.apiReceiveMsg }}
</el-descriptions-item>
</el-descriptions>
<template #footer>
<el-button @click="open = false"> </el-button>
</template>
</Dialog>
</content-wrap>
</template>
<script setup lang="ts" name="SmsLog">
import { allSchemas } from './sms.log.data'
import * as SmsLoglApi from '@/api/system/sms/smsLog'
const { t } = useI18n() //
<script setup lang="ts" name="smsLog">
import { DICT_TYPE, getDictOptions, getDictLabel } from '@/utils/dict'
import download from '@/utils/download'
import { parseTime } from '@/utils/formatTime'
import * as SmsChannelApi from '@/api/system/sms/smsChannel'
import * as SmsLogApi from '@/api/system/sms/smsLog'
//
const [registerTable, { exportList }] = useXTable({
allSchemas: allSchemas,
getListApi: SmsLoglApi.getSmsLogPageApi,
exportListApi: SmsLoglApi.exportSmsLogApi
const message = useMessage() //
// ================= ================
const showSearch = ref(true) //
const queryForm = ref() // queryFormRef
//
const queryParams = ref<SmsLogApi.SmsLogPageReqVO>({
pageNo: 1,
pageSize: 10,
channelId: null,
templateId: null,
mobile: '',
sendStatus: null,
receiveStatus: null,
sendTime: [],
receiveTime: []
})
//
const dialogVisible = ref(false) //
const dialogTitle = ref(t('action.detail')) //
const actionType = ref('') //
// ========== ==========
const detailData = ref() // Ref
const handleDetail = (row: SmsLoglApi.SmsLogVO) => {
//
actionType.value = 'detail'
detailData.value = row
dialogVisible.value = true
//
const channelOptions = ref<SmsChannelApi.SmsChannelListVO[]>([])
onMounted(() => {
SmsChannelApi.getSimpleSmsChannels().then((res) => {
channelOptions.value = res
})
})
const optionLabel = computed(
() => (type: string, code: string) => `${getDictLabel(type, code)}`
)
/** 格式化短信渠道 */
const formatChannelSignature = (channelId: number | null) => {
channelOptions.value.forEach((item) => {
if (item.id === channelId) {
return item.signature
}
})
return '找不到签名:' + channelId
}
// ================= ================
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
resetForm()
handleQuery()
}
/** 重置搜索表单 */
const resetForm = () => {
queryParams.value = {
pageNo: 1,
pageSize: 10,
channelId: null,
templateId: null,
mobile: '',
sendStatus: null,
receiveStatus: null,
sendTime: [],
receiveTime: []
}
queryForm.value?.resetFields()
}
//
const exportLoading = ref(false)
/** 导出按钮操作 */
const handleExport = () => {
//
let params = queryParams.value as SmsLogApi.SmsLogExportReqVO
//
message
.confirm('是否确认导出所有短信日志数据项?')
.then(() => {
exportLoading.value = true
return SmsLogApi.exportSmsLogApi(params)
})
.then((response) => {
download.excel(response, '短信日志.xls')
exportLoading.value = false
})
.catch(() => {})
}
// ================== ====================
//
const total = ref(0)
//
const smsLoglist = ref([])
const loading = ref(false)
/** 查询列表 */
const getList = () => {
loading.value = true
//
SmsLogApi.getSmsLogPageApi(queryParams.value).then((response) => {
smsLoglist.value = response.list
total.value = response.total
loading.value = false
})
}
// ================== ====================
const open = ref(false)
const formData = ref<SmsLogApi.SmsLogVO>({
id: null,
channelId: null,
channelCode: '',
templateId: null,
templateCode: '',
templateType: null,
templateContent: '',
templateParams: null,
apiTemplateId: '',
mobile: '',
userId: null,
userType: null,
sendStatus: null,
sendTime: null,
sendCode: null,
sendMsg: '',
apiSendCode: '',
apiSendMsg: '',
apiRequestId: '',
apiSerialNo: '',
receiveStatus: null,
receiveTime: null,
apiReceiveCode: '',
apiReceiveMsg: '',
createTime: null
})
/** 详细按钮操作 */
const handleView = (row: SmsLogApi.SmsLogVO) => {
formData.value = row
open.value = true
}
getList()
</script>

View File

@ -1,93 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
const { t } = useI18n() // 国际化
const authorizedGrantOptions = getStrDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE)
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: 'id',
primaryTitle: '日志编号',
action: true,
columns: [
{
title: '手机号',
field: 'mobile',
isSearch: true
},
{
title: '短信内容',
field: 'templateContent'
},
{
title: '模板编号',
field: 'templateId',
isSearch: true
},
{
title: '短信渠道',
field: 'channelId',
// dictType: DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE,
// dictClass: 'number',
isSearch: true,
// table: {
// component: 'Select',
componentProps: {
options: authorizedGrantOptions
// multiple: false,
// filterable: true
}
// }
},
{
title: '发送状态',
field: 'sendStatus',
dictType: DICT_TYPE.SYSTEM_SMS_SEND_STATUS,
dictClass: 'number',
isSearch: true
},
{
title: '发送时间',
field: 'sendTime',
formatter: 'formatDate',
search: {
show: true,
itemRender: {
name: 'XDataTimePicker'
}
}
},
{
title: '短信类型',
field: 'templateType',
dictType: DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE,
dictClass: 'number',
isSearch: true
},
{
title: '接收状态',
field: 'receiveStatus',
dictType: DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS,
dictClass: 'number',
isSearch: true
},
{
title: '接收时间',
field: 'receiveTime',
formatter: 'formatDate',
search: {
show: true,
itemRender: {
name: 'XDataTimePicker'
}
}
},
{
title: t('common.createTime'),
field: 'createTime',
formatter: 'formatDate'
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -0,0 +1,267 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible">
<!-- 修改/新增 -->
<el-form
v-if="['template.addTitle', 'template.updtaeTitle'].includes(formType)"
ref="formRef"
:model="formData"
:rules="formRules"
label-width="140px"
>
<el-form-item label="短信渠道编号" prop="channelId">
<el-select v-model="formData.channelId" placeholder="请选择短信渠道编号">
<el-option
v-for="channel in channelOptions"
:key="channel.id"
:value="channel.id"
:label="
channel.signature + optionLabel(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, channel.code)
"
/>
</el-select>
</el-form-item>
<el-form-item label="短信类型" prop="type">
<el-select v-model="formData.type" placeholder="请选择短信类型">
<el-option
v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE)"
:key="dict.value"
:label="dict.label"
:value="parseInt(dict.value)"
/>
</el-select>
</el-form-item>
<el-form-item label="模板编号" prop="code">
<el-input v-model="formData.code" placeholder="请输入模板编号" />
</el-form-item>
<el-form-item label="模板名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入模板名称" />
</el-form-item>
<el-form-item label="模板内容" prop="content">
<el-input type="textarea" v-model="formData.content" placeholder="请输入模板内容" />
</el-form-item>
<el-form-item label="开启状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="parseInt(dict.value)"
>{{ dict.label }}</el-radio
>
</el-radio-group>
</el-form-item>
<el-form-item label="短信 API 模板编号" prop="apiTemplateId">
<el-input v-model="formData.apiTemplateId" placeholder="请输入短信 API 的模板编号" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<!-- 短信测试 -->
<el-form
v-if="formType === 'template.sendSms'"
ref="sendSmsFormRef"
:model="sendSmsForm"
:rules="sendSmsRules"
>
<el-form-item label="模板内容" prop="content">
<el-input
v-model="sendSmsForm.content"
type="textarea"
placeholder="请输入模板内容"
readonly
/>
</el-form-item>
<el-form-item label="手机号" prop="mobile">
<el-input v-model="sendSmsForm.mobile" placeholder="请输入手机号" />
</el-form-item>
<el-form-item
v-for="param in sendSmsForm.params"
:key="param"
:label="'参数 {' + param + '}'"
:prop="'templateParams.' + param"
>
<el-input
v-model="sendSmsForm.templateParams[param]"
:placeholder="'请输入 ' + param + ' 参数'"
/>
</el-form-item>
</el-form>
<template #footer>
<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" name="SmsTemplateFrom">
import { DICT_TYPE, getDictOptions, getDictLabel } from '@/utils/dict'
import * as templateApi from '@/api/system/sms/smsTemplate'
import * as SmsChannelApi from '@/api/system/sms/smsChannel'
const { t } = useI18n() //
const message = useMessage() //
defineProps({
channelOptions: {
type: Array as PropType<SmsChannelApi.SmsChannelListVO[]>,
define: () => {}
}
})
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') //
const formData = ref<templateApi.SmsTemplateVO>({
id: null,
type: null,
status: null,
code: '',
name: '',
content: '',
remark: '',
apiTemplateId: '',
channelId: null
})
const formRules = reactive({
type: [{ required: true, message: '短信类型不能为空', trigger: 'change' }],
status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }],
code: [{ required: true, message: '模板编码不能为空', trigger: 'blur' }],
name: [{ required: true, message: '模板名称不能为空', trigger: 'blur' }],
content: [{ required: true, message: '模板内容不能为空', trigger: 'blur' }],
apiTemplateId: [{ required: true, message: '短信 API 的模板编号不能为空', trigger: 'blur' }],
channelId: [{ required: true, message: '短信渠道编号不能为空', trigger: 'change' }]
})
const formRef = ref() // Ref
const optionLabel = computed(
() => (type: string, code: string) => `${getDictLabel(type, code)}`
)
//
const sendSmsForm = ref({
content: '',
params: {},
mobile: '',
templateCode: '',
templateParams: {}
})
const sendSmsRules = reactive({
mobile: [{ required: true, message: '手机不能为空', trigger: 'blur' }],
templateCode: [{ required: true, message: '模版编码不能为空', trigger: 'blur' }],
templateParams: {}
})
const sendSmsFormRef = ref()
/** 打开弹窗 */
interface openModalOption {
type: string
// id
id?: ''
// row
row?: any
}
const openModal = async (option: openModalOption) => {
modelVisible.value = true
modelTitle.value = t('dialog.sms.' + option.type)
formType.value = option.type
resetForm()
resetSendSms()
//
if (option.row) {
sendSmsForm.value.content = option.row.content
sendSmsForm.value.params = option.row.params
sendSmsForm.value.templateCode = option.row.code
sendSmsForm.value.templateParams = option.row.params.reduce(function (obj, item) {
obj[item] = undefined
return obj
}, {})
sendSmsRules.templateParams = option.row.params.reduce(function (obj, item) {
obj[item] = { required: true, message: '参数 ' + item + ' 不能为空', trigger: 'change' }
return obj
}, {})
}
//
if (option.id) {
formLoading.value = true
try {
formData.value = await templateApi.getSmsTemplateApi(option.id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ openModal }) // openModal
const emit = defineEmits(['success']) // success
/** 提交表单 */
const submitForm = async () => {
formLoading.value = true
//
if (['template.addTitle', 'template.updtaeTitle'].includes(formType.value)) {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
try {
const data = formData.value as templateApi.SmsTemplateVO
if (formType.value === 'template.addTitle') {
await templateApi.createSmsTemplateApi(data)
message.success(t('common.createSuccess'))
} else {
await templateApi.updateSmsTemplateApi(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
if (formType.value === 'template.sendSms') {
sendSmsTest()
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: null,
type: null,
status: null,
code: '',
name: '',
content: '',
remark: '',
apiTemplateId: '',
channelId: null
}
formRef.value?.resetFields()
}
/** 重置发送短信的表单 */
const resetSendSms = () => {
// row
sendSmsForm.value = {
content: '',
params: {},
mobile: '',
templateCode: '',
templateParams: {}
}
sendSmsFormRef.value?.resetFields()
}
/** 发送短信 */
const sendSmsTest = async () => {
const data: templateApi.SendSmsReqVO = {
mobile: sendSmsForm.value.mobile,
templateCode: sendSmsForm.value.templateCode,
templateParams: sendSmsForm.value.templateParams as unknown as Map<string, Object>
}
const res = await templateApi.sendSmsApi(data)
if (res) {
message.success('提交发送成功!发送结果,见发送日志编号:' + res)
}
formLoading.value = false
modelVisible.value = false
}
</script>

View File

@ -1,232 +1,340 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<!-- 操作新增 -->
<template #toolbar_buttons>
<XButton
type="primary"
preIcon="ep:zoom-in"
:title="t('action.add')"
v-hasPermi="['system:sms-channel:create']"
@click="handleCreate()"
/>
</template>
<template #actionbtns_default="{ row }">
<XTextButton
preIcon="ep:cpu"
:title="t('action.test')"
v-hasPermi="['system:sms-template:send-sms']"
@click="handleSendSms(row)"
/>
<!-- 操作修改 -->
<XTextButton
preIcon="ep:edit"
:title="t('action.edit')"
v-hasPermi="['system:sms-template:update']"
@click="handleUpdate(row.id)"
/>
<!-- 操作详情 -->
<XTextButton
preIcon="ep:view"
:title="t('action.detail')"
v-hasPermi="['system:sms-template:query']"
@click="handleDetail(row.id)"
/>
<!-- 操作删除 -->
<XTextButton
preIcon="ep:delete"
:title="t('action.del')"
v-hasPermi="['system:sms-template:delete']"
@click="deleteData(row.id)"
/>
</template>
</XTable>
</ContentWrap>
<XModal id="smsTemplate" v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(添加 / 修改) -->
<Form
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
ref="formRef"
/>
<!-- 对话框(详情) -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailData"
/>
<!-- 操作按钮 -->
<template #footer>
<!-- 按钮保存 -->
<XButton
v-if="['create', 'update'].includes(actionType)"
type="primary"
:title="t('action.save')"
:loading="actionLoading"
@click="submitForm()"
/>
<!-- 按钮关闭 -->
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
</template>
</XModal>
<XModal id="sendTest" v-model="sendVisible" title="测试">
<el-form :model="sendSmsForm" :rules="sendSmsRules" label-width="200px" label-position="top">
<el-form-item label="模板内容" prop="content">
<content-wrap>
<!-- 搜索工作栏 -->
<el-form
:model="queryParams"
ref="queryForm"
:inline="true"
v-show="showSearch"
label-width="150px"
>
<el-form-item label="短信类型" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择短信类型" clearable>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="开启状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择开启状态" clearable>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="模板编码" prop="code">
<el-input
v-model="sendSmsForm.content"
type="textarea"
placeholder="请输入模板内容"
readonly
v-model="queryParams.code"
placeholder="请输入模板编码"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="手机号" prop="mobile">
<el-input v-model="sendSmsForm.mobile" placeholder="请输入手机号" />
</el-form-item>
<el-form-item
v-for="param in sendSmsForm.params"
:key="param"
:label="'参数 {' + param + '}'"
:prop="'templateParams.' + param"
>
<el-form-item label="短信 API 的模板编号" prop="apiTemplateId">
<el-input
v-model="sendSmsForm.templateParams[param]"
:placeholder="'请输入 ' + param + ' 参数'"
v-model="queryParams.apiTemplateId"
placeholder="请输入短信 API 的模板编号"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="短信渠道" prop="channelId">
<el-select v-model="queryParams.channelId" placeholder="请选择短信渠道" clearable>
<el-option
v-for="channel in channelOptions"
:key="channel.id"
:value="channel.id"
:label="
channel.signature + optionLabel(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, channel.code)
"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
style="width: 240px"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery"
><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button
>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
</el-form-item>
</el-form>
<!-- 操作按钮 -->
<template #footer>
<XButton
type="primary"
:title="t('action.test')"
:loading="actionLoading"
@click="sendSmsTest()"
<!-- 操作工具栏 -->
<el-row class="mb-10px">
<el-col :span="12">
<el-row :gutter="10">
<el-col :span="1.5">
<el-button
type="primary"
plain
@click="handleAdd('template.addTitle')"
v-hasPermi="['system:sms-template:create']"
><Icon icon="ep:plus" class="mr-5px" />新增</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['system:sms-template:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-col>
</el-row>
</el-col>
<el-col :span="12">
<right-toolbar v-model:showSearch="showSearch" @query-table="getList" />
</el-col>
</el-row>
<!-- 列表 -->
<el-table v-loading="loading" :data="templateList" align="center">
<el-table-column
label="模板编码"
align="center"
prop="code"
width="120"
:show-overflow-tooltip="true"
/>
<XButton :title="t('dialog.close')" @click="sendVisible = false" />
</template>
</XModal>
<el-table-column
label="模板名称"
align="center"
prop="name"
width="120"
:show-overflow-tooltip="true"
/>
<el-table-column
label="模板内容"
align="center"
prop="content"
width="200"
:show-overflow-tooltip="true"
/>
<el-table-column label="短信类型" align="center" prop="type">
<template #default="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE" :value="scope.row.type" />
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" width="80">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column
label="短信 API 的模板编号"
align="center"
prop="apiTemplateId"
width="200"
:show-overflow-tooltip="true"
/>
<el-table-column label="短信渠道" align="center" width="120">
<template #default="scope">
<div>{{ formatChannelSignature(scope.row.channelId) }}</div>
<dict-tag :type="DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE" :value="scope.row.channelCode" />
</template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column
label="操作"
align="center"
class-name="small-padding fixed-width"
width="210"
fixed="right"
>
<template #default="scope">
<el-button
link
type="primary"
@click="handleSendSms('template.sendSms', scope.row)"
v-hasPermi="['system:sms-template:send-sms']"
><Icon icon="ep:share" class="mr-3px" />测试</el-button
>
<el-button
link
type="primary"
@click="handleUpdate('template.updtaeTitle', scope.row)"
v-hasPermi="['system:sms-template:update']"
><Icon icon="ep:edit" class="mr-3px" />修改</el-button
>
<el-button
link
type="primary"
@click="handleDelete(scope.row)"
v-hasPermi="['system:sms-template:delete']"
><Icon icon="ep:delete" class="mr-3px" />删除</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>
<!-- 表单弹窗添加/修改 -->
<SmsTemplateFrom ref="modalRef" :channelOptions="channelOptions" @success="getList" />
</template>
<script setup lang="ts" name="SmsTemplate">
import type { FormExpose } from '@/components/Form'
// import
import * as SmsTemplateApi from '@/api/system/sms/smsTemplate'
import { rules, allSchemas } from './sms.template.data'
const { t } = useI18n() //
import { DICT_TYPE, getDictOptions, getDictLabel } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as templateApi from '@/api/system/sms/smsTemplate'
import * as SmsChannelApi from '@/api/system/sms/smsChannel'
import download from '@/utils/download'
import SmsTemplateFrom from './form.vue'
const message = useMessage() //
// const { t } = useI18n() //
//
const [registerTable, { reload, deleteData }] = useXTable({
allSchemas: allSchemas,
getListApi: SmsTemplateApi.getSmsTemplatePageApi,
deleteApi: SmsTemplateApi.deleteSmsTemplateApi
// ref
const modalRef = ref()
//
const loading = ref(true)
//
// const exportLoading = ref(false)
//
const showSearch = ref(true)
// ref
const queryForm = ref()
//
const queryParams = ref<templateApi.SmsTemplatePageReqVO>({
pageNo: 1,
pageSize: 10,
type: null,
status: null,
code: '',
content: '',
apiTemplateId: '',
channelId: null,
createTime: []
})
//
const dialogVisible = ref(false) //
const dialogTitle = ref('edit') //
const actionType = ref('') //
const actionLoading = ref(false) // Loading
const formRef = ref<FormExpose>() // Ref
const detailData = ref() // Ref
//
const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type)
actionType.value = type
dialogVisible.value = true
}
//
const handleCreate = () => {
setDialogTile('create')
}
//
const handleUpdate = async (rowId: number) => {
setDialogTile('update')
//
const res = await SmsTemplateApi.getSmsTemplateApi(rowId)
unref(formRef)?.setValues(res)
}
//
const handleDetail = async (rowId: number) => {
setDialogTile('detail')
//
const res = await SmsTemplateApi.getSmsTemplateApi(rowId)
detailData.value = res
}
//
const submitForm = async () => {
const elForm = unref(formRef)?.getElFormRef()
if (!elForm) return
elForm.validate(async (valid) => {
if (valid) {
actionLoading.value = true
//
try {
const data = unref(formRef)?.formModel as SmsTemplateApi.SmsTemplateVO
if (actionType.value === 'create') {
await SmsTemplateApi.createSmsTemplateApi(data)
message.success(t('common.createSuccess'))
} else {
await SmsTemplateApi.updateSmsTemplateApi(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
} finally {
actionLoading.value = false
//
await reload()
}
}
//
const total = ref(0)
//
const templateList = ref([])
/** 查询列表 */
const getList = () => {
loading.value = true
//
templateApi.getSmsTemplatePageApi(queryParams.value).then((response) => {
templateList.value = response.list
total.value = response.total
loading.value = false
})
}
// ========== ==========
const sendSmsForm = ref({
content: '',
params: {},
mobile: '',
templateCode: '',
templateParams: {}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
resetForm()
handleQuery()
}
/** 重置搜索表单 */
const resetForm = () => {
queryParams.value = {
pageNo: 1,
pageSize: 10,
type: null,
status: null,
code: '',
content: '',
apiTemplateId: '',
channelId: null,
createTime: []
}
queryForm.value?.resetFields()
}
//
const channelOptions = ref<SmsChannelApi.SmsChannelListVO[]>([])
onMounted(() => {
SmsChannelApi.getSimpleSmsChannels().then((res) => {
channelOptions.value = res
})
})
const sendSmsRules = ref({
mobile: [{ required: true, message: '手机不能为空', trigger: 'blur' }],
templateCode: [{ required: true, message: '模版编号不能为空', trigger: 'blur' }],
templateParams: {}
})
const sendVisible = ref(false)
const handleSendSms = (row: any) => {
sendSmsForm.value.content = row.content
sendSmsForm.value.params = row.params
sendSmsForm.value.templateCode = row.code
sendSmsForm.value.templateParams = row.params.reduce(function (obj, item) {
obj[item] = undefined
return obj
}, {})
sendSmsRules.value.templateParams = row.params.reduce(function (obj, item) {
obj[item] = { required: true, message: '参数 ' + item + ' 不能为空', trigger: 'change' }
return obj
}, {})
sendVisible.value = true
const optionLabel = computed(
() => (type: string, code: string) => `${getDictLabel(type, code)}`
)
/** 格式化短信渠道 */
const formatChannelSignature = (channelId: number) => {
channelOptions.value.forEach((item) => {
if (item.id === channelId) {
return item.signature
}
})
return '找不到签名:' + channelId
}
/** 新增按钮操作 */
const handleAdd = (type: string) => {
modalRef.value.openModal({ type })
}
/** 修改按钮操作 */
// const handleUpdate = (row) => {}
const exportLoading = ref(false)
/** 导出按钮操作 */
const handleExport = () => {
//
let params = { ...queryParams.value } as templateApi.SmsTemplateExportReqVO
//
message
.confirm('是否确认导出所有短信模板数据项?', '警告')
.then(() => {
exportLoading.value = true
return templateApi.exportPostApi(params)
})
.then((response) => {
download.excel(response, '短信模板.xls')
exportLoading.value = false
})
.catch(() => {})
}
/** 发送短信按钮 */
const handleSendSms = (type, row: any) => {
modalRef.value.openModal({ type, row })
}
const handleUpdate = (type: string, { id }: { id: number }) => {
modalRef.value.openModal({ type, id })
}
/** 删除按钮操作 */
const handleDelete = ({ id }: { id: number }) => {
message
.confirm('是否确认删除短信模板编号为"' + id + '"的数据项?')
.then(function () {
return templateApi.deleteSmsTemplateApi(id)
})
.then(() => {
getList()
message.success('删除成功')
})
.catch(() => {})
}
const sendSmsTest = async () => {
const data: SmsTemplateApi.SendSmsReqVO = {
mobile: sendSmsForm.value.mobile,
templateCode: sendSmsForm.value.templateCode,
templateParams: sendSmsForm.value.templateParams as unknown as Map<string, Object>
}
const res = await SmsTemplateApi.sendSmsApi(data)
if (res) {
message.success('提交发送成功!发送结果,见发送日志编号:' + res)
}
sendVisible.value = false
}
getList()
</script>

View File

@ -1,107 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
import * as smsApi from '@/api/system/sms/smsChannel'
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({
type: [required],
status: [required],
code: [required],
name: [required],
content: [required],
apiTemplateId: [required],
channelId: [required]
})
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: 'id',
primaryTitle: '模板编号',
action: true,
actionWidth: '280',
columns: [
{
title: '短信渠道编码',
field: 'channelId',
isSearch: false,
isForm: true,
isTable: false,
form: {
component: 'Select',
componentProps: {
options: tenantPackageOption
}
}
},
{
title: '模板编码',
field: 'code',
isSearch: true
},
{
title: '模板名称',
field: 'name',
isSearch: true
},
{
title: '模板内容',
field: 'content'
},
{
title: '短信 API 的模板编号',
field: 'apiTemplateId',
isSearch: true
},
{
title: '短信类型',
field: 'type',
dictType: DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE,
dictClass: 'number',
isSearch: true,
table: {
width: 80
}
},
{
title: t('common.status'),
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
dictClass: 'number',
isSearch: true,
table: {
width: 80
}
},
{
title: t('form.remark'),
field: 'remark',
isTable: false
},
{
title: t('common.createTime'),
field: 'createTime',
formatter: 'formatDate',
isForm: false,
search: {
show: true,
itemRender: {
name: 'XDataTimePicker'
}
}
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -32,6 +32,7 @@
"vite-plugin-svg-icons/client",
"@form-create/element-ui/types"
],
"outDir": "target", // tsconfig.json
"typeRoots": ["./node_modules/@types/", "./types"]
},
"include": [
@ -40,5 +41,5 @@
"src/types/auto-imports.d.ts",
"src/types/auto-components.d.ts"
],
"exclude": ["dist", "node_modules"]
"exclude": ["dist", "target", "node_modules"]
}