Commit a00c612f by dhb52

Merge remote-tracking branch 'yudao/dev' into contrib

parents 7e3f6190 1a1bfe0e
......@@ -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
{
// 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)
}
......
import XModal from './src/XModal.vue'
export { XModal }
<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>
import XTable from './src/XTable.vue'
export { XTable }
// 修改样式变量
//@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';
// @import 'vxe-table/styles/variable.scss';
// @import 'vxe-table/styles/modules.scss';
// @import './theme/light.scss';
i {
border-color: initial;
}
// 修改样式变量
// /*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';
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)
......
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
}
}
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)
......
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
}
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>
)
}
})
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>
)
}
})
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>
}
})
import { VXETable } from 'vxe-table'
// 图片渲染
VXETable.renderer.add('XHtml', {
// 默认显示模板
renderDefault(_renderOpts, params) {
const { row, column } = params
return <span v-html={row[column.field]}></span>
}
})
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>
)
}
})
import './dataPicker'
import './dataTimeRangePicker'
import './dict'
import './html'
import './link'
import './img'
import './preview'
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>
)
}
})
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',
......
<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"
......
......@@ -14,6 +14,19 @@
</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">
<el-option
......@@ -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],
......
<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>
<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>
<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,125 +131,101 @@
</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)
newsType: {
// 图文类型:1、已发布图文;2、草稿箱图文
type: String as PropType<string>,
default: '1'
}
})
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 emit = defineEmits(['select-material'])
const getMaterialPageFun = async () => {
let data = await getMaterialPage({
...queryParams,
type: objDataRef.type
})
list.value = data.list
total.value = data.total
loading.value = false
}
// 遮罩层
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 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 selectMaterialFun = (item) => {
emit('select-material', item)
}
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
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
}
onMounted(async () => {
getPage()
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
}
return {
handleQuery,
dateFormatter,
selectMaterialFun,
getMaterialPageFun,
getPage,
formatDate,
queryParams,
objDataRef,
list,
total,
loading
}
}
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;
}
......
export enum MsgType {
Event = 'event',
Text = 'text',
Voice = 'voice',
Image = 'image',
Video = 'video',
Link = 'link',
Location = 'location',
Music = 'music',
News = 'news'
}
......@@ -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;
......
......@@ -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) => {
userId.value = row.userId
open.value = true
}
const openDialog = () => {
open.value = true
/** 打开消息发送窗口 */
const handleSend = async (row: any) => {
userId.value = row.userId
showMessageBox.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>
<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>
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)
<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>
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)
<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>
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: ''
})
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment