Merge remote-tracking branch 'yudao/dev' into contrib
commit
a00c612f36
15
.env.front
15
.env.front
|
@ -17,3 +17,18 @@ VITE_API_URL=/admin-api
|
|||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/
|
||||
|
||||
# 项目本地运行端口号, 与.vscode/launch.json配合
|
||||
VITE_PORT=80
|
||||
|
||||
# 是否删除debugger
|
||||
VITE_DROP_DEBUGGER=false
|
||||
|
||||
# 是否删除console.log
|
||||
VITE_DROP_CONSOLE=false
|
||||
|
||||
# 是否sourcemap
|
||||
VITE_SOURCEMAP=true
|
||||
|
||||
# 验证码的开关
|
||||
VITE_APP_CAPTCHA_ENABLE=false
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "msedge",
|
||||
"request": "launch",
|
||||
"name": "Launch Edge against localhost",
|
||||
"url": "http://localhost",
|
||||
"webRoot": "${workspaceFolder}/src",
|
||||
"sourceMaps": true
|
||||
}
|
||||
]
|
||||
}
|
|
@ -42,7 +42,6 @@
|
|||
| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 4.9.5 |
|
||||
| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.33 |
|
||||
| [vueuse](https://vueuse.org/) | 常用工具集 | 9.13.0 |
|
||||
| [vxe-table](https://vxetable.cn/) | Vue 最强表单 | 4.3.10 |
|
||||
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.2 |
|
||||
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 4.1.6 |
|
||||
| [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 |
|
||||
|
|
|
@ -13,7 +13,7 @@ import Components from 'unplugin-vue-components/vite'
|
|||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||||
import viteCompression from 'vite-plugin-compression'
|
||||
import topLevelAwait from 'vite-plugin-top-level-await'
|
||||
import vueSetupExtend from 'vite-plugin-vue-setup-extend'
|
||||
import vueSetupExtend from 'vite-plugin-vue-setup-extend-plus'
|
||||
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||
|
||||
|
@ -98,7 +98,8 @@ export function createVitePlugins() {
|
|||
deleteOriginFile: false //压缩后是否删除源文件
|
||||
}),
|
||||
ViteEjsPlugin(),
|
||||
topLevelAwait({ // https://juejin.cn/post/7152191742513512485
|
||||
topLevelAwait({
|
||||
// https://juejin.cn/post/7152191742513512485
|
||||
// The export name of top-level await promise for each chunk module
|
||||
promiseExportName: '__tla',
|
||||
// The function to generate import names of top-level await promise in each chunk module
|
||||
|
|
|
@ -18,10 +18,6 @@ const include = [
|
|||
'lodash-es',
|
||||
'nprogress',
|
||||
'animate.css',
|
||||
'vxe-table',
|
||||
'vxe-table/es/style',
|
||||
'vxe-table/lib/locale/lang/zh-CN',
|
||||
'vxe-table/lib/locale/lang/en-US',
|
||||
'web-storage-cache',
|
||||
'@iconify/iconify',
|
||||
'@vueuse/core',
|
||||
|
@ -79,7 +75,8 @@ const include = [
|
|||
'element-plus/es/components/dropdown-item/style/css',
|
||||
'element-plus/es/components/badge/style/css',
|
||||
'element-plus/es/components/breadcrumb/style/css',
|
||||
'element-plus/es/components/breadcrumb-item/style/css'
|
||||
'element-plus/es/components/breadcrumb-item/style/css',
|
||||
'element-plus/es/components/image/style/css'
|
||||
]
|
||||
|
||||
const exclude = ['@iconify/json']
|
||||
|
|
|
@ -68,7 +68,6 @@
|
|||
"vue-router": "^4.1.6",
|
||||
"vue-types": "^5.0.2",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vxe-table": "^4.3.11",
|
||||
"web-storage-cache": "^1.1.1",
|
||||
"xe-utils": "^3.5.7",
|
||||
"xml-js": "^1.6.11"
|
||||
|
@ -126,7 +125,7 @@
|
|||
"vite-plugin-purge-icons": "^0.9.2",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vite-plugin-top-level-await": "^1.3.0",
|
||||
"vite-plugin-vue-setup-extend": "^0.4.0",
|
||||
"vite-plugin-vue-setup-extend-plus": "^0.1.0",
|
||||
"vite-plugin-windicss": "^1.8.10",
|
||||
"vue-tsc": "^1.2.0",
|
||||
"windicss": "^3.5.6"
|
||||
|
|
|
@ -39,9 +39,9 @@ export const getMerchant = (id: number) => {
|
|||
}
|
||||
|
||||
// 根据商户名称搜索商户列表
|
||||
export const getMerchantListByName = (name: string) => {
|
||||
export const getMerchantListByName = (name?: string) => {
|
||||
return request.get({
|
||||
url: '/pay/merchant/list-by-name?id=',
|
||||
url: '/pay/merchant/list-by-name',
|
||||
params: {
|
||||
name: name
|
||||
}
|
||||
|
|
|
@ -88,6 +88,11 @@ export const getOrder = async (id: number) => {
|
|||
return await request.get({ url: '/pay/order/get?id=' + id })
|
||||
}
|
||||
|
||||
// 获得支付订单的明细
|
||||
export const getOrderDetail = async (id: number) => {
|
||||
return await request.get({ url: '/pay/order/get-detail?id=' + id })
|
||||
}
|
||||
|
||||
// 新增支付订单
|
||||
export const createOrder = async (data: OrderVO) => {
|
||||
return await request.post({ url: '/pay/order/create', data })
|
||||
|
|
|
@ -95,7 +95,7 @@ watch(
|
|||
return props.modelValue
|
||||
},
|
||||
() => {
|
||||
if (props.modelValue) {
|
||||
if (props.modelValue && props.modelValue.indexOf(':') >= 0) {
|
||||
currentActiveType.value = props.modelValue.substring(0, props.modelValue.indexOf(':') + 1)
|
||||
icon.value = props.modelValue.substring(props.modelValue.indexOf(':') + 1)
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
import XModal from './src/XModal.vue'
|
||||
|
||||
export { XModal }
|
|
@ -1,44 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
const slots = useSlots()
|
||||
|
||||
const props = defineProps({
|
||||
id: propTypes.string.def('model_1'),
|
||||
modelValue: propTypes.bool.def(false),
|
||||
fullscreen: propTypes.bool.def(false),
|
||||
loading: propTypes.bool.def(false),
|
||||
title: propTypes.string.def('弹窗'),
|
||||
width: propTypes.string.def('40%'),
|
||||
height: propTypes.string,
|
||||
minWidth: propTypes.string.def('460'),
|
||||
minHeight: propTypes.string.def('320'),
|
||||
showFooter: propTypes.bool.def(true),
|
||||
maskClosable: propTypes.bool.def(false),
|
||||
escClosable: propTypes.bool.def(false)
|
||||
})
|
||||
|
||||
const getBindValue = computed(() => {
|
||||
const attrs = useAttrs()
|
||||
const obj = { ...attrs, ...props }
|
||||
return obj
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<vxe-modal v-bind="getBindValue" destroy-on-close show-zoom resize transfer>
|
||||
<template v-if="slots.header" #header>
|
||||
<slot name="header"></slot>
|
||||
</template>
|
||||
<ElScrollbar>
|
||||
<template v-if="slots.default" #default>
|
||||
<slot name="default"></slot>
|
||||
</template>
|
||||
</ElScrollbar>
|
||||
<template v-if="slots.corner" #corner>
|
||||
<slot name="corner"></slot>
|
||||
</template>
|
||||
<template v-if="slots.footer" #footer>
|
||||
<slot name="footer"></slot>
|
||||
</template>
|
||||
</vxe-modal>
|
||||
</template>
|
|
@ -1,3 +0,0 @@
|
|||
import XTable from './src/XTable.vue'
|
||||
|
||||
export { XTable }
|
|
@ -1,442 +0,0 @@
|
|||
<template>
|
||||
<VxeGrid v-bind="getProps" ref="xGrid" :class="`${prefixCls}`" class="xtable-scrollbar">
|
||||
<template #[item]="data" v-for="item in Object.keys($slots)" :key="item">
|
||||
<slot :name="item" v-bind="data || {}"></slot>
|
||||
</template>
|
||||
</VxeGrid>
|
||||
</template>
|
||||
<script setup lang="ts" name="XTable">
|
||||
import { PropType } from 'vue'
|
||||
import { SizeType, VxeGridInstance } from 'vxe-table'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { XTableProps } from './type'
|
||||
import { isBoolean, isFunction } from '@/utils/is'
|
||||
import styleCss from './style/dark.scss?inline'
|
||||
import download from '@/utils/download'
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
const prefixCls = getPrefixCls('x-vxe-table')
|
||||
|
||||
const attrs = useAttrs()
|
||||
const emit = defineEmits(['register'])
|
||||
const removeStyles = () => {
|
||||
const filename = 'cssTheme'
|
||||
//移除引入的文件名
|
||||
const targetelement = 'style'
|
||||
const targetattr = 'id'
|
||||
const allsuspects = document.getElementsByTagName(targetelement)
|
||||
for (let i = allsuspects.length; i >= 0; i--) {
|
||||
if (
|
||||
allsuspects[i] &&
|
||||
allsuspects[i].getAttribute(targetattr) != null &&
|
||||
allsuspects[i].getAttribute(targetattr)?.indexOf(filename) != -1
|
||||
) {
|
||||
console.log(allsuspects[i], 'node')
|
||||
allsuspects[i].parentNode?.removeChild(allsuspects[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
const reImport = () => {
|
||||
const head = document.getElementsByTagName('head')[0]
|
||||
const style = document.createElement('style')
|
||||
style.innerText = styleCss
|
||||
style.id = 'cssTheme'
|
||||
head.appendChild(style)
|
||||
}
|
||||
watch(
|
||||
() => appStore.getIsDark,
|
||||
() => {
|
||||
if (appStore.getIsDark) {
|
||||
reImport()
|
||||
}
|
||||
if (!appStore.getIsDark) {
|
||||
removeStyles()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const currentSize = computed(() => {
|
||||
let resSize: SizeType = 'small'
|
||||
const appsize = appStore.getCurrentSize
|
||||
switch (appsize) {
|
||||
case 'large':
|
||||
resSize = 'medium'
|
||||
break
|
||||
case 'default':
|
||||
resSize = 'small'
|
||||
break
|
||||
case 'small':
|
||||
resSize = 'mini'
|
||||
break
|
||||
}
|
||||
return resSize
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
options: {
|
||||
type: Object as PropType<XTableProps>,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
const innerProps = ref<Partial<XTableProps>>()
|
||||
|
||||
const getProps = computed(() => {
|
||||
const options = innerProps.value || props.options
|
||||
options.size = currentSize as any
|
||||
options.height = 700
|
||||
getOptionInitConfig(options)
|
||||
getColumnsConfig(options)
|
||||
getProxyConfig(options)
|
||||
getPageConfig(options)
|
||||
getToolBarConfig(options)
|
||||
// console.log(options);
|
||||
return {
|
||||
...options,
|
||||
...attrs
|
||||
}
|
||||
})
|
||||
|
||||
const xGrid = ref<VxeGridInstance>() // 列表 Grid Ref
|
||||
|
||||
let proxyForm = false
|
||||
|
||||
const getOptionInitConfig = (options: XTableProps) => {
|
||||
options.size = currentSize as any
|
||||
options.rowConfig = {
|
||||
isCurrent: true, // 当鼠标点击行时,是否要高亮当前行
|
||||
isHover: true // 当鼠标移到行时,是否要高亮当前行
|
||||
}
|
||||
}
|
||||
|
||||
// columns
|
||||
const getColumnsConfig = (options: XTableProps) => {
|
||||
const { allSchemas } = options
|
||||
if (!allSchemas) return
|
||||
if (allSchemas.printSchema) {
|
||||
options.printConfig = {
|
||||
columns: allSchemas.printSchema
|
||||
}
|
||||
}
|
||||
if (allSchemas.formSchema) {
|
||||
proxyForm = true
|
||||
options.formConfig = {
|
||||
enabled: true,
|
||||
titleWidth: 110,
|
||||
titleAlign: 'right',
|
||||
items: allSchemas.searchSchema
|
||||
}
|
||||
}
|
||||
if (allSchemas.tableSchema) {
|
||||
options.columns = allSchemas.tableSchema
|
||||
}
|
||||
}
|
||||
|
||||
// 动态请求
|
||||
const getProxyConfig = (options: XTableProps) => {
|
||||
const { getListApi, proxyConfig, data, isList } = options
|
||||
if (proxyConfig || data) return
|
||||
if (getListApi && isFunction(getListApi)) {
|
||||
if (!isList) {
|
||||
options.proxyConfig = {
|
||||
seq: true, // 启用动态序号代理(分页之后索引自动计算为当前页的起始序号)
|
||||
form: proxyForm, // 启用表单代理,当点击表单提交按钮时会自动触发 reload 行为
|
||||
props: { result: 'list', total: 'total' },
|
||||
ajax: {
|
||||
query: async ({ page, form }) => {
|
||||
let queryParams: any = Object.assign({}, JSON.parse(JSON.stringify(form)))
|
||||
if (options.params) {
|
||||
queryParams = Object.assign(queryParams, options.params)
|
||||
}
|
||||
if (!options?.treeConfig) {
|
||||
queryParams.pageSize = page.pageSize
|
||||
queryParams.pageNo = page.currentPage
|
||||
}
|
||||
return new Promise(async (resolve) => {
|
||||
resolve(await getListApi(queryParams))
|
||||
})
|
||||
},
|
||||
delete: ({ body }) => {
|
||||
return new Promise(async (resolve) => {
|
||||
if (options.deleteApi) {
|
||||
resolve(await options.deleteApi(JSON.stringify(body)))
|
||||
} else {
|
||||
Promise.reject('未设置deleteApi')
|
||||
}
|
||||
})
|
||||
},
|
||||
queryAll: ({ form }) => {
|
||||
const queryParams = Object.assign({}, JSON.parse(JSON.stringify(form)))
|
||||
return new Promise(async (resolve) => {
|
||||
if (options.getAllListApi) {
|
||||
resolve(await options.getAllListApi(queryParams))
|
||||
} else {
|
||||
resolve(await getListApi(queryParams))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
options.proxyConfig = {
|
||||
seq: true, // 启用动态序号代理(分页之后索引自动计算为当前页的起始序号)
|
||||
form: true, // 启用表单代理,当点击表单提交按钮时会自动触发 reload 行为
|
||||
props: { result: 'data' },
|
||||
ajax: {
|
||||
query: ({ form }) => {
|
||||
let queryParams: any = Object.assign({}, JSON.parse(JSON.stringify(form)))
|
||||
if (options?.params) {
|
||||
queryParams = Object.assign(queryParams, options.params)
|
||||
}
|
||||
return new Promise(async (resolve) => {
|
||||
resolve(await getListApi(queryParams))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options.exportListApi) {
|
||||
options.exportConfig = {
|
||||
filename: options?.exportName,
|
||||
// 默认选中类型
|
||||
type: 'csv',
|
||||
// 自定义数据量列表
|
||||
modes: options?.getAllListApi ? ['current', 'all'] : ['current'],
|
||||
columns: options?.allSchemas?.printSchema
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 分页
|
||||
const getPageConfig = (options: XTableProps) => {
|
||||
const { pagination, pagerConfig, treeConfig, isList } = options
|
||||
if (isList) return
|
||||
if (treeConfig) {
|
||||
options.treeConfig = options.treeConfig
|
||||
return
|
||||
}
|
||||
if (pagerConfig) return
|
||||
if (pagination) {
|
||||
if (isBoolean(pagination)) {
|
||||
options.pagerConfig = {
|
||||
border: false, // 带边框
|
||||
background: false, // 带背景颜色
|
||||
perfect: false, // 配套的样式
|
||||
pageSize: 10, // 每页大小
|
||||
pagerCount: 7, // 显示页码按钮的数量
|
||||
autoHidden: false, // 当只有一页时自动隐藏
|
||||
pageSizes: [5, 10, 20, 30, 50, 100], // 每页大小选项列表
|
||||
layouts: [
|
||||
'PrevJump',
|
||||
'PrevPage',
|
||||
'JumpNumber',
|
||||
'NextPage',
|
||||
'NextJump',
|
||||
'Sizes',
|
||||
'FullJump',
|
||||
'Total'
|
||||
]
|
||||
}
|
||||
return
|
||||
}
|
||||
options.pagerConfig = pagination
|
||||
} else {
|
||||
if (pagination != false) {
|
||||
options.pagerConfig = {
|
||||
border: false, // 带边框
|
||||
background: false, // 带背景颜色
|
||||
perfect: false, // 配套的样式
|
||||
pageSize: 10, // 每页大小
|
||||
pagerCount: 7, // 显示页码按钮的数量
|
||||
autoHidden: false, // 当只有一页时自动隐藏
|
||||
pageSizes: [5, 10, 20, 30, 50, 100], // 每页大小选项列表
|
||||
layouts: [
|
||||
'Sizes',
|
||||
'PrevJump',
|
||||
'PrevPage',
|
||||
'Number',
|
||||
'NextPage',
|
||||
'NextJump',
|
||||
'FullJump',
|
||||
'Total'
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tool bar
|
||||
const getToolBarConfig = (options: XTableProps) => {
|
||||
const { toolBar, toolbarConfig, topActionSlots } = options
|
||||
if (toolbarConfig) return
|
||||
if (toolBar) {
|
||||
if (!isBoolean(toolBar)) {
|
||||
console.info(2)
|
||||
options.toolbarConfig = toolBar
|
||||
return
|
||||
}
|
||||
} else if (topActionSlots != false) {
|
||||
options.toolbarConfig = {
|
||||
slots: { buttons: 'toolbar_buttons' }
|
||||
}
|
||||
} else {
|
||||
options.toolbarConfig = {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新列表
|
||||
const reload = () => {
|
||||
const g = unref(xGrid)
|
||||
if (!g) {
|
||||
return
|
||||
}
|
||||
g.commitProxy('query')
|
||||
}
|
||||
|
||||
// 删除
|
||||
const deleteData = async (id: string | number) => {
|
||||
const g = unref(xGrid)
|
||||
if (!g) {
|
||||
return
|
||||
}
|
||||
const options = innerProps.value || props.options
|
||||
if (!options.deleteApi) {
|
||||
console.error('未传入delListApi')
|
||||
return
|
||||
}
|
||||
return new Promise(async () => {
|
||||
message.delConfirm().then(async () => {
|
||||
await (options?.deleteApi && options?.deleteApi(id))
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
reload()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
const deleteBatch = async () => {
|
||||
const g = unref(xGrid)
|
||||
if (!g) {
|
||||
return
|
||||
}
|
||||
const rows = g.getCheckboxRecords() || g.getRadioRecord()
|
||||
let ids: any[] = []
|
||||
if (rows.length == 0) {
|
||||
message.error('请选择数据')
|
||||
return
|
||||
} else {
|
||||
rows.forEach((row) => {
|
||||
ids.push(row.id)
|
||||
})
|
||||
}
|
||||
const options = innerProps.value || props.options
|
||||
if (options.deleteListApi) {
|
||||
return new Promise(async () => {
|
||||
message.delConfirm().then(async () => {
|
||||
await (options?.deleteListApi && options?.deleteListApi(ids))
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
reload()
|
||||
})
|
||||
})
|
||||
} else if (options.deleteApi) {
|
||||
return new Promise(async () => {
|
||||
message.delConfirm().then(async () => {
|
||||
ids.forEach(async (id) => {
|
||||
await (options?.deleteApi && options?.deleteApi(id))
|
||||
})
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
reload()
|
||||
})
|
||||
})
|
||||
} else {
|
||||
console.error('未传入delListApi')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 导出
|
||||
const exportList = async (fileName?: string) => {
|
||||
const g = unref(xGrid)
|
||||
if (!g) {
|
||||
return
|
||||
}
|
||||
const options = innerProps.value || props.options
|
||||
if (!options?.exportListApi) {
|
||||
console.error('未传入exportListApi')
|
||||
return
|
||||
}
|
||||
const queryParams = Object.assign({}, JSON.parse(JSON.stringify(g.getProxyInfo()?.form)))
|
||||
message.exportConfirm().then(async () => {
|
||||
const res = await (options?.exportListApi && options?.exportListApi(queryParams))
|
||||
download.excel(res as unknown as Blob, fileName ? fileName : 'excel.xls')
|
||||
})
|
||||
}
|
||||
|
||||
// 获取查询参数
|
||||
const getSearchData = () => {
|
||||
const g = unref(xGrid)
|
||||
if (!g) {
|
||||
return
|
||||
}
|
||||
const queryParams = Object.assign({}, JSON.parse(JSON.stringify(g.getProxyInfo()?.form)))
|
||||
return queryParams
|
||||
}
|
||||
|
||||
// 获取当前列
|
||||
const getCurrentColumn = () => {
|
||||
const g = unref(xGrid)
|
||||
if (!g) {
|
||||
return
|
||||
}
|
||||
return g.getCurrentColumn()
|
||||
}
|
||||
|
||||
// 获取当前选中列,redio
|
||||
const getRadioRecord = () => {
|
||||
const g = unref(xGrid)
|
||||
if (!g) {
|
||||
return
|
||||
}
|
||||
return g.getRadioRecord(false)
|
||||
}
|
||||
|
||||
// 获取当前选中列,checkbox
|
||||
const getCheckboxRecords = () => {
|
||||
const g = unref(xGrid)
|
||||
if (!g) {
|
||||
return
|
||||
}
|
||||
return g.getCheckboxRecords(false)
|
||||
}
|
||||
const setProps = (prop: Partial<XTableProps>) => {
|
||||
innerProps.value = { ...unref(innerProps), ...prop }
|
||||
}
|
||||
|
||||
defineExpose({ reload, Ref: xGrid, getSearchData, deleteData, exportList })
|
||||
emit('register', {
|
||||
reload,
|
||||
getSearchData,
|
||||
setProps,
|
||||
deleteData,
|
||||
deleteBatch,
|
||||
exportList,
|
||||
getCurrentColumn,
|
||||
getRadioRecord,
|
||||
getCheckboxRecords
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@import './style/index.scss';
|
||||
</style>
|
|
@ -1,81 +0,0 @@
|
|||
// 修改样式变量
|
||||
//@import 'vxe-table/styles/variable.scss';
|
||||
|
||||
/*font*/
|
||||
$vxe-font-color: #e5e7eb;
|
||||
// $vxe-font-size: 14px !default;
|
||||
// $vxe-font-size-medium: 16px !default;
|
||||
// $vxe-font-size-small: 14px !default;
|
||||
// $vxe-font-size-mini: 12px !default;
|
||||
|
||||
/*color*/
|
||||
$vxe-primary-color: #409eff !default;
|
||||
$vxe-success-color: #67c23a !default;
|
||||
$vxe-info-color: #909399 !default;
|
||||
$vxe-warning-color: #e6a23c !default;
|
||||
$vxe-danger-color: #f56c6c !default;
|
||||
$vxe-disabled-color: #bfbfbf !default;
|
||||
$vxe-primary-disabled-color: #c0c4cc !default;
|
||||
|
||||
/*loading*/
|
||||
$vxe-loading-color: $vxe-primary-color !default;
|
||||
$vxe-loading-background-color: #1d1e1f !default;
|
||||
$vxe-loading-z-index: 999 !default;
|
||||
|
||||
/*icon*/
|
||||
$vxe-icon-font-family: Verdana, Arial, Tahoma !default;
|
||||
$vxe-icon-background-color: #e5e7eb !default;
|
||||
|
||||
/*toolbar*/
|
||||
$vxe-toolbar-background-color: #1d1e1f !default;
|
||||
$vxe-toolbar-button-border: #dcdfe6 !default;
|
||||
$vxe-toolbar-custom-active-background-color: #d9dadb !default;
|
||||
$vxe-toolbar-panel-background-color: #e5e7eb !default;
|
||||
|
||||
$vxe-table-font-color: #e5e7eb;
|
||||
$vxe-table-header-background-color: #1d1e1f;
|
||||
$vxe-table-body-background-color: #141414;
|
||||
$vxe-table-row-striped-background-color: #1d1d1d;
|
||||
$vxe-table-row-hover-background-color: #1d1e1f;
|
||||
$vxe-table-row-hover-striped-background-color: #1e1e1e;
|
||||
$vxe-table-footer-background-color: #1d1e1f;
|
||||
$vxe-table-row-current-background-color: #302d2d;
|
||||
$vxe-table-column-current-background-color: #302d2d;
|
||||
$vxe-table-column-hover-background-color: #302d2d;
|
||||
$vxe-table-row-hover-current-background-color: #302d2d;
|
||||
$vxe-table-row-checkbox-checked-background-color: #3e3c37 !default;
|
||||
$vxe-table-row-hover-checkbox-checked-background-color: #615a4a !default;
|
||||
$vxe-table-menu-background-color: #1d1e1f;
|
||||
$vxe-table-border-width: 1px !default;
|
||||
$vxe-table-border-color: #4c4d4f !default;
|
||||
$vxe-table-fixed-left-scrolling-box-shadow: 8px 0px 10px -5px rgba(0, 0, 0, 0.12) !default;
|
||||
$vxe-table-fixed-right-scrolling-box-shadow: -8px 0px 10px -5px rgba(0, 0, 0, 0.12) !default;
|
||||
|
||||
$vxe-form-background-color: #141414;
|
||||
|
||||
/*pager*/
|
||||
$vxe-pager-background-color: #1d1e1f !default;
|
||||
$vxe-pager-perfect-background-color: #262727 !default;
|
||||
$vxe-pager-perfect-button-background-color: #a7a3a3 !default;
|
||||
|
||||
$vxe-input-background-color: #141414;
|
||||
$vxe-input-border-color: #4c4d4f !default;
|
||||
|
||||
$vxe-select-option-hover-background-color: #262626 !default;
|
||||
$vxe-select-panel-background-color: #141414 !default;
|
||||
$vxe-select-empty-color: #262626 !default;
|
||||
$vxe-optgroup-title-color: #909399 !default;
|
||||
|
||||
/*button*/
|
||||
$vxe-button-default-background-color: #262626;
|
||||
$vxe-button-dropdown-panel-background-color: #141414;
|
||||
|
||||
/*modal*/
|
||||
$vxe-modal-header-background-color: #141414;
|
||||
$vxe-modal-body-background-color: #141414;
|
||||
$vxe-modal-border-color: #3b3b3b;
|
||||
|
||||
/*pulldown*/
|
||||
$vxe-pulldown-panel-background-color: #262626 !default;
|
||||
|
||||
@import 'vxe-table/styles/index.scss';
|
|
@ -1,6 +0,0 @@
|
|||
// @import 'vxe-table/styles/variable.scss';
|
||||
// @import 'vxe-table/styles/modules.scss';
|
||||
// @import './theme/light.scss';
|
||||
i {
|
||||
border-color: initial;
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
// 修改样式变量
|
||||
// /*font*/
|
||||
// $vxe-font-size: 12px !default;
|
||||
// $vxe-font-size-medium: 16px !default;
|
||||
// $vxe-font-size-small: 14px !default;
|
||||
// $vxe-font-size-mini: 12px !default;
|
||||
/*color*/
|
||||
$vxe-primary-color: #409eff !default;
|
||||
$vxe-success-color: #67c23a !default;
|
||||
$vxe-info-color: #909399 !default;
|
||||
$vxe-warning-color: #e6a23c !default;
|
||||
$vxe-danger-color: #f56c6c !default;
|
||||
$vxe-disabled-color: #bfbfbf !default;
|
||||
$vxe-primary-disabled-color: #c0c4cc !default;
|
||||
|
||||
@import 'vxe-table/styles/index.scss';
|
|
@ -1,26 +0,0 @@
|
|||
import { CrudSchema } from '@/hooks/web/useCrudSchemas'
|
||||
import type { VxeGridProps, VxeGridPropTypes, VxeTablePropTypes } from 'vxe-table'
|
||||
|
||||
export type XTableProps<D = any> = VxeGridProps<D> & {
|
||||
allSchemas?: CrudSchema
|
||||
height?: number // 高度 默认730
|
||||
topActionSlots?: boolean // 是否开启表格内顶部操作栏插槽
|
||||
treeConfig?: VxeTablePropTypes.TreeConfig // 树形表单配置
|
||||
isList?: boolean // 是否不带分页的list
|
||||
getListApi?: Function // 获取列表接口
|
||||
getAllListApi?: Function // 获取全部数据接口 用于 vxe 导出
|
||||
deleteApi?: Function // 删除接口
|
||||
deleteListApi?: Function // 批量删除接口
|
||||
exportListApi?: Function // 导出接口
|
||||
exportName?: string // 导出文件夹名称
|
||||
params?: any // 其他查询参数
|
||||
pagination?: boolean | VxeGridPropTypes.PagerConfig // 分页配置参数
|
||||
toolBar?: boolean | VxeGridPropTypes.ToolbarConfig // 右侧工具栏配置参数
|
||||
}
|
||||
export type XColumns = VxeGridPropTypes.Columns
|
||||
|
||||
export type VxeTableColumn = {
|
||||
field: string
|
||||
title?: string
|
||||
children?: VxeTableColumn[]
|
||||
} & Recordable
|
|
@ -188,7 +188,13 @@
|
|||
<!-- <div id="js-properties-panel" class="panel"></div> -->
|
||||
<!-- <div class="my-process-designer__canvas" ref="bpmn-canvas"></div> -->
|
||||
</div>
|
||||
<XModal title="预览" width="80%" height="90%" v-model="previewModelVisible" destroy-on-close>
|
||||
<Dialog
|
||||
title="预览"
|
||||
v-model="previewModelVisible"
|
||||
width="80%"
|
||||
:scroll="true"
|
||||
max-height="600px"
|
||||
>
|
||||
<!-- append-to-body -->
|
||||
<div v-highlight>
|
||||
<code class="hljs">
|
||||
|
@ -196,10 +202,7 @@
|
|||
{{ previewResult }}
|
||||
</code>
|
||||
</div>
|
||||
<!-- <pre>
|
||||
<code class="hljs" v-html="highlightedCode(previewType, previewResult)"></code>
|
||||
</pre> -->
|
||||
</XModal>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -231,7 +234,7 @@ import activitiModdleExtension from './plugins/extension-moddle/activiti'
|
|||
import flowableModdleExtension from './plugins/extension-moddle/flowable'
|
||||
// 引入json转换与高亮
|
||||
// import xml2js from 'xml-js'
|
||||
import xml2js from 'fast-xml-parser'
|
||||
// import xml2js from 'fast-xml-parser'
|
||||
import { XmlNode, XmlNodeType, parseXmlString } from 'steady-xml'
|
||||
// 代码高亮插件
|
||||
// import hljs from 'highlight.js/lib/highlight'
|
||||
|
@ -626,7 +629,7 @@ const elementsAlign = (align) => {
|
|||
const previewProcessXML = () => {
|
||||
console.log(bpmnModeler.saveXML, 'bpmnModeler')
|
||||
bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
|
||||
console.log(xml, 'xml111111')
|
||||
// console.log(xml, 'xml111111')
|
||||
previewResult.value = xml
|
||||
previewType.value = 'xml'
|
||||
previewModelVisible.value = true
|
||||
|
@ -634,7 +637,7 @@ const previewProcessXML = () => {
|
|||
}
|
||||
const previewProcessJson = () => {
|
||||
bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
|
||||
console.log(xml, 'xml')
|
||||
// console.log(xml, 'xml')
|
||||
|
||||
// const rootNode = parseXmlString(xml)
|
||||
// console.log(rootNode, 'rootNoderootNode')
|
||||
|
@ -644,9 +647,9 @@ const previewProcessJson = () => {
|
|||
// console.log(JSON.stringify(rootNodes.parent.toJsObject()), 'rootNodes.toJSON()')
|
||||
// console.log(JSON.stringify(rootNodes.parent.toJSON()), 'rootNodes.toJSON()')
|
||||
|
||||
const parser = new xml2js.XMLParser()
|
||||
let jObj = parser.parse(xml)
|
||||
console.log(jObj, 'jObjjObjjObjjObjjObj')
|
||||
// const parser = new xml2js.XMLParser()
|
||||
// let jObj = parser.parse(xml)
|
||||
// console.log(jObj, 'jObjjObjjObjjObjjObj')
|
||||
// const builder = new xml2js.XMLBuilder(xml)
|
||||
// const xmlContent = builder
|
||||
// console.log(xmlContent, 'xmlContent')
|
||||
|
|
|
@ -3,8 +3,6 @@ import { Icon } from './Icon'
|
|||
import { Form } from '@/components/Form'
|
||||
import { Table } from '@/components/Table'
|
||||
import { Search } from '@/components/Search'
|
||||
import { XModal } from '@/components/XModal'
|
||||
import { XTable } from '@/components/XTable'
|
||||
import { XButton, XTextButton } from '@/components/XButton'
|
||||
import { DictTag } from '@/components/DictTag'
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
|
@ -15,8 +13,6 @@ export const setupGlobCom = (app: App<Element>): void => {
|
|||
app.component('Form', Form)
|
||||
app.component('Table', Table)
|
||||
app.component('Search', Search)
|
||||
app.component('XModal', XModal)
|
||||
app.component('XTable', XTable)
|
||||
app.component('XButton', XButton)
|
||||
app.component('XTextButton', XTextButton)
|
||||
app.component('DictTag', DictTag)
|
||||
|
|
|
@ -1,354 +0,0 @@
|
|||
import {
|
||||
FormItemRenderOptions,
|
||||
VxeColumnPropTypes,
|
||||
VxeFormItemProps,
|
||||
VxeGridPropTypes,
|
||||
VxeTableDefines
|
||||
} from 'vxe-table'
|
||||
import { eachTree } from 'xe-utils'
|
||||
|
||||
import { getBoolDictOptions, getDictOptions, getIntDictOptions } from '@/utils/dict'
|
||||
import { FormSchema } from '@/types/form'
|
||||
import { VxeTableColumn } from '@/types/table'
|
||||
import { ComponentOptions } from '@/types/components'
|
||||
import { DescriptionsSchema } from '@/types/descriptions'
|
||||
|
||||
export type VxeCrudSchema = {
|
||||
primaryKey?: string // 主键ID
|
||||
primaryTitle?: string // 主键标题 默认为序号
|
||||
primaryType?: VxeColumnPropTypes.Type | 'id' // 还支持 "id" | "seq" | "radio" | "checkbox" | "expand" | "html" | null
|
||||
firstColumn?: VxeColumnPropTypes.Type // 第一列显示类型
|
||||
action?: boolean // 是否开启表格内右侧操作栏插槽
|
||||
actionTitle?: string // 操作栏标题 默认为操作
|
||||
actionWidth?: string // 操作栏插槽宽度,一般2个字带图标 text 类型按钮 50-70
|
||||
columns: VxeCrudColumns[]
|
||||
searchSpan?: number
|
||||
}
|
||||
type VxeCrudColumns = Omit<VxeTableColumn, 'children'> & {
|
||||
field: string // 字段名
|
||||
title?: string // 标题名
|
||||
formatter?: VxeColumnPropTypes.Formatter // vxe formatter格式化
|
||||
isSearch?: boolean // 是否在查询显示
|
||||
search?: CrudSearchParams // 查询的详细配置
|
||||
isTable?: boolean // 是否在列表显示
|
||||
table?: CrudTableParams // 列表的详细配置
|
||||
isForm?: boolean // 是否在表单显示
|
||||
form?: CrudFormParams // 表单的详细配置
|
||||
isDetail?: boolean // 是否在详情显示
|
||||
detail?: CrudDescriptionsParams // 详情的详细配置
|
||||
print?: CrudPrintParams // vxe 打印的字段
|
||||
children?: VxeCrudColumns[] // 子级
|
||||
dictType?: string // 字典类型
|
||||
dictClass?: 'string' | 'number' | 'boolean' // 字典数据类型 string | number | boolean
|
||||
}
|
||||
|
||||
type CrudSearchParams = {
|
||||
// 是否显示在查询项
|
||||
show?: boolean
|
||||
} & Omit<VxeFormItemProps, 'field'>
|
||||
|
||||
type CrudTableParams = {
|
||||
// 是否显示表头
|
||||
show?: boolean
|
||||
} & Omit<VxeTableDefines.ColumnOptions, 'field'>
|
||||
|
||||
type CrudFormParams = {
|
||||
// 是否显示表单项
|
||||
show?: boolean
|
||||
} & Omit<FormSchema, 'field'>
|
||||
|
||||
type CrudDescriptionsParams = {
|
||||
// 是否显示表单项
|
||||
show?: boolean
|
||||
} & Omit<DescriptionsSchema, 'field'>
|
||||
|
||||
type CrudPrintParams = {
|
||||
// 是否显示打印项
|
||||
show?: boolean
|
||||
} & Omit<VxeTableDefines.ColumnInfo[], 'field'>
|
||||
|
||||
export type VxeAllSchemas = {
|
||||
searchSchema: VxeFormItemProps[]
|
||||
tableSchema: VxeGridPropTypes.Columns
|
||||
formSchema: FormSchema[]
|
||||
detailSchema: DescriptionsSchema[]
|
||||
printSchema: VxeTableDefines.ColumnInfo[]
|
||||
}
|
||||
|
||||
// 过滤所有结构
|
||||
export const useVxeCrudSchemas = (
|
||||
crudSchema: VxeCrudSchema
|
||||
): {
|
||||
allSchemas: VxeAllSchemas
|
||||
} => {
|
||||
// 所有结构数据
|
||||
const allSchemas = reactive<VxeAllSchemas>({
|
||||
searchSchema: [],
|
||||
tableSchema: [],
|
||||
formSchema: [],
|
||||
detailSchema: [],
|
||||
printSchema: []
|
||||
})
|
||||
|
||||
const searchSchema = filterSearchSchema(crudSchema)
|
||||
allSchemas.searchSchema = searchSchema || []
|
||||
|
||||
const tableSchema = filterTableSchema(crudSchema)
|
||||
allSchemas.tableSchema = tableSchema || []
|
||||
|
||||
const formSchema = filterFormSchema(crudSchema)
|
||||
allSchemas.formSchema = formSchema
|
||||
|
||||
const detailSchema = filterDescriptionsSchema(crudSchema)
|
||||
allSchemas.detailSchema = detailSchema
|
||||
|
||||
const printSchema = filterPrintSchema(crudSchema)
|
||||
allSchemas.printSchema = printSchema
|
||||
|
||||
return {
|
||||
allSchemas
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤 Search 结构
|
||||
const filterSearchSchema = (crudSchema: VxeCrudSchema): VxeFormItemProps[] => {
|
||||
const { t } = useI18n()
|
||||
const span = crudSchema.searchSpan ? crudSchema.searchSpan : 6
|
||||
const spanLength = 24 / span
|
||||
const searchSchema: VxeFormItemProps[] = []
|
||||
eachTree(crudSchema.columns, (schemaItem: VxeCrudColumns) => {
|
||||
// 判断是否显示
|
||||
if (schemaItem?.isSearch || schemaItem.search?.show) {
|
||||
let itemRenderName = schemaItem?.search?.itemRender?.name || '$input'
|
||||
const options: any[] = []
|
||||
let itemRender: FormItemRenderOptions = {}
|
||||
|
||||
if (schemaItem.dictType) {
|
||||
const allOptions = { label: '全部', value: '' }
|
||||
options.push(allOptions)
|
||||
getDictOptions(schemaItem.dictType).forEach((dict) => {
|
||||
options.push(dict)
|
||||
})
|
||||
itemRender.options = options
|
||||
if (!schemaItem?.search?.itemRender?.name) itemRenderName = '$select'
|
||||
itemRender = {
|
||||
name: itemRenderName,
|
||||
options: options,
|
||||
props: { placeholder: t('common.selectText') }
|
||||
}
|
||||
} else {
|
||||
if (schemaItem.search?.itemRender) {
|
||||
itemRender = schemaItem.search.itemRender
|
||||
} else {
|
||||
itemRender = {
|
||||
name: itemRenderName,
|
||||
props:
|
||||
itemRenderName == '$input'
|
||||
? { placeholder: t('common.inputText') }
|
||||
: { placeholder: t('common.selectText') }
|
||||
}
|
||||
}
|
||||
}
|
||||
const searchSchemaItem = {
|
||||
// 默认为 input
|
||||
folding: searchSchema.length > spanLength - 1,
|
||||
itemRender: schemaItem.itemRender ? schemaItem.itemRender : itemRender,
|
||||
field: schemaItem.field,
|
||||
title: schemaItem.search?.title || schemaItem.title,
|
||||
slots: schemaItem.search?.slots,
|
||||
span: span
|
||||
}
|
||||
searchSchema.push(searchSchemaItem)
|
||||
}
|
||||
})
|
||||
if (searchSchema.length > 0) {
|
||||
// 添加搜索按钮
|
||||
const buttons: VxeFormItemProps = {
|
||||
span: 24,
|
||||
align: 'right',
|
||||
collapseNode: searchSchema.length > spanLength,
|
||||
itemRender: {
|
||||
name: '$buttons',
|
||||
children: [
|
||||
{ props: { type: 'submit', content: t('common.query'), status: 'primary' } },
|
||||
{ props: { type: 'reset', content: t('common.reset') } }
|
||||
]
|
||||
}
|
||||
}
|
||||
searchSchema.push(buttons)
|
||||
}
|
||||
return searchSchema
|
||||
}
|
||||
|
||||
// 过滤 table 结构
|
||||
const filterTableSchema = (crudSchema: VxeCrudSchema): VxeGridPropTypes.Columns => {
|
||||
const { t } = useI18n()
|
||||
const tableSchema: VxeGridPropTypes.Columns = []
|
||||
// 第一列
|
||||
if (crudSchema.firstColumn) {
|
||||
const tableSchemaItem = {
|
||||
type: crudSchema.firstColumn,
|
||||
width: '50px'
|
||||
}
|
||||
tableSchema.push(tableSchemaItem)
|
||||
}
|
||||
// 主键ID
|
||||
if (crudSchema.primaryKey && crudSchema.primaryType) {
|
||||
const primaryTitle = crudSchema.primaryTitle ? crudSchema.primaryTitle : t('common.index')
|
||||
const primaryWidth = primaryTitle.length * 30 + 'px'
|
||||
|
||||
let tableSchemaItem: { [x: string]: any } = {
|
||||
title: primaryTitle,
|
||||
field: crudSchema.primaryKey,
|
||||
width: primaryWidth
|
||||
}
|
||||
if (crudSchema.primaryType != 'id') {
|
||||
tableSchemaItem = {
|
||||
...tableSchemaItem,
|
||||
type: crudSchema.primaryType
|
||||
}
|
||||
}
|
||||
tableSchema.push(tableSchemaItem)
|
||||
}
|
||||
|
||||
eachTree(crudSchema.columns, (schemaItem: VxeCrudColumns) => {
|
||||
// 判断是否显示
|
||||
if (schemaItem?.isTable !== false && schemaItem?.table?.show !== false) {
|
||||
const tableSchemaItem = {
|
||||
...schemaItem.table,
|
||||
field: schemaItem.field,
|
||||
title: schemaItem.table?.title || schemaItem.title,
|
||||
minWidth: '80px'
|
||||
}
|
||||
tableSchemaItem.showOverflow = 'tooltip'
|
||||
if (schemaItem?.formatter) {
|
||||
tableSchemaItem.formatter = schemaItem.formatter
|
||||
tableSchemaItem.width = tableSchemaItem.width ? tableSchemaItem.width : 160
|
||||
}
|
||||
if (schemaItem?.dictType) {
|
||||
tableSchemaItem.cellRender = {
|
||||
name: 'XDict',
|
||||
content: schemaItem.dictType
|
||||
}
|
||||
tableSchemaItem.width = tableSchemaItem.width ? tableSchemaItem.width : 160
|
||||
}
|
||||
|
||||
tableSchema.push(tableSchemaItem)
|
||||
}
|
||||
})
|
||||
// 操作栏插槽
|
||||
if (crudSchema.action && crudSchema.action == true) {
|
||||
const tableSchemaItem = {
|
||||
title: crudSchema.actionTitle ? crudSchema.actionTitle : t('table.action'),
|
||||
field: 'actionbtns',
|
||||
fixed: 'right' as unknown as VxeColumnPropTypes.Fixed,
|
||||
width: crudSchema.actionWidth ? crudSchema.actionWidth : '200px',
|
||||
slots: {
|
||||
default: 'actionbtns_default'
|
||||
}
|
||||
}
|
||||
tableSchema.push(tableSchemaItem)
|
||||
}
|
||||
return tableSchema
|
||||
}
|
||||
|
||||
// 过滤 form 结构
|
||||
const filterFormSchema = (crudSchema: VxeCrudSchema): FormSchema[] => {
|
||||
const formSchema: FormSchema[] = []
|
||||
|
||||
eachTree(crudSchema.columns, (schemaItem: VxeCrudColumns) => {
|
||||
// 判断是否显示
|
||||
if (schemaItem?.isForm !== false && schemaItem?.form?.show !== false) {
|
||||
// 默认为 input
|
||||
let component = schemaItem?.form?.component || 'Input'
|
||||
let defaultValue: any = ''
|
||||
if (schemaItem.form?.value) {
|
||||
defaultValue = schemaItem.form?.value
|
||||
} else {
|
||||
if (component === 'InputNumber') {
|
||||
defaultValue = 0
|
||||
}
|
||||
}
|
||||
let comonentProps = {}
|
||||
if (schemaItem.dictType) {
|
||||
const options: ComponentOptions[] = []
|
||||
if (schemaItem.dictClass && schemaItem.dictClass === 'number') {
|
||||
getIntDictOptions(schemaItem.dictType).forEach((dict) => {
|
||||
options.push(dict)
|
||||
})
|
||||
} else if (schemaItem.dictClass && schemaItem.dictClass === 'boolean') {
|
||||
getBoolDictOptions(schemaItem.dictType).forEach((dict) => {
|
||||
options.push(dict)
|
||||
})
|
||||
} else {
|
||||
getDictOptions(schemaItem.dictType).forEach((dict) => {
|
||||
options.push(dict)
|
||||
})
|
||||
}
|
||||
comonentProps = {
|
||||
options: options
|
||||
}
|
||||
if (!(schemaItem.form && schemaItem.form.component)) component = 'Select'
|
||||
}
|
||||
const formSchemaItem = {
|
||||
component: component,
|
||||
componentProps: comonentProps,
|
||||
value: defaultValue,
|
||||
...schemaItem.form,
|
||||
field: schemaItem.field,
|
||||
label: schemaItem.form?.label || schemaItem.title
|
||||
}
|
||||
|
||||
formSchema.push(formSchemaItem)
|
||||
}
|
||||
})
|
||||
|
||||
return formSchema
|
||||
}
|
||||
|
||||
// 过滤 descriptions 结构
|
||||
const filterDescriptionsSchema = (crudSchema: VxeCrudSchema): DescriptionsSchema[] => {
|
||||
const descriptionsSchema: DescriptionsSchema[] = []
|
||||
|
||||
eachTree(crudSchema.columns, (schemaItem: VxeCrudColumns) => {
|
||||
// 判断是否显示
|
||||
if (schemaItem?.isDetail !== false && schemaItem.detail?.show !== false) {
|
||||
const descriptionsSchemaItem = {
|
||||
...schemaItem.detail,
|
||||
field: schemaItem.field,
|
||||
label: schemaItem.detail?.label || schemaItem.title
|
||||
}
|
||||
if (schemaItem.dictType) {
|
||||
descriptionsSchemaItem.dictType = schemaItem.dictType
|
||||
}
|
||||
if (schemaItem.detail?.dateFormat || schemaItem.formatter == 'formatDate') {
|
||||
// 优先使用 detail 下的配置,如果没有默认为 YYYY-MM-DD HH:mm:ss
|
||||
descriptionsSchemaItem.dateFormat = schemaItem?.detail?.dateFormat
|
||||
? schemaItem?.detail?.dateFormat
|
||||
: 'YYYY-MM-DD HH:mm:ss'
|
||||
}
|
||||
|
||||
descriptionsSchema.push(descriptionsSchemaItem)
|
||||
}
|
||||
})
|
||||
|
||||
return descriptionsSchema
|
||||
}
|
||||
|
||||
// 过滤 打印 结构
|
||||
const filterPrintSchema = (crudSchema: VxeCrudSchema): any[] => {
|
||||
const printSchema: any[] = []
|
||||
|
||||
eachTree(crudSchema.columns, (schemaItem: VxeCrudColumns) => {
|
||||
// 判断是否显示
|
||||
if (schemaItem?.print?.show !== false) {
|
||||
const printSchemaItem = {
|
||||
field: schemaItem.field
|
||||
}
|
||||
|
||||
printSchema.push(printSchemaItem)
|
||||
}
|
||||
})
|
||||
|
||||
return printSchema
|
||||
}
|
|
@ -1,264 +0,0 @@
|
|||
import { computed, nextTick, reactive } from 'vue'
|
||||
import { SizeType, VxeGridProps, VxeTablePropTypes } from 'vxe-table'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { VxeAllSchemas } from './useVxeCrudSchemas'
|
||||
|
||||
import download from '@/utils/download'
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
interface UseVxeGridConfig<T = any> {
|
||||
allSchemas: VxeAllSchemas
|
||||
height?: number // 高度 默认730
|
||||
topActionSlots?: boolean // 是否开启表格内顶部操作栏插槽
|
||||
treeConfig?: VxeTablePropTypes.TreeConfig // 树形表单配置
|
||||
isList?: boolean // 是否不带分页的list
|
||||
getListApi: (option: any) => Promise<T> // 获取列表接口
|
||||
getAllListApi?: (option: any) => Promise<T> // 获取全部数据接口 用于VXE导出
|
||||
deleteApi?: (option: any) => Promise<T> // 删除接口
|
||||
exportListApi?: (option: any) => Promise<T> // 导出接口
|
||||
exportName?: string // 导出文件夹名称
|
||||
queryParams?: any // 其他查询参数
|
||||
}
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const currentSize = computed(() => {
|
||||
let resSize: SizeType = 'small'
|
||||
const appsize = appStore.getCurrentSize
|
||||
switch (appsize) {
|
||||
case 'large':
|
||||
resSize = 'medium'
|
||||
break
|
||||
case 'default':
|
||||
resSize = 'small'
|
||||
break
|
||||
case 'small':
|
||||
resSize = 'mini'
|
||||
break
|
||||
}
|
||||
return resSize
|
||||
})
|
||||
|
||||
export const useVxeGrid = <T = any>(config?: UseVxeGridConfig<T>) => {
|
||||
/**
|
||||
* grid options 初始化
|
||||
*/
|
||||
const gridOptions = reactive<VxeGridProps<any>>({
|
||||
loading: true,
|
||||
size: currentSize as any,
|
||||
height: config?.height ? config.height : 730,
|
||||
rowConfig: {
|
||||
isCurrent: true, // 当鼠标点击行时,是否要高亮当前行
|
||||
isHover: true // 当鼠标移到行时,是否要高亮当前行
|
||||
},
|
||||
toolbarConfig: {
|
||||
slots:
|
||||
!config?.topActionSlots && config?.topActionSlots != false
|
||||
? { buttons: 'toolbar_buttons' }
|
||||
: {}
|
||||
},
|
||||
printConfig: {
|
||||
columns: config?.allSchemas.printSchema
|
||||
},
|
||||
formConfig: {
|
||||
enabled: true,
|
||||
titleWidth: 100,
|
||||
titleAlign: 'right',
|
||||
items: config?.allSchemas.searchSchema
|
||||
},
|
||||
columns: config?.allSchemas.tableSchema,
|
||||
proxyConfig: {
|
||||
seq: true, // 启用动态序号代理(分页之后索引自动计算为当前页的起始序号)
|
||||
form: true, // 启用表单代理,当点击表单提交按钮时会自动触发 reload 行为
|
||||
props: { result: 'list', total: 'total' },
|
||||
ajax: {
|
||||
query: ({ page, form }) => {
|
||||
let queryParams: any = Object.assign({}, JSON.parse(JSON.stringify(form)))
|
||||
if (config?.queryParams) {
|
||||
queryParams = Object.assign(queryParams, config.queryParams)
|
||||
}
|
||||
if (!config?.treeConfig) {
|
||||
queryParams.pageSize = page.pageSize
|
||||
queryParams.pageNo = page.currentPage
|
||||
}
|
||||
gridOptions.loading = false
|
||||
return new Promise(async (resolve) => {
|
||||
resolve(await config?.getListApi(queryParams))
|
||||
})
|
||||
},
|
||||
delete: ({ body }) => {
|
||||
return new Promise(async (resolve) => {
|
||||
if (config?.deleteApi) {
|
||||
resolve(await config?.deleteApi(JSON.stringify(body)))
|
||||
} else {
|
||||
Promise.reject('未设置deleteApi')
|
||||
}
|
||||
})
|
||||
},
|
||||
queryAll: ({ form }) => {
|
||||
const queryParams = Object.assign({}, JSON.parse(JSON.stringify(form)))
|
||||
return new Promise(async (resolve) => {
|
||||
if (config?.getAllListApi) {
|
||||
resolve(await config?.getAllListApi(queryParams))
|
||||
} else {
|
||||
resolve(await config?.getListApi(queryParams))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
exportConfig: {
|
||||
filename: config?.exportName,
|
||||
// 默认选中类型
|
||||
type: 'csv',
|
||||
// 自定义数据量列表
|
||||
modes: config?.getAllListApi ? ['current', 'all'] : ['current'],
|
||||
columns: config?.allSchemas.printSchema
|
||||
}
|
||||
})
|
||||
|
||||
if (config?.treeConfig) {
|
||||
gridOptions.treeConfig = config.treeConfig
|
||||
} else if (config?.isList) {
|
||||
gridOptions.proxyConfig = {
|
||||
seq: true, // 启用动态序号代理(分页之后索引自动计算为当前页的起始序号)
|
||||
form: true, // 启用表单代理,当点击表单提交按钮时会自动触发 reload 行为
|
||||
props: { result: 'data' },
|
||||
ajax: {
|
||||
query: ({ form }) => {
|
||||
let queryParams: any = Object.assign({}, JSON.parse(JSON.stringify(form)))
|
||||
if (config?.queryParams) {
|
||||
queryParams = Object.assign(queryParams, config.queryParams)
|
||||
}
|
||||
gridOptions.loading = false
|
||||
return new Promise(async (resolve) => {
|
||||
resolve(await config?.getListApi(queryParams))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gridOptions.pagerConfig = {
|
||||
border: false, // 带边框
|
||||
background: true, // 带背景颜色
|
||||
perfect: false, // 配套的样式
|
||||
pageSize: 10, // 每页大小
|
||||
pagerCount: 7, // 显示页码按钮的数量
|
||||
autoHidden: false, // 当只有一页时自动隐藏
|
||||
pageSizes: [5, 10, 20, 30, 50, 100], // 每页大小选项列表
|
||||
layouts: [
|
||||
'PrevJump',
|
||||
'PrevPage',
|
||||
'JumpNumber',
|
||||
'NextPage',
|
||||
'NextJump',
|
||||
'Sizes',
|
||||
'FullJump',
|
||||
'Total'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新列表
|
||||
* @param ref
|
||||
* @returns
|
||||
*/
|
||||
const getList = async (ref) => {
|
||||
if (!ref) {
|
||||
console.error('未传入gridRef')
|
||||
return
|
||||
}
|
||||
await nextTick()
|
||||
ref.value.commitProxy('query')
|
||||
}
|
||||
|
||||
// 获取查询参数
|
||||
const getSearchData = async (ref) => {
|
||||
if (!ref) {
|
||||
console.error('未传入gridRef')
|
||||
return
|
||||
}
|
||||
await nextTick()
|
||||
const queryParams = Object.assign(
|
||||
{},
|
||||
JSON.parse(JSON.stringify(ref.value.getProxyInfo()?.form))
|
||||
)
|
||||
return queryParams
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param ref
|
||||
* @param ids rowid
|
||||
* @returns
|
||||
*/
|
||||
const deleteData = async (ref, ids: string | number) => {
|
||||
if (!ref) {
|
||||
console.error('未传入gridRef')
|
||||
return
|
||||
}
|
||||
if (!config?.deleteApi) {
|
||||
console.error('未传入delListApi')
|
||||
return
|
||||
}
|
||||
await nextTick()
|
||||
return new Promise(async () => {
|
||||
message.delConfirm().then(async () => {
|
||||
await (config?.deleteApi && config?.deleteApi(ids))
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
ref.value.commitProxy('query')
|
||||
})
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 导出
|
||||
* @param ref
|
||||
* @param fileName 文件名,默认excel.xls
|
||||
* @returns
|
||||
*/
|
||||
const exportList = async (ref, fileName?: string) => {
|
||||
if (!ref) {
|
||||
console.error('未传入gridRef')
|
||||
return
|
||||
}
|
||||
if (!config?.exportListApi) {
|
||||
console.error('未传入exportListApi')
|
||||
return
|
||||
}
|
||||
await nextTick()
|
||||
const queryParams = Object.assign(
|
||||
{},
|
||||
JSON.parse(JSON.stringify(ref.value?.getProxyInfo()?.form))
|
||||
)
|
||||
message.exportConfirm().then(async () => {
|
||||
const res = await (config?.exportListApi && config?.exportListApi(queryParams))
|
||||
download.excel(res as unknown as Blob, fileName ? fileName : 'excel.xls')
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 表格最大/最小化
|
||||
* @param ref
|
||||
* @returns
|
||||
*/
|
||||
const zoom = async (ref) => {
|
||||
if (!ref) {
|
||||
console.error('未传入gridRef')
|
||||
return
|
||||
}
|
||||
await nextTick()
|
||||
ref.value.zoom(!ref.value.isMaximized())
|
||||
}
|
||||
|
||||
return {
|
||||
gridOptions,
|
||||
getList,
|
||||
getSearchData,
|
||||
deleteData,
|
||||
exportList,
|
||||
zoom
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
import { XTableProps } from '@/components/XTable/src/type'
|
||||
|
||||
export interface tableMethod {
|
||||
reload: () => void // 刷新表格
|
||||
setProps: (props: XTableProps) => void
|
||||
deleteData: (id: string | number) => void // 删除数据
|
||||
deleteBatch: () => void // 批量删除
|
||||
exportList: (fileName?: string) => void // 导出列表
|
||||
getCurrentColumn: () => void // 获取当前列
|
||||
getRadioRecord: () => void // 获取当前选中列,radio
|
||||
getCheckboxRecords: () => void //获取当前选中列, checkbox
|
||||
}
|
||||
|
||||
export const useXTable = (props: XTableProps): [Function, tableMethod] => {
|
||||
const tableRef = ref<Nullable<tableMethod>>(null)
|
||||
|
||||
const register = (instance) => {
|
||||
tableRef.value = instance
|
||||
props && instance.setProps(props)
|
||||
}
|
||||
const getInstance = (): tableMethod => {
|
||||
const table = unref(tableRef)
|
||||
if (!table) {
|
||||
console.error('表格实例不存在')
|
||||
}
|
||||
return table as tableMethod
|
||||
}
|
||||
const methods: tableMethod = {
|
||||
reload: () => getInstance().reload(),
|
||||
setProps: (props) => getInstance().setProps(props),
|
||||
deleteData: (id: string | number) => getInstance().deleteData(id),
|
||||
deleteBatch: () => getInstance().deleteBatch(),
|
||||
exportList: (fileName?: string) => getInstance().exportList(fileName),
|
||||
getCurrentColumn: () => getInstance().getCheckboxRecords(),
|
||||
getRadioRecord: () => getInstance().getRadioRecord(),
|
||||
getCheckboxRecords: () => getInstance().getCheckboxRecords()
|
||||
}
|
||||
return [register, methods]
|
||||
}
|
|
@ -16,9 +16,6 @@ import { setupGlobCom } from '@/components'
|
|||
// 引入 element-plus
|
||||
import { setupElementPlus } from '@/plugins/elementPlus'
|
||||
|
||||
// 引入 vxe-table
|
||||
import { setupVxeTable } from '@/plugins/vxeTable'
|
||||
|
||||
// 引入 form-create
|
||||
import { setupFormCreate } from '@/plugins/formCreate'
|
||||
|
||||
|
@ -83,8 +80,6 @@ const setupAll = async () => {
|
|||
|
||||
setupElementPlus(app)
|
||||
|
||||
setupVxeTable(app)
|
||||
|
||||
setupFormCreate(app)
|
||||
|
||||
setupRouter(app)
|
||||
|
|
|
@ -1,223 +0,0 @@
|
|||
import { App } from 'vue'
|
||||
import XEUtils from 'xe-utils'
|
||||
import './renderer'
|
||||
import 'vxe-table/lib/style.css'
|
||||
import { i18n } from '@/plugins/vueI18n'
|
||||
import zhCN from 'vxe-table/lib/locale/lang/zh-CN'
|
||||
import enUS from 'vxe-table/lib/locale/lang/en-US'
|
||||
import {
|
||||
// 全局对象
|
||||
VXETable,
|
||||
// 表格功能
|
||||
Filter,
|
||||
Edit,
|
||||
Menu,
|
||||
Export,
|
||||
Keyboard,
|
||||
Validator,
|
||||
// 可选组件
|
||||
Icon,
|
||||
Column,
|
||||
Colgroup,
|
||||
Grid,
|
||||
Tooltip,
|
||||
Toolbar,
|
||||
Pager,
|
||||
Form,
|
||||
FormItem,
|
||||
FormGather,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RadioButton,
|
||||
Switch,
|
||||
Input,
|
||||
Select,
|
||||
Optgroup,
|
||||
Option,
|
||||
Textarea,
|
||||
Button,
|
||||
Modal,
|
||||
List,
|
||||
Pulldown,
|
||||
// 表格
|
||||
Table
|
||||
} from 'vxe-table'
|
||||
|
||||
// 全局默认参数
|
||||
VXETable.setup({
|
||||
size: 'medium', // 全局尺寸
|
||||
version: 0, // 版本号,对于某些带数据缓存的功能有用到,上升版本号可以用于重置数据
|
||||
zIndex: 1008, // 全局 zIndex 起始值,如果项目的的 z-index 样式值过大时就需要跟随设置更大,避免被遮挡
|
||||
loadingText: '加载中...', // 全局loading提示内容,如果为null则不显示文本
|
||||
height: 600,
|
||||
table: {
|
||||
border: 'inner', // default(默认), full(完整边框), outer(外边框), inner(内边框), none(无边框)
|
||||
align: 'center', // eft(左对齐), center(居中对齐), right(右对齐)
|
||||
autoResize: true, // 自动监听父元素的变化去重新计算表格
|
||||
resizable: true, // 列是否允许拖动列宽调整大小
|
||||
emptyText: '暂无数据', // 空表单
|
||||
highlightHoverRow: true, // 自动监听父元素的变化去重新计算表格
|
||||
treeConfig: {
|
||||
rowField: 'id',
|
||||
parentField: 'parentId',
|
||||
children: 'children',
|
||||
indent: 20,
|
||||
showIcon: true
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
toolbarConfig: {
|
||||
refresh: true,
|
||||
export: true,
|
||||
print: true,
|
||||
zoom: true,
|
||||
custom: true
|
||||
},
|
||||
pagerConfig: {
|
||||
border: false,
|
||||
background: false,
|
||||
autoHidden: true,
|
||||
perfect: true,
|
||||
pageSize: 10,
|
||||
pagerCount: 7,
|
||||
pageSizes: [5, 10, 15, 20, 50, 100, 200, 500],
|
||||
layouts: [
|
||||
'Sizes',
|
||||
'PrevJump',
|
||||
'PrevPage',
|
||||
'Number',
|
||||
'NextPage',
|
||||
'NextJump',
|
||||
'FullJump',
|
||||
'Total'
|
||||
]
|
||||
}
|
||||
},
|
||||
pager: {
|
||||
background: false,
|
||||
autoHidden: false,
|
||||
perfect: true,
|
||||
pageSize: 10,
|
||||
pagerCount: 7,
|
||||
pageSizes: [10, 15, 20, 50, 100],
|
||||
layouts: ['PrevJump', 'PrevPage', 'Jump', 'PageCount', 'NextPage', 'NextJump', 'Sizes', 'Total']
|
||||
},
|
||||
input: {
|
||||
clearable: true
|
||||
},
|
||||
form: {
|
||||
titleColon: true // 是否显示标题冒号
|
||||
},
|
||||
modal: {
|
||||
width: 800, // 窗口的宽度
|
||||
height: 600, // 窗口的高度
|
||||
minWidth: 460,
|
||||
minHeight: 320,
|
||||
showZoom: true, // 标题是否标显示最大化与还原按钮
|
||||
resize: true, // 是否允许窗口边缘拖动调整窗口大小
|
||||
marginSize: 0, // 只对 resize 启用后有效,用于设置可拖动界限范围,如果为负数则允许拖动超出屏幕边界
|
||||
remember: false, // 记忆功能,会记住最后操作状态,再次打开窗口时还原窗口状态
|
||||
destroyOnClose: true, // 在窗口关闭时销毁内容
|
||||
storage: false, // 是否启用 localStorage 本地保存,会将窗口拖动的状态保存到本地
|
||||
transfer: true, // 是否将弹框容器插入于 body 内
|
||||
showFooter: true, // 是否显示底部
|
||||
mask: true, // 是否显示遮罩层
|
||||
maskClosable: true, // 是否允许点击遮罩层关闭窗口
|
||||
escClosable: true // 是否允许按 Esc 键关闭窗口
|
||||
},
|
||||
i18n: (key, args) => {
|
||||
return unref(i18n.global.locale) === 'zh-CN'
|
||||
? XEUtils.toFormatString(XEUtils.get(zhCN, key), args)
|
||||
: XEUtils.toFormatString(XEUtils.get(enUS, key), args)
|
||||
}
|
||||
})
|
||||
// 自定义全局的格式化处理函数
|
||||
VXETable.formats.mixin({
|
||||
// 格式精简日期,默认 yyyy-MM-dd HH:mm:ss
|
||||
formatDay({ cellValue }, format) {
|
||||
if (cellValue != null) {
|
||||
return XEUtils.toDateString(cellValue, format || 'yyyy-MM-dd')
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
// 格式完整日期,默认 yyyy-MM-dd HH:mm:ss
|
||||
formatDate({ cellValue }, format) {
|
||||
if (cellValue != null) {
|
||||
return XEUtils.toDateString(cellValue, format || 'yyyy-MM-dd HH:mm:ss')
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
// 四舍五入金额,每隔3位逗号分隔,默认2位数
|
||||
formatAmount({ cellValue }, digits = 2) {
|
||||
return XEUtils.commafy(Number(cellValue), { digits })
|
||||
},
|
||||
// 格式化银行卡,默认每4位空格隔开
|
||||
formatBankcard({ cellValue }) {
|
||||
return XEUtils.commafy(XEUtils.toValueString(cellValue), { spaceNumber: 4, separator: ' ' })
|
||||
},
|
||||
// 四舍五入,默认两位数
|
||||
formatFixedNumber({ cellValue }, digits = 2) {
|
||||
return XEUtils.toFixed(XEUtils.round(cellValue, digits), digits)
|
||||
},
|
||||
// 向下舍入,默认两位数
|
||||
formatCutNumber({ cellValue }, digits = 2) {
|
||||
return XEUtils.toFixed(XEUtils.floor(cellValue, digits), digits)
|
||||
},
|
||||
// 格式化图片,将图片链接转换为html标签
|
||||
formatImg({ cellValue }) {
|
||||
return '<img height="40" src="' + cellValue + '"> '
|
||||
},
|
||||
// 格式化文件大小
|
||||
formatSize({ cellValue }, digits = 0) {
|
||||
const unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
const srcSize = parseFloat(cellValue)
|
||||
const index = Math.floor(Math.log(srcSize) / Math.log(1024))
|
||||
const size = srcSize / Math.pow(1024, index)
|
||||
return XEUtils.toFixed(XEUtils.floor(size, 2), 2) + ' ' + unitArr[digits]
|
||||
}
|
||||
})
|
||||
export const setupVxeTable = (app: App<Element>) => {
|
||||
// 表格功能
|
||||
app.use(Filter).use(Edit).use(Menu).use(Export).use(Keyboard).use(Validator)
|
||||
|
||||
// 可选组件
|
||||
app
|
||||
.use(Icon)
|
||||
.use(Column)
|
||||
.use(Colgroup)
|
||||
.use(Grid)
|
||||
.use(Tooltip)
|
||||
.use(Toolbar)
|
||||
.use(Pager)
|
||||
.use(Form)
|
||||
.use(FormItem)
|
||||
.use(FormGather)
|
||||
.use(Checkbox)
|
||||
.use(CheckboxGroup)
|
||||
.use(Radio)
|
||||
.use(RadioGroup)
|
||||
.use(RadioButton)
|
||||
.use(Switch)
|
||||
.use(Input)
|
||||
.use(Select)
|
||||
.use(Optgroup)
|
||||
.use(Option)
|
||||
.use(Textarea)
|
||||
.use(Button)
|
||||
.use(Modal)
|
||||
.use(List)
|
||||
.use(Pulldown)
|
||||
|
||||
// 安装表格
|
||||
.use(Table)
|
||||
|
||||
// 给 vue 实例挂载内部对象,例如:
|
||||
// app.config.globalProperties.$XModal = VXETable.modal
|
||||
// app.config.globalProperties.$XPrint = VXETable.print
|
||||
// app.config.globalProperties.$XSaveFile = VXETable.saveFile
|
||||
// app.config.globalProperties.$XReadFile = VXETable.readFile
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import { ElDatePicker } from 'element-plus'
|
||||
import { VXETable } from 'vxe-table'
|
||||
|
||||
// 日期区间选择渲染
|
||||
VXETable.renderer.add('XDataPicker', {
|
||||
// 默认显示模板
|
||||
renderItemContent(renderOpts, params) {
|
||||
const { data, field } = params
|
||||
const { content } = renderOpts
|
||||
return (
|
||||
<ElDatePicker
|
||||
v-model={data[field]}
|
||||
style="width: 100%"
|
||||
type={content ? (content as any) : 'datetime'}
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
clearable
|
||||
></ElDatePicker>
|
||||
)
|
||||
}
|
||||
})
|
|
@ -1,23 +0,0 @@
|
|||
import { ElDatePicker } from 'element-plus'
|
||||
import { VXETable } from 'vxe-table'
|
||||
|
||||
// 日期区间选择渲染
|
||||
VXETable.renderer.add('XDataTimePicker', {
|
||||
// 默认显示模板
|
||||
renderItemContent(renderOpts, params) {
|
||||
const { t } = useI18n()
|
||||
const { data, field } = params
|
||||
const { content } = renderOpts
|
||||
return (
|
||||
<ElDatePicker
|
||||
v-model={data[field]}
|
||||
style="width: 100%"
|
||||
type={content ? (content as any) : 'datetimerange'}
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
range-separator="-"
|
||||
start-placeholder={t('common.startTimeText')}
|
||||
end-placeholder={t('common.endTimeText')}
|
||||
></ElDatePicker>
|
||||
)
|
||||
}
|
||||
})
|
|
@ -1,12 +0,0 @@
|
|||
import { DictTag } from '@/components/DictTag'
|
||||
import { VXETable } from 'vxe-table'
|
||||
|
||||
// 字典渲染
|
||||
VXETable.renderer.add('XDict', {
|
||||
// 默认显示模板
|
||||
renderDefault(renderOpts, params) {
|
||||
const { row, column } = params
|
||||
const { content } = renderOpts
|
||||
return <DictTag type={content as unknown as string} value={row[column.field]}></DictTag>
|
||||
}
|
||||
})
|
|
@ -1,10 +0,0 @@
|
|||
import { VXETable } from 'vxe-table'
|
||||
|
||||
// 图片渲染
|
||||
VXETable.renderer.add('XHtml', {
|
||||
// 默认显示模板
|
||||
renderDefault(_renderOpts, params) {
|
||||
const { row, column } = params
|
||||
return <span v-html={row[column.field]}></span>
|
||||
}
|
||||
})
|
|
@ -1,20 +0,0 @@
|
|||
import { VXETable } from 'vxe-table'
|
||||
import { ElImage } from 'element-plus'
|
||||
|
||||
// 图片渲染
|
||||
VXETable.renderer.add('XImg', {
|
||||
// 默认显示模板
|
||||
renderDefault(_renderOpts, params) {
|
||||
const { row, column } = params
|
||||
return (
|
||||
<ElImage
|
||||
style="width: 80px; height: 50px"
|
||||
src={row[column.field]}
|
||||
key={row[column.field]}
|
||||
preview-src-list={[row[column.field]]}
|
||||
fit="contain"
|
||||
lazy
|
||||
></ElImage>
|
||||
)
|
||||
}
|
||||
})
|
|
@ -1,7 +0,0 @@
|
|||
import './dataPicker'
|
||||
import './dataTimeRangePicker'
|
||||
import './dict'
|
||||
import './html'
|
||||
import './link'
|
||||
import './img'
|
||||
import './preview'
|
|
@ -1,15 +0,0 @@
|
|||
import { VXETable } from 'vxe-table'
|
||||
|
||||
// 超链接渲染
|
||||
VXETable.renderer.add('XLink', {
|
||||
// 默认显示模板
|
||||
renderDefault(renderOpts, params) {
|
||||
const { row, column } = params
|
||||
const { events = {} } = renderOpts
|
||||
return (
|
||||
<a class="link" onClick={() => events.click(params)}>
|
||||
{row[column.field]}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
})
|
|
@ -1,35 +0,0 @@
|
|||
import { VXETable } from 'vxe-table'
|
||||
import { ElImage, ElLink } from 'element-plus'
|
||||
|
||||
// 图片渲染
|
||||
VXETable.renderer.add('XPreview', {
|
||||
// 默认显示模板
|
||||
renderDefault(_renderOpts, params) {
|
||||
const { row, column } = params
|
||||
if (row.type.indexOf('image/') === 0) {
|
||||
return (
|
||||
<ElImage
|
||||
style="width: 80px; height: 50px"
|
||||
src={row[column.field]}
|
||||
key={row[column.field]}
|
||||
preview-src-list={[row[column.field]]}
|
||||
fit="contain"
|
||||
lazy
|
||||
></ElImage>
|
||||
)
|
||||
} else if (row.type.indexOf('video/') === 0) {
|
||||
return (
|
||||
<video>
|
||||
<source src={row[column.field]}></source>
|
||||
</video>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
// @ts-ignore
|
||||
<ElLink href={row[column.field]} target="_blank">
|
||||
{row[column.field]}
|
||||
</ElLink>
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
|
@ -120,8 +120,6 @@ declare module '@vue/runtime-core' {
|
|||
VerifyPoints: typeof import('./../components/Verifition/src/Verify/VerifyPoints.vue')['default']
|
||||
VerifySlide: typeof import('./../components/Verifition/src/Verify/VerifySlide.vue')['default']
|
||||
XButton: typeof import('./../components/XButton/src/XButton.vue')['default']
|
||||
XModal: typeof import('./../components/XModal/src/XModal.vue')['default']
|
||||
XTable: typeof import('./../components/XTable/src/XTable.vue')['default']
|
||||
XTextButton: typeof import('./../components/XButton/src/XTextButton.vue')['default']
|
||||
}
|
||||
export interface ComponentCustomProperties {
|
||||
|
|
|
@ -6,7 +6,6 @@ export {}
|
|||
declare global {
|
||||
const DICT_TYPE: typeof import('@/utils/dict')['DICT_TYPE']
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
|
|
|
@ -114,6 +114,10 @@ export const PayChannelEnum = {
|
|||
ALIPAY_QR: {
|
||||
code: 'alipay_qr',
|
||||
name: '支付宝扫码支付'
|
||||
},
|
||||
ALIPAY_BAR: {
|
||||
code: 'alipay_bar',
|
||||
name: '支付宝条码支付'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ export interface DictDataType {
|
|||
}
|
||||
|
||||
export const getDictOptions = (dictType: string) => {
|
||||
return dictStore.getDictByType(dictType)
|
||||
return dictStore.getDictByType(dictType) || []
|
||||
}
|
||||
|
||||
export const getIntDictOptions = (dictType: string) => {
|
||||
|
@ -117,6 +117,7 @@ export enum DICT_TYPE {
|
|||
INFRA_API_ERROR_LOG_PROCESS_STATUS = 'infra_api_error_log_process_status',
|
||||
INFRA_CONFIG_TYPE = 'infra_config_type',
|
||||
INFRA_CODEGEN_TEMPLATE_TYPE = 'infra_codegen_template_type',
|
||||
INFRA_CODEGEN_FRONT_TYPE = 'infra_codegen_front_type',
|
||||
INFRA_CODEGEN_SCENE = 'infra_codegen_scene',
|
||||
INFRA_FILE_STORAGE = 'infra_file_storage',
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<Dialog title="详情" v-model="modelVisible" :scroll="true" :max-height="200">
|
||||
<Dialog title="详情" v-model="dialogVisible" :scroll="true" :max-height="200">
|
||||
<el-descriptions border :column="1">
|
||||
<el-descriptions-item label="请假类型">
|
||||
<dict-tag :type="DICT_TYPE.BPM_OA_LEAVE_TYPE" :value="detailData.type" />
|
||||
|
@ -21,13 +21,13 @@ import { DICT_TYPE } from '@/utils/dict'
|
|||
import { formatDate } from '@/utils/formatTime'
|
||||
import * as LeaveApi from '@/api/bpm/leave'
|
||||
|
||||
const modelVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const detailLoading = ref(false) // 表单的加载中
|
||||
const detailData = ref() // 详情数据
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (data: LeaveApi.LeaveVO) => {
|
||||
modelVisible.value = true
|
||||
dialogVisible.value = true
|
||||
// 设置数据
|
||||
detailLoading.value = true
|
||||
try {
|
||||
|
|
|
@ -30,7 +30,7 @@ const { query } = useRoute() // 查询参数
|
|||
const { delView } = useTagsViewStore() // 视图操作
|
||||
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const activeName = ref('basicInfo') // Tag 激活的窗口
|
||||
const activeName = ref('colum') // Tag 激活的窗口
|
||||
const basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>()
|
||||
const columInfoRef = ref<ComponentRef<typeof ColumInfoForm>>()
|
||||
const generateInfoRef = ref<ComponentRef<typeof GenerateInfoForm>>()
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
v-loading="loading"
|
||||
element-loading-text="生成文件目录中..."
|
||||
>
|
||||
<el-scrollbar height="calc(100vh - 88px - 40px - 50px)">
|
||||
<el-scrollbar height="calc(100vh - 88px - 40px)">
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
node-key="id"
|
||||
|
|
|
@ -13,6 +13,19 @@
|
|||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="frontType" label="前端类型">
|
||||
<el-select v-model="formData.frontType">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_FRONT_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="scene" label="生成场景">
|
||||
<el-select v-model="formData.scene">
|
||||
|
@ -25,6 +38,26 @@
|
|||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<span>
|
||||
上级菜单
|
||||
<el-tooltip content="分配到指定菜单下,例如 系统管理" placement="top">
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-tree-select
|
||||
v-model="formData.parentMenuId"
|
||||
placeholder="请选择系统菜单"
|
||||
node-key="id"
|
||||
check-strictly
|
||||
:data="menus"
|
||||
:props="menuTreeProps"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- <el-col :span="12">-->
|
||||
<!-- <el-form-item prop="packageName">-->
|
||||
|
@ -115,27 +148,6 @@
|
|||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<span>
|
||||
上级菜单
|
||||
<el-tooltip content="分配到指定菜单下,例如 系统管理" placement="top">
|
||||
<Icon icon="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-tree-select
|
||||
v-model="formData.parentMenuId"
|
||||
placeholder="请选择系统菜单"
|
||||
node-key="id"
|
||||
check-strictly
|
||||
:data="menus"
|
||||
:props="menuTreeProps"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24" v-if="formData.genType === '1'">
|
||||
<el-form-item prop="genPath">
|
||||
<template #label>
|
||||
|
@ -297,6 +309,7 @@ const props = defineProps({
|
|||
const formRef = ref()
|
||||
const formData = ref({
|
||||
templateType: null,
|
||||
frontType: null,
|
||||
scene: null,
|
||||
moduleName: '',
|
||||
businessName: '',
|
||||
|
@ -315,6 +328,7 @@ const formData = ref({
|
|||
|
||||
const rules = reactive({
|
||||
templateType: [required],
|
||||
frontType: [required],
|
||||
scene: [required],
|
||||
moduleName: [required],
|
||||
businessName: [required],
|
||||
|
|
|
@ -3,33 +3,16 @@
|
|||
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form class="-mb-15px" :model="queryParams" :inline="true" label-width="68px">
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<el-select v-model="queryParams.accountId" placeholder="请选择公众号" class="!w-240px">
|
||||
<el-option
|
||||
v-for="item in accountList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
|
||||
<WxMpSelect @change="onAccountChanged" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- tab 切换 -->
|
||||
<ContentWrap>
|
||||
<el-tabs v-model="type" @tab-change="handleTabChange">
|
||||
<el-tabs v-model="msgType" @tab-change="handleTabChange">
|
||||
<!-- 操作工具栏 -->
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
|
@ -38,26 +21,26 @@
|
|||
plain
|
||||
@click="handleAdd"
|
||||
v-hasPermi="['mp:auto-reply:create']"
|
||||
v-if="type !== '1' || list.length <= 0"
|
||||
v-if="msgType !== MsgType.Follow || list.length <= 0"
|
||||
>
|
||||
<Icon icon="ep:plus" />新增
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- tab 项 -->
|
||||
<el-tab-pane name="1">
|
||||
<el-tab-pane :name="MsgType.Follow">
|
||||
<template #label>
|
||||
<span><Icon icon="ep:star-off" /> 关注时回复</span>
|
||||
<span><Icon icon="ep:star" /> 关注时回复</span>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="2">
|
||||
<el-tab-pane :name="MsgType.Message">
|
||||
<template #label>
|
||||
<span><Icon icon="ep:chat-line-round" /> 消息回复</span>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="3">
|
||||
<el-tab-pane :name="MsgType.Keyword">
|
||||
<template #label>
|
||||
<span><Icon icon="ep:news" /> 关键词回复</span>
|
||||
<span><Icon icon="fa:newspaper-o" /> 关键词回复</span>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
@ -67,10 +50,20 @@
|
|||
label="请求消息类型"
|
||||
align="center"
|
||||
prop="requestMessageType"
|
||||
v-if="type === '2'"
|
||||
v-if="msgType === MsgType.Message"
|
||||
/>
|
||||
<el-table-column label="关键词" align="center" prop="requestKeyword" v-if="type === '3'" />
|
||||
<el-table-column label="匹配类型" align="center" prop="requestMatch" v-if="type === '3'">
|
||||
<el-table-column
|
||||
label="关键词"
|
||||
align="center"
|
||||
prop="requestKeyword"
|
||||
v-if="msgType === MsgType.Keyword"
|
||||
/>
|
||||
<el-table-column
|
||||
label="匹配类型"
|
||||
align="center"
|
||||
prop="requestMatch"
|
||||
v-if="msgType === MsgType.Keyword"
|
||||
>
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH" :value="scope.row.requestMatch" />
|
||||
</template>
|
||||
|
@ -84,7 +77,7 @@
|
|||
<template #default="scope">
|
||||
<div v-if="scope.row.responseMessageType === 'text'">{{ scope.row.responseContent }}</div>
|
||||
<div v-else-if="scope.row.responseMessageType === 'voice'">
|
||||
<WxVoicePlayer :url="scope.row.responseMediaUrl" />
|
||||
<WxVoicePlayer v-if="scope.row.responseMediaUrl" :url="scope.row.responseMediaUrl" />
|
||||
</div>
|
||||
<div v-else-if="scope.row.responseMessageType === 'image'">
|
||||
<a target="_blank" :href="scope.row.responseMediaUrl">
|
||||
|
@ -97,7 +90,11 @@
|
|||
scope.row.responseMessageType === 'shortvideo'
|
||||
"
|
||||
>
|
||||
<WxVideoPlayer :url="scope.row.responseMediaUrl" style="margin-top: 10px" />
|
||||
<WxVideoPlayer
|
||||
v-if="scope.row.responseMediaUrl"
|
||||
:url="scope.row.responseMediaUrl"
|
||||
style="margin-top: 10px"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="scope.row.responseMessageType === 'news'">
|
||||
<WxNews :articles="scope.row.responseArticles" />
|
||||
|
@ -143,21 +140,21 @@
|
|||
</el-table>
|
||||
|
||||
<!-- 添加或修改自动回复的对话框 -->
|
||||
<el-dialog :title="title" v-model="open" width="800px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="消息类型" prop="requestMessageType" v-if="type === '2'">
|
||||
<el-select v-model="form.requestMessageType" placeholder="请选择">
|
||||
<el-dialog :title="title" v-model="showReplyFormDialog" width="800px" append-to-body>
|
||||
<el-form ref="formRef" :model="replyForm" :rules="rules" label-width="80px">
|
||||
<el-form-item label="消息类型" prop="requestMessageType" v-if="msgType === MsgType.Message">
|
||||
<el-select v-model="replyForm.requestMessageType" placeholder="请选择">
|
||||
<template v-for="dict in getDictOptions(DICT_TYPE.MP_MESSAGE_TYPE)" :key="dict.value">
|
||||
<el-option
|
||||
v-if="requestMessageTypes.includes(dict.value)"
|
||||
v-if="RequestMessageTypes.includes(dict.value)"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</template>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="匹配类型" prop="requestMatch" v-if="type === '3'">
|
||||
<el-select v-model="form.requestMatch" placeholder="请选择匹配类型" clearable>
|
||||
<el-form-item label="匹配类型" prop="requestMatch" v-if="msgType === MsgType.Keyword">
|
||||
<el-select v-model="replyForm.requestMatch" placeholder="请选择匹配类型" clearable>
|
||||
<el-option
|
||||
v-for="dict in getDictOptions(DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH)"
|
||||
:key="dict.value"
|
||||
|
@ -166,8 +163,8 @@
|
|||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="关键词" prop="requestKeyword" v-if="type === '3'">
|
||||
<el-input v-model="form.requestKeyword" placeholder="请输入内容" clearable />
|
||||
<el-form-item label="关键词" prop="requestKeyword" v-if="msgType === MsgType.Keyword">
|
||||
<el-input v-model="replyForm.requestKeyword" placeholder="请输入内容" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="回复消息">
|
||||
<WxReplySelect :objData="objData" v-if="hackResetWxReplySelect" />
|
||||
|
@ -180,45 +177,47 @@
|
|||
</el-dialog>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup name="MpAutoReply">
|
||||
import { ref, reactive, onMounted, nextTick } from 'vue'
|
||||
<script setup lang="ts" name="MpAutoReply">
|
||||
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
|
||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
|
||||
import WxMusic from '@/views/mp/components/wx-music/main.vue'
|
||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
|
||||
import { getSimpleAccountList } from '@/api/mp/account'
|
||||
import {
|
||||
createAutoReply,
|
||||
deleteAutoReply,
|
||||
getAutoReply,
|
||||
getAutoReplyPage,
|
||||
updateAutoReply
|
||||
} from '@/api/mp/autoReply'
|
||||
|
||||
import WxMpSelect from '@/views/mp/components/WxMpSelect.vue'
|
||||
import * as MpAutoReplyApi from '@/api/mp/autoReply'
|
||||
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import type { TabPaneName } from 'element-plus'
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const queryFormRef = ref()
|
||||
const formRef = ref()
|
||||
|
||||
// tab 类型(1、关注时回复;2、消息回复;3、关键词回复)
|
||||
const type = ref('3')
|
||||
// 消息类型(Follow: 关注时回复;Message: 消息回复;Keyword: 关键词回复)
|
||||
// 作为tab.name
|
||||
enum MsgType {
|
||||
Follow = 1,
|
||||
Message = 2,
|
||||
Keyword = 3
|
||||
}
|
||||
const msgType = ref<MsgType>(MsgType.Keyword)
|
||||
// 允许选择的请求消息类型
|
||||
const requestMessageTypes = ['text', 'image', 'voice', 'video', 'shortvideo', 'location', 'link']
|
||||
const RequestMessageTypes = ['text', 'image', 'voice', 'video', 'shortvideo', 'location', 'link']
|
||||
// 遮罩层
|
||||
const loading = ref(true)
|
||||
// 显示搜索条件
|
||||
// const showSearch = ref(true)
|
||||
// 总条数
|
||||
const total = ref(0)
|
||||
// 自动回复列表
|
||||
const list = ref([])
|
||||
const list = ref<any[]>([])
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
interface QueryParams {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
accountId?: number
|
||||
}
|
||||
const queryParams: QueryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
accountId: undefined
|
||||
|
@ -227,12 +226,50 @@ const queryParams = reactive({
|
|||
// 弹出层标题
|
||||
const title = ref('')
|
||||
// 是否显示弹出层
|
||||
const open = ref(false)
|
||||
const showReplyFormDialog = ref(false)
|
||||
// 表单参数
|
||||
const form = ref({})
|
||||
type ReplyType = 'text' | 'image' | 'voice' | 'video' | 'shortvideo' | 'location' | 'link'
|
||||
interface ReplyForm {
|
||||
// relation:
|
||||
id?: number
|
||||
accountId?: number
|
||||
type?: MsgType
|
||||
// request:
|
||||
requestMessageType?: ReplyType
|
||||
requestMatch?: number
|
||||
requestKeyword?: string
|
||||
// response:
|
||||
responseMessageType?: ReplyType
|
||||
responseContent?: string
|
||||
responseMediaId?: number
|
||||
responseMediaUrl?: string
|
||||
responseTitle?: string
|
||||
responseDescription?: number
|
||||
responseThumbMediaId?: string
|
||||
responseThumbMediaUrl?: string
|
||||
responseArticles?: any[]
|
||||
responseMusicUrl?: string
|
||||
responseHqMusicUrl?: string
|
||||
}
|
||||
interface ObjData {
|
||||
type: ReplyType
|
||||
accountId?: number
|
||||
content?: string
|
||||
mediaId?: number
|
||||
url?: string
|
||||
title?: string
|
||||
description?: string
|
||||
thumbMediaId?: number
|
||||
thumbMediaUrl?: string
|
||||
articles?: any[]
|
||||
musicUrl?: string
|
||||
hqMusicUrl?: string
|
||||
}
|
||||
const replyForm = ref<ReplyForm>({})
|
||||
// 回复消息
|
||||
const objData = ref({
|
||||
type: 'text'
|
||||
const objData = ref<ObjData>({
|
||||
type: 'text',
|
||||
accountId: undefined
|
||||
})
|
||||
// 表单校验
|
||||
const rules = {
|
||||
|
@ -240,43 +277,27 @@ const rules = {
|
|||
requestMatch: [{ required: true, message: '请求的关键字的匹配不能为空', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const hackResetWxReplySelect = ref(false) // 重置 WxReplySelect 组件,解决无法清除的问题
|
||||
// 重置 WxReplySelect 组件,解决无法清除的问题
|
||||
const hackResetWxReplySelect = ref(false)
|
||||
|
||||
// 公众号账号列表
|
||||
const accountList = ref([])
|
||||
|
||||
onMounted(() => {
|
||||
getSimpleAccountList().then((data) => {
|
||||
accountList.value = data
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
queryParams.accountId = accountList.value[0].id
|
||||
}
|
||||
// 加载数据
|
||||
getList()
|
||||
})
|
||||
})
|
||||
const onAccountChanged = (id?: number) => {
|
||||
queryParams.accountId = id
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
// 如果没有选中公众号账号,则进行提示。
|
||||
if (!queryParams.accountId) {
|
||||
message.error('未选中公众号,无法查询自动回复')
|
||||
return false
|
||||
}
|
||||
|
||||
loading.value = false
|
||||
// 处理查询参数
|
||||
let params = {
|
||||
...queryParams,
|
||||
type: type.value
|
||||
}
|
||||
// 执行查询
|
||||
getAutoReplyPage(params).then((data) => {
|
||||
try {
|
||||
const data = await MpAutoReplyApi.getAutoReplyPage({
|
||||
...queryParams,
|
||||
type: msgType.value
|
||||
})
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
|
@ -285,18 +306,8 @@ const handleQuery = () => {
|
|||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields()
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
queryParams.accountId = accountList.value[0].id
|
||||
}
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
const handleTabChange = (tabName) => {
|
||||
type.value = tabName
|
||||
const handleTabChange = (tabName: TabPaneName) => {
|
||||
msgType.value = tabName as MsgType
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
|
@ -305,94 +316,87 @@ const handleAdd = () => {
|
|||
reset()
|
||||
resetEditor()
|
||||
// 打开表单,并设置初始化
|
||||
open.value = true
|
||||
title.value = '新增自动回复'
|
||||
objData.value = {
|
||||
type: 'text',
|
||||
accountId: queryParams.accountId
|
||||
}
|
||||
|
||||
title.value = '新增自动回复'
|
||||
showReplyFormDialog.value = true
|
||||
}
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = (row) => {
|
||||
const handleUpdate = async (row: any) => {
|
||||
reset()
|
||||
resetEditor()
|
||||
console.log(row)
|
||||
|
||||
getAutoReply(row.id).then((data) => {
|
||||
// 设置属性
|
||||
form.value = { ...data }
|
||||
delete form.value['responseMessageType']
|
||||
delete form.value['responseContent']
|
||||
delete form.value['responseMediaId']
|
||||
delete form.value['responseMediaUrl']
|
||||
delete form.value['responseDescription']
|
||||
delete form.value['responseArticles']
|
||||
objData.value = {
|
||||
type: data.responseMessageType,
|
||||
accountId: queryParams.accountId,
|
||||
content: data.responseContent,
|
||||
mediaId: data.responseMediaId,
|
||||
url: data.responseMediaUrl,
|
||||
title: data.responseTitle,
|
||||
description: data.responseDescription,
|
||||
thumbMediaId: data.responseThumbMediaId,
|
||||
thumbMediaUrl: data.responseThumbMediaUrl,
|
||||
articles: data.responseArticles,
|
||||
musicUrl: data.responseMusicUrl,
|
||||
hqMusicUrl: data.responseHqMusicUrl
|
||||
}
|
||||
const data = await MpAutoReplyApi.getAutoReply(row.id)
|
||||
// 设置属性
|
||||
replyForm.value = { ...data }
|
||||
delete replyForm.value['responseMessageType']
|
||||
delete replyForm.value['responseContent']
|
||||
delete replyForm.value['responseMediaId']
|
||||
delete replyForm.value['responseMediaUrl']
|
||||
delete replyForm.value['responseDescription']
|
||||
delete replyForm.value['responseArticles']
|
||||
objData.value = {
|
||||
type: data.responseMessageType,
|
||||
accountId: queryParams.accountId,
|
||||
content: data.responseContent,
|
||||
mediaId: data.responseMediaId,
|
||||
url: data.responseMediaUrl,
|
||||
title: data.responseTitle,
|
||||
description: data.responseDescription,
|
||||
thumbMediaId: data.responseThumbMediaId,
|
||||
thumbMediaUrl: data.responseThumbMediaUrl,
|
||||
articles: data.responseArticles,
|
||||
musicUrl: data.responseMusicUrl,
|
||||
hqMusicUrl: data.responseHqMusicUrl
|
||||
}
|
||||
|
||||
// 打开表单
|
||||
open.value = true
|
||||
title.value = '修改自动回复'
|
||||
})
|
||||
// 打开表单
|
||||
title.value = '修改自动回复'
|
||||
showReplyFormDialog.value = true
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
formRef.value?.validate((valid) => {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
const handleSubmit = async () => {
|
||||
const valid = await formRef.value?.validate()
|
||||
if (!valid) return
|
||||
|
||||
// 处理回复消息
|
||||
const form = { ...form.value }
|
||||
form.responseMessageType = objData.value.type
|
||||
form.responseContent = objData.value.content
|
||||
form.responseMediaId = objData.value.mediaId
|
||||
form.responseMediaUrl = objData.value.url
|
||||
form.responseTitle = objData.value.title
|
||||
form.responseDescription = objData.value.description
|
||||
form.responseThumbMediaId = objData.value.thumbMediaId
|
||||
form.responseThumbMediaUrl = objData.value.thumbMediaUrl
|
||||
form.responseArticles = objData.value.articles
|
||||
form.responseMusicUrl = objData.value.musicUrl
|
||||
form.responseHqMusicUrl = objData.value.hqMusicUrl
|
||||
// 处理回复消息
|
||||
const submitForm: any = { ...replyForm.value }
|
||||
submitForm.responseMessageType = objData.value.type
|
||||
submitForm.responseContent = objData.value.content
|
||||
submitForm.responseMediaId = objData.value.mediaId
|
||||
submitForm.responseMediaUrl = objData.value.url
|
||||
submitForm.responseTitle = objData.value.title
|
||||
submitForm.responseDescription = objData.value.description
|
||||
submitForm.responseThumbMediaId = objData.value.thumbMediaId
|
||||
submitForm.responseThumbMediaUrl = objData.value.thumbMediaUrl
|
||||
submitForm.responseArticles = objData.value.articles
|
||||
submitForm.responseMusicUrl = objData.value.musicUrl
|
||||
submitForm.responseHqMusicUrl = objData.value.hqMusicUrl
|
||||
|
||||
if (form.value.id !== undefined) {
|
||||
updateAutoReply(form).then(() => {
|
||||
message.success('修改成功')
|
||||
open.value = false
|
||||
getList()
|
||||
})
|
||||
} else {
|
||||
createAutoReply(form).then(() => {
|
||||
message.success('新增成功')
|
||||
open.value = false
|
||||
getList()
|
||||
})
|
||||
}
|
||||
})
|
||||
if (replyForm.value.id !== undefined) {
|
||||
await MpAutoReplyApi.updateAutoReply(submitForm)
|
||||
message.success('修改成功')
|
||||
} else {
|
||||
await MpAutoReplyApi.createAutoReply(submitForm)
|
||||
message.success('新增成功')
|
||||
}
|
||||
|
||||
showReplyFormDialog.value = false
|
||||
getList()
|
||||
}
|
||||
|
||||
// 表单重置
|
||||
const reset = () => {
|
||||
form.value = {
|
||||
replyForm.value = {
|
||||
id: undefined,
|
||||
accountId: queryParams.accountId,
|
||||
type: type.value,
|
||||
type: msgType.value,
|
||||
requestKeyword: undefined,
|
||||
requestMatch: type.value === '3' ? 1 : undefined,
|
||||
requestMatch: msgType.value === MsgType.Keyword ? 1 : undefined,
|
||||
requestMessageType: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
|
@ -400,7 +404,7 @@ const reset = () => {
|
|||
|
||||
// 取消按钮
|
||||
const cancel = () => {
|
||||
open.value = false
|
||||
showReplyFormDialog.value = false
|
||||
reset()
|
||||
}
|
||||
|
||||
|
@ -414,7 +418,7 @@ const resetEditor = () => {
|
|||
|
||||
const handleDelete = async (row) => {
|
||||
await message.confirm('是否确认删除此数据?')
|
||||
await deleteAutoReply(row.id)
|
||||
await MpAutoReplyApi.deleteAutoReply(row.id)
|
||||
await getList()
|
||||
message.success('删除成功')
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<el-select v-model="account.id" placeholder="请选择公众号" class="!w-240px" @change="onChanged">
|
||||
<el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</template>
|
||||
<!-- TODO @芋艿:WxMpSelect 改成 WxAccountSelect,然后挪到现有的 wx-account-select 包下 -->
|
||||
<script lang="ts" setup name="WxMpSelect">
|
||||
import * as MpAccountApi from '@/api/mp/account'
|
||||
|
||||
const account: MpAccountApi.AccountVO = reactive({
|
||||
id: undefined,
|
||||
name: ''
|
||||
})
|
||||
const accountList: Ref<MpAccountApi.AccountVO[]> = ref([])
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'change', id?: number, name?: string): void
|
||||
}>()
|
||||
|
||||
onMounted(() => {
|
||||
handleQuery()
|
||||
})
|
||||
|
||||
const handleQuery = async () => {
|
||||
accountList.value = await MpAccountApi.getSimpleAccountList()
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
account.id = accountList.value[0].id
|
||||
emit('change', account.id, account.name)
|
||||
}
|
||||
}
|
||||
|
||||
const onChanged = () => {
|
||||
emit('change', account.id, account.name)
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,44 @@
|
|||
<template>
|
||||
<el-form class="-mb-15px" ref="queryFormRef" :inline="true" label-width="68px">
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<!-- TODO 芋艿:需要将 el-form 和 el-select 解耦 -->
|
||||
<el-select
|
||||
v-model="accountId"
|
||||
placeholder="请选择公众号"
|
||||
class="!w-240px"
|
||||
@change="accountChanged()"
|
||||
>
|
||||
<el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<slot name="actions"></slot>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup name="WxAccountSelect">
|
||||
import * as MpAccountApi from '@/api/mp/account'
|
||||
const accountId = ref()
|
||||
const accountList = ref([])
|
||||
const queryFormRef = ref()
|
||||
|
||||
const emit = defineEmits(['change'])
|
||||
|
||||
onMounted(() => {
|
||||
handleQuery()
|
||||
})
|
||||
|
||||
const handleQuery = async () => {
|
||||
accountList.value = await MpAccountApi.getSimpleAccountList()
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
accountId.value = accountList.value[0].id
|
||||
emit('change', accountId.value)
|
||||
}
|
||||
}
|
||||
|
||||
const accountChanged = () => {
|
||||
emit('change', accountId.value)
|
||||
}
|
||||
</script>
|
|
@ -1,11 +1,11 @@
|
|||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { QuillEditor } from '@vueup/vue-quill'
|
||||
import '@vueup/vue-quill/dist/vue-quill.snow.css'
|
||||
import { getAccessToken } from '@/utils/auth'
|
||||
import editorOptions from './quill-options'
|
||||
import { Editor } from '@/components/Editor'
|
||||
|
||||
const BASE_URL = import.meta.env.VITE_BASE_URL
|
||||
const actionUrl = BASE_URL + '/admin-api/mp/material/upload-news-image' // 这里写你要上传的图片服务器地址
|
||||
const headers = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
|
@ -30,21 +30,16 @@ const props = defineProps({
|
|||
const emit = defineEmits(['input'])
|
||||
|
||||
const myQuillEditorRef = ref()
|
||||
|
||||
const content = ref(props.value.replace(/data-src/g, 'src'))
|
||||
|
||||
const loading = ref(false) // 根据图片上传状态来确定是否显示loading动画,刚开始是false,不显示
|
||||
|
||||
const actionUrl = ref(BASE_URL + '/admin-api/mp/material/upload-news-image') // 这里写你要上传的图片服务器地址
|
||||
const headers = ref({ Authorization: 'Bearer ' + getAccessToken() }) // 设置上传的请求头部
|
||||
const uploadData = reactive({
|
||||
type: 'image', // TODO 芋艿:试试要不要换成 thumb
|
||||
accountId: props.accountId
|
||||
})
|
||||
|
||||
const onEditorChange = () => {
|
||||
const onEditorChange = (text) => {
|
||||
//内容改变事件
|
||||
emit('input', content.value)
|
||||
emit('input', text)
|
||||
}
|
||||
|
||||
// 富文本图片上传前
|
||||
|
@ -98,104 +93,12 @@ const uploadError = () => {
|
|||
:on-error="uploadError"
|
||||
:before-upload="beforeUpload"
|
||||
/>
|
||||
<QuillEditor
|
||||
class="editor"
|
||||
v-model="content"
|
||||
<Editor
|
||||
editor-id="wxEditor"
|
||||
ref="quillEditorRef"
|
||||
:options="editorOptions"
|
||||
@change="onEditorChange($event)"
|
||||
:modelValue="content"
|
||||
@change="(editor) => onEditorChange(editor.getText())"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.editor {
|
||||
line-height: normal !important;
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.ql-snow .ql-tooltip[data-mode='link']::before {
|
||||
content: '请输入链接地址:';
|
||||
}
|
||||
|
||||
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
|
||||
border-right: 0;
|
||||
content: '保存';
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.ql-snow .ql-tooltip[data-mode='video']::before {
|
||||
content: '请输入视频地址:';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
|
||||
content: '14px';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='small']::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before {
|
||||
content: '10px';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='large']::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before {
|
||||
content: '18px';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='huge']::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before {
|
||||
content: '32px';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
|
||||
content: '文本';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before {
|
||||
content: '标题1';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before {
|
||||
content: '标题2';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before {
|
||||
content: '标题3';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before {
|
||||
content: '标题4';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before {
|
||||
content: '标题5';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before {
|
||||
content: '标题6';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
|
||||
content: '标准字体';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='serif']::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before {
|
||||
content: '衬线字体';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='monospace']::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before {
|
||||
content: '等宽字体';
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
/>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-icon><Location /></el-icon>
|
||||
<Icon icon="ep:location" />
|
||||
{{ label }}
|
||||
</el-row>
|
||||
|
@ -39,6 +38,7 @@
|
|||
</el-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="WxLocation">
|
||||
const props = defineProps({
|
||||
locationX: {
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
<p class="item-name">{{ item.name }}</p>
|
||||
<el-row class="ope-row">
|
||||
<el-button type="success" @click="selectMaterialFun(item)">
|
||||
选择 <Icon icon="ep:circle-check" />
|
||||
选择
|
||||
<Icon icon="ep:circle-check" />
|
||||
</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
|
@ -48,7 +49,8 @@
|
|||
<el-table-column label="操作" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="selectMaterialFun(scope.row)"
|
||||
>选择<Icon icon="ep:plus" />
|
||||
>选择
|
||||
<Icon icon="ep:plus" />
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
@ -89,7 +91,8 @@
|
|||
>
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="selectMaterialFun(scope.row)"
|
||||
>选择<Icon icon="akar-icons:circle-plus" />
|
||||
>选择
|
||||
<Icon icon="akar-icons:circle-plus" />
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
@ -110,7 +113,8 @@
|
|||
<WxNews :articles="item.content.newsItem" />
|
||||
<el-row class="ope-row">
|
||||
<el-button type="success" @click="selectMaterialFun(item)">
|
||||
选择<Icon icon="ep:circle-check" />
|
||||
选择
|
||||
<Icon icon="ep:circle-check" />
|
||||
</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
|
@ -127,126 +131,102 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="WxMaterialSelect">
|
||||
<script lang="ts" setup name="WxMaterialSelect">
|
||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
|
||||
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
|
||||
import { getMaterialPage } from '@/api/mp/material'
|
||||
import { getFreePublishPage } from '@/api/mp/freePublish'
|
||||
import { getDraftPage } from '@/api/mp/draft'
|
||||
import * as MpMaterialApi from '@/api/mp/material'
|
||||
import * as MpFreePublishApi from '@/api/mp/freePublish'
|
||||
import * as MpDraftApi from '@/api/mp/draft'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
WxNews,
|
||||
WxVoicePlayer,
|
||||
WxVideoPlayer
|
||||
const props = defineProps({
|
||||
objData: {
|
||||
type: Object, // type - 类型;accountId - 公众号账号编号
|
||||
required: true
|
||||
},
|
||||
props: {
|
||||
objData: {
|
||||
type: Object, // type - 类型;accountId - 公众号账号编号
|
||||
required: true
|
||||
},
|
||||
newsType: {
|
||||
// 图文类型:1、已发布图文;2、草稿箱图文
|
||||
type: String as PropType<string>,
|
||||
default: '1'
|
||||
}
|
||||
},
|
||||
setup(props, ctx) {
|
||||
// 遮罩层
|
||||
const loading = ref(false)
|
||||
// 总条数
|
||||
const total = ref(0)
|
||||
// 数据列表
|
||||
const list = ref([])
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
accountId: props.objData.accountId
|
||||
})
|
||||
const objDataRef = reactive(props.objData)
|
||||
const newsTypeRef = ref(props.newsType)
|
||||
|
||||
const selectMaterialFun = (item) => {
|
||||
ctx.emit('select-material', item)
|
||||
}
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getPage()
|
||||
}
|
||||
const getPage = () => {
|
||||
loading.value = true
|
||||
if (objDataRef.type === 'news' && newsTypeRef.value === '1') {
|
||||
// 【图文】+ 【已发布】
|
||||
getFreePublishPageFun()
|
||||
} else if (objDataRef.type === 'news' && newsTypeRef.value === '2') {
|
||||
// 【图文】+ 【草稿】
|
||||
getDraftPageFun()
|
||||
} else {
|
||||
// 【素材】
|
||||
getMaterialPageFun()
|
||||
}
|
||||
}
|
||||
|
||||
const getMaterialPageFun = async () => {
|
||||
let data = await getMaterialPage({
|
||||
...queryParams,
|
||||
type: objDataRef.type
|
||||
})
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const getFreePublishPageFun = async () => {
|
||||
let data = await getFreePublishPage(queryParams)
|
||||
data.list.forEach((item) => {
|
||||
const newsItem = item.content.newsItem
|
||||
newsItem.forEach((article) => {
|
||||
article.picUrl = article.thumbUrl
|
||||
})
|
||||
})
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const getDraftPageFun = async () => {
|
||||
let data = await getDraftPage(queryParams)
|
||||
data.list.forEach((item) => {
|
||||
const newsItem = item.content.newsItem
|
||||
newsItem.forEach((article) => {
|
||||
article.picUrl = article.thumbUrl
|
||||
})
|
||||
})
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
getPage()
|
||||
})
|
||||
|
||||
return {
|
||||
handleQuery,
|
||||
dateFormatter,
|
||||
selectMaterialFun,
|
||||
getMaterialPageFun,
|
||||
getPage,
|
||||
formatDate,
|
||||
queryParams,
|
||||
objDataRef,
|
||||
list,
|
||||
total,
|
||||
loading
|
||||
}
|
||||
newsType: {
|
||||
// 图文类型:1、已发布图文;2、草稿箱图文
|
||||
type: String as PropType<string>,
|
||||
default: '1'
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['select-material'])
|
||||
|
||||
// 遮罩层
|
||||
const loading = ref(false)
|
||||
// 总条数
|
||||
const total = ref(0)
|
||||
// 数据列表
|
||||
const list = ref([])
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
accountId: props.objData.accountId
|
||||
})
|
||||
const objDataRef = reactive(props.objData)
|
||||
const newsTypeRef = ref(props.newsType)
|
||||
|
||||
const selectMaterialFun = (item) => {
|
||||
emit('select-material', item)
|
||||
}
|
||||
|
||||
const getPage = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
if (objDataRef.type === 'news' && newsTypeRef.value === '1') {
|
||||
// 【图文】+ 【已发布】
|
||||
await getFreePublishPageFun()
|
||||
} else if (objDataRef.type === 'news' && newsTypeRef.value === '2') {
|
||||
// 【图文】+ 【草稿】
|
||||
await getDraftPageFun()
|
||||
} else {
|
||||
// 【素材】
|
||||
await getMaterialPageFun()
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const getMaterialPageFun = async () => {
|
||||
const data = await MpMaterialApi.getMaterialPage({
|
||||
...queryParams,
|
||||
type: objDataRef.type
|
||||
})
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
}
|
||||
|
||||
const getFreePublishPageFun = async () => {
|
||||
const data = await MpFreePublishApi.getFreePublishPage(queryParams)
|
||||
data.list.forEach((item) => {
|
||||
const newsItem = item.content.newsItem
|
||||
newsItem.forEach((article) => {
|
||||
article.picUrl = article.thumbUrl
|
||||
})
|
||||
})
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
}
|
||||
|
||||
const getDraftPageFun = async () => {
|
||||
const data = await MpDraftApi.getDraftPage(queryParams)
|
||||
data.list.forEach((item) => {
|
||||
const newsItem = item.content.newsItem
|
||||
newsItem.forEach((article) => {
|
||||
article.picUrl = article.thumbUrl
|
||||
})
|
||||
})
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
getPage()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
/*瀑布流样式*/
|
||||
|
@ -276,6 +256,7 @@ p {
|
|||
.waterfall {
|
||||
column-count: 3;
|
||||
}
|
||||
|
||||
p {
|
||||
color: red;
|
||||
}
|
||||
|
@ -285,6 +266,7 @@ p {
|
|||
.waterfall {
|
||||
column-count: 2;
|
||||
}
|
||||
|
||||
p {
|
||||
color: orange;
|
||||
}
|
||||
|
|
|
@ -39,79 +39,79 @@
|
|||
:style="item.sendFrom === 2 ? 'background: #6BED72;' : ''"
|
||||
>
|
||||
<!-- 【事件】区域 -->
|
||||
<div v-if="item.type === 'event' && item.event === 'subscribe'">
|
||||
<div v-if="item.type === MsgType.Event && item.event === 'subscribe'">
|
||||
<el-tag type="success">关注</el-tag>
|
||||
</div>
|
||||
<div v-else-if="item.type === 'event' && item.event === 'unsubscribe'">
|
||||
<div v-else-if="item.type === MsgType.Event && item.event === 'unsubscribe'">
|
||||
<el-tag type="danger">取消关注</el-tag>
|
||||
</div>
|
||||
<div v-else-if="item.type === 'event' && item.event === 'CLICK'">
|
||||
<div v-else-if="item.type === MsgType.Event && item.event === 'CLICK'">
|
||||
<el-tag>点击菜单</el-tag>
|
||||
【{{ item.eventKey }}】
|
||||
</div>
|
||||
<div v-else-if="item.type === 'event' && item.event === 'VIEW'">
|
||||
<div v-else-if="item.type === MsgType.Event && item.event === 'VIEW'">
|
||||
<el-tag>点击菜单链接</el-tag>
|
||||
【{{ item.eventKey }}】
|
||||
</div>
|
||||
<div v-else-if="item.type === 'event' && item.event === 'scancode_waitmsg'">
|
||||
<div v-else-if="item.type === MsgType.Event && item.event === 'scancode_waitmsg'">
|
||||
<el-tag>扫码结果</el-tag>
|
||||
【{{ item.eventKey }}】
|
||||
</div>
|
||||
<div v-else-if="item.type === 'event' && item.event === 'scancode_push'">
|
||||
<div v-else-if="item.type === MsgType.Event && item.event === 'scancode_push'">
|
||||
<el-tag>扫码结果</el-tag>
|
||||
【{{ item.eventKey }}】
|
||||
</div>
|
||||
<div v-else-if="item.type === 'event' && item.event === 'pic_sysphoto'">
|
||||
<div v-else-if="item.type === MsgType.Event && item.event === 'pic_sysphoto'">
|
||||
<el-tag>系统拍照发图</el-tag>
|
||||
</div>
|
||||
<div v-else-if="item.type === 'event' && item.event === 'pic_photo_or_album'">
|
||||
<div v-else-if="item.type === MsgType.Event && item.event === 'pic_photo_or_album'">
|
||||
<el-tag>拍照或者相册</el-tag>
|
||||
</div>
|
||||
<div v-else-if="item.type === 'event' && item.event === 'pic_weixin'">
|
||||
<div v-else-if="item.type === MsgType.Event && item.event === 'pic_weixin'">
|
||||
<el-tag>微信相册</el-tag>
|
||||
</div>
|
||||
<div v-else-if="item.type === 'event' && item.event === 'location_select'">
|
||||
<div v-else-if="item.type === MsgType.Event && item.event === 'location_select'">
|
||||
<el-tag>选择地理位置</el-tag>
|
||||
</div>
|
||||
<div v-else-if="item.type === 'event'">
|
||||
<div v-else-if="item.type === MsgType.Event">
|
||||
<el-tag type="danger">未知事件类型</el-tag>
|
||||
</div>
|
||||
<!-- 【消息】区域 -->
|
||||
<div v-else-if="item.type === 'text'">{{ item.content }}</div>
|
||||
<div v-else-if="item.type === 'voice'">
|
||||
<wx-voice-player :url="item.mediaUrl" :content="item.recognition" />
|
||||
<div v-else-if="item.type === MsgType.Text">{{ item.content }}</div>
|
||||
<div v-else-if="item.type === MsgType.Voice">
|
||||
<WxVoicePlayer :url="item.mediaUrl" :content="item.recognition" />
|
||||
</div>
|
||||
<div v-else-if="item.type === 'image'">
|
||||
<div v-else-if="item.type === MsgType.Image">
|
||||
<a target="_blank" :href="item.mediaUrl">
|
||||
<img :src="item.mediaUrl" style="width: 100px" />
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="item.type === 'video' || item.type === 'shortvideo'"
|
||||
v-else-if="item.type === MsgType.Video || item.type === 'shortvideo'"
|
||||
style="text-align: center"
|
||||
>
|
||||
<wx-video-player :url="item.mediaUrl" />
|
||||
<WxVideoPlayer :url="item.mediaUrl" />
|
||||
</div>
|
||||
<div v-else-if="item.type === 'link'" class="avue-card__detail">
|
||||
<div v-else-if="item.type === MsgType.Link" class="avue-card__detail">
|
||||
<el-link type="success" :underline="false" target="_blank" :href="item.url">
|
||||
<div class="avue-card__title"><i class="el-icon-link"></i>{{ item.title }}</div>
|
||||
</el-link>
|
||||
<div class="avue-card__info" style="height: unset">{{ item.description }}</div>
|
||||
</div>
|
||||
<!-- TODO 芋艿:待完善 -->
|
||||
<div v-else-if="item.type === 'location'">
|
||||
<wx-location
|
||||
<div v-else-if="item.type === MsgType.Location">
|
||||
<WxLocation
|
||||
:label="item.label"
|
||||
:location-y="item.locationY"
|
||||
:location-x="item.locationX"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="item.type === 'news'" style="width: 300px">
|
||||
<div v-else-if="item.type === MsgType.News" style="width: 300px">
|
||||
<!-- TODO 芋艿:待测试;详情页也存在类似的情况 -->
|
||||
<wx-news :articles="item.articles" />
|
||||
<WxNews :articles="item.articles" />
|
||||
</div>
|
||||
<div v-else-if="item.type === 'music'">
|
||||
<wx-music
|
||||
<div v-else-if="item.type === MsgType.Music">
|
||||
<WxMusic
|
||||
:title="item.title"
|
||||
:description="item.description"
|
||||
:thumb-media-url="item.thumbMediaUrl"
|
||||
|
@ -125,182 +125,185 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="msg-send" v-loading="sendLoading">
|
||||
<wx-reply-select ref="replySelect" :objData="objData" />
|
||||
<WxReplySelect ref="replySelectRef" :objData="objData" />
|
||||
<el-button type="success" size="small" class="send-but" @click="sendMsg">发送(S)</el-button>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="WxMsg">
|
||||
import { getMessagePage, sendMessage } from '@/api/mp/message'
|
||||
<script setup lang="ts" name="WxMsg">
|
||||
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
|
||||
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
|
||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
|
||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||
import WxLocation from '@/views/mp/components/wx-location/main.vue'
|
||||
import WxMusic from '@/views/mp/components/wx-music/main.vue'
|
||||
import { getMessagePage, sendMessage } from '@/api/mp/message'
|
||||
import { getUser } from '@/api/mp/user'
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import profile from '@/assets/imgs/profile.jpg'
|
||||
import wechat from '@/assets/imgs/wechat.png'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { MsgType } from './types'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
WxReplySelect,
|
||||
WxVideoPlayer,
|
||||
WxVoicePlayer,
|
||||
WxNews,
|
||||
WxLocation,
|
||||
WxMusic
|
||||
},
|
||||
props: {
|
||||
userId: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const nowStr = ref(new Date().getTime()) // 当前的时间戳,用于每次消息加载后,回到原位置;具体见 :id="'msg-div' + nowStr" 处
|
||||
const loading = ref(false) // 消息列表是否正在加载中
|
||||
const loadMore = ref(true) // 是否可以加载更多
|
||||
const list = ref<any[]>([]) // 消息列表
|
||||
const queryParams = reactive({
|
||||
pageNo: 1, // 当前页数
|
||||
pageSize: 14, // 每页显示多少条
|
||||
accountId: undefined
|
||||
})
|
||||
const user = reactive({
|
||||
// 由于微信不再提供昵称,直接使用“用户”展示
|
||||
nickname: '用户',
|
||||
avatar: profile,
|
||||
accountId: 0 // 公众号账号编号
|
||||
})
|
||||
const mp = reactive({
|
||||
nickname: '公众号',
|
||||
avatar: wechat
|
||||
})
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
// ========= 消息发送 =========
|
||||
const sendLoading = ref(false) // 发送消息是否加载中
|
||||
const objData = reactive({
|
||||
// 微信发送消息
|
||||
type: 'text',
|
||||
accountId: null,
|
||||
articles: []
|
||||
})
|
||||
|
||||
const replySelect = ref(null)
|
||||
// 执行发送
|
||||
const sendMsg = async () => {
|
||||
if (!objData) {
|
||||
return
|
||||
}
|
||||
// // 公众号限制:客服消息,公众号只允许发送一条
|
||||
if (objData.type === 'news' && objData.articles.length > 1) {
|
||||
objData.articles = [objData.articles[0]]
|
||||
message.success('图文消息条数限制在 1 条以内,已默认发送第一条')
|
||||
}
|
||||
let data = await sendMessage(Object.assign({ userId: props.userId }, { ...objData }))
|
||||
sendLoading.value = false
|
||||
list.value = [...list.value, ...[data]]
|
||||
scrollToBottom()
|
||||
//ts檢查的時候會判斷這個組件可能是空的,所以需要進行斷言。
|
||||
//避免 tab 的数据未清理
|
||||
const deleteObj = (replySelect.value as any).deleteObj
|
||||
if (deleteObj) {
|
||||
deleteObj()
|
||||
}
|
||||
}
|
||||
const loadingMore = () => {
|
||||
queryParams.pageNo++
|
||||
getPage(queryParams, null)
|
||||
}
|
||||
const getPage = async (page, params) => {
|
||||
loading.value = true
|
||||
let dataTemp = await getMessagePage(
|
||||
Object.assign(
|
||||
{
|
||||
pageNo: page.pageNo,
|
||||
pageSize: page.pageSize,
|
||||
userId: props.userId,
|
||||
accountId: page.accountId
|
||||
},
|
||||
params
|
||||
)
|
||||
)
|
||||
const msgDiv = document.getElementById('msg-div' + nowStr.value)
|
||||
let scrollHeight = 0
|
||||
if (msgDiv) {
|
||||
scrollHeight = msgDiv.scrollHeight
|
||||
}
|
||||
// 处理数据
|
||||
let data = dataTemp.list.reverse()
|
||||
list.value = [...data, ...list.value]
|
||||
loading.value = false
|
||||
if (data.length < queryParams.pageSize || data.length === 0) {
|
||||
loadMore.value = false
|
||||
}
|
||||
queryParams.pageNo = page.pageNo
|
||||
queryParams.pageSize = page.pageSize
|
||||
// 滚动到原来的位置
|
||||
if (queryParams.pageNo === 1) {
|
||||
// 定位到消息底部
|
||||
scrollToBottom()
|
||||
} else if (data.length !== 0) {
|
||||
// 定位滚动条
|
||||
await nextTick(() => {
|
||||
if (scrollHeight !== 0) {
|
||||
let div = document.getElementById('msg-div' + nowStr.value)
|
||||
if (div && msgDiv) {
|
||||
msgDiv.scrollTop = div.scrollHeight - scrollHeight - 100
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
const refreshChange = () => {
|
||||
getPage(queryParams, null)
|
||||
}
|
||||
/** 定位到消息底部 */
|
||||
const scrollToBottom = () => {
|
||||
nextTick(() => {
|
||||
let div = document.getElementById('msg-div' + nowStr.value)
|
||||
if (div) {
|
||||
div.scrollTop = div.scrollHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
let data = await getUser(props.userId)
|
||||
user.nickname = data.nickname && data.nickname.length > 0 ? data.nickname : user.nickname
|
||||
user.avatar = data.avatar && user.avatar.length > 0 ? data.avatar : user.avatar
|
||||
user.accountId = data.accountId
|
||||
queryParams.accountId = data.accountId
|
||||
objData.accountId = data.accountId
|
||||
refreshChange()
|
||||
})
|
||||
return {
|
||||
sendMsg,
|
||||
loadingMore,
|
||||
formatDate,
|
||||
scrollToBottom,
|
||||
objData,
|
||||
mp,
|
||||
user,
|
||||
queryParams,
|
||||
list,
|
||||
loadMore,
|
||||
loading,
|
||||
nowStr,
|
||||
sendLoading
|
||||
}
|
||||
const props = defineProps({
|
||||
userId: {
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const nowStr = ref(new Date().getTime()) // 当前的时间戳,用于每次消息加载后,回到原位置;具体见 :id="'msg-div' + nowStr" 处
|
||||
const loading = ref(false) // 消息列表是否正在加载中
|
||||
const loadMore = ref(true) // 是否可以加载更多
|
||||
const list = ref<any[]>([]) // 消息列表
|
||||
const queryParams = reactive({
|
||||
pageNo: 1, // 当前页数
|
||||
pageSize: 14, // 每页显示多少条
|
||||
accountId: undefined
|
||||
})
|
||||
|
||||
interface User {
|
||||
nickname: string
|
||||
avatar: string
|
||||
accountId: number
|
||||
}
|
||||
// 由于微信不再提供昵称,直接使用“用户”展示
|
||||
const user: User = reactive({
|
||||
nickname: '用户',
|
||||
avatar: profile,
|
||||
accountId: 0 // 公众号账号编号
|
||||
})
|
||||
|
||||
interface Mp {
|
||||
nickname: string
|
||||
avatar: string
|
||||
}
|
||||
const mp: Mp = reactive({
|
||||
nickname: '公众号',
|
||||
avatar: wechat
|
||||
})
|
||||
|
||||
// ========= 消息发送 =========
|
||||
const sendLoading = ref(false) // 发送消息是否加载中
|
||||
interface ObjData {
|
||||
type: MsgType
|
||||
accountId: number | null
|
||||
articles: any[]
|
||||
}
|
||||
|
||||
// 微信发送消息
|
||||
const objData: ObjData = reactive({
|
||||
type: MsgType.Text,
|
||||
accountId: null,
|
||||
articles: []
|
||||
})
|
||||
|
||||
const replySelectRef = ref<InstanceType<typeof WxReplySelect> | null>(null)
|
||||
|
||||
/** 完成加载 */
|
||||
onMounted(async () => {
|
||||
const data = await getUser(props.userId)
|
||||
user.nickname = data.nickname?.length > 0 ? data.nickname : user.nickname
|
||||
user.avatar = user.avatar?.length > 0 ? data.avatar : user.avatar
|
||||
user.accountId = data.accountId
|
||||
queryParams.accountId = data.accountId
|
||||
objData.accountId = data.accountId
|
||||
|
||||
refreshChange()
|
||||
})
|
||||
|
||||
// 执行发送
|
||||
const sendMsg = async () => {
|
||||
if (!objData) {
|
||||
return
|
||||
}
|
||||
// 公众号限制:客服消息,公众号只允许发送一条
|
||||
if (objData.type === MsgType.News && objData.articles.length > 1) {
|
||||
objData.articles = [objData.articles[0]]
|
||||
message.success('图文消息条数限制在 1 条以内,已默认发送第一条')
|
||||
}
|
||||
|
||||
const data = await sendMessage(Object.assign({ userId: props.userId }, { ...objData }))
|
||||
sendLoading.value = false
|
||||
|
||||
list.value = [...list.value, ...[data]]
|
||||
scrollToBottom()
|
||||
|
||||
//ts检查的時候会判断这个组件可能是空的,所以需要进行断言。
|
||||
//避免 tab 的数据未清理
|
||||
const deleteObj = replySelectRef.value?.deleteObj
|
||||
if (deleteObj) {
|
||||
deleteObj()
|
||||
}
|
||||
}
|
||||
|
||||
const loadingMore = () => {
|
||||
queryParams.pageNo++
|
||||
getPage(queryParams, null)
|
||||
}
|
||||
|
||||
const getPage = async (page, params) => {
|
||||
loading.value = true
|
||||
let dataTemp = await getMessagePage(
|
||||
Object.assign(
|
||||
{
|
||||
pageNo: page.pageNo,
|
||||
pageSize: page.pageSize,
|
||||
userId: props.userId,
|
||||
accountId: page.accountId
|
||||
},
|
||||
params
|
||||
)
|
||||
)
|
||||
|
||||
const msgDiv = document.getElementById('msg-div' + nowStr.value)
|
||||
let scrollHeight = 0
|
||||
if (msgDiv) {
|
||||
scrollHeight = msgDiv.scrollHeight
|
||||
}
|
||||
// 处理数据
|
||||
const data = dataTemp.list.reverse()
|
||||
list.value = [...data, ...list.value]
|
||||
loading.value = false
|
||||
if (data.length < queryParams.pageSize || data.length === 0) {
|
||||
loadMore.value = false
|
||||
}
|
||||
queryParams.pageNo = page.pageNo
|
||||
queryParams.pageSize = page.pageSize
|
||||
// 滚动到原来的位置
|
||||
if (queryParams.pageNo === 1) {
|
||||
// 定位到消息底部
|
||||
scrollToBottom()
|
||||
} else if (data.length !== 0) {
|
||||
// 定位滚动条
|
||||
await nextTick(() => {
|
||||
if (scrollHeight !== 0) {
|
||||
let div = document.getElementById('msg-div' + nowStr.value)
|
||||
if (div && msgDiv) {
|
||||
msgDiv.scrollTop = div.scrollHeight - scrollHeight - 100
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const refreshChange = () => {
|
||||
getPage(queryParams, null)
|
||||
}
|
||||
|
||||
/** 定位到消息底部 */
|
||||
const scrollToBottom = () => {
|
||||
nextTick(() => {
|
||||
let div = document.getElementById('msg-div' + nowStr.value)
|
||||
if (div) {
|
||||
div.scrollTop = div.scrollHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 因为 joolun 实现依赖 avue 组件,该页面使用了 comment.scss、card.scc */
|
||||
@import './comment.scss';
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
export enum MsgType {
|
||||
Event = 'event',
|
||||
Text = 'text',
|
||||
Voice = 'voice',
|
||||
Image = 'image',
|
||||
Video = 'video',
|
||||
Link = 'link',
|
||||
Location = 'location',
|
||||
Music = 'music',
|
||||
News = 'news'
|
||||
}
|
|
@ -11,18 +11,9 @@
|
|||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<el-select v-model="queryParams.accountId" placeholder="请选择公众号">
|
||||
<el-option
|
||||
v-for="item in accountList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<WxMpSelect @change="onAccountChanged" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
|
||||
<el-button type="primary" plain @click="handleAdd" v-hasPermi="['mp:draft:create']">
|
||||
<Icon icon="ep:plus" />新增
|
||||
</el-button>
|
||||
|
@ -35,7 +26,7 @@
|
|||
<div class="waterfall" v-loading="loading">
|
||||
<template v-for="item in list" :key="item.articleId">
|
||||
<div class="waterfall-item" v-if="item.content && item.content.newsItem">
|
||||
<wx-news :articles="item.content.newsItem" />
|
||||
<WxNews :articles="item.content.newsItem" />
|
||||
<!-- 操作按钮 -->
|
||||
<el-row class="ope-row">
|
||||
<el-button
|
||||
|
@ -75,215 +66,220 @@
|
|||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- TODO @Dhb52:迁移成独立路由 -->
|
||||
<div class="app-container">
|
||||
<!-- 添加或修改草稿对话框 -->
|
||||
<Teleport to="body">
|
||||
<el-dialog
|
||||
:title="operateMaterial === 'add' ? '新建图文' : '修改图文'"
|
||||
width="80%"
|
||||
top="20px"
|
||||
v-model="dialogNewsVisible"
|
||||
:before-close="dialogNewsClose"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="left">
|
||||
<div class="select-item">
|
||||
<div v-for="(news, index) in articlesAdd" :key="news.id">
|
||||
<div
|
||||
class="news-main father"
|
||||
v-if="index === 0"
|
||||
:class="{ activeAddNews: isActiveAddNews === index }"
|
||||
@click="activeNews(index)"
|
||||
>
|
||||
<div class="news-content">
|
||||
<img class="material-img" v-if="news.thumbUrl" :src="news.thumbUrl" />
|
||||
<div class="news-content-title">{{ news.title }}</div>
|
||||
</div>
|
||||
<div class="child" v-if="articlesAdd.length > 1">
|
||||
<el-button size="small" @click="downNews(index)"
|
||||
><Icon icon="ep:sort-down" />下移</el-button
|
||||
>
|
||||
<el-button v-if="operateMaterial === 'add'" size="small" @click="minusNews(index)"
|
||||
><Icon icon="ep:delete" />删除
|
||||
</el-button>
|
||||
</div>
|
||||
<el-dialog
|
||||
:title="operateMaterial === 'add' ? '新建图文' : '修改图文'"
|
||||
width="80%"
|
||||
top="20px"
|
||||
v-model="dialogNewsVisible"
|
||||
:before-close="dialogNewsClose"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="left">
|
||||
<div class="select-item">
|
||||
<div v-for="(news, index) in articlesAdd" :key="news.id">
|
||||
<div
|
||||
class="news-main father"
|
||||
v-if="index === 0"
|
||||
:class="{ activeAddNews: isActiveAddNews === index }"
|
||||
@click="activeNews(index)"
|
||||
>
|
||||
<div class="news-content">
|
||||
<img class="material-img" v-if="news.thumbUrl" :src="news.thumbUrl" />
|
||||
<div class="news-content-title">{{ news.title }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="news-main-item father"
|
||||
v-if="index > 0"
|
||||
:class="{ activeAddNews: isActiveAddNews === index }"
|
||||
@click="activeNews(index)"
|
||||
>
|
||||
<div class="news-content-item">
|
||||
<div class="news-content-item-title">{{ news.title }}</div>
|
||||
<div class="news-content-item-img">
|
||||
<img
|
||||
class="material-img"
|
||||
v-if="news.thumbUrl"
|
||||
:src="news.thumbUrl"
|
||||
height="100%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="child">
|
||||
<el-button
|
||||
v-if="articlesAdd.length > index + 1"
|
||||
size="small"
|
||||
@click="downNews(index)"
|
||||
><Icon icon="ep:sort-down" />下移
|
||||
</el-button>
|
||||
<el-button size="small" @click="upNews(index)"
|
||||
><Icon icon="ep:sort-up" />上移</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="operateMaterial === 'add'"
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="minusNews(index)"
|
||||
><Icon icon="ep:delete" />删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-row justify="center" class="ope-row">
|
||||
<el-button
|
||||
type="primary"
|
||||
circle
|
||||
@click="plusNews(item)"
|
||||
v-if="articlesAdd.length < 8 && operateMaterial === 'add'"
|
||||
>
|
||||
<Icon icon="ep:plus" />
|
||||
</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right" v-loading="addMaterialLoading" v-if="articlesAdd.length > 0">
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<!-- 标题、作者、原文地址 -->
|
||||
<el-input v-model="articlesAdd[isActiveAddNews].title" placeholder="请输入标题(必填)" />
|
||||
<el-input
|
||||
v-model="articlesAdd[isActiveAddNews].author"
|
||||
placeholder="请输入作者"
|
||||
style="margin-top: 5px"
|
||||
/>
|
||||
<el-input
|
||||
v-model="articlesAdd[isActiveAddNews].contentSourceUrl"
|
||||
placeholder="请输入原文地址"
|
||||
style="margin-top: 5px"
|
||||
/>
|
||||
<!-- 封面和摘要 -->
|
||||
<div class="input-tt">封面和摘要:</div>
|
||||
<div>
|
||||
<div class="thumb-div">
|
||||
<img
|
||||
class="material-img"
|
||||
v-if="articlesAdd[isActiveAddNews].thumbUrl"
|
||||
:src="articlesAdd[isActiveAddNews].thumbUrl"
|
||||
:class="isActiveAddNews === 0 ? 'avatar' : 'avatar1'"
|
||||
/>
|
||||
<Icon
|
||||
v-else
|
||||
icon="ep:plus"
|
||||
class="avatar-uploader-icon"
|
||||
:class="isActiveAddNews === 0 ? 'avatar' : 'avatar1'"
|
||||
/>
|
||||
<div class="thumb-but">
|
||||
<el-upload
|
||||
:action="actionUrl"
|
||||
:headers="headers"
|
||||
multiple
|
||||
:limit="1"
|
||||
:file-list="fileList"
|
||||
:data="uploadData"
|
||||
:before-upload="beforeThumbImageUpload"
|
||||
:on-success="handleUploadSuccess"
|
||||
<div class="child" v-if="articlesAdd.length > 1">
|
||||
<el-button size="small" @click="downNews(index)"
|
||||
><Icon icon="ep:sort-down" />下移</el-button
|
||||
>
|
||||
<template #trigger>
|
||||
<el-button size="small" type="primary">本地上传</el-button>
|
||||
</template>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="openMaterial"
|
||||
style="margin-left: 5px"
|
||||
>素材库选择</el-button
|
||||
>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<el-button v-if="operateMaterial === 'add'" size="small" @click="minusNews(index)"
|
||||
><Icon icon="ep:delete" />删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="news-main-item father"
|
||||
v-if="index > 0"
|
||||
:class="{ activeAddNews: isActiveAddNews === index }"
|
||||
@click="activeNews(index)"
|
||||
>
|
||||
<div class="news-content-item">
|
||||
<div class="news-content-item-title">{{ news.title }}</div>
|
||||
<div class="news-content-item-img">
|
||||
<img
|
||||
class="material-img"
|
||||
v-if="news.thumbUrl"
|
||||
:src="news.thumbUrl"
|
||||
height="100%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="child">
|
||||
<el-button
|
||||
v-if="articlesAdd.length > index + 1"
|
||||
size="small"
|
||||
@click="downNews(index)"
|
||||
><Icon icon="ep:sort-down" />下移
|
||||
</el-button>
|
||||
<el-button size="small" @click="upNews(index)"
|
||||
><Icon icon="ep:sort-up" />上移</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="operateMaterial === 'add'"
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="minusNews(index)"
|
||||
><Icon icon="ep:delete" />删除
|
||||
</el-button>
|
||||
</div>
|
||||
<Teleport to="body">
|
||||
<el-dialog title="选择图片" v-model="dialogImageVisible" width="80%">
|
||||
<WxMaterialSelect
|
||||
ref="materialSelectRef"
|
||||
:objData="{ type: 'image', accountId: queryParams.accountId }"
|
||||
@select-material="selectMaterial"
|
||||
/>
|
||||
</el-dialog>
|
||||
</Teleport>
|
||||
</div>
|
||||
<el-input
|
||||
:rows="8"
|
||||
type="textarea"
|
||||
v-model="articlesAdd[isActiveAddNews].digest"
|
||||
placeholder="请输入摘要"
|
||||
class="digest"
|
||||
maxlength="120"
|
||||
style="float: right"
|
||||
/>
|
||||
</div>
|
||||
<!--富文本编辑器组件-->
|
||||
<el-row>
|
||||
<wx-editor
|
||||
v-model="articlesAdd[isActiveAddNews].content"
|
||||
:account-id="uploadData.accountId"
|
||||
v-if="hackResetEditor"
|
||||
/>
|
||||
<el-row justify="center" class="ope-row">
|
||||
<el-button
|
||||
type="primary"
|
||||
circle
|
||||
@click="plusNews"
|
||||
v-if="articlesAdd.length < 8 && operateMaterial === 'add'"
|
||||
>
|
||||
<Icon icon="ep:plus" />
|
||||
</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="dialogNewsVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitForm">提 交</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</Teleport>
|
||||
</div>
|
||||
<div class="right" v-loading="addMaterialLoading" v-if="articlesAdd.length > 0">
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<!-- 标题、作者、原文地址 -->
|
||||
<el-input v-model="articlesAdd[isActiveAddNews].title" placeholder="请输入标题(必填)" />
|
||||
<el-input
|
||||
v-model="articlesAdd[isActiveAddNews].author"
|
||||
placeholder="请输入作者"
|
||||
style="margin-top: 5px"
|
||||
/>
|
||||
<el-input
|
||||
v-model="articlesAdd[isActiveAddNews].contentSourceUrl"
|
||||
placeholder="请输入原文地址"
|
||||
style="margin-top: 5px"
|
||||
/>
|
||||
<!-- 封面和摘要 -->
|
||||
<div class="input-tt">封面和摘要:</div>
|
||||
<div>
|
||||
<div class="thumb-div">
|
||||
<img
|
||||
class="material-img"
|
||||
v-if="articlesAdd[isActiveAddNews].thumbUrl"
|
||||
:src="articlesAdd[isActiveAddNews].thumbUrl"
|
||||
:class="isActiveAddNews === 0 ? 'avatar' : 'avatar1'"
|
||||
/>
|
||||
<Icon
|
||||
v-else
|
||||
icon="ep:plus"
|
||||
class="avatar-uploader-icon"
|
||||
:class="isActiveAddNews === 0 ? 'avatar' : 'avatar1'"
|
||||
/>
|
||||
<div class="thumb-but">
|
||||
<el-upload
|
||||
:action="uploadUrl"
|
||||
:headers="headers"
|
||||
multiple
|
||||
:limit="1"
|
||||
:file-list="fileList"
|
||||
:data="uploadData"
|
||||
:before-upload="beforeThumbImageUpload"
|
||||
:on-success="handleUploadSuccess"
|
||||
>
|
||||
<template #trigger>
|
||||
<el-button size="small" type="primary">本地上传</el-button>
|
||||
</template>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="openMaterial"
|
||||
style="margin-left: 5px"
|
||||
>素材库选择</el-button
|
||||
>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
<el-dialog title="选择图片" v-model="dialogImageVisible" width="80%" append-to-body>
|
||||
<WxMaterialSelect
|
||||
ref="materialSelectRef"
|
||||
:objData="{ type: 'image', accountId: queryParams.accountId }"
|
||||
@select-material="selectMaterial"
|
||||
/>
|
||||
</el-dialog>
|
||||
</div>
|
||||
<el-input
|
||||
:rows="8"
|
||||
type="textarea"
|
||||
v-model="articlesAdd[isActiveAddNews].digest"
|
||||
placeholder="请输入摘要"
|
||||
class="digest"
|
||||
maxlength="120"
|
||||
style="float: right"
|
||||
/>
|
||||
</div>
|
||||
<!--富文本编辑器组件-->
|
||||
<el-row>
|
||||
<WxEditor
|
||||
v-model="articlesAdd[isActiveAddNews].content"
|
||||
:account-id="uploadData.accountId"
|
||||
v-if="hackResetEditor"
|
||||
/>
|
||||
</el-row>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="dialogNewsVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitForm">提 交</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup name="MpDraft">
|
||||
|
||||
<script setup lang="ts" name="MpDraft">
|
||||
import WxEditor from '@/views/mp/components/wx-editor/WxEditor.vue'
|
||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
|
||||
import WxMpSelect from '@/views/mp/components/WxMpSelect.vue'
|
||||
import { getAccessToken } from '@/utils/auth'
|
||||
import * as MpAccountApi from '@/api/mp/account'
|
||||
import * as MpDraftApi from '@/api/mp/draft'
|
||||
import * as MpFreePublishApi from '@/api/mp/freePublish'
|
||||
const message = useMessage() // 消息
|
||||
import type { UploadFiles, UploadProps, UploadRawFile } from 'element-plus'
|
||||
// 可以用改本地数据模拟,避免API调用超限
|
||||
// import drafts from './mock'
|
||||
const message = useMessage() // 消息
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<any[]>([]) // 列表的数据
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
interface QueryParams {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
accountId?: number
|
||||
}
|
||||
const queryParams: QueryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
accountId: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const accountList = ref([]) // 公众号账号列表
|
||||
|
||||
// ========== 文件上传 ==========
|
||||
const materialSelectRef = ref()
|
||||
const BASE_URL = import.meta.env.VITE_BASE_URL
|
||||
const actionUrl = ref(BASE_URL + '/admin-api/mp/material/upload-permanent') // 上传永久素材的地址
|
||||
const headers = ref({ Authorization: 'Bearer ' + getAccessToken() }) // 设置上传的请求头部
|
||||
const fileList = ref([])
|
||||
const uploadData = reactive({
|
||||
const uploadUrl = BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传永久素材的地址
|
||||
const headers = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部
|
||||
|
||||
const materialSelectRef = ref<InstanceType<typeof WxMaterialSelect> | null>(null)
|
||||
const fileList = ref<UploadFiles>([])
|
||||
interface UploadData {
|
||||
type: 'image' | 'video' | 'audio'
|
||||
accountId?: number
|
||||
}
|
||||
const uploadData: UploadData = reactive({
|
||||
type: 'image',
|
||||
accountId: 1
|
||||
})
|
||||
|
@ -291,39 +287,28 @@ const uploadData = reactive({
|
|||
// ========== 草稿新建 or 修改 ==========
|
||||
const dialogNewsVisible = ref(false)
|
||||
const addMaterialLoading = ref(false) // 添加草稿的 loading 标识
|
||||
const articlesAdd = ref([])
|
||||
const articlesAdd = ref<any[]>([])
|
||||
const isActiveAddNews = ref(0)
|
||||
const dialogImageVisible = ref(false)
|
||||
const operateMaterial = ref('add')
|
||||
const operateMaterial = ref<'add' | 'edit'>('add')
|
||||
const articlesMediaId = ref('')
|
||||
const hackResetEditor = ref(false)
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
accountList.value = await MpAccountApi.getSimpleAccountList()
|
||||
// 选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
// @ts-ignore
|
||||
queryParams.accountId = accountList.value[0].id
|
||||
}
|
||||
await getList()
|
||||
})
|
||||
/** 侦听公众号变化 **/
|
||||
const onAccountChanged = (id?: number) => {
|
||||
setAccountId(id)
|
||||
getList()
|
||||
}
|
||||
|
||||
// ======================== 列表查询 ========================
|
||||
/** 设置账号编号 */
|
||||
const setAccountId = (accountId) => {
|
||||
queryParams.accountId = accountId
|
||||
uploadData.accountId = accountId
|
||||
const setAccountId = (id?: number) => {
|
||||
queryParams.accountId = id
|
||||
uploadData.accountId = id
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
// 如果没有选中公众号账号,则进行提示。
|
||||
if (!queryParams.accountId) {
|
||||
message.error('未选中公众号,无法查询草稿箱')
|
||||
return false
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const drafts = await MpDraftApi.getDraftPage(queryParams)
|
||||
|
@ -341,26 +326,6 @@ const getList = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
// 默认选中第一个
|
||||
if (queryParams.accountId) {
|
||||
setAccountId(queryParams.accountId)
|
||||
}
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
setAccountId(accountList.value[0].id)
|
||||
}
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
// ======================== 新增/修改草稿 ========================
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
|
@ -372,7 +337,7 @@ const handleAdd = () => {
|
|||
}
|
||||
|
||||
/** 更新按钮操作 */
|
||||
const handleUpdate = (item) => {
|
||||
const handleUpdate = (item: any) => {
|
||||
resetEditor()
|
||||
reset()
|
||||
articlesMediaId.value = item.mediaId
|
||||
|
@ -383,39 +348,30 @@ const handleUpdate = (item) => {
|
|||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
// TODO @Dhb52: 参考别的模块写法,改成 await 方式
|
||||
const submitForm = async () => {
|
||||
addMaterialLoading.value = true
|
||||
if (operateMaterial.value === 'add') {
|
||||
MpDraftApi.createDraft(queryParams.accountId, articlesAdd.value)
|
||||
.then(() => {
|
||||
message.notifySuccess('新增成功')
|
||||
dialogNewsVisible.value = false
|
||||
getList()
|
||||
})
|
||||
.finally(() => {
|
||||
addMaterialLoading.value = false
|
||||
})
|
||||
} else {
|
||||
MpDraftApi.updateDraft(queryParams.accountId, articlesMediaId.value, articlesAdd.value)
|
||||
.then(() => {
|
||||
message.notifySuccess('更新成功')
|
||||
dialogNewsVisible.value = false
|
||||
getList()
|
||||
})
|
||||
.finally(() => {
|
||||
addMaterialLoading.value = false
|
||||
})
|
||||
try {
|
||||
if (operateMaterial.value === 'add') {
|
||||
await MpDraftApi.createDraft(queryParams.accountId, articlesAdd.value)
|
||||
message.notifySuccess('新增成功')
|
||||
} else {
|
||||
await MpDraftApi.updateDraft(queryParams.accountId, articlesMediaId.value, articlesAdd.value)
|
||||
message.notifySuccess('更新成功')
|
||||
}
|
||||
} finally {
|
||||
dialogNewsVisible.value = false
|
||||
addMaterialLoading.value = false
|
||||
await getList()
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const dialogNewsClose = async (done) => {
|
||||
const dialogNewsClose = async (onDone: () => {}) => {
|
||||
try {
|
||||
await message.confirm('修改内容可能还未保存,确定关闭吗?')
|
||||
reset()
|
||||
resetEditor()
|
||||
done()
|
||||
onDone()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
|
@ -434,7 +390,7 @@ const resetEditor = () => {
|
|||
}
|
||||
|
||||
// 将图文向下移动
|
||||
const downNews = (index) => {
|
||||
const downNews = (index: number) => {
|
||||
let temp = articlesAdd.value[index]
|
||||
articlesAdd.value[index] = articlesAdd.value[index + 1]
|
||||
articlesAdd.value[index + 1] = temp
|
||||
|
@ -450,13 +406,13 @@ const upNews = (index) => {
|
|||
}
|
||||
|
||||
// 选中指定 index 的图文
|
||||
const activeNews = (index) => {
|
||||
const activeNews = (index: number) => {
|
||||
resetEditor()
|
||||
isActiveAddNews.value = index
|
||||
}
|
||||
|
||||
// 删除指定 index 的图文
|
||||
const minusNews = async (index) => {
|
||||
const minusNews = async (index: number) => {
|
||||
try {
|
||||
await message.confirm('确定删除该图文吗?')
|
||||
articlesAdd.value.splice(index, 1)
|
||||
|
@ -489,20 +445,17 @@ const buildEmptyArticle = () => {
|
|||
}
|
||||
|
||||
// ======================== 文件上传 ========================
|
||||
const beforeThumbImageUpload = (file) => {
|
||||
const beforeThumbImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
|
||||
addMaterialLoading.value = true
|
||||
const isType =
|
||||
file.type === 'image/jpeg' ||
|
||||
file.type === 'image/png' ||
|
||||
file.type === 'image/gif' ||
|
||||
file.type === 'image/bmp' ||
|
||||
file.type === 'image/jpg'
|
||||
const isType = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/jpg'].includes(
|
||||
rawFile.type
|
||||
)
|
||||
if (!isType) {
|
||||
message.error('上传图片格式不对!')
|
||||
addMaterialLoading.value = false
|
||||
return false
|
||||
}
|
||||
const isLt = file.size / 1024 / 1024 < 2
|
||||
const isLt = rawFile.size / 1024 / 1024 < 2
|
||||
if (!isLt) {
|
||||
message.error('上传图片大小不能超过 2M!')
|
||||
addMaterialLoading.value = false
|
||||
|
@ -512,7 +465,7 @@ const beforeThumbImageUpload = (file) => {
|
|||
return true
|
||||
}
|
||||
|
||||
const handleUploadSuccess = (response, file, fileList) => {
|
||||
const handleUploadSuccess: UploadProps['onSuccess'] = (response: any) => {
|
||||
addMaterialLoading.value = false
|
||||
if (response.code !== 0) {
|
||||
message.error('上传出错:' + response.msg)
|
||||
|
@ -528,7 +481,7 @@ const handleUploadSuccess = (response, file, fileList) => {
|
|||
}
|
||||
|
||||
// 选择 or 上传完素材,设置回草稿
|
||||
const selectMaterial = (item) => {
|
||||
const selectMaterial = (item: any) => {
|
||||
dialogImageVisible.value = false
|
||||
articlesAdd.value[isActiveAddNews.value].thumbMediaId = item.mediaId
|
||||
articlesAdd.value[isActiveAddNews.value].thumbUrl = item.url
|
||||
|
@ -537,14 +490,10 @@ const selectMaterial = (item) => {
|
|||
// 打开素材选择
|
||||
const openMaterial = () => {
|
||||
dialogImageVisible.value = true
|
||||
try {
|
||||
materialSelectRef.value.queryParams.accountId = queryParams.accountId // 强制设置下 accountId,避免二次查询不对
|
||||
materialSelectRef.value.handleQuery() // 刷新列表,失败也无所谓
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// ======================== 草稿箱发布 ========================
|
||||
const handlePublish = async (item) => {
|
||||
const handlePublish = async (item: any) => {
|
||||
const accountId = queryParams.accountId
|
||||
const mediaId = item.mediaId
|
||||
const content =
|
||||
|
@ -553,19 +502,19 @@ const handlePublish = async (item) => {
|
|||
await message.confirm(content)
|
||||
await MpFreePublishApi.submitFreePublish(accountId, mediaId)
|
||||
message.notifySuccess('发布成功')
|
||||
await getList()
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (item) => {
|
||||
const handleDelete = async (item: any) => {
|
||||
const accountId = queryParams.accountId
|
||||
const mediaId = item.mediaId
|
||||
try {
|
||||
await message.confirm('此操作将永久删除该草稿, 是否继续?')
|
||||
await MpDraftApi.deleteDraft(accountId, mediaId)
|
||||
message.notifySuccess('删除成功')
|
||||
await getList()
|
||||
getList()
|
||||
} catch {}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -11,18 +11,7 @@
|
|||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<el-select v-model="queryParams.accountId" placeholder="请选择公众号" class="!w-240px">
|
||||
<el-option
|
||||
v-for="item in accountList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<WxMpSelect @change="onAccountChanged" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
@ -59,31 +48,37 @@
|
|||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="MpFreePublish">
|
||||
<script lang="ts" setup name="MpFreePublish">
|
||||
import * as FreePublishApi from '@/api/mp/freePublish'
|
||||
import * as MpAccountApi from '@/api/mp/account'
|
||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||
import WxMpSelect from '@/views/mp/components/WxMpSelect.vue'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
const list = ref<any[]>([]) // 列表的数据
|
||||
|
||||
interface QueryParams {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
accountId?: number
|
||||
}
|
||||
const queryParams: QueryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
accountId: undefined // 当前页数
|
||||
accountId: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const accountList = ref<MpAccountApi.AccountVO[]>([]) // 公众号账号列表
|
||||
|
||||
/** 侦听公众号变化 **/
|
||||
const onAccountChanged = (id: number | undefined) => {
|
||||
queryParams.accountId = id
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
// 如果没有选中公众号账号,则进行提示。
|
||||
if (!queryParams.accountId) {
|
||||
message.error('未选中公众号,无法查询已发表图文')
|
||||
return false
|
||||
}
|
||||
try {
|
||||
loading.value = true
|
||||
const data = await FreePublishApi.getFreePublishPage(queryParams)
|
||||
|
@ -94,24 +89,8 @@ const getList = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
queryParams.accountId = accountList.value[0].id
|
||||
}
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (item) => {
|
||||
const handleDelete = async (item: any) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm('删除后用户将无法访问此页面,确定删除?')
|
||||
|
@ -122,16 +101,8 @@ const handleDelete = async (item) => {
|
|||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
accountList.value = await MpAccountApi.getSimpleAccountList()
|
||||
// 选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
queryParams.accountId = accountList.value[0].id
|
||||
}
|
||||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ope-row {
|
||||
margin-top: 5px;
|
||||
|
|
|
@ -2,40 +2,23 @@
|
|||
<doc-alert title="公众号素材" url="https://doc.iocoder.cn/mp/material/" />
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form class="-mb-15px" :inline="true" label-width="68px">
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<el-select v-model="queryParams.accountId" placeholder="请选择公众号" class="!w-240px">
|
||||
<el-option
|
||||
v-for="item in accountList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
|
||||
<WxMpSelect @change="onAccountChanged" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<ContentWrap>
|
||||
<el-tabs v-model="type" @tab-change="handleTabChange">
|
||||
<el-tabs v-model="type" @tab-change="onTabChange">
|
||||
<!-- tab 1:图片 -->
|
||||
<el-tab-pane name="image">
|
||||
<template #label>
|
||||
<span><Icon icon="ep:picture" />图片</span>
|
||||
<span> <Icon icon="ep:picture" />图片 </span>
|
||||
</template>
|
||||
<div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
|
||||
<el-upload
|
||||
:action="actionUrl"
|
||||
:action="uploadUrl"
|
||||
:headers="headers"
|
||||
multiple
|
||||
:limit="1"
|
||||
|
@ -58,7 +41,7 @@
|
|||
<img class="material-img" :src="item.url" />
|
||||
<div class="item-name">{{ item.name }}</div>
|
||||
</a>
|
||||
<el-row class="ope-row" justify="center">
|
||||
<el-row justify="center">
|
||||
<el-button
|
||||
type="danger"
|
||||
circle
|
||||
|
@ -82,11 +65,11 @@
|
|||
<!-- tab 2:语音 -->
|
||||
<el-tab-pane name="voice">
|
||||
<template #label>
|
||||
<span><Icon icon="ep:microphone" />语音</span>
|
||||
<span> <Icon icon="ep:microphone" />语音 </span>
|
||||
</template>
|
||||
<div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
|
||||
<el-upload
|
||||
:action="actionUrl"
|
||||
:action="uploadUrl"
|
||||
:headers="headers"
|
||||
multiple
|
||||
:limit="1"
|
||||
|
@ -103,17 +86,25 @@
|
|||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
|
||||
<!-- 列表 -->
|
||||
<el-table :data="list" stripe border v-loading="loading" style="margin-top: 10px">
|
||||
<el-table-column label="编号" align="center" prop="mediaId" />
|
||||
<el-table-column label="文件名" align="center" prop="name" />
|
||||
<el-table-column label="语音" align="center">
|
||||
<template #default="scope">
|
||||
<WxVoicePlayer :url="scope.row.url" />
|
||||
<WxVoicePlayer v-if="scope.row.url" :url="scope.row.url" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="上传时间" align="center" prop="createTime" width="180">
|
||||
<el-table-column
|
||||
label="上传时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180"
|
||||
>
|
||||
<template #default="scope">
|
||||
<span>{{ formatDate(scope.row.createTime) }}</span>
|
||||
<span>{{ scope.row.createTime }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
|
@ -145,7 +136,7 @@
|
|||
<!-- tab 3:视频 -->
|
||||
<el-tab-pane name="video">
|
||||
<template #label>
|
||||
<span><Icon icon="ep:video-play" /> 视频</span>
|
||||
<span> <Icon icon="ep:video-play" /> 视频 </span>
|
||||
</template>
|
||||
<div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
|
||||
<el-button type="primary" plain @click="handleAddVideo">新建视频</el-button>
|
||||
|
@ -158,7 +149,7 @@
|
|||
v-loading="addMaterialLoading"
|
||||
>
|
||||
<el-upload
|
||||
:action="actionUrl"
|
||||
:action="uploadUrl"
|
||||
:headers="headers"
|
||||
multiple
|
||||
:limit="1"
|
||||
|
@ -202,6 +193,7 @@
|
|||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 列表 -->
|
||||
<el-table :data="list" stripe border v-loading="loading" style="margin-top: 10px">
|
||||
<el-table-column label="编号" align="center" prop="mediaId" />
|
||||
<el-table-column label="文件名" align="center" prop="name" />
|
||||
|
@ -209,19 +201,25 @@
|
|||
<el-table-column label="介绍" align="center" prop="introduction" />
|
||||
<el-table-column label="视频" align="center">
|
||||
<template #default="scope">
|
||||
<WxVideoPlayer :url="scope.row.url" />
|
||||
<WxVideoPlayer v-if="scope.row.url" :url="scope.row.url" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="上传时间" align="center" prop="createTime" width="180">
|
||||
<el-table-column
|
||||
label="上传时间"
|
||||
align="center"
|
||||
:formatter="dateFormatter"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
>
|
||||
<template #default="scope">
|
||||
<span>{{ formatDate(scope.row.createTime) }}</span>
|
||||
<span>{{ scope.row.createTime }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link plain @click="handleDownload(scope.row)"
|
||||
><Icon icon="ep:download" />下载</el-button
|
||||
>
|
||||
<el-button type="primary" link plain @click="handleDownload(scope.row)">
|
||||
<Icon icon="ep:download" />下载
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
|
@ -246,41 +244,66 @@
|
|||
</el-tabs>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup name="MpMaterial">
|
||||
|
||||
<script lang="ts" setup name="MpMaterial">
|
||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
|
||||
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
|
||||
import { getSimpleAccountList } from '@/api/mp/account'
|
||||
import { getMaterialPage, deletePermanentMaterial } from '@/api/mp/material'
|
||||
import { getAccessToken } from '@/utils/auth'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import WxMpSelect from '@/views/mp/components/WxMpSelect.vue'
|
||||
import * as MpMaterialApi from '@/api/mp/material'
|
||||
import * as authUtil from '@/utils/auth'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import type {
|
||||
FormInstance,
|
||||
FormRules,
|
||||
TabPaneName,
|
||||
UploadInstance,
|
||||
UploadProps,
|
||||
UploadRawFile,
|
||||
UploadUserFile
|
||||
} from 'element-plus'
|
||||
|
||||
const BASE_URL = import.meta.env.VITE_BASE_URL
|
||||
const uploadUrl = BASE_URL + '/admin-api/mp/material/upload-permanent'
|
||||
const headers = { Authorization: 'Bearer ' + authUtil.getAccessToken() }
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const queryFormRef = ref()
|
||||
const uploadFormRef = ref()
|
||||
const uploadVideoRef = ref()
|
||||
const uploadFormRef = ref<FormInstance>()
|
||||
const uploadVideoRef = ref<UploadInstance>()
|
||||
|
||||
const type = ref('image')
|
||||
// 遮罩层
|
||||
const loading = ref(false)
|
||||
// 总条数
|
||||
const total = ref(0)
|
||||
// 数据列表
|
||||
const list = ref([])
|
||||
const uploadRules: FormRules = {
|
||||
title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
|
||||
introduction: [{ required: true, message: '请输入描述', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
// 素材类型
|
||||
type MaterialType = 'image' | 'voice' | 'video'
|
||||
const type = ref<MaterialType>('image')
|
||||
const loading = ref(false) // 遮罩层
|
||||
const list = ref<any[]>([]) // 总条数
|
||||
const total = ref(0) // 数据列表
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
interface QueryParams {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
accountId?: number
|
||||
permanent: boolean
|
||||
}
|
||||
const queryParams: QueryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
accountId: undefined,
|
||||
permanent: true
|
||||
})
|
||||
|
||||
const actionUrl = BASE_URL + '/admin-api/mp/material/upload-permanent'
|
||||
const headers = { Authorization: 'Bearer ' + getAccessToken() }
|
||||
const fileList = ref([])
|
||||
const uploadData = reactive({
|
||||
const fileList = ref<UploadUserFile[]>([])
|
||||
|
||||
interface UploadData {
|
||||
type: MaterialType
|
||||
title: string
|
||||
introduction: string
|
||||
}
|
||||
const uploadData: UploadData = reactive({
|
||||
type: 'image',
|
||||
title: '',
|
||||
introduction: ''
|
||||
|
@ -289,139 +312,93 @@ const uploadData = reactive({
|
|||
// === 视频上传,独有变量 ===
|
||||
const dialogVideoVisible = ref(false)
|
||||
const addMaterialLoading = ref(false)
|
||||
const uploadRules = reactive({
|
||||
// 视频上传的校验规则
|
||||
title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
|
||||
introduction: [{ required: true, message: '请输入描述', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
// 公众号账号列表
|
||||
const accountList = ref([])
|
||||
|
||||
onMounted(() => {
|
||||
getSimpleAccountList().then((data) => {
|
||||
accountList.value = data
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
setAccountId(accountList.value[0].id)
|
||||
}
|
||||
// 加载数据
|
||||
getList()
|
||||
})
|
||||
})
|
||||
|
||||
// ======================== 列表查询 ========================
|
||||
/** 设置账号编号 */
|
||||
const setAccountId = (accountId) => {
|
||||
queryParams.accountId = accountId
|
||||
uploadData.accountId = accountId
|
||||
/** 侦听公众号变化 **/
|
||||
const onAccountChanged = (id?: number) => {
|
||||
queryParams.accountId = id
|
||||
getList()
|
||||
}
|
||||
|
||||
// ======================== 列表查询 ========================
|
||||
/** 查询列表 */
|
||||
const getList = () => {
|
||||
// 如果没有选中公众号账号,则进行提示。
|
||||
if (!queryParams.accountId) {
|
||||
message.error('未选中公众号,无法查询草稿箱')
|
||||
return false
|
||||
}
|
||||
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
getMaterialPage({
|
||||
...queryParams,
|
||||
type: type.value
|
||||
})
|
||||
.then((data) => {
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
try {
|
||||
const data = await MpMaterialApi.getMaterialPage({
|
||||
...queryParams,
|
||||
type: type.value
|
||||
})
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
// 默认选中第一个
|
||||
if (queryParams.accountId) {
|
||||
setAccountId(queryParams.accountId)
|
||||
}
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields()
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
setAccountId(accountList.value[0].id)
|
||||
}
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
const handleTabChange = (tabName) => {
|
||||
const onTabChange = (tabName: TabPaneName) => {
|
||||
// 设置 type
|
||||
uploadData.type = tabName
|
||||
uploadData.type = tabName as MaterialType
|
||||
|
||||
// 提前情况数据,避免tab切换后显示垃圾数据
|
||||
list.value = []
|
||||
total.value = 0
|
||||
|
||||
// 从第一页开始查询
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
// ======================== 文件上传 ========================
|
||||
const beforeImageUpload = (file) => {
|
||||
const isType =
|
||||
file.type === 'image/jpeg' ||
|
||||
file.type === 'image/png' ||
|
||||
file.type === 'image/gif' ||
|
||||
file.type === 'image/bmp' ||
|
||||
file.type === 'image/jpg'
|
||||
if (!isType) {
|
||||
message.error('上传图片格式不对!')
|
||||
const beforeUpload = (rawFile: UploadRawFile, category: 'image' | 'audio' | 'video'): boolean => {
|
||||
let allowTypes: string[] = []
|
||||
let maxSizeMB = 0
|
||||
let name = ''
|
||||
|
||||
switch (category) {
|
||||
case 'image':
|
||||
allowTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/jpg']
|
||||
maxSizeMB = 2
|
||||
name = '图片'
|
||||
break
|
||||
case 'audio':
|
||||
allowTypes = ['audio/mp3', 'audio/mpeg', 'audio/wma', 'audio/wav', 'audio/amr']
|
||||
maxSizeMB = 2
|
||||
name = '图片'
|
||||
break
|
||||
case 'video':
|
||||
allowTypes = ['video/mp4']
|
||||
maxSizeMB = 10
|
||||
name = '视频'
|
||||
}
|
||||
|
||||
if (!allowTypes.includes(rawFile.type)) {
|
||||
message.error(`上传${name}格式不对!`)
|
||||
return false
|
||||
}
|
||||
const isLt = file.size / 1024 / 1024 < 2
|
||||
if (!isLt) {
|
||||
message.error('上传图片大小不能超过 2M!')
|
||||
// 校验大小
|
||||
if (rawFile.size / 1024 / 1024 > maxSizeMB) {
|
||||
message.error(`上传${name}大小不能超过${maxSizeMB}M!`)
|
||||
return false
|
||||
}
|
||||
loading.value = true
|
||||
return true
|
||||
}
|
||||
|
||||
const beforeVoiceUpload = (file) => {
|
||||
const isType =
|
||||
file.type === 'audio/mp3' ||
|
||||
file.type === 'audio/wma' ||
|
||||
file.type === 'audio/wav' ||
|
||||
file.type === 'audio/amr'
|
||||
const isLt = file.size / 1024 / 1024 < 2
|
||||
if (!isType) {
|
||||
message.error('上传语音格式不对!')
|
||||
return false
|
||||
}
|
||||
if (!isLt) {
|
||||
message.error('上传语音大小不能超过 2M!')
|
||||
return false
|
||||
}
|
||||
loading.value = true
|
||||
return true
|
||||
}
|
||||
const beforeImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
|
||||
beforeUpload(rawFile, 'image')
|
||||
|
||||
const beforeVideoUpload = (file) => {
|
||||
const isType = file.type === 'video/mp4'
|
||||
if (!isType) {
|
||||
message.error('上传视频格式不对!')
|
||||
return false
|
||||
}
|
||||
const isLt = file.size / 1024 / 1024 < 10
|
||||
if (!isLt) {
|
||||
message.error('上传视频大小不能超过 10M!')
|
||||
return false
|
||||
}
|
||||
addMaterialLoading.value = true
|
||||
return true
|
||||
}
|
||||
const beforeVoiceUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
|
||||
beforeUpload(rawFile, 'audio')
|
||||
|
||||
const handleUploadSuccess = (response, file, fileList) => {
|
||||
const beforeVideoUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
|
||||
beforeUpload(rawFile, 'video')
|
||||
|
||||
const handleUploadSuccess: UploadProps['onSuccess'] = (response: any) => {
|
||||
loading.value = false
|
||||
addMaterialLoading.value = false
|
||||
if (response.code !== 0) {
|
||||
|
@ -440,20 +417,21 @@ const handleUploadSuccess = (response, file, fileList) => {
|
|||
}
|
||||
|
||||
// 下载文件
|
||||
const handleDownload = (row) => {
|
||||
const handleDownload = (row: any) => {
|
||||
window.open(row.url, '_blank')
|
||||
}
|
||||
|
||||
// 提交 video 新建的表单
|
||||
const submitVideo = () => {
|
||||
uploadFormRef.value.validate((valid) => {
|
||||
uploadFormRef.value?.validate((valid) => {
|
||||
if (!valid) {
|
||||
return false
|
||||
}
|
||||
uploadVideoRef.value.submit()
|
||||
uploadVideoRef.value?.submit()
|
||||
})
|
||||
}
|
||||
|
||||
// 弹出 video 新建的表单
|
||||
const handleAddVideo = () => {
|
||||
resetVideo()
|
||||
dialogVideoVisible.value = true
|
||||
|
@ -474,9 +452,9 @@ const resetVideo = () => {
|
|||
}
|
||||
|
||||
// ======================== 其它操作 ========================
|
||||
const handleDelete = async (item) => {
|
||||
const handleDelete = async (item: any) => {
|
||||
await message.confirm('此操作将永久删除该文件, 是否继续?')
|
||||
await deletePermanentMaterial(item.id)
|
||||
await MpMaterialApi.deletePermanentMaterial(item.id)
|
||||
message.alertSuccess('删除成功')
|
||||
}
|
||||
</script>
|
||||
|
@ -487,40 +465,48 @@ const handleDelete = async (item) => {
|
|||
width: 100%;
|
||||
column-gap: 10px;
|
||||
column-count: 5;
|
||||
margin-top: 10px; /* 芋道源码:增加 10px,避免顶着上面 */
|
||||
margin-top: 10px;
|
||||
/* 芋道源码:增加 10px,避免顶着上面 */
|
||||
}
|
||||
|
||||
.waterfall-item {
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
break-inside: avoid;
|
||||
border: 1px solid #eaeaea;
|
||||
}
|
||||
|
||||
.material-img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) and (max-width: 1300px) {
|
||||
.waterfall {
|
||||
column-count: 3;
|
||||
}
|
||||
|
||||
p {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.waterfall {
|
||||
column-count: 2;
|
||||
}
|
||||
|
||||
p {
|
||||
color: orange;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.waterfall {
|
||||
column-count: 1;
|
||||
}
|
||||
}
|
||||
/*瀑布流样式*/
|
||||
</style>
|
||||
|
|
|
@ -4,18 +4,7 @@
|
|||
<ContentWrap>
|
||||
<el-form class="-mb-15px" ref="queryFormRef" :inline="true" label-width="68px">
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<el-select v-model="accountId" placeholder="请选择公众号" class="!w-240px">
|
||||
<el-option
|
||||
v-for="item in accountList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
|
||||
<WxMpSelect @change="onAccountChanged" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
@ -26,7 +15,7 @@
|
|||
<!--左边配置菜单-->
|
||||
<div class="left">
|
||||
<div class="weixin-hd">
|
||||
<div class="weixin-title">{{ name }}</div>
|
||||
<div class="weixin-title">{{ accountName }}</div>
|
||||
</div>
|
||||
<div class="weixin-menu menu_main clearfix">
|
||||
<div class="menu_bottom" v-for="(item, i) of menuList" :key="i">
|
||||
|
@ -82,7 +71,7 @@
|
|||
<div v-if="showRightFlag" class="right">
|
||||
<div class="configure_page">
|
||||
<div class="delete_btn">
|
||||
<el-button size="small" type="danger" @click="handleDeleteMenu(tempObj)">
|
||||
<el-button size="small" type="danger" @click="handleDeleteMenu">
|
||||
删除当前菜单<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</div>
|
||||
|
@ -169,7 +158,7 @@
|
|||
<div v-else>
|
||||
<el-row justify="center">
|
||||
<el-col :span="24" style="text-align: center">
|
||||
<el-button type="success" @click="openMaterial">
|
||||
<el-button type="success" @click="dialogNewsVisible = true">
|
||||
素材库选择<Icon icon="ep:circle-check" />
|
||||
</el-button>
|
||||
</el-col>
|
||||
|
@ -199,26 +188,26 @@
|
|||
</div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup name="MpMenu">
|
||||
import { handleTree } from '@/utils/tree'
|
||||
<script lang="ts" setup name="MpMenu">
|
||||
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
|
||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
|
||||
import { deleteMenu, getMenuList, saveMenu } from '@/api/mp/menu'
|
||||
import * as MpAccountApi from '@/api/mp/account'
|
||||
import WxMpSelect from '@/views/mp/components/WxMpSelect.vue'
|
||||
import * as MpMenuApi from '@/api/mp/menu'
|
||||
import { handleTree } from '@/utils/tree'
|
||||
import menuOptions from './menuOptions'
|
||||
|
||||
const message = useMessage() // 消息
|
||||
|
||||
// ======================== 列表查询 ========================
|
||||
const loading = ref(true) // 遮罩层
|
||||
const accountId = ref(undefined) // 公众号Id
|
||||
const name = ref('') // 公众号名
|
||||
const menuList = ref({ children: [] })
|
||||
const accountList = ref([]) // 公众号账号列表
|
||||
const loading = ref(false) // 遮罩层
|
||||
const accountId = ref<number | undefined>()
|
||||
const accountName = ref<string | undefined>('')
|
||||
const menuList = ref<any>({ children: [] })
|
||||
|
||||
// ======================== 菜单操作 ========================
|
||||
const isActive = ref(-1) // 一级菜单点中样式
|
||||
const isSubMenuActive = ref(-1) // 一级菜单点中样式
|
||||
const isSubMenuActive = ref<string | number>(-1) // 一级菜单点中样式
|
||||
const isSubMenuFlag = ref(-1) // 二级菜单显示标志
|
||||
|
||||
// ======================== 菜单编辑 ========================
|
||||
|
@ -226,67 +215,42 @@ const showRightFlag = ref(false) // 右边配置显示默认详情还是配置
|
|||
const nameMaxLength = ref(0) // 菜单名称最大长度;1 级是 4 字符;2 级是 7 字符;
|
||||
const showConfigureContent = ref(true) // 是否展示配置内容;如果有子菜单,就不显示配置内容
|
||||
const hackResetWxReplySelect = ref(false) // 重置 WxReplySelect 组件
|
||||
const tempObj = ref({}) // 右边临时变量,作为中间值牵引关系
|
||||
const tempObj = ref<any>({}) // 右边临时变量,作为中间值牵引关系
|
||||
|
||||
const tempSelfObj = ref({
|
||||
// 一些临时值放在这里进行判断,如果放在 tempObj,由于引用关系,menu 也会多了多余的参数
|
||||
})
|
||||
// 一些临时值放在这里进行判断,如果放在 tempObj,由于引用关系,menu 也会多了多余的参数
|
||||
const tempSelfObj = ref<any>({})
|
||||
const dialogNewsVisible = ref(false) // 跳转图文时的素材选择弹窗
|
||||
|
||||
onMounted(async () => {
|
||||
accountList.value = await MpAccountApi.getSimpleAccountList()
|
||||
// 选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
// @ts-ignore
|
||||
setAccountId(accountList.value[0].id)
|
||||
}
|
||||
await getList()
|
||||
})
|
||||
|
||||
// ======================== 列表查询 ========================
|
||||
/** 设置账号编号 */
|
||||
const setAccountId = (id) => {
|
||||
/** 侦听公众号变化 **/
|
||||
const onAccountChanged = (id?: number, name?: string) => {
|
||||
accountId.value = id
|
||||
name.value = accountList.value.find((item) => item.id === accountId.value)?.name
|
||||
accountName.value = name
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 查询并转换菜单 **/
|
||||
const getList = async () => {
|
||||
loading.value = false
|
||||
getMenuList(accountId.value)
|
||||
.then((response) => {
|
||||
const menuData = convertMenuList(response)
|
||||
menuList.value = handleTree(menuData, 'id')
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
try {
|
||||
const data = await MpMenuApi.getMenuList(accountId.value)
|
||||
const menuData = convertMenuList(data)
|
||||
menuList.value = handleTree(menuData, 'id')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
resetForm()
|
||||
// 默认选中第一个
|
||||
if (accountId.value) {
|
||||
setAccountId(accountId.value)
|
||||
}
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
resetForm()
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
setAccountId(accountList.value[0].id)
|
||||
}
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
// 将后端返回的 menuList,转换成前端的 menuList
|
||||
const convertMenuList = (list) => {
|
||||
const convertMenuList = (list: any[]) => {
|
||||
if (!list) return []
|
||||
|
||||
const menuList = []
|
||||
const result: any[] = []
|
||||
list.forEach((item) => {
|
||||
const menu = {
|
||||
...item
|
||||
|
@ -313,9 +277,9 @@ const convertMenuList = (list) => {
|
|||
hqMusicUrl: item.replyHqMusicUrl
|
||||
}
|
||||
}
|
||||
menuList.push(menu)
|
||||
result.push(menu)
|
||||
})
|
||||
return menuList
|
||||
return result
|
||||
}
|
||||
|
||||
// 重置表单,清空表单数据
|
||||
|
@ -328,7 +292,7 @@ const resetForm = () => {
|
|||
// 菜单编辑
|
||||
showRightFlag.value = false
|
||||
nameMaxLength.value = 0
|
||||
showConfigureContent.value = 0
|
||||
showConfigureContent.value = false
|
||||
hackResetWxReplySelect.value = false
|
||||
tempObj.value = {}
|
||||
tempSelfObj.value = {}
|
||||
|
@ -337,7 +301,7 @@ const resetForm = () => {
|
|||
|
||||
// ======================== 菜单操作 ========================
|
||||
// 一级菜单点击事件
|
||||
const menuClick = (i, item) => {
|
||||
const menuClick = (i: number, item: any) => {
|
||||
// 右侧的表单相关
|
||||
resetEditor()
|
||||
showRightFlag.value = true // 右边菜单
|
||||
|
@ -354,11 +318,10 @@ const menuClick = (i, item) => {
|
|||
}
|
||||
|
||||
// 二级菜单点击事件
|
||||
const subMenuClick = (subItem, index, k) => {
|
||||
const subMenuClick = (subItem: any, index: number, k: number) => {
|
||||
// 右侧的表单相关
|
||||
resetEditor()
|
||||
showRightFlag.value = true // 右边菜单
|
||||
console.log(subItem)
|
||||
tempObj.value = subItem // 将点击的数据放到临时变量,对象有引用作用
|
||||
tempSelfObj.value.grand = '2' // 表示二级菜单
|
||||
tempSelfObj.value.index = index // 表示一级菜单索引
|
||||
|
@ -373,7 +336,7 @@ const subMenuClick = (subItem, index, k) => {
|
|||
|
||||
// 添加横向一级菜单
|
||||
const addMenu = () => {
|
||||
const menuKeyLength = menuList.value.length
|
||||
const menuKeyLength: number = menuList.value.length
|
||||
const addButton = {
|
||||
name: '菜单名称',
|
||||
children: [],
|
||||
|
@ -384,10 +347,10 @@ const addMenu = () => {
|
|||
}
|
||||
}
|
||||
menuList.value[menuKeyLength] = addButton
|
||||
menuClick(menuKeyLength.value - 1, addButton)
|
||||
menuClick(menuKeyLength - 1, addButton)
|
||||
}
|
||||
// 添加横向二级菜单;item 表示要操作的父菜单
|
||||
const addSubMenu = (i, item) => {
|
||||
const addSubMenu = (i: number, item: any) => {
|
||||
// 清空父菜单的属性,因为它只需要 name 属性即可
|
||||
if (!item.children || item.children.length <= 0) {
|
||||
item.children = []
|
||||
|
@ -403,8 +366,8 @@ const addSubMenu = (i, item) => {
|
|||
showConfigureContent.value = false
|
||||
}
|
||||
|
||||
let subMenuKeyLength = item.children.length // 获取二级菜单key长度
|
||||
let addButton = {
|
||||
const subMenuKeyLength = item.children.length // 获取二级菜单key长度
|
||||
const addButton = {
|
||||
name: '子菜单名称',
|
||||
reply: {
|
||||
// 用于存储回复内容
|
||||
|
@ -441,9 +404,9 @@ const handleDeleteMenu = async () => {
|
|||
// ======================== 菜单编辑 ========================
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
await message.confirm('确定要删除吗?')
|
||||
await message.confirm('确定要保存吗?')
|
||||
loading.value = true
|
||||
await saveMenu(accountId.value, convertMenuFormList())
|
||||
await MpMenuApi.saveMenu(accountId.value, convertMenuFormList())
|
||||
getList()
|
||||
message.notifySuccess('发布成功')
|
||||
} finally {
|
||||
|
@ -455,7 +418,6 @@ const handleSave = async () => {
|
|||
const resetEditor = () => {
|
||||
hackResetWxReplySelect.value = false // 销毁组件
|
||||
nextTick(() => {
|
||||
console.log('nextTick')
|
||||
hackResetWxReplySelect.value = true // 重建组件
|
||||
})
|
||||
}
|
||||
|
@ -464,7 +426,7 @@ const handleDelete = async () => {
|
|||
try {
|
||||
await message.confirm('确定要删除吗?')
|
||||
loading.value = true
|
||||
await deleteMenu(accountId.value)
|
||||
await MpMenuApi.deleteMenu(accountId.value)
|
||||
handleQuery()
|
||||
message.notifySuccess('清空成功')
|
||||
} finally {
|
||||
|
@ -474,9 +436,9 @@ const handleDelete = async () => {
|
|||
|
||||
// 将前端的 menuList,转换成后端接收的 menuList
|
||||
const convertMenuFormList = () => {
|
||||
const result = []
|
||||
const result: any[] = []
|
||||
menuList.value.forEach((item) => {
|
||||
let menu = convertMenuForm(item)
|
||||
const menu = convertMenuForm(item)
|
||||
result.push(menu)
|
||||
|
||||
// 处理子菜单
|
||||
|
@ -492,7 +454,7 @@ const convertMenuFormList = () => {
|
|||
}
|
||||
|
||||
// 将前端的 menu,转换成后端接收的 menu
|
||||
const convertMenuForm = (menu) => {
|
||||
const convertMenuForm = (menu: any) => {
|
||||
let result = {
|
||||
...menu,
|
||||
children: undefined, // 不处理子节点
|
||||
|
@ -515,11 +477,7 @@ const convertMenuForm = (menu) => {
|
|||
}
|
||||
|
||||
// ======================== 菜单编辑(素材选择) ========================
|
||||
const openMaterial = () => {
|
||||
dialogNewsVisible.value = true
|
||||
}
|
||||
|
||||
const selectMaterial = (item) => {
|
||||
const selectMaterial = (item: any) => {
|
||||
const articleId = item.articleId
|
||||
const articles = item.content.newsItem
|
||||
// 提示,针对多图文
|
||||
|
@ -546,6 +504,7 @@ const deleteMaterial = () => {
|
|||
delete tempObj.value['replyArticles']
|
||||
}
|
||||
</script>
|
||||
|
||||
<!--本组件样式-->
|
||||
<style lang="scss" scoped="scoped">
|
||||
/* 公共颜色变量 */
|
||||
|
|
|
@ -9,14 +9,7 @@
|
|||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<el-select v-model="queryParams.accountId" placeholder="请选择公众号" class="!w-240px">
|
||||
<el-option
|
||||
v-for="item in accountList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<WxMpSelect @change="onAccountChanged" />
|
||||
</el-form-item>
|
||||
<el-form-item label="消息类型" prop="type">
|
||||
<el-select v-model="queryParams.type" placeholder="请选择消息类型" class="!w-240px">
|
||||
|
@ -84,70 +77,76 @@
|
|||
<el-table-column label="内容" prop="content">
|
||||
<template #default="scope">
|
||||
<!-- 【事件】区域 -->
|
||||
<div v-if="scope.row.type === 'event' && scope.row.event === 'subscribe'">
|
||||
<div v-if="scope.row.type === MsgType.Event && scope.row.event === 'subscribe'">
|
||||
<el-tag type="success">关注</el-tag>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'unsubscribe'">
|
||||
<div v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'unsubscribe'">
|
||||
<el-tag type="danger">取消关注</el-tag>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'CLICK'">
|
||||
<div v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'CLICK'">
|
||||
<el-tag>点击菜单</el-tag>
|
||||
【{{ scope.row.eventKey }}】
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'VIEW'">
|
||||
<div v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'VIEW'">
|
||||
<el-tag>点击菜单链接</el-tag>
|
||||
【{{ scope.row.eventKey }}】
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'scancode_waitmsg'">
|
||||
<div
|
||||
v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'scancode_waitmsg'"
|
||||
>
|
||||
<el-tag>扫码结果</el-tag>
|
||||
【{{ scope.row.eventKey }}】
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'scancode_push'">
|
||||
<div v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'scancode_push'">
|
||||
<el-tag>扫码结果</el-tag>
|
||||
【{{ scope.row.eventKey }}】
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_sysphoto'">
|
||||
<div v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'pic_sysphoto'">
|
||||
<el-tag>系统拍照发图</el-tag>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_photo_or_album'">
|
||||
<div
|
||||
v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'pic_photo_or_album'"
|
||||
>
|
||||
<el-tag>拍照或者相册</el-tag>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_weixin'">
|
||||
<div v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'pic_weixin'">
|
||||
<el-tag>微信相册</el-tag>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'location_select'">
|
||||
<div
|
||||
v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'location_select'"
|
||||
>
|
||||
<el-tag>选择地理位置</el-tag>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === 'event'">
|
||||
<div v-else-if="scope.row.type === MsgType.Event">
|
||||
<el-tag type="danger">未知事件类型</el-tag>
|
||||
</div>
|
||||
<!-- 【消息】区域 -->
|
||||
<div v-else-if="scope.row.type === 'text'">{{ scope.row.content }}</div>
|
||||
<div v-else-if="scope.row.type === 'voice'">
|
||||
<div v-else-if="scope.row.type === MsgType.Text">{{ scope.row.content }}</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Voice">
|
||||
<wx-voice-player :url="scope.row.mediaUrl" :content="scope.row.recognition" />
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === 'image'">
|
||||
<div v-else-if="scope.row.type === MsgType.Image">
|
||||
<a target="_blank" :href="scope.row.mediaUrl">
|
||||
<img :src="scope.row.mediaUrl" style="width: 100px" />
|
||||
</a>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === 'video' || scope.row.type === 'shortvideo'">
|
||||
<div v-else-if="scope.row.type === MsgType.Video || scope.row.type === 'shortvideo'">
|
||||
<wx-video-player :url="scope.row.mediaUrl" style="margin-top: 10px" />
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === 'link'">
|
||||
<div v-else-if="scope.row.type === MsgType.Link">
|
||||
<el-tag>链接</el-tag>
|
||||
:
|
||||
<a :href="scope.row.url" target="_blank">{{ scope.row.title }}</a>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === 'location'">
|
||||
<wx-location
|
||||
<div v-else-if="scope.row.type === MsgType.Location">
|
||||
<WxLocation
|
||||
:label="scope.row.label"
|
||||
:location-y="scope.row.locationY"
|
||||
:location-x="scope.row.locationX"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === 'music'">
|
||||
<wx-music
|
||||
<div v-else-if="scope.row.type === MsgType.Music">
|
||||
<WxMusic
|
||||
:title="scope.row.title"
|
||||
:description="scope.row.description"
|
||||
:thumb-media-url="scope.row.thumbMediaUrl"
|
||||
|
@ -155,8 +154,8 @@
|
|||
:hq-music-url="scope.row.hqMusicUrl"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === 'news'">
|
||||
<wx-news :articles="scope.row.articles" />
|
||||
<div v-else-if="scope.row.type === MsgType.News">
|
||||
<WxNews :articles="scope.row.articles" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-tag type="danger">未知消息类型</el-tag>
|
||||
|
@ -177,7 +176,7 @@
|
|||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页组件 -->
|
||||
<pagination
|
||||
<Pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
|
@ -186,9 +185,14 @@
|
|||
/>
|
||||
|
||||
<!-- 发送消息的弹窗 -->
|
||||
<el-dialog title="粉丝消息列表" v-model="open" @click="openDialog()" width="50%">
|
||||
<el-dialog
|
||||
title="粉丝消息列表"
|
||||
v-model="showMessageBox"
|
||||
@click="showMessageBox = true"
|
||||
width="50%"
|
||||
>
|
||||
<template #footer>
|
||||
<wx-msg :user-id="userId" v-if="open" />
|
||||
<WxMsg :user-id="userId" v-if="showMessageBox" />
|
||||
</template>
|
||||
</el-dialog>
|
||||
</ContentWrap>
|
||||
|
@ -200,17 +204,27 @@ import WxMsg from '@/views/mp/components/wx-msg/main.vue'
|
|||
import WxLocation from '@/views/mp/components/wx-location/main.vue'
|
||||
import WxMusic from '@/views/mp/components/wx-music/main.vue'
|
||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||
import * as MpAccountApi from '@/api/mp/account'
|
||||
import WxMpSelect from '@/views/mp/components/WxMpSelect.vue'
|
||||
import * as MpMessageApi from '@/api/mp/message'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { MsgType } from '@/views/mp/components/wx-msg/types'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
const list = ref<any[]>([]) // 列表的数据
|
||||
|
||||
// 搜索参数
|
||||
interface QueryParams {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
openid: string | null
|
||||
accountId: number | null
|
||||
type: MsgType | null
|
||||
createTime: string[] | []
|
||||
}
|
||||
const queryParams: QueryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
openid: null,
|
||||
|
@ -218,19 +232,18 @@ const queryParams = reactive({
|
|||
type: null,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
// TODO 芋艿:下面应该移除
|
||||
const open = ref(false) // 是否显示弹出层
|
||||
const queryFormRef = ref<FormInstance | null>(null) // 搜索的表单
|
||||
const showMessageBox = ref(false) // 是否显示弹出层
|
||||
const userId = ref(0) // 操作的用户编号
|
||||
const accountList = ref<MpAccountApi.AccountVO[]>([]) // 公众号账号列表
|
||||
|
||||
/** 侦听accountId */
|
||||
const onAccountChanged = (id?: number) => {
|
||||
queryParams.accountId = id as number
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
// 如果没有选中公众号账号,则进行提示。
|
||||
if (!queryParams.accountId) {
|
||||
await message.error('未选中公众号,无法查询消息')
|
||||
return
|
||||
}
|
||||
try {
|
||||
loading.value = true
|
||||
const data = await MpMessageApi.getMessagePage(queryParams)
|
||||
|
@ -249,34 +262,15 @@ const handleQuery = () => {
|
|||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = async () => {
|
||||
queryFormRef.value.resetFields()
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
// @ts-ignore
|
||||
queryParams.accountId = accountList.value[0].id
|
||||
}
|
||||
const accountId = queryParams.accountId
|
||||
queryFormRef.value?.resetFields()
|
||||
queryParams.accountId = accountId
|
||||
handleQuery()
|
||||
}
|
||||
const handleSend = async (row) => {
|
||||
|
||||
/** 打开消息发送窗口 */
|
||||
const handleSend = async (row: any) => {
|
||||
userId.value = row.userId
|
||||
open.value = true
|
||||
showMessageBox.value = true
|
||||
}
|
||||
|
||||
const openDialog = () => {
|
||||
open.value = true
|
||||
}
|
||||
// const closeDiaLog = () => {
|
||||
// open.value = false
|
||||
// }
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
accountList.value = await MpAccountApi.getSimpleAccountList()
|
||||
// 选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
// @ts-ignore
|
||||
queryParams.accountId = accountList.value[0].id
|
||||
}
|
||||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -19,24 +19,30 @@
|
|||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as MpTagApi from '@/api/mp/tag'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formType = ref<'create' | 'update' | ''>('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
accountId: -1,
|
||||
name: ''
|
||||
})
|
||||
const formRules = reactive({
|
||||
const formRules: FormRules = {
|
||||
name: [{ required: true, message: '请输入标签名称', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
}
|
||||
const formRef = ref<FormInstance | null>(null) // 表单 Ref
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'success'): void
|
||||
}>()
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, accountId: number, id?: number) => {
|
||||
const open = async (type: 'create' | 'update', accountId: number, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
|
@ -55,11 +61,10 @@ const open = async (type: string, accountId: number, id?: number) => {
|
|||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
const valid = await formRef.value?.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
|
|
|
@ -11,29 +11,9 @@
|
|||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<el-select v-model="queryParams.accountId" placeholder="请选择公众号" class="!w-240px">
|
||||
<el-option
|
||||
v-for="item in accountList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="标签名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入标签名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
<WxMpSelect @change="onAccountChanged" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
</el-form-item>
|
||||
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['mp:tag:create']">
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
|
@ -93,30 +73,36 @@
|
|||
<script setup lang="ts" name="MpTag">
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as MpTagApi from '@/api/mp/tag'
|
||||
import * as MpAccountApi from '@/api/mp/account'
|
||||
import TagForm from './TagForm.vue'
|
||||
import WxMpSelect from '@/views/mp/components/WxMpSelect.vue'
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
const list = ref<any>([]) // 列表的数据
|
||||
|
||||
interface QueryParams {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
accountId?: number
|
||||
}
|
||||
const queryParams: QueryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
accountId: undefined,
|
||||
name: null
|
||||
accountId: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const accountList = ref<MpAccountApi.AccountVO[]>([]) // 公众号账号列表
|
||||
const formRef = ref<InstanceType<typeof TagForm> | null>(null)
|
||||
|
||||
/** 侦听公众号变化 **/
|
||||
const onAccountChanged = (id?: number) => {
|
||||
queryParams.pageNo = 1
|
||||
queryParams.accountId = id
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
// 如果没有选中公众号账号,则进行提示。
|
||||
if (!queryParams.accountId) {
|
||||
await message.error('未选中公众号,无法查询标签')
|
||||
return
|
||||
}
|
||||
try {
|
||||
loading.value = true
|
||||
const data = await MpTagApi.getTagPage(queryParams)
|
||||
|
@ -127,26 +113,9 @@ const getList = async () => {
|
|||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
queryParams.accountId = accountList.value[0].id
|
||||
}
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, queryParams.accountId, id)
|
||||
formRef.value?.open(type, queryParams.accountId as number, id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
|
@ -166,20 +135,9 @@ const handleDelete = async (id: number) => {
|
|||
const handleSync = async () => {
|
||||
try {
|
||||
await message.confirm('是否确认同步标签?')
|
||||
// @ts-ignore
|
||||
await MpTagApi.syncTag(queryParams.accountId)
|
||||
await MpTagApi.syncTag(queryParams.accountId as number)
|
||||
message.success('同步标签成功')
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
accountList.value = await MpAccountApi.getSimpleAccountList()
|
||||
// 选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
queryParams.accountId = accountList.value[0].id
|
||||
}
|
||||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -11,14 +11,7 @@
|
|||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<el-select v-model="queryParams.accountId" placeholder="请选择公众号" class="!w-240px">
|
||||
<el-option
|
||||
v-for="item in accountList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<WxMpSelect @change="onAccountChanged" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户标识" prop="openid">
|
||||
<el-input
|
||||
|
@ -39,8 +32,8 @@
|
|||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
|
||||
<el-button @click="handleQuery"> <Icon icon="ep:search" />搜索 </el-button>
|
||||
<el-button @click="resetQuery"> <Icon icon="ep:refresh" />重置 </el-button>
|
||||
<el-button type="success" plain @click="handleSync" v-hasPermi="['mp:user:sync']">
|
||||
<Icon icon="ep:refresh" class="mr-5px" /> 同步
|
||||
</el-button>
|
||||
|
@ -102,33 +95,44 @@
|
|||
</template>
|
||||
<script lang="ts" setup name="MpUser">
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as MpAccountApi from '@/api/mp/account'
|
||||
import * as MpUserApi from '@/api/mp/user'
|
||||
import * as MpTagApi from '@/api/mp/tag'
|
||||
import WxMpSelect from '@/views/mp/components/WxMpSelect.vue'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import UserForm from './UserForm.vue'
|
||||
|
||||
const message = useMessage() // 消息
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
const list = ref<any[]>([]) // 列表的数据
|
||||
|
||||
interface QueryParams {
|
||||
pageNo: number
|
||||
pageSize: number
|
||||
accountId?: number
|
||||
openid: string | null
|
||||
nickname: string | null
|
||||
}
|
||||
const queryParams: QueryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
accountId: null,
|
||||
accountId: undefined,
|
||||
openid: null,
|
||||
nickname: null
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const accountList = ref([]) // 公众号账号列表
|
||||
const tagList = ref([]) // 公众号标签列表
|
||||
const queryFormRef = ref<FormInstance | null>(null) // 搜索的表单
|
||||
const tagList = ref<any[]>([]) // 公众号标签列表
|
||||
|
||||
/** 侦听公众号变化 **/
|
||||
const onAccountChanged = (id?: number) => {
|
||||
queryParams.pageNo = 1
|
||||
queryParams.accountId = id
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
// 如果没有选中公众号账号,则进行提示。
|
||||
if (!queryParams.accountId) {
|
||||
message.error('未选中公众号,无法查询用户')
|
||||
return false
|
||||
}
|
||||
try {
|
||||
loading.value = true
|
||||
const data = await MpUserApi.getUserPage(queryParams)
|
||||
|
@ -147,26 +151,23 @@ const handleQuery = () => {
|
|||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
queryParams.accountId = accountList.value[0].id
|
||||
}
|
||||
const accountId = queryParams.accountId
|
||||
queryFormRef.value?.resetFields()
|
||||
queryParams.accountId = accountId
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const formRef = ref<InstanceType<typeof UserForm> | null>(null)
|
||||
const openForm = (id: number) => {
|
||||
formRef.value.open(id)
|
||||
formRef.value?.open(id)
|
||||
}
|
||||
|
||||
/** 同步标签 */
|
||||
const handleSync = async () => {
|
||||
const accountId = queryParams.accountId
|
||||
try {
|
||||
await message.confirm('是否确认同步粉丝?')
|
||||
await MpUserApi.syncUser(accountId)
|
||||
await MpUserApi.syncUser(queryParams.accountId)
|
||||
message.success('开始从微信公众号同步粉丝信息,同步需要一段时间,建议稍后再查询')
|
||||
await getList()
|
||||
} catch {}
|
||||
|
@ -174,14 +175,6 @@ const handleSync = async () => {
|
|||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
// 加载标签
|
||||
tagList.value = await MpTagApi.getSimpleTagList()
|
||||
|
||||
// 加载账号
|
||||
accountList.value = await MpAccountApi.getSimpleAccountList()
|
||||
if (accountList.value.length > 0) {
|
||||
queryParams.accountId = accountList.value[0].id
|
||||
}
|
||||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="160px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="应用名" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入应用名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属商户" prop="merchantId">
|
||||
<el-select v-model="formData.merchantId" placeholder="请选择所属商户">
|
||||
<el-option
|
||||
v-for="item in merchantList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="开启状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="支付结果的回调地址" prop="payNotifyUrl">
|
||||
<el-input v-model="formData.payNotifyUrl" placeholder="请输入支付结果的回调地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="退款结果的回调地址" prop="refundNotifyUrl">
|
||||
<el-input v-model="formData.refundNotifyUrl" placeholder="请输入退款结果的回调地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import * as AppApi from '@/api/pay/app'
|
||||
import * as MerchantApi from '@/api/pay/merchant'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
packageId: undefined,
|
||||
contactName: undefined,
|
||||
contactMobile: undefined,
|
||||
accountCount: undefined,
|
||||
expireTime: undefined,
|
||||
domain: undefined,
|
||||
status: CommonStatusEnum.ENABLE
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '应用名不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }],
|
||||
payNotifyUrl: [{ required: true, message: '支付结果的回调地址不能为空', trigger: 'blur' }],
|
||||
refundNotifyUrl: [{ required: true, message: '退款结果的回调地址不能为空', trigger: 'blur' }],
|
||||
merchantId: [{ required: true, message: '商户编号不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const merchantList = ref([]) // 商户列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await AppApi.getApp(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
// 加载商户列表
|
||||
merchantList.value = await MerchantApi.getMerchantListByName()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as AppApi.AppVO
|
||||
if (formType.value === 'create') {
|
||||
await AppApi.createApp(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await AppApi.updateApp(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
remark: undefined,
|
||||
payNotifyUrl: undefined,
|
||||
refundNotifyUrl: undefined,
|
||||
merchantId: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -1,71 +0,0 @@
|
|||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
name: [required],
|
||||
status: [required],
|
||||
payNotifyUrl: [required],
|
||||
refundNotifyUrl: [required],
|
||||
merchantId: [required]
|
||||
})
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: 'seq',
|
||||
primaryTitle: '编号',
|
||||
action: true,
|
||||
columns: [
|
||||
{
|
||||
title: '应用名',
|
||||
field: 'name',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '商户名称',
|
||||
field: 'payMerchant',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.COMMON_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '支付结果的回调地址',
|
||||
field: 'payNotifyUrl',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '退款结果的回调地址',
|
||||
field: 'refundNotifyUrl',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '商户名称',
|
||||
field: 'merchantName',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
field: 'remark',
|
||||
isTable: false,
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
isForm: false,
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
|
@ -1,155 +1,463 @@
|
|||
<template>
|
||||
<!-- 搜索 -->
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<template #toolbar_buttons>
|
||||
<!-- 操作:新增 -->
|
||||
<XButton
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="应用名" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="请输入应用名"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="商户名称" prop="contactName">
|
||||
<el-input
|
||||
v-model="queryParams.contactName"
|
||||
placeholder="请输入商户名称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="开启状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择开启状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" />搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
preIcon="ep:zoom-in"
|
||||
:title="t('action.add')"
|
||||
v-hasPermi="['pay:app:create']"
|
||||
@click="handleCreate()"
|
||||
/>
|
||||
<!-- 操作:导出 -->
|
||||
<XButton
|
||||
type="warning"
|
||||
preIcon="ep:download"
|
||||
:title="t('action.export')"
|
||||
v-hasPermi="['pay:app:export']"
|
||||
@click="exportList('应用信息.xls')"
|
||||
/>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:修改 -->
|
||||
<XTextButton
|
||||
preIcon="ep:edit"
|
||||
:title="t('action.edit')"
|
||||
v-hasPermi="['pay:app:update']"
|
||||
@click="handleUpdate(row.id)"
|
||||
/>
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton
|
||||
preIcon="ep:view"
|
||||
:title="t('action.detail')"
|
||||
v-hasPermi="['pay:app:query']"
|
||||
@click="handleDetail(row.id)"
|
||||
/>
|
||||
<!-- 操作:删除 -->
|
||||
<XTextButton
|
||||
preIcon="ep:delete"
|
||||
:title="t('action.del')"
|
||||
v-hasPermi="['pay:app:delete']"
|
||||
@click="deleteData(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['system:tenant:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
v-hasPermi="['system:tenant:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<XModal v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
:schema="allSchemas.formSchema"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
/>
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions
|
||||
v-if="actionType === 'detail'"
|
||||
:schema="allSchemas.detailSchema"
|
||||
:data="detailData"
|
||||
/>
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<!-- 按钮:保存 -->
|
||||
<XButton
|
||||
v-if="['create', 'update'].includes(actionType)"
|
||||
type="primary"
|
||||
:title="t('action.save')"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm()"
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="应用编号" align="center" prop="id" />
|
||||
<el-table-column label="应用名" align="center" prop="name" />
|
||||
<el-table-column label="开启状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="商户名称" align="center" prop="payMerchant.name" />
|
||||
<el-table-column label="支付宝配置" align="center">
|
||||
<el-table-column :label="PayChannelEnum.ALIPAY_APP.name" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="success"
|
||||
v-if="isChannelExists(scope.row.channelCodes, PayChannelEnum.ALIPAY_APP.code)"
|
||||
@click="
|
||||
handleUpdateChannel(scope.row, PayChannelEnum.ALIPAY_APP.code, PayType.ALIPAY)
|
||||
"
|
||||
circle
|
||||
>
|
||||
<Icon icon="ep:check" />
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
type="danger"
|
||||
circle
|
||||
@click="
|
||||
handleCreateChannel(scope.row, PayChannelEnum.ALIPAY_APP.code, PayType.ALIPAY)
|
||||
"
|
||||
>
|
||||
<Icon icon="ep:close" />
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="PayChannelEnum.ALIPAY_PC.name" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="success"
|
||||
circle
|
||||
v-if="isChannelExists(scope.row.channelCodes, PayChannelEnum.ALIPAY_PC.code)"
|
||||
@click="handleUpdateChannel(scope.row, PayChannelEnum.ALIPAY_PC.code, PayType.ALIPAY)"
|
||||
>
|
||||
<Icon icon="ep:check" />
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
type="danger"
|
||||
circle
|
||||
@click="handleCreateChannel(scope.row, PayChannelEnum.ALIPAY_PC.code, PayType.ALIPAY)"
|
||||
>
|
||||
<Icon icon="ep:close" />
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="PayChannelEnum.ALIPAY_WAP.name" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="success"
|
||||
circle
|
||||
v-if="isChannelExists(scope.row.channelCodes, PayChannelEnum.ALIPAY_WAP.code)"
|
||||
@click="
|
||||
handleUpdateChannel(scope.row, PayChannelEnum.ALIPAY_WAP.code, PayType.ALIPAY)
|
||||
"
|
||||
>
|
||||
<Icon icon="ep:check" />
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
type="danger"
|
||||
circle
|
||||
@click="
|
||||
handleCreateChannel(scope.row, PayChannelEnum.ALIPAY_WAP.code, PayType.ALIPAY)
|
||||
"
|
||||
>
|
||||
<Icon icon="ep:close" />
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="PayChannelEnum.ALIPAY_QR.name" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="success"
|
||||
circle
|
||||
v-if="isChannelExists(scope.row.channelCodes, PayChannelEnum.ALIPAY_QR.code)"
|
||||
@click="handleUpdateChannel(scope.row, PayChannelEnum.ALIPAY_QR.code, PayType.ALIPAY)"
|
||||
>
|
||||
<Icon icon="ep:check" />
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
type="danger"
|
||||
circle
|
||||
@click="handleCreateChannel(scope.row, PayChannelEnum.ALIPAY_QR.code, PayType.ALIPAY)"
|
||||
>
|
||||
<Icon icon="ep:close" />
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="PayChannelEnum.ALIPAY_BAR.name" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="success"
|
||||
circle
|
||||
v-if="isChannelExists(scope.row.channelCodes, PayChannelEnum.ALIPAY_BAR.code)"
|
||||
@click="
|
||||
handleUpdateChannel(scope.row, PayChannelEnum.ALIPAY_BAR.code, PayType.ALIPAY)
|
||||
"
|
||||
>
|
||||
<Icon icon="ep:check" />
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
type="danger"
|
||||
circle
|
||||
@click="
|
||||
handleCreateChannel(scope.row, PayChannelEnum.ALIPAY_BAR.code, PayType.ALIPAY)
|
||||
"
|
||||
>
|
||||
<Icon icon="ep:close" />
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
<el-table-column label="微信配置" align="center">
|
||||
<el-table-column :label="PayChannelEnum.WX_LITE.name" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="success"
|
||||
circle
|
||||
v-if="isChannelExists(scope.row.channelCodes, PayChannelEnum.WX_LITE.code)"
|
||||
@click="handleUpdateChannel(scope.row, PayChannelEnum.WX_LITE.code, PayType.WECHAT)"
|
||||
>
|
||||
<Icon icon="ep:check" />
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
type="danger"
|
||||
circle
|
||||
@click="handleCreateChannel(scope.row, PayChannelEnum.WX_LITE.code, PayType.WECHAT)"
|
||||
>
|
||||
<Icon icon="ep:close" />
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="PayChannelEnum.WX_PUB.name" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="success"
|
||||
circle
|
||||
v-if="isChannelExists(scope.row.channelCodes, PayChannelEnum.WX_PUB.code)"
|
||||
@click="handleUpdateChannel(scope.row, PayChannelEnum.WX_PUB.code, PayType.WECHAT)"
|
||||
>
|
||||
<Icon icon="ep:check" />
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
type="danger"
|
||||
circle
|
||||
@click="handleCreateChannel(scope.row, PayChannelEnum.WX_PUB.code, PayType.WECHAT)"
|
||||
>
|
||||
<Icon icon="ep:close" />
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="PayChannelEnum.WX_APP.name" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="success"
|
||||
circle
|
||||
v-if="isChannelExists(scope.row.channelCodes, PayChannelEnum.WX_APP.code)"
|
||||
@click="handleUpdateChannel(scope.row, PayChannelEnum.WX_APP.code, PayType.WECHAT)"
|
||||
>
|
||||
<Icon icon="ep:check" />
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
type="danger"
|
||||
circle
|
||||
@click="handleCreateChannel(scope.row, PayChannelEnum.WX_APP.code, PayType.WECHAT)"
|
||||
>
|
||||
<Icon icon="ep:close" />
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
<el-table-column label="操作" align="center" min-width="110" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openForm('update', scope.row.id)"
|
||||
v-hasPermi="['system:tenant:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['system:tenant:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<AppForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script setup lang="ts" name="PayApp">
|
||||
import type { FormExpose } from '@/components/Form'
|
||||
import { rules, allSchemas } from './app.data'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as AppApi from '@/api/pay/app'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
import AppForm from '@/views/pay/app/AppForm.vue'
|
||||
import { PayChannelEnum, PayType } from '@/utils/constants'
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 列表相关的变量
|
||||
const [registerTable, { reload, deleteData, exportList }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: AppApi.getAppPage,
|
||||
deleteApi: AppApi.deleteApp,
|
||||
exportListApi: AppApi.exportApp
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: undefined,
|
||||
status: undefined,
|
||||
remark: undefined,
|
||||
payNotifyUrl: undefined,
|
||||
refundNotifyUrl: undefined,
|
||||
merchantName: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const channelParam = reactive({
|
||||
loading: false,
|
||||
edit: false, // 是否修改
|
||||
wechatOpen: false, // 微信是否显示
|
||||
aliPayOpen: false, // 支付宝是否显示
|
||||
appId: null, // 应用 ID
|
||||
payCode: null, // 渠道编码
|
||||
// 商户对象
|
||||
payMerchant: {
|
||||
id: null, // 编号
|
||||
name: null // 名称
|
||||
}
|
||||
}) // 微信组件传参参数
|
||||
|
||||
// ========== CRUD 相关 ==========
|
||||
const actionLoading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const detailData = ref() // 详情 Ref
|
||||
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await AppApi.getAppPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
const handleUpdate = async (rowId: number) => {
|
||||
setDialogTile('update')
|
||||
// 设置数据
|
||||
const res = await AppApi.getApp(rowId)
|
||||
unref(formRef)?.setValues(res)
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (rowId: number) => {
|
||||
setDialogTile('detail')
|
||||
const res = await AppApi.getApp(rowId)
|
||||
detailData.value = res
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
const submitForm = async () => {
|
||||
const elForm = unref(formRef)?.getElFormRef()
|
||||
if (!elForm) return
|
||||
elForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
actionLoading.value = true
|
||||
// 提交请求
|
||||
try {
|
||||
const data = unref(formRef)?.formModel as AppApi.AppVO
|
||||
if (actionType.value === 'create') {
|
||||
await AppApi.createApp(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await AppApi.updateApp(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 AppApi.deleteApp(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await AppApi.exportApp(queryParams)
|
||||
download.excel(data, '支付应用信息.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据渠道编码判断渠道列表中是否存在
|
||||
*
|
||||
* @param channels 渠道列表
|
||||
* @param channelCode 渠道编码
|
||||
*/
|
||||
const isChannelExists = (channels, channelCode) => {
|
||||
if (!channels) {
|
||||
return false
|
||||
}
|
||||
return channels.indexOf(channelCode) !== -1
|
||||
}
|
||||
|
||||
// TODO @芋艿:handleUpdateChannel 和 handleCreateChannel 合并,成为 openChannelForm
|
||||
/**
|
||||
* 修改支付渠道信息
|
||||
*
|
||||
* @param row 行记录
|
||||
* @param payCode 支付编码
|
||||
* @param type 支付类型
|
||||
*/
|
||||
const handleUpdateChannel = async (row, payCode, type) => {
|
||||
// TODO @芋艿:表单未实现
|
||||
message.alert('待实现')
|
||||
await settingChannelParam(row, payCode, type)
|
||||
channelParam.edit = true
|
||||
channelParam.loading = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增支付渠道信息
|
||||
*/
|
||||
const handleCreateChannel = async (row, payCode, type) => {
|
||||
message.alert('待实现')
|
||||
await settingChannelParam(row, payCode, type)
|
||||
channelParam.edit = false
|
||||
channelParam.loading = false
|
||||
}
|
||||
|
||||
const settingChannelParam = async (row, payCode, type) => {
|
||||
if (type === PayType.WECHAT) {
|
||||
channelParam.wechatOpen = true
|
||||
channelParam.aliPayOpen = false
|
||||
}
|
||||
if (type === PayType.ALIPAY) {
|
||||
channelParam.aliPayOpen = true
|
||||
channelParam.wechatOpen = false
|
||||
}
|
||||
channelParam.edit = false
|
||||
channelParam.loading = false
|
||||
channelParam.appId = row.id
|
||||
channelParam.payCode = payCode
|
||||
channelParam.payMerchant = row.payMerchant
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
<template>
|
||||
<Dialog title="详情" v-model="dialogVisible" width="50%">
|
||||
<el-descriptions :column="2">
|
||||
<el-descriptions-item label="商户名称">{{ detailData.merchantName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="应用名称">{{ detailData.appName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="商品名称">{{ detailData.subject }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-divider />
|
||||
<el-descriptions :column="2">
|
||||
<el-descriptions-item label="商户订单号">
|
||||
<el-tag>{{ detailData.merchantOrderId }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="渠道订单号">
|
||||
<el-tag class="tag-purple">{{ detailData.channelOrderNo }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="支付订单号">
|
||||
<el-tag v-if="detailData.payOrderExtension" class="tag-pink">
|
||||
{{ detailData.payOrderExtension.no }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="金额">
|
||||
<el-tag type="success">¥{{ parseFloat(detailData.amount / 100, 2).toFixed(2) }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="手续费">
|
||||
<el-tag type="warning">
|
||||
¥{{ parseFloat(detailData.channelFeeAmount / 100, 2).toFixed(2) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="手续费比例">
|
||||
{{ parseFloat(detailData.channelFeeRate / 100, 2) }}%
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="支付状态">
|
||||
<dict-tag :type="DICT_TYPE.PAY_ORDER_STATUS" :value="detailData.status" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="回调状态">
|
||||
<dict-tag :type="DICT_TYPE.PAY_ORDER_NOTIFY_STATUS" :value="detailData.notifyStatus" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="回调地址">{{ detailData.notifyUrl }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDate(detailData.createTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="支付时间">
|
||||
{{ formatDate(detailData.successTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="失效时间">
|
||||
{{ formatDate(detailData.expireTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="通知时间">
|
||||
{{ formatDate(detailData.notifyTime) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-divider />
|
||||
<el-descriptions :column="2">
|
||||
<el-descriptions-item label="支付渠道"
|
||||
>{{ detailData.channelCodeName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="支付IP">{{ detailData.userIp }}</el-descriptions-item>
|
||||
<el-descriptions-item label="退款状态">
|
||||
<dict-tag :type="DICT_TYPE.PAY_ORDER_REFUND_STATUS" :value="detailData.refundStatus" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="退款次数">{{ detailData.refundTimes }}</el-descriptions-item>
|
||||
<el-descriptions-item label="退款金额">
|
||||
<el-tag type="warning">
|
||||
{{ parseFloat(detailData.refundAmount / 100, 2) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-divider />
|
||||
<el-descriptions :column="1" direction="vertical" border>
|
||||
<el-descriptions-item label="商品描述">
|
||||
{{ detailData.body }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="支付通道异步回调内容">
|
||||
<div style="width: 700px; overflow: auto">
|
||||
{{ detailData.payOrderExtension?.channelNotifyData }}
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts" name="orderForm">
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import * as OrderApi from '@/api/pay/order'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const detailLoading = ref(false) // 表单的加载中
|
||||
const detailData = ref({})
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: number) => {
|
||||
dialogVisible.value = true
|
||||
// 设置数据
|
||||
detailLoading.value = true
|
||||
try {
|
||||
detailData.value = await OrderApi.getOrderDetail(id)
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
</script>
|
||||
<style>
|
||||
.tag-purple {
|
||||
color: #722ed1;
|
||||
background: #f9f0ff;
|
||||
border-color: #d3adf7;
|
||||
}
|
||||
|
||||
.tag-pink {
|
||||
color: #eb2f96;
|
||||
background: #fff0f6;
|
||||
border-color: #ffadd2;
|
||||
}
|
||||
</style>
|
|
@ -1,79 +1,336 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<template #toolbar_buttons>
|
||||
<!-- 操作:新增 -->
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:zoom-in"
|
||||
:title="t('action.add')"
|
||||
v-hasPermi="['pay:order:create']"
|
||||
@click="handleCreate()"
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="所属商户" prop="merchantId">
|
||||
<el-select
|
||||
v-model="queryParams.merchantId"
|
||||
clearable
|
||||
placeholder="请选择所属商户"
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in merchantList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="应用编号" prop="appId">
|
||||
<el-select
|
||||
clearable
|
||||
v-model="queryParams.appId"
|
||||
placeholder="请选择应用信息"
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option v-for="item in appList" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="渠道编码" prop="channelCode">
|
||||
<el-select
|
||||
v-model="queryParams.channelCode"
|
||||
placeholder="请输入渠道编码"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.PAY_CHANNEL_CODE_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="商户订单编号" prop="merchantOrderId">
|
||||
<el-input
|
||||
v-model="queryParams.merchantOrderId"
|
||||
placeholder="请输入商户订单编号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
<!-- 操作:导出 -->
|
||||
<XButton
|
||||
type="warning"
|
||||
preIcon="ep:download"
|
||||
:title="t('action.export')"
|
||||
v-hasPermi="['pay:order:export']"
|
||||
@click="exportList('订单数据.xls')"
|
||||
</el-form-item>
|
||||
<el-form-item label="渠道订单号" prop="channelOrderNo">
|
||||
<el-input
|
||||
v-model="queryParams.channelOrderNo"
|
||||
placeholder="请输入渠道订单号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton
|
||||
preIcon="ep:view"
|
||||
:title="t('action.detail')"
|
||||
v-hasPermi="['pay:order:query']"
|
||||
@click="handleDetail(row.id)"
|
||||
</el-form-item>
|
||||
<el-form-item label="支付状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择支付状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PAY_ORDER_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="退款状态" prop="refundStatus">
|
||||
<el-select
|
||||
v-model="queryParams.refundStatus"
|
||||
placeholder="请选择退款状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PAY_ORDER_REFUND_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="回调商户状态" prop="notifyStatus">
|
||||
<el-select
|
||||
v-model="queryParams.notifyStatus"
|
||||
placeholder="请选择订单回调商户状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PAY_ORDER_NOTIFY_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</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="['system:tenant:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
<XModal v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions :schema="allSchemas.detailSchema" :data="detailData" />
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="订单编号" align="center" prop="id" />
|
||||
<el-table-column label="商户名称" align="center" prop="merchantName" width="120" />
|
||||
<el-table-column label="应用名称" align="center" prop="appName" width="120" />
|
||||
<el-table-column label="渠道名称" align="center" prop="channelCodeName" width="120" />
|
||||
<el-table-column label="渠道订单号" align="center" prop="merchantOrderId" width="120" />
|
||||
<el-table-column label="商品标题" align="center" prop="subject" width="250" />
|
||||
<el-table-column label="商品描述" align="center" prop="body" width="250" />
|
||||
<el-table-column label="异步通知地址" align="center" prop="notifyUrl" width="250" />
|
||||
<el-table-column label="回调状态" align="center" prop="notifyStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PAY_ORDER_NOTIFY_STATUS" :value="scope.row.notifyStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="支付订单" width="280">
|
||||
<template #default="scope">
|
||||
<p class="order-font">
|
||||
<el-tag>商户</el-tag>
|
||||
{{ scope.row.merchantOrderId }}
|
||||
</p>
|
||||
<p class="order-font">
|
||||
<el-tag type="warning">支付</el-tag>
|
||||
{{ scope.row.channelOrderNo }}
|
||||
</p>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="支付金额" align="center" prop="amount">
|
||||
<template #default="scope">
|
||||
¥{{ parseFloat(scope.row.amount / 100).toFixed(2) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="手续金额" align="center" prop="channelFeeAmount">
|
||||
<template #default="scope">
|
||||
¥{{ parseFloat(scope.row.channelFeeAmount / 100).toFixed(2) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="退款金额" align="center" prop="refundAmount">
|
||||
<template #default="scope">
|
||||
¥{{ parseFloat(scope.row.refundAmount / 100).toFixed(2) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="支付状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PAY_ORDER_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="回调状态" align="center" prop="notifyStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PAY_ORDER_NOTIFY_STATUS" :value="scope.row.notifyStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="支付时间"
|
||||
align="center"
|
||||
prop="successTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="openDetail(scope.row.id)"
|
||||
v-hasPermi="['pay:order:query']"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:预览 -->
|
||||
<OrderDetail ref="detailRef" @success="getList" />
|
||||
</template>
|
||||
<script setup lang="ts" name="PayOrder">
|
||||
import { allSchemas } from './order.data'
|
||||
import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as MerchantApi from '@/api/pay/merchant'
|
||||
import * as OrderApi from '@/api/pay/order'
|
||||
import OrderDetail from './OrderDetail.vue'
|
||||
const message = useMessage() // 消息弹窗
|
||||
import download from '@/utils/download'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
// 列表相关的变量
|
||||
const [registerTable, { exportList }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: OrderApi.getOrderPage,
|
||||
exportListApi: OrderApi.exportOrder
|
||||
const loading = ref(false) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
merchantId: undefined,
|
||||
appId: undefined,
|
||||
channelId: undefined,
|
||||
channelCode: undefined,
|
||||
merchantOrderId: undefined,
|
||||
subject: undefined,
|
||||
body: undefined,
|
||||
notifyUrl: undefined,
|
||||
notifyStatus: undefined,
|
||||
amount: undefined,
|
||||
channelFeeRate: undefined,
|
||||
channelFeeAmount: undefined,
|
||||
status: undefined,
|
||||
userIp: undefined,
|
||||
successExtensionId: undefined,
|
||||
refundStatus: undefined,
|
||||
refundTimes: undefined,
|
||||
refundAmount: undefined,
|
||||
channelUserId: undefined,
|
||||
channelOrderNo: undefined,
|
||||
expireTime: [],
|
||||
successTime: [],
|
||||
notifyTime: [],
|
||||
createTime: []
|
||||
})
|
||||
// ========== CRUD 相关 ==========
|
||||
const actionLoading = ref(false) // 遮罩层
|
||||
const actionType = ref('') // 操作按钮的类型
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const dialogTitle = ref('edit') // 弹出层标题
|
||||
const detailData = ref() // 详情 Ref
|
||||
// 设置标题
|
||||
const setDialogTile = (type: string) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
actionType.value = type
|
||||
dialogVisible.value = true
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出等待
|
||||
const merchantList = ref([]) // 商户列表
|
||||
const appList = ref([]) // 支付应用列表集合
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// 新增操作
|
||||
const handleCreate = () => {
|
||||
setDialogTile('create')
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await OrderApi.getOrderPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (rowId: number) => {
|
||||
setDialogTile('detail')
|
||||
const res = await OrderApi.getOrder(rowId)
|
||||
detailData.value = res
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await OrderApi.exportOrder(queryParams)
|
||||
download.excel(data, '支付订单.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 预览详情 */
|
||||
const detailRef = ref()
|
||||
const openDetail = (id: number) => {
|
||||
detailRef.value.open(id)
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
// 加载商户列表
|
||||
merchantList.value = await MerchantApi.getMerchantListByName()
|
||||
// 加载 App 列表
|
||||
// TODO 芋艿:候选少一个查询应用列表的接口
|
||||
// appList.value = await AppApi.getAppListByMerchantId()
|
||||
})
|
||||
</script>
|
||||
<style>
|
||||
.order-font {
|
||||
font-size: 12px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,152 +0,0 @@
|
|||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
merchantId: [required],
|
||||
appId: [required],
|
||||
merchantOrderId: [required],
|
||||
subject: [required],
|
||||
body: [required],
|
||||
notifyUrl: [required],
|
||||
notifyStatus: [required],
|
||||
amount: [required],
|
||||
status: [required],
|
||||
userIp: [required],
|
||||
expireTime: [required],
|
||||
refundStatus: [required],
|
||||
refundTimes: [required],
|
||||
refundAmount: [required]
|
||||
})
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: 'seq',
|
||||
primaryTitle: '岗位编号',
|
||||
action: true,
|
||||
columns: [
|
||||
{
|
||||
title: '商户编号',
|
||||
field: 'merchantId',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '应用编号',
|
||||
field: 'appId',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '渠道编号',
|
||||
field: 'channelId'
|
||||
},
|
||||
{
|
||||
title: '渠道编码',
|
||||
field: 'channelCode',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '渠道订单号',
|
||||
field: 'merchantOrderId',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '商品标题',
|
||||
field: 'subject'
|
||||
},
|
||||
{
|
||||
title: '商品描述',
|
||||
field: 'body'
|
||||
},
|
||||
{
|
||||
title: '异步通知地址',
|
||||
field: 'notifyUrl'
|
||||
},
|
||||
{
|
||||
title: '回调状态',
|
||||
field: 'notifyStatus',
|
||||
dictType: DICT_TYPE.PAY_ORDER_NOTIFY_STATUS,
|
||||
dictClass: 'number'
|
||||
},
|
||||
{
|
||||
title: '支付金额',
|
||||
field: 'amount',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '渠道手续费',
|
||||
field: 'channelFeeRate',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '渠道手续金额',
|
||||
field: 'channelFeeAmount',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '支付状态',
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.PAY_ORDER_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '用户 IP',
|
||||
field: 'userIp'
|
||||
},
|
||||
{
|
||||
title: '订单失效时间',
|
||||
field: 'expireTime',
|
||||
formatter: 'formatDate'
|
||||
},
|
||||
{
|
||||
title: '支付时间',
|
||||
field: 'successTime',
|
||||
formatter: 'formatDate'
|
||||
},
|
||||
{
|
||||
title: '支付通知时间',
|
||||
field: 'notifyTime',
|
||||
formatter: 'formatDate'
|
||||
},
|
||||
{
|
||||
title: '拓展编号',
|
||||
field: 'successExtensionId'
|
||||
},
|
||||
{
|
||||
title: '退款状态',
|
||||
field: 'refundStatus',
|
||||
dictType: DICT_TYPE.PAY_ORDER_REFUND_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '退款次数',
|
||||
field: 'refundTimes'
|
||||
},
|
||||
{
|
||||
title: '退款总金额',
|
||||
field: 'refundAmount'
|
||||
},
|
||||
{
|
||||
title: '渠道用户编号',
|
||||
field: 'channelUserId'
|
||||
},
|
||||
{
|
||||
title: '渠道订单号',
|
||||
field: 'channelOrderNo'
|
||||
},
|
||||
{
|
||||
title: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
isForm: false,
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
|
@ -0,0 +1,115 @@
|
|||
<template>
|
||||
<Dialog title="详情" v-model="dialogVisible" width="50%">
|
||||
<el-descriptions :column="2">
|
||||
<el-descriptions-item label="商户名称">{{ detailData.merchantName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="应用名称">{{ detailData.appName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="商品名称">{{ detailData.subject }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-divider />
|
||||
<el-descriptions :column="2">
|
||||
<el-descriptions-item label="商户退款单号">
|
||||
<el-tag>{{ detailData.merchantRefundNo }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="商户订单号">
|
||||
{{ detailData.merchantOrderId }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="交易订单号">{{ detailData.tradeNo }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-divider />
|
||||
<el-descriptions :column="2">
|
||||
<el-descriptions-item label="支付金额">
|
||||
<el-tag type="success">¥{{ parseFloat(detailData.payAmount / 100, 2).toFixed(2) }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="退款金额">
|
||||
<el-tag class="tag-purple">
|
||||
¥{{ parseFloat(detailData.refundAmount / 100).toFixed(2) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="退款类型">
|
||||
<dict-tag :type="DICT_TYPE.PAY_REFUND_ORDER_TYPE" :value="detailData.type" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="退款状态">
|
||||
<dict-tag :type="DICT_TYPE.PAY_REFUND_ORDER_STATUS" :value="detailData.status" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDate(detailData.createTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="退款成功时间">
|
||||
{{ formatDate(detailData.successTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="退款失效时间">
|
||||
{{ formatDate(detailData.expireTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatDate(detailData.updateTime) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-divider />
|
||||
<el-descriptions :column="2">
|
||||
<el-descriptions-item label="支付渠道">
|
||||
{{ detailData.channelCodeName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="支付 IP">
|
||||
{{ detailData.userIp }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="回调地址">{{ detailData.notifyUrl }}</el-descriptions-item>
|
||||
<el-descriptions-item label="回调状态">
|
||||
<dict-tag :type="DICT_TYPE.PAY_ORDER_NOTIFY_STATUS" :value="detailData.notifyStatus" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="回调时间">
|
||||
{{ formatDate(detailData.notifyTime) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-divider />
|
||||
<el-descriptions :column="2">
|
||||
<el-descriptions-item label="渠道订单号">
|
||||
{{ detailData.channelOrderNo }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="渠道退款单号">
|
||||
{{ detailData.channelRefundNo }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="渠道错误码">
|
||||
{{ detailData.channelErrorCode }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="渠道错误码描述">
|
||||
{{ detailData.channelErrorMsg }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<br />
|
||||
<el-descriptions :column="1" direction="vertical" border>
|
||||
<el-descriptions-item label="渠道额外参数">
|
||||
{{ detailData.channelExtras }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="退款原因">{{ detailData.reason }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts" name="refundForm">
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import * as RefundApi from '@/api/pay/refund'
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const detailLoading = ref(false) // 表单的加载中
|
||||
const detailData = ref({})
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: number) => {
|
||||
dialogVisible.value = true
|
||||
// 设置数据
|
||||
detailLoading.value = true
|
||||
try {
|
||||
detailData.value = await RefundApi.getRefund(id)
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
</script>
|
||||
<style>
|
||||
.tag-purple {
|
||||
color: #722ed1;
|
||||
background: #f9f0ff;
|
||||
border-color: #d3adf7;
|
||||
}
|
||||
</style>
|
|
@ -1,59 +1,341 @@
|
|||
<template>
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<XTable @register="registerTable">
|
||||
<template #toolbar_buttons>
|
||||
<!-- 操作:导出 -->
|
||||
<XButton
|
||||
type="warning"
|
||||
preIcon="ep:download"
|
||||
:title="t('action.export')"
|
||||
v-hasPermi="['pay:refund:export']"
|
||||
@click="exportList('退款订单.xls')"
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item label="所属商户" prop="merchantId">
|
||||
<el-select
|
||||
v-model="queryParams.merchantId"
|
||||
clearable
|
||||
placeholder="请选择所属商户"
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in merchantList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="应用编号" prop="appId">
|
||||
<el-select
|
||||
v-model="queryParams.appId"
|
||||
clearable
|
||||
placeholder="请选择应用信息"
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option v-for="item in appList" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="渠道编码" prop="channelCode">
|
||||
<el-select
|
||||
v-model="queryParams.channelCode"
|
||||
placeholder="请输入渠道编码"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getStrDictOptions(DICT_TYPE.PAY_CHANNEL_CODE_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="退款类型" prop="type">
|
||||
<el-select
|
||||
v-model="queryParams.type"
|
||||
placeholder="请选择退款类型"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PAY_REFUND_ORDER_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="商户退款订单号" prop="merchantRefundNo">
|
||||
<el-input
|
||||
v-model="queryParams.merchantRefundNo"
|
||||
placeholder="请输入商户退款订单号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作:详情 -->
|
||||
<XTextButton
|
||||
preIcon="ep:view"
|
||||
:title="t('action.detail')"
|
||||
v-hasPermi="['pay:refund:query']"
|
||||
@click="handleDetail(row.id)"
|
||||
</el-form-item>
|
||||
<el-form-item label="退款状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择退款状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PAY_REFUND_ORDER_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="退款回调状态" prop="notifyStatus">
|
||||
<el-select
|
||||
v-model="queryParams.notifyStatus"
|
||||
placeholder="请选择通知商户退款结果的回调状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.PAY_ORDER_NOTIFY_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</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="['system:tenant:export']"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<XModal v-model="dialogVisible" :title="t('action.detail')">
|
||||
<!-- 对话框(详情) -->
|
||||
<Descriptions :schema="allSchemas.detailSchema" :data="detailData" />
|
||||
<!-- 操作按钮 -->
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
|
||||
</template>
|
||||
</XModal>
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<el-table-column label="商户名称" align="center" prop="merchantName" width="120" />
|
||||
<el-table-column label="应用名称" align="center" prop="appName" width="120" />
|
||||
<el-table-column label="渠道名称" align="center" prop="channelCodeName" width="120" />
|
||||
<el-table-column label="交易订单号" align="center" prop="tradeNo" width="140" />
|
||||
<el-table-column label="商户订单编号" align="center" prop="merchantOrderId" width="140" />
|
||||
<el-table-column label="商户订单号" align="left" width="230">
|
||||
<template #default="scope">
|
||||
<p class="order-font">
|
||||
<el-tag>退款</el-tag>
|
||||
{{ scope.row.merchantRefundNo }}
|
||||
</p>
|
||||
<p class="order-font">
|
||||
<el-tag type="success">交易</el-tag>
|
||||
{{ scope.row.merchantOrderId }}
|
||||
</p>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="支付订单号" align="center" prop="merchantRefundNo" width="250">
|
||||
<template #default="scope">
|
||||
<p class="order-font">
|
||||
<el-tag>交易</el-tag>
|
||||
{{ scope.row.tradeNo }}
|
||||
</p>
|
||||
<p class="order-font">
|
||||
<el-tag type="warning">渠道</el-tag>
|
||||
{{ scope.row.channelOrderNo }}
|
||||
</p>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="支付金额(元)" align="center" prop="payAmount" width="100">
|
||||
<template #default="scope">
|
||||
¥{{ parseFloat(scope.row.payAmount / 100).toFixed(2) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="退款金额(元)" align="center" prop="refundAmount" width="100">
|
||||
<template #default="scope">
|
||||
¥{{ parseFloat(scope.row.refundAmount / 100).toFixed(2) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="退款类型" align="center" prop="type" width="80">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PAY_REFUND_ORDER_TYPE" :value="scope.row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="退款状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PAY_REFUND_ORDER_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="回调状态" align="center" prop="notifyStatus">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.PAY_ORDER_NOTIFY_STATUS" :value="scope.row.notifyStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="退款原因"
|
||||
align="center"
|
||||
prop="reason"
|
||||
width="140"
|
||||
:show-overflow-tooltip="true"
|
||||
/>
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="退款成功时间"
|
||||
align="center"
|
||||
prop="successTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="openDetail(scope.row.id)"
|
||||
v-hasPermi="['pay:order:query']"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:预览 -->
|
||||
<RefundDetail ref="detailRef" @success="getList" />
|
||||
</template>
|
||||
<script setup lang="ts" name="PayRefund">
|
||||
import { allSchemas } from './refund.data'
|
||||
import { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as MerchantApi from '@/api/pay/merchant'
|
||||
import * as RefundApi from '@/api/pay/refund'
|
||||
import RefundDetail from './RefundDetail.vue'
|
||||
const message = useMessage() // 消息弹窗
|
||||
import download from '@/utils/download'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 列表相关的变量
|
||||
const [registerTable, { exportList }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: RefundApi.getRefundPage,
|
||||
exportListApi: RefundApi.exportRefund
|
||||
const loading = ref(false) // 列表遮罩层
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
merchantId: undefined,
|
||||
appId: undefined,
|
||||
channelId: undefined,
|
||||
channelCode: undefined,
|
||||
orderId: undefined,
|
||||
tradeNo: undefined,
|
||||
merchantOrderId: undefined,
|
||||
merchantRefundNo: undefined,
|
||||
notifyUrl: undefined,
|
||||
notifyStatus: undefined,
|
||||
status: undefined,
|
||||
type: undefined,
|
||||
payAmount: undefined,
|
||||
refundAmount: undefined,
|
||||
reason: undefined,
|
||||
userIp: undefined,
|
||||
channelOrderNo: undefined,
|
||||
channelRefundNo: undefined,
|
||||
channelErrorCode: undefined,
|
||||
channelErrorMsg: undefined,
|
||||
channelExtras: undefined,
|
||||
expireTime: [],
|
||||
successTime: [],
|
||||
notifyTime: [],
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出等待
|
||||
const appList = ref([]) // 支付应用列表集合
|
||||
const merchantList = ref([]) // 商户列表
|
||||
|
||||
// ========== CRUD 相关 ==========
|
||||
const dialogVisible = ref(false) // 是否显示弹出层
|
||||
const detailData = ref() // 详情 Ref
|
||||
|
||||
// 详情操作
|
||||
const handleDetail = async (rowId: number) => {
|
||||
// 设置数据
|
||||
detailData.value = RefundApi.getRefund(rowId)
|
||||
dialogVisible.value = true
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await RefundApi.getRefundPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
// 导出的二次确认
|
||||
await message.exportConfirm()
|
||||
// 发起导出
|
||||
exportLoading.value = true
|
||||
const data = await RefundApi.exportRefund(queryParams)
|
||||
download.excel(data, '支付订单.xls')
|
||||
} catch {
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 预览详情 */
|
||||
const detailRef = ref()
|
||||
const openDetail = (id: number) => {
|
||||
detailRef.value.open(id)
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
// 加载商户列表
|
||||
merchantList.value = await MerchantApi.getMerchantListByName()
|
||||
// TODO 芋艿:候选少一个查询应用列表的接口
|
||||
// appList.value = await AppApi.getAppListByMerchantId()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.order-font {
|
||||
font-size: 12px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,173 +0,0 @@
|
|||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: 'seq',
|
||||
primaryTitle: '序号',
|
||||
action: true,
|
||||
columns: [
|
||||
{
|
||||
title: '商户编号',
|
||||
field: 'merchantId',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '应用编号',
|
||||
field: 'appId',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '渠道编号',
|
||||
field: 'channelId',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '渠道编码',
|
||||
field: 'channelCode',
|
||||
dictType: DICT_TYPE.PAY_CHANNEL_CODE_TYPE,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '支付订单编号',
|
||||
field: 'orderId',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '交易订单号',
|
||||
field: 'tradeNo',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '商户订单号',
|
||||
field: 'merchantOrderId',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '商户退款单号',
|
||||
field: 'merchantRefundNo',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '回调地址',
|
||||
field: 'notifyUrl',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '回调状态',
|
||||
field: 'notifyStatus',
|
||||
dictType: DICT_TYPE.PAY_ORDER_NOTIFY_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '退款类型',
|
||||
field: 'type',
|
||||
dictType: DICT_TYPE.PAY_REFUND_ORDER_TYPE,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.PAY_REFUND_ORDER_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '支付金额',
|
||||
field: 'payAmount',
|
||||
formatter: 'formatAmount',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '退款金额',
|
||||
field: 'refundAmount',
|
||||
formatter: 'formatAmount',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '退款原因',
|
||||
field: 'reason',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '用户IP',
|
||||
field: 'userIp',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '渠道订单号',
|
||||
field: 'channelOrderNo',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '渠道退款单号',
|
||||
field: 'channelRefundNo',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '渠道调用报错时',
|
||||
field: 'channelErrorCode'
|
||||
},
|
||||
{
|
||||
title: '渠道调用报错时',
|
||||
field: 'channelErrorMsg'
|
||||
},
|
||||
{
|
||||
title: '支付渠道的额外参数',
|
||||
field: 'channelExtras'
|
||||
},
|
||||
{
|
||||
title: '退款失效时间',
|
||||
field: 'expireTime',
|
||||
formatter: 'formatDate',
|
||||
isForm: false,
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '退款成功时间',
|
||||
field: 'successTime',
|
||||
formatter: 'formatDate',
|
||||
isForm: false,
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '退款通知时间',
|
||||
field: 'notifyTime',
|
||||
formatter: 'formatDate',
|
||||
isForm: false,
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
isForm: false,
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
|
@ -19,7 +19,7 @@
|
|||
<el-form-item label="部门状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择不么你状态"
|
||||
placeholder="请选择部门状态"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
|
|
|
@ -115,11 +115,12 @@ const colorTypeOptions = readonly([
|
|||
])
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
const open = async (type: string, id?: number, dictType?: string) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
formData.value.dictType = dictType
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
|
|
|
@ -167,7 +167,7 @@ const resetQuery = () => {
|
|||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (type: string, id?: number) => {
|
||||
formRef.value.open(type, id)
|
||||
formRef.value.open(type, id, queryParams.dictType)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
|
|
|
@ -19,7 +19,7 @@ export const rules = reactive({
|
|||
sslEnable: [required]
|
||||
})
|
||||
|
||||
// CrudSchema:https://kailong110120130.gitee.io/vue-element-plus-admin-doc/hooks/useCrudSchemas.html
|
||||
// CrudSchema:https://doc.iocoder.cn/vue3/crud-schema/
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: '邮箱',
|
||||
|
|
|
@ -72,7 +72,7 @@ import MailAccountDetail from './MailAccountDetail.vue'
|
|||
|
||||
// tableObject:表格的属性对象,可获得分页大小、条数等属性
|
||||
// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作
|
||||
// 详细可见:https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/table.html#usetable
|
||||
// 详细可见:https://doc.iocoder.cn/vue3/crud-schema/
|
||||
const { tableObject, tableMethods } = useTable({
|
||||
getListApi: MailAccountApi.getMailAccountPage, // 分页接口
|
||||
delListApi: MailAccountApi.deleteMailAccount // 删除接口
|
||||
|
|
|
@ -41,7 +41,7 @@ import MailLogDetail from './MailLogDetail.vue'
|
|||
|
||||
// tableObject:表格的属性对象,可获得分页大小、条数等属性
|
||||
// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作
|
||||
// 详细可见:https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/table.html#usetable
|
||||
// 详细可见:https://doc.iocoder.cn/vue3/crud-schema/
|
||||
const { tableObject, tableMethods } = useTable({
|
||||
getListApi: MailLogApi.getMailLogPage // 分页接口
|
||||
})
|
||||
|
|
|
@ -5,7 +5,7 @@ import * as MailAccountApi from '@/api/system/mail/account'
|
|||
// 邮箱账号的列表
|
||||
const accountList = await MailAccountApi.getSimpleMailAccountList()
|
||||
|
||||
// CrudSchema:https://kailong110120130.gitee.io/vue-element-plus-admin-doc/hooks/useCrudSchemas.html
|
||||
// CrudSchema:https://doc.iocoder.cn/vue3/crud-schema/
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: '编号',
|
||||
|
|
|
@ -73,7 +73,7 @@ import MailTemplateSendForm from './MailTemplateSendForm.vue'
|
|||
|
||||
// tableObject:表格的属性对象,可获得分页大小、条数等属性
|
||||
// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作
|
||||
// 详细可见:https://kailong110120130.gitee.io/vue-element-plus-admin-doc/components/table.html#usetable
|
||||
// 详细可见:https://doc.iocoder.cn/vue3/crud-schema/
|
||||
const { tableObject, tableMethods } = useTable({
|
||||
getListApi: MailTemplateApi.getMailTemplatePage, // 分页接口
|
||||
delListApi: MailTemplateApi.deleteMailTemplate // 删除接口
|
||||
|
|
|
@ -17,7 +17,7 @@ export const rules = reactive({
|
|||
status: [required]
|
||||
})
|
||||
|
||||
// CrudSchema:https://kailong110120130.gitee.io/vue-element-plus-admin-doc/hooks/useCrudSchemas.html
|
||||
// CrudSchema:https://doc.iocoder.cn/vue3/crud-schema/
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
label: '模板编码',
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
</ContentWrap>
|
||||
|
||||
<!-- 添加/修改的弹窗 -->
|
||||
<XModal id="templateModel" :loading="modelLoading" v-model="dialogVisible" :title="dialogTitle">
|
||||
<Dialog id="templateModel" :loading="modelLoading" v-model="dialogVisible" :title="dialogTitle">
|
||||
<!-- 表单:添加/修改 -->
|
||||
<Form
|
||||
ref="formRef"
|
||||
|
@ -72,10 +72,10 @@
|
|||
<!-- 按钮:关闭 -->
|
||||
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</Dialog>
|
||||
|
||||
<!-- 测试站内信的弹窗 -->
|
||||
<XModal id="sendTest" v-model="sendVisible" title="测试">
|
||||
<Dialog id="sendTest" v-model="sendVisible" title="测试">
|
||||
<el-form :model="sendForm" :rules="sendRules" label-width="200px" label-position="top">
|
||||
<el-form-item label="模板内容" prop="content">
|
||||
<el-input type="textarea" v-model="sendForm.content" readonly />
|
||||
|
@ -112,7 +112,7 @@
|
|||
/>
|
||||
<XButton :title="t('dialog.close')" @click="sendVisible = false" />
|
||||
</template>
|
||||
</XModal>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts" name="SystemNotifyTemplate">
|
||||
import { FormExpose } from '@/components/Form'
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
<el-form-item label="岗位编码" prop="code">
|
||||
<el-input v-model="formData.code" placeholder="请输入岗位编码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="岗位顺序" prop="sort">
|
||||
<el-input v-model="formData.sort" placeholder="请输入岗位顺序" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="formData.status" placeholder="请选择状态" clearable>
|
||||
<el-option
|
||||
|
@ -49,7 +52,7 @@ const formData = ref({
|
|||
id: undefined,
|
||||
name: '',
|
||||
code: '',
|
||||
sort: undefined,
|
||||
sort: 0,
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
remark: ''
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue