This commit is contained in:
fengjingtao 2023-03-29 20:24:11 +08:00
commit 29fd92e60c
145 changed files with 10383 additions and 5657 deletions
.gitignore
build/vite
index.htmlpackage.json
src
api
bpm
definition
form
model
userGroup
infra
apiAccessLog
apiErrorLog
mp
account
message
tag
system
dept
loginLog
menu
permission
post
role
sensitiveWord
sms
smsChannel
smsLog
smsTemplate
tenant
user
components
Descriptions/src
DictTag/src
Editor/src
XTable/src
layout/components
Breadcrumb/src
Message/src
UserInfo/src
router/modules
utils
views

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ dist-ssr
/dist*
*-lock.*
pnpm-debug
.idea

View File

@ -15,7 +15,7 @@ import vueSetupExtend from 'vite-plugin-vue-setup-extend'
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
export function createVitePlugins(VITE_APP_TITLE: string) {
export function createVitePlugins() {
const root = process.cwd()
// 路径查找
function pathResolve(dir: string) {
@ -95,8 +95,6 @@ export function createVitePlugins(VITE_APP_TITLE: string) {
ext: '.gz', // 生成的压缩包后缀
deleteOriginFile: false //压缩后是否删除源文件
}),
ViteEjsPlugin({
title: VITE_APP_TITLE
})
ViteEjsPlugin()
]
}

View File

@ -13,7 +13,7 @@
name="description"
content="芋道管理系统 基于 vue3 + CompositionAPI + typescript + vite3 + element plus 的后台开源免费管理系统!"
/>
<title><%= title %></title>
<title>%VITE_APP_TITLE%</title>
</head>
<body>
<div id="app">
@ -137,7 +137,7 @@
<div class="app-loading-wrap">
<div class="app-loading-title">
<img src="/logo.gif" class="app-loading-logo" alt="Logo" />
<div class="app-loading-title"><%= title %></div>
<div class="app-loading-title">%VITE_APP_TITLE%</div>
</div>
<div class="app-loading-item">
<div class="app-loading-outter"></div>

View File

@ -1,6 +1,6 @@
{
"name": "yudao-ui-admin-vue3",
"version": "1.7.1-snapshot.1941",
"version": "1.7.1-snapshot.1961",
"description": "基于vue3、vite4、element-plus、typesScript",
"author": "xingyu",
"private": false,
@ -35,6 +35,7 @@
"@zxcvbn-ts/core": "^2.2.1",
"animate.css": "^4.1.1",
"axios": "^1.3.4",
"benz-amr-recorder": "^1.1.5",
"bpmn-js-token-simulation": "^0.10.0",
"camunda-bpmn-moddle": "^7.0.1",
"cropperjs": "^1.5.13",
@ -43,7 +44,7 @@
"diagram-js": "^11.6.0",
"echarts": "^5.4.1",
"echarts-wordcloud": "^2.1.0",
"element-plus": "2.2.34",
"element-plus": "2.3.1",
"fast-xml-parser": "^4.1.3",
"highlight.js": "^11.7.0",
"intro.js": "^6.0.0",
@ -62,57 +63,57 @@
"vue-router": "^4.1.6",
"vue-types": "^5.0.2",
"vuedraggable": "^4.1.0",
"vxe-table": "^4.3.10",
"vxe-table": "^4.3.11",
"web-storage-cache": "^1.1.1",
"xe-utils": "^3.5.7",
"xml-js": "^1.6.11"
},
"devDependencies": {
"@commitlint/cli": "^17.4.4",
"@commitlint/cli": "^17.5.0",
"@commitlint/config-conventional": "^17.4.4",
"@iconify/json": "^2.2.31",
"@intlify/unplugin-vue-i18n": "^0.8.2",
"@iconify/json": "^2.2.38",
"@intlify/unplugin-vue-i18n": "^0.10.0",
"@purge-icons/generated": "^0.9.0",
"@types/intro.js": "^5.1.1",
"@types/lodash-es": "^4.17.6",
"@types/node": "^18.14.6",
"@types/lodash-es": "^4.17.7",
"@types/node": "^18.15.5",
"@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.5.0",
"@types/qs": "^6.9.7",
"@typescript-eslint/eslint-plugin": "^5.54.1",
"@typescript-eslint/parser": "^5.54.1",
"@vitejs/plugin-legacy": "^4.0.1",
"@vitejs/plugin-vue": "^4.0.0",
"@vitejs/plugin-vue-jsx": "^3.0.0",
"autoprefixer": "^10.4.13",
"@typescript-eslint/eslint-plugin": "^5.56.0",
"@typescript-eslint/parser": "^5.56.0",
"@vitejs/plugin-legacy": "^4.0.2",
"@vitejs/plugin-vue": "^4.1.0",
"@vitejs/plugin-vue-jsx": "^3.0.1",
"autoprefixer": "^10.4.14",
"bpmn-js": "^8.9.0",
"bpmn-js-properties-panel": "^0.46.0",
"consola": "^2.15.3",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.7.0",
"eslint-define-config": "^1.15.0",
"eslint": "^8.36.0",
"eslint-config-prettier": "^8.8.0",
"eslint-define-config": "^1.17.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.9.0",
"lint-staged": "^13.1.2",
"lint-staged": "^13.2.0",
"postcss": "^8.4.21",
"postcss-html": "^1.5.0",
"postcss-scss": "^4.0.6",
"prettier": "^2.8.4",
"rimraf": "^4.3.1",
"rollup": "^3.18.0",
"sass": "^1.58.3",
"stylelint": "^15.2.0",
"prettier": "^2.8.6",
"rimraf": "^4.4.1",
"rollup": "^3.20.0",
"sass": "^1.59.3",
"stylelint": "^15.3.0",
"stylelint-config-html": "^1.1.0",
"stylelint-config-prettier": "^9.0.5",
"stylelint-config-recommended": "^10.0.1",
"stylelint-config-standard": "^30.0.1",
"stylelint-order": "^6.0.2",
"terser": "^5.16.5",
"typescript": "4.9.5",
"stylelint-config-recommended": "^11.0.0",
"stylelint-config-standard": "^31.0.0",
"stylelint-order": "^6.0.3",
"terser": "^5.16.6",
"typescript": "5.0.2",
"unplugin-auto-import": "^0.15.1",
"unplugin-element-plus": "^0.7.0",
"unplugin-vue-components": "^0.24.1",
"vite": "4.1.4",
"vite": "4.2.1",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-ejs": "^1.6.4",
"vite-plugin-eslint": "^1.8.1",
@ -125,7 +126,7 @@
"windicss": "^3.5.6"
},
"engines": {
"node": ">=16.0.0"
"node": ">=16.18.0"
},
"license": "MIT",
"repository": {

View File

@ -1,19 +1,19 @@
import request from '@/config/axios'
export const getProcessDefinitionBpmnXMLApi = async (id: number) => {
export const getProcessDefinitionBpmnXML = async (id: number) => {
return await request.get({
url: '/bpm/process-definition/get-bpmn-xml?id=' + id
})
}
export const getProcessDefinitionPageApi = async (params) => {
export const getProcessDefinitionPage = async (params) => {
return await request.get({
url: '/bpm/process-definition/page',
params
})
}
export const getProcessDefinitionListApi = async (params) => {
export const getProcessDefinitionList = async (params) => {
return await request.get({
url: '/bpm/process-definition/list',
params

View File

@ -11,7 +11,7 @@ export type FormVO = {
}
// 创建工作流的表单定义
export const createFormApi = async (data: FormVO) => {
export const createForm = async (data: FormVO) => {
return await request.post({
url: '/bpm/form/create',
data: data
@ -19,7 +19,7 @@ export const createFormApi = async (data: FormVO) => {
}
// 更新工作流的表单定义
export const updateFormApi = async (data: FormVO) => {
export const updateForm = async (data: FormVO) => {
return await request.put({
url: '/bpm/form/update',
data: data
@ -27,21 +27,21 @@ export const updateFormApi = async (data: FormVO) => {
}
// 删除工作流的表单定义
export const deleteFormApi = async (id: number) => {
export const deleteForm = async (id: number) => {
return await request.delete({
url: '/bpm/form/delete?id=' + id
})
}
// 获得工作流的表单定义
export const getFormApi = async (id: number) => {
export const getForm = async (id: number) => {
return await request.get({
url: '/bpm/form/get?id=' + id
})
}
// 获得工作流的表单定义分页
export const getFormPageApi = async (params) => {
export const getFormPage = async (params) => {
return await request.get({
url: '/bpm/form/page',
params
@ -49,7 +49,7 @@ export const getFormPageApi = async (params) => {
}
// 获得动态表单的精简列表
export const getSimpleFormsApi = async () => {
export const getSimpleFormList = async () => {
return await request.get({
url: '/bpm/form/list-all-simple'
})

View File

@ -25,20 +25,20 @@ export type ModelVO = {
bpmnXml: string
}
export const getModelPageApi = async (params) => {
export const getModelPage = async (params) => {
return await request.get({ url: '/bpm/model/page', params })
}
export const getModelApi = async (id: number) => {
export const getModel = async (id: number) => {
return await request.get({ url: '/bpm/model/get?id=' + id })
}
export const updateModelApi = async (data: ModelVO) => {
export const updateModel = async (data: ModelVO) => {
return await request.put({ url: '/bpm/model/update', data: data })
}
// 任务状态修改
export const updateModelStateApi = async (id: number, state: number) => {
export const updateModelState = async (id: number, state: number) => {
const data = {
id: id,
state: state
@ -46,14 +46,14 @@ export const updateModelStateApi = async (id: number, state: number) => {
return await request.put({ url: '/bpm/model/update-state', data: data })
}
export const createModelApi = async (data: ModelVO) => {
export const createModel = async (data: ModelVO) => {
return await request.post({ url: '/bpm/model/create', data: data })
}
export const deleteModelApi = async (id: number) => {
export const deleteModel = async (id: number) => {
return await request.delete({ url: '/bpm/model/delete?id=' + id })
}
export const deployModelApi = async (id: number) => {
export const deployModel = async (id: number) => {
return await request.post({ url: '/bpm/model/deploy?id=' + id })
}

View File

@ -11,7 +11,7 @@ export type UserGroupVO = {
}
// 创建用户组
export const createUserGroupApi = async (data: UserGroupVO) => {
export const createUserGroup = async (data: UserGroupVO) => {
return await request.post({
url: '/bpm/user-group/create',
data: data
@ -19,7 +19,7 @@ export const createUserGroupApi = async (data: UserGroupVO) => {
}
// 更新用户组
export const updateUserGroupApi = async (data: UserGroupVO) => {
export const updateUserGroup = async (data: UserGroupVO) => {
return await request.put({
url: '/bpm/user-group/update',
data: data
@ -27,21 +27,21 @@ export const updateUserGroupApi = async (data: UserGroupVO) => {
}
// 删除用户组
export const deleteUserGroupApi = async (id: number) => {
export const deleteUserGroup = async (id: number) => {
return await request.delete({ url: '/bpm/user-group/delete?id=' + id })
}
// 获得用户组
export const getUserGroupApi = async (id: number) => {
export const getUserGroup = async (id: number) => {
return await request.get({ url: '/bpm/user-group/get?id=' + id })
}
// 获得用户组分页
export const getUserGroupPageApi = async (params) => {
export const getUserGroupPage = async (params) => {
return await request.get({ url: '/bpm/user-group/page', params })
}
// 获取用户组精简信息列表
export const listSimpleUserGroupsApi = async () => {
export const getSimpleUserGroupList = async (): Promise<UserGroupVO[]> => {
return await request.get({ url: '/bpm/user-group/list-all-simple' })
}

View File

@ -19,32 +19,12 @@ export interface ApiAccessLogVO {
createTime: Date
}
export interface ApiAccessLogPageReqVO extends PageParam {
userId?: number
userType?: number
applicationName?: string
requestUrl?: string
beginTime?: Date[]
duration?: number
resultCode?: number
}
export interface ApiAccessLogExportReqVO {
userId?: number
userType?: number
applicationName?: string
requestUrl?: string
beginTime?: Date[]
duration?: number
resultCode?: number
}
// 查询列表API 访问日志
export const getApiAccessLogPageApi = (params: ApiAccessLogPageReqVO) => {
export const getApiAccessLogPage = (params: PageParam) => {
return request.get({ url: '/infra/api-access-log/page', params })
}
// 导出API 访问日志
export const exportApiAccessLogApi = (params: ApiAccessLogExportReqVO) => {
export const exportApiAccessLog = (params) => {
return request.download({ url: '/infra/api-access-log/export-excel', params })
}

View File

@ -27,38 +27,20 @@ export interface ApiErrorLogVO {
createTime: Date
}
export interface ApiErrorLogPageReqVO extends PageParam {
userId?: number
userType?: number
applicationName?: string
requestUrl?: string
exceptionTime?: Date[]
processStatus: number
}
export interface ApiErrorLogExportReqVO {
userId?: number
userType?: number
applicationName?: string
requestUrl?: string
exceptionTime?: Date[]
processStatus: number
}
// 查询列表API 访问日志
export const getApiErrorLogPageApi = (params: ApiErrorLogPageReqVO) => {
export const getApiErrorLogPage = (params: PageParam) => {
return request.get({ url: '/infra/api-error-log/page', params })
}
// 更新 API 错误日志的处理状态
export const updateApiErrorLogPageApi = (id: number, processStatus: number) => {
export const updateApiErrorLogPage = (id: number, processStatus: number) => {
return request.put({
url: '/infra/api-error-log/update-status?id=' + id + '&processStatus=' + processStatus
})
}
// 导出API 访问日志
export const exportApiErrorLogApi = (params: ApiErrorLogExportReqVO) => {
export const exportApiErrorLog = (params) => {
return request.download({
url: '/infra/api-error-log/export-excel',
params

View File

@ -1,5 +1,10 @@
import request from '@/config/axios'
export interface AccountVO {
id?: number
name: string
}
// 创建公众号账号
export const createAccount = async (data) => {
return await request.post({ url: '/mp/account/create', data })
@ -26,7 +31,7 @@ export const getAccountPage = async (query) => {
}
// 获取公众号账号精简信息列表
export const getSimpleAccounts = async () => {
export const getSimpleAccountList = async () => {
return request.get({ url: '/mp/account/list-all-simple' })
}

View File

@ -1,7 +1,7 @@
import request from '@/config/axios'
// 获得公众号消息分页
export const getMessagePage = (query) => {
export const getMessagePage = (query: PageParam) => {
return request.get({
url: '/mp/message/page',
params: query

View File

@ -1,7 +1,14 @@
import request from '@/config/axios'
export interface TagVO {
id?: number
name: string
accountId: number
createTime: Date
}
// 创建公众号标签
export const createTag = (data) => {
export const createTag = (data: TagVO) => {
return request.post({
url: '/mp/tag/create',
data: data
@ -9,7 +16,7 @@ export const createTag = (data) => {
}
// 更新公众号标签
export const updateTag = (data) => {
export const updateTag = (data: TagVO) => {
return request.put({
url: '/mp/tag/update',
data: data
@ -17,21 +24,21 @@ export const updateTag = (data) => {
}
// 删除公众号标签
export const deleteTag = (id) => {
export const deleteTag = (id: number) => {
return request.delete({
url: '/mp/tag/delete?id=' + id
})
}
// 获得公众号标签
export const getTag = (id) => {
export const getTag = (id: number) => {
return request.get({
url: '/mp/tag/get?id=' + id
})
}
// 获得公众号标签分页
export const getTagPage = (query) => {
export const getTagPage = (query: PageParam) => {
return request.get({
url: '/mp/tag/page',
params: query
@ -39,14 +46,14 @@ export const getTagPage = (query) => {
}
// 获取公众号标签精简信息列表
export const getSimpleTags = () => {
export const getSimpleTagList = () => {
return request.get({
url: '/mp/tag/list-all-simple'
})
}
// 同步公众号标签
export const syncTag = (accountId) => {
export const syncTag = (accountId: number) => {
return request.post({
url: '/mp/tag/sync?accountId=' + accountId
})

View File

@ -18,7 +18,7 @@ export interface DeptPageReqVO {
}
// 查询部门(精简)列表
export const listSimpleDeptApi = async () => {
export const getSimpleDeptList = async (): Promise<DeptVO[]> => {
return await request.get({ url: '/system/dept/list-all-simple' })
}

View File

@ -13,18 +13,12 @@ export interface LoginLogVO {
createTime: Date
}
export interface LoginLogReqVO extends PageParam {
userIp?: string
username?: string
status?: boolean
createTime?: Date[]
}
// 查询登录日志列表
export const getLoginLogPageApi = (params: LoginLogReqVO) => {
export const getLoginLogPage = (params: PageParam) => {
return request.get({ url: '/system/login-log/page', params })
}
// 导出登录日志
export const exportLoginLogApi = (params: LoginLogReqVO) => {
export const exportLoginLog = (params) => {
return request.download({ url: '/system/login-log/export', params })
}

View File

@ -18,18 +18,13 @@ export interface MenuVO {
createTime: Date
}
export interface MenuPageReqVO {
name?: string
status?: number
}
// 查询菜单(精简)列表
export const listSimpleMenusApi = () => {
export const getSimpleMenusList = () => {
return request.get({ url: '/system/menu/list-all-simple' })
}
// 查询菜单列表
export const getMenuListApi = (params: MenuPageReqVO) => {
export const getMenuList = (params) => {
return request.get({ url: '/system/menu/list', params })
}
@ -39,16 +34,16 @@ export const getMenuApi = (id: number) => {
}
// 新增菜单
export const createMenuApi = (data: MenuVO) => {
export const createMenu = (data: MenuVO) => {
return request.post({ url: '/system/menu/create', data })
}
// 修改菜单
export const updateMenuApi = (data: MenuVO) => {
export const updateMenu = (data: MenuVO) => {
return request.put({ url: '/system/menu/update', data })
}
// 删除菜单
export const deleteMenuApi = (id: number) => {
export const deleteMenu = (id: number) => {
return request.delete({ url: '/system/menu/delete?id=' + id })
}

View File

@ -37,6 +37,6 @@ export const listUserRolesApi = async (userId: number) => {
}
// 赋予用户角色
export const aassignUserRoleApi = async (data: PermissionAssignUserRoleReqVO) => {
export const assignUserRoleApi = async (data: PermissionAssignUserRoleReqVO) => {
return await request.post({ url: '/system/permission/assign-user-role', data })
}

View File

@ -10,49 +10,37 @@ export interface PostVO {
createTime?: Date
}
export interface PostPageReqVO extends PageParam {
code?: string
name?: string
status?: number
}
export interface PostExportReqVO {
code?: string
name?: string
status?: number
}
// 查询岗位列表
export const getPostPageApi = async (params: PostPageReqVO) => {
export const getPostPage = async (params: PageParam) => {
return await request.get({ url: '/system/post/page', params })
}
// 获取岗位精简信息列表
export const listSimplePostsApi = async () => {
export const getSimplePostList = async (): Promise<PostVO[]> => {
return await request.get({ url: '/system/post/list-all-simple' })
}
// 查询岗位详情
export const getPostApi = async (id: number) => {
export const getPost = async (id: number) => {
return await request.get({ url: '/system/post/get?id=' + id })
}
// 新增岗位
export const createPostApi = async (data: PostVO) => {
export const createPost = async (data: PostVO) => {
return await request.post({ url: '/system/post/create', data })
}
// 修改岗位
export const updatePostApi = async (data: PostVO) => {
export const updatePost = async (data: PostVO) => {
return await request.put({ url: '/system/post/update', data })
}
// 删除岗位
export const deletePostApi = async (id: number) => {
export const deletePost = async (id: number) => {
return await request.delete({ url: '/system/post/delete?id=' + id })
}
// 导出岗位
export const exportPostApi = async (params: PostExportReqVO) => {
export const exportPost = async (params) => {
return await request.download({ url: '/system/post/export', params })
}

View File

@ -10,49 +10,49 @@ export interface RoleVO {
createTime: Date
}
export interface RolePageReqVO extends PageParam {
name?: string
code?: string
status?: number
createTime?: Date[]
}
export interface UpdateStatusReqVO {
id: number
status: number
}
// 查询角色列表
export const getRolePageApi = async (params: RolePageReqVO) => {
export const getRolePage = async (params: PageParam) => {
return await request.get({ url: '/system/role/page', params })
}
// 查询角色(精简)列表
export const listSimpleRolesApi = async () => {
export const getSimpleRoleList = async (): Promise<RoleVO[]> => {
return await request.get({ url: '/system/role/list-all-simple' })
}
// 查询角色详情
export const getRoleApi = async (id: number) => {
export const getRole = async (id: number) => {
return await request.get({ url: '/system/role/get?id=' + id })
}
// 新增角色
export const createRoleApi = async (data: RoleVO) => {
export const createRole = async (data: RoleVO) => {
return await request.post({ url: '/system/role/create', data })
}
// 修改角色
export const updateRoleApi = async (data: RoleVO) => {
export const updateRole = async (data: RoleVO) => {
return await request.put({ url: '/system/role/update', data })
}
// 修改角色状态
export const updateRoleStatusApi = async (data: UpdateStatusReqVO) => {
export const updateRoleStatus = async (data: UpdateStatusReqVO) => {
return await request.put({ url: '/system/role/update-status', data })
}
// 删除角色
export const deleteRoleApi = async (id: number) => {
export const deleteRole = async (id: number) => {
return await request.delete({ url: '/system/role/delete?id=' + id })
}
// 导出角色
export const exportRole = (params) => {
return request.download({
url: '/system/role/export-excel',
params
})
}

View File

@ -1,4 +1,5 @@
import request from '@/config/axios'
import qs from 'qs'
export interface SensitiveWordVO {
id: number
@ -9,56 +10,49 @@ export interface SensitiveWordVO {
createTime: Date
}
export interface SensitiveWordPageReqVO extends PageParam {
name?: string
tag?: string
status?: number
createTime?: Date[]
}
export interface SensitiveWordExportReqVO {
name?: string
tag?: string
status?: number
createTime?: Date[]
export interface SensitiveWordTestReqVO {
text: string
tag: string[]
}
// 查询敏感词列表
export const getSensitiveWordPageApi = (params: SensitiveWordPageReqVO) => {
export const getSensitiveWordPage = (params: PageParam) => {
return request.get({ url: '/system/sensitive-word/page', params })
}
// 查询敏感词详情
export const getSensitiveWordApi = (id: number) => {
export const getSensitiveWord = (id: number) => {
return request.get({ url: '/system/sensitive-word/get?id=' + id })
}
// 新增敏感词
export const createSensitiveWordApi = (data: SensitiveWordVO) => {
export const createSensitiveWord = (data: SensitiveWordVO) => {
return request.post({ url: '/system/sensitive-word/create', data })
}
// 修改敏感词
export const updateSensitiveWordApi = (data: SensitiveWordVO) => {
export const updateSensitiveWord = (data: SensitiveWordVO) => {
return request.put({ url: '/system/sensitive-word/update', data })
}
// 删除敏感词
export const deleteSensitiveWordApi = (id: number) => {
export const deleteSensitiveWord = (id: number) => {
return request.delete({ url: '/system/sensitive-word/delete?id=' + id })
}
// 导出敏感词
export const exportSensitiveWordApi = (params: SensitiveWordExportReqVO) => {
export const exportSensitiveWord = (params) => {
return request.download({ url: '/system/sensitive-word/export-excel', params })
}
// 获取所有敏感词的标签数组
export const getSensitiveWordTagsApi = () => {
export const getSensitiveWordTagList = () => {
return request.get({ url: '/system/sensitive-word/get-tags' })
}
// 获得文本所包含的不合法的敏感词数组
export const validateTextApi = (id: number) => {
return request.get({ url: '/system/sensitive-word/validate-text?' + id })
export const validateText = (query: SensitiveWordTestReqVO) => {
return request.get({
url: '/system/sensitive-word/validate-text?' + qs.stringify(query, { arrayFormat: 'repeat' })
})
}

View File

@ -12,39 +12,32 @@ export interface SmsChannelVO {
createTime: Date
}
export interface SmsChannelPageReqVO extends PageParam {
signature?: string
code?: string
status?: number
createTime?: Date[]
}
// 查询短信渠道列表
export const getSmsChannelPageApi = (params: SmsChannelPageReqVO) => {
export const getSmsChannelPage = (params: PageParam) => {
return request.get({ url: '/system/sms-channel/page', params })
}
// 获得短信渠道精简列表
export function getSimpleSmsChannels() {
export function getSimpleSmsChannelList() {
return request.get({ url: '/system/sms-channel/list-all-simple' })
}
// 查询短信渠道详情
export const getSmsChannelApi = (id: number) => {
export const getSmsChannel = (id: number) => {
return request.get({ url: '/system/sms-channel/get?id=' + id })
}
// 新增短信渠道
export const createSmsChannelApi = (data: SmsChannelVO) => {
export const createSmsChannel = (data: SmsChannelVO) => {
return request.post({ url: '/system/sms-channel/create', data })
}
// 修改短信渠道
export const updateSmsChannelApi = (data: SmsChannelVO) => {
export const updateSmsChannel = (data: SmsChannelVO) => {
return request.put({ url: '/system/sms-channel/update', data })
}
// 删除短信渠道
export const deleteSmsChannelApi = (id: number) => {
export const deleteSmsChannel = (id: number) => {
return request.delete({ url: '/system/sms-channel/delete?id=' + id })
}

View File

@ -1,57 +1,39 @@
import request from '@/config/axios'
export interface SmsLogVO {
id: number
channelId: number
id: number | null
channelId: number | null
channelCode: string
templateId: number
templateId: number | null
templateCode: string
templateType: number
templateType: number | null
templateContent: string
templateParams: Map<string, object>
templateParams: Map<string, object> | null
apiTemplateId: string
mobile: string
userId: number
userType: number
sendStatus: number
sendTime: Date
sendCode: number
userId: number | null
userType: number | null
sendStatus: number | null
sendTime: Date | null
sendCode: number | null
sendMsg: string
apiSendCode: string
apiSendMsg: string
apiRequestId: string
apiSerialNo: string
receiveStatus: number
receiveTime: Date
receiveStatus: number | null
receiveTime: Date | null
apiReceiveCode: string
apiReceiveMsg: string
createTime: Date
}
export interface SmsLogPageReqVO extends PageParam {
channelId?: number
templateId?: number
mobile?: string
sendStatus?: number
sendTime?: Date[]
receiveStatus?: number
receiveTime?: Date[]
}
export interface SmsLogExportReqVO {
channelId?: number
templateId?: number
mobile?: string
sendStatus?: number
sendTime?: Date[]
receiveStatus?: number
receiveTime?: Date[]
createTime: Date | null
}
// 查询短信日志列表
export const getSmsLogPageApi = (params: SmsLogPageReqVO) => {
export const getSmsLogPage = (params: PageParam) => {
return request.get({ url: '/system/sms-log/page', params })
}
// 导出短信日志
export const exportSmsLogApi = (params: SmsLogExportReqVO) => {
export const exportSmsLog = (params) => {
return request.download({ url: '/system/sms-log/export-excel', params })
}

View File

@ -1,18 +1,18 @@
import request from '@/config/axios'
export interface SmsTemplateVO {
id: number
type: number
id: number | null
type: number | null
status: number
code: string
name: string
content: string
remark: string
apiTemplateId: string
channelId: number
channelCode: string
params: string[]
createTime: Date
channelId: number | null
channelCode?: string
params?: string[]
createTime?: Date
}
export interface SendSmsReqVO {
@ -21,60 +21,40 @@ export interface SendSmsReqVO {
templateParams: Map<String, Object>
}
export interface SmsTemplatePageReqVO {
type?: number
status?: number
code?: string
content?: string
apiTemplateId?: string
channelId?: number
createTime?: Date[]
}
export interface SmsTemplateExportReqVO {
type?: number
status?: number
code?: string
content?: string
apiTemplateId?: string
channelId?: number
createTime?: Date[]
}
// 查询短信模板列表
export const getSmsTemplatePageApi = (params: SmsTemplatePageReqVO) => {
export const getSmsTemplatePage = (params: PageParam) => {
return request.get({ url: '/system/sms-template/page', params })
}
// 查询短信模板详情
export const getSmsTemplateApi = (id: number) => {
export const getSmsTemplate = (id: number) => {
return request.get({ url: '/system/sms-template/get?id=' + id })
}
// 新增短信模板
export const createSmsTemplateApi = (data: SmsTemplateVO) => {
export const createSmsTemplate = (data: SmsTemplateVO) => {
return request.post({ url: '/system/sms-template/create', data })
}
// 修改短信模板
export const updateSmsTemplateApi = (data: SmsTemplateVO) => {
export const updateSmsTemplate = (data: SmsTemplateVO) => {
return request.put({ url: '/system/sms-template/update', data })
}
// 删除短信模板
export const deleteSmsTemplateApi = (id: number) => {
export const deleteSmsTemplate = (id: number) => {
return request.delete({ url: '/system/sms-template/delete?id=' + id })
}
// 发送短信
export const sendSmsApi = (data: SendSmsReqVO) => {
return request.post({ url: '/system/sms-template/send-sms', data })
}
// 导出短信模板
export const exportPostApi = (params: SmsTemplateExportReqVO) => {
export const exportSmsTemplate = (params) => {
return request.download({
url: '/system/sms-template/export-excel',
params
})
}
// 发送短信
export const sendSms = (data: SendSmsReqVO) => {
return request.post({ url: '/system/sms-template/send-sms', data })
}

View File

@ -32,31 +32,31 @@ export interface TenantExportReqVO {
}
// 查询租户列表
export const getTenantPageApi = (params: TenantPageReqVO) => {
export const getTenantPage = (params: TenantPageReqVO) => {
return request.get({ url: '/system/tenant/page', params })
}
// 查询租户详情
export const getTenantApi = (id: number) => {
export const getTenant = (id: number) => {
return request.get({ url: '/system/tenant/get?id=' + id })
}
// 新增租户
export const createTenantApi = (data: TenantVO) => {
export const createTenant = (data: TenantVO) => {
return request.post({ url: '/system/tenant/create', data })
}
// 修改租户
export const updateTenantApi = (data: TenantVO) => {
export const updateTenant = (data: TenantVO) => {
return request.put({ url: '/system/tenant/update', data })
}
// 删除租户
export const deleteTenantApi = (id: number) => {
export const deleteTenant = (id: number) => {
return request.delete({ url: '/system/tenant/delete?id=' + id })
}
// 导出租户
export const exportTenantApi = (params: TenantExportReqVO) => {
export const exportTenant = (params: TenantExportReqVO) => {
return request.download({ url: '/system/tenant/export-excel', params })
}

View File

@ -43,12 +43,12 @@ export const getUserApi = (id: number) => {
}
// 新增用户
export const createUserApi = (data: UserVO) => {
export const createUserApi = (data: UserVO | Recordable) => {
return request.post({ url: '/system/user/create', data })
}
// 修改用户
export const updateUserApi = (data: UserVO) => {
export const updateUserApi = (data: UserVO | Recordable) => {
return request.put({ url: '/system/user/update', data })
}
@ -86,6 +86,6 @@ export const updateUserStatusApi = (id: number, status: number) => {
}
// 获取用户精简信息列表
export const getListSimpleUsersApi = () => {
export const getSimpleUserList = (): Promise<UserVO[]> => {
return request.get({ url: '/system/user/list-all-simple' })
}

View File

@ -76,7 +76,7 @@ const toggleClick = () => {
v-if="title"
:class="[
`${prefixCls}-header`,
'h-50px flex justify-between items-center mb-10px border-bottom-1 border-solid border-[var(--tags-view-border-color)] px-10px cursor-pointer dark:border-[var(--el-border-color)]'
'h-50px flex justify-between items-center border-bottom-1 border-solid border-[var(--tags-view-border-color)] px-10px cursor-pointer dark:border-[var(--el-border-color)]'
]"
@click="toggleClick"
>

View File

@ -34,7 +34,7 @@ export default defineComponent({
return null
}
//
if (props.value === undefined) {
if (props.value === undefined || props.value === null) {
return null
}
getDictObj(props.type, props.value.toString())

View File

@ -178,7 +178,7 @@ defineExpose({
</script>
<template>
<div class="border-1 border-solid border-[var(--tags-view-border-color)] z-3000">
<div class="border-1 border-solid border-[var(--tags-view-border-color)] z-99">
<!-- 工具栏 -->
<Toolbar
:editor="editorRef"

View File

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

View File

@ -37,7 +37,7 @@ export default defineComponent({
})
const getBreadcrumb = () => {
const currentPath = currentRoute.value.path
const currentPath = currentRoute.value.matched.slice(-1)[0].path
levelList.value = filter<AppRouteRecordRaw>(unref(menuRouters), (node: AppRouteRecordRaw) => {
return node.path === currentPath
@ -47,7 +47,7 @@ export default defineComponent({
const renderBreadcrumb = () => {
const breadcrumbList = treeToList<AppRouteRecordRaw[]>(unref(levelList))
return breadcrumbList.map((v) => {
const disabled = v.redirect === 'noredirect'
const disabled = !v.redirect || v.redirect === 'noredirect'
const meta = v.meta as RouteMeta
return (
<ElBreadcrumbItem to={{ path: disabled ? '' : v.path }} key={v.name}>

View File

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

View File

@ -66,9 +66,9 @@ const toDocument = () => {
<Icon icon="ep:menu" />
<div @click="toDocument">{{ t('common.document') }}</div>
</ElDropdownItem>
<ElDropdownItem divided>
<ElDropdownItem divided @click="loginOut">
<Icon icon="ep:switch-button" />
<div @click="loginOut">{{ t('common.loginOut') }}</div>
<div>{{ t('common.loginOut') }}</div>
</ElDropdownItem>
</ElDropdownMenu>
</template>

View File

@ -225,26 +225,26 @@ const remainingRouter: AppRouteRecordRaw[] = [
children: [
{
path: '/manager/form/edit',
component: () => import('@/views/bpm/form/formEditor.vue'),
component: () => import('@/views/bpm/form/editor/index.vue'),
name: 'bpmFormEditor',
meta: {
noCache: true,
hidden: true,
canTo: true,
title: '设计流程表单',
activeMenu: 'bpm/manager/form/formEditor'
activeMenu: '/bpm/manager/form'
}
},
{
path: '/manager/model/edit',
component: () => import('@/views/bpm/model/modelEditor.vue'),
component: () => import('@/views/bpm/model/editor/index.vue'),
name: 'modelEditor',
meta: {
noCache: true,
hidden: true,
canTo: true,
title: '设计流程',
activeMenu: 'bpm/manager/model/design'
activeMenu: '/bpm/manager/model'
}
},
{
@ -256,7 +256,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
hidden: true,
canTo: true,
title: '流程定义',
activeMenu: 'bpm/definition/index'
activeMenu: '/bpm/manager/model'
}
},
{

View File

@ -70,6 +70,23 @@ export const getDictObj = (dictType: string, value: any) => {
})
}
/**
*
*
* @param dictType
* @param value
*/
export const getDictLabel = (dictType: string, value: any) => {
const dictOptions: DictDataType[] = getDictOptions(dictType)
const dictLabel = ref('')
dictOptions.forEach((dict: DictDataType) => {
if (dict.value === value) {
dictLabel.value = dict.label
}
})
return dictLabel.value
}
export enum DICT_TYPE {
USER_TYPE = 'user_type',
COMMON_STATUS = 'common_status',
@ -123,5 +140,9 @@ export enum DICT_TYPE {
PAY_ORDER_STATUS = 'pay_order_status', // 商户支付订单状态
PAY_ORDER_REFUND_STATUS = 'pay_order_refund_status', // 商户支付订单退款状态
PAY_REFUND_ORDER_STATUS = 'pay_refund_order_status', // 退款订单状态
PAY_REFUND_ORDER_TYPE = 'pay_refund_order_type' // 退款订单类别
PAY_REFUND_ORDER_TYPE = 'pay_refund_order_type', // 退款订单类别
// ========== MP 模块 ==========
MP_AUTO_REPLY_REQUEST_MATCH = 'mp_auto_reply_request_match', // 自动回复请求匹配类型
MP_MESSAGE_TYPE = 'mp_message_type' // 消息类型
}

View File

@ -11,10 +11,65 @@ import dayjs from 'dayjs'
* @description format + + "YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ"
* @returns
*/
export function formatDate(date: Date, format: string): string {
export function formatDate(date: Date, format?: string): string {
// 日期不存在,则返回空
if (!date) {
return ''
}
// 日期存在,则进行格式化
if (format === undefined) {
format = 'YYYY-MM-DD HH:mm:ss'
}
return dayjs(date).format(format)
}
// TODO 芋艿:稍后去掉
// 日期格式化
export function parseTime(time: any, pattern?: string) {
if (arguments.length === 0 || !time) {
return null
}
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
} else {
if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
time = parseInt(time)
} else if (typeof time === 'string') {
time = time
.replace(new RegExp(/-/gm), '/')
.replace('T', ' ')
.replace(new RegExp(/\.\d{3}/gm), '')
}
if (typeof time === 'number' && time.toString().length === 10) {
time = time * 1000
}
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
let value = formatObj[key]
// Note: getDay() returns 0 on Sunday
if (key === 'a') {
return ['日', '一', '二', '三', '四', '五', '六'][value]
}
if (result.length > 0 && value < 10) {
value = '0' + value
}
return value || 0
})
return time_str
}
/**
*
* @param dateTime
@ -139,5 +194,5 @@ export const dateFormatter = (row, column, cellValue) => {
if (!cellValue) {
return
}
return dayjs(cellValue).format('YYYY-MM-DD HH:mm:ss')
return formatDate(cellValue)
}

View File

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

View File

@ -1,79 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: null,
action: true,
columns: [
{
title: '定义编号',
field: 'id',
table: {
width: 360
}
},
{
title: '定义名称',
field: 'name',
table: {
// width: 120,
slots: {
default: 'name_default'
}
}
},
{
title: '定义分类',
field: 'category',
// dictType: DICT_TYPE.BPM_MODEL_CATEGORY,
// dictClass: 'number',
table: {
// width: 120,
slots: {
default: 'category_default'
}
}
},
{
title: '表单信息',
field: 'formId',
table: {
// width: 200,
slots: {
default: 'formId_default'
}
}
},
{
title: '流程版本',
field: 'version',
table: {
// width: 80,
slots: {
default: 'version_default'
}
}
},
{
title: '激活状态',
field: 'suspensionState',
table: {
// width: 80,
slots: {
default: 'suspensionState_default'
}
}
},
{
title: '部署时间',
field: 'deploymentTime',
isForm: false,
formatter: 'formatDate'
// table: {
// width: 180
// }
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -1,92 +1,138 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<!-- 流程名称 -->
<template #name_default="{ row }">
<XTextButton :title="row.name" @click="handleBpmnDetail(row.id)" />
</template>
<!-- 流程分类 -->
<template #category_default="{ row }">
<DictTag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="Number(row?.category)" />
</template>
<!-- 表单信息 -->
<template #formId_default="{ row }">
<XTextButton
v-if="row.formType === 10"
:title="row.formName"
@click="handleFormDetail(row)"
/>
<XTextButton v-else :title="row.formCustomCreatePath" @click="handleFormDetail(row)" />
</template>
<!-- 流程版本 -->
<template #version_default="{ row }">
<el-tag>v{{ row.version }}</el-tag>
</template>
<!-- 激活状态 -->
<template #suspensionState_default="{ row }">
<el-tag type="success" v-if="row.suspensionState === 1">激活</el-tag>
<el-tag type="warning" v-if="row.suspensionState === 2">挂起</el-tag>
</template>
<!-- 操作 -->
<template #actionbtns_default="{ row }">
<XTextButton
preIcon="ep:user"
title="分配规则"
v-hasPermi="['bpm:task-assign-rule:query']"
@click="handleAssignRule(row)"
/>
</template>
</XTable>
<!-- 表单详情的弹窗 -->
<XModal v-model="formDetailVisible" width="800" title="表单详情" :show-footer="false">
<form-create
:rule="formDetailPreview.rule"
:option="formDetailPreview.option"
v-if="formDetailVisible"
<el-table v-loading="loading" :data="list">
<el-table-column label="定义编号" align="center" prop="id" width="400" />
<el-table-column label="流程名称" align="center" prop="name" width="200">
<template #default="scope">
<el-button type="text" @click="handleBpmnDetail(scope.row)">
<span>{{ scope.row.name }}</span>
</el-button>
</template>
</el-table-column>
<el-table-column label="定义分类" align="center" prop="category" width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" />
</template>
</el-table-column>
<el-table-column label="表单信息" align="center" prop="formType" width="200">
<template #default="scope">
<el-button
v-if="scope.row.formType === 10"
type="text"
@click="handleFormDetail(scope.row)"
>
<span>{{ scope.row.formName }}</span>
</el-button>
<el-button v-else type="text" @click="handleFormDetail(scope.row)">
<span>{{ scope.row.formCustomCreatePath }}</span>
</el-button>
</template>
</el-table-column>
<el-table-column label="流程版本" align="center" prop="processDefinition.version" width="80">
<template #default="scope">
<el-tag v-if="scope.row">v{{ scope.row.version }}</el-tag>
<el-tag type="warning" v-else>未部署</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="version" width="80">
<template #default="scope">
<el-tag type="success" v-if="scope.row.suspensionState === 1">激活</el-tag>
<el-tag type="warning" v-if="scope.row.suspensionState === 2">挂起</el-tag>
</template>
</el-table-column>
<el-table-column
label="部署时间"
align="center"
prop="deploymentTime"
width="180"
:formatter="dateFormatter"
/>
</XModal>
<!-- 流程模型图的预览 -->
<XModal title="流程图" v-model="showBpmnOpen" width="80%" height="90%">
<my-process-viewer
key="designer"
v-model="bpmnXML"
:value="bpmnXML"
v-bind="bpmnControlForm"
:prefix="bpmnControlForm.prefix"
<el-table-column
label="定义描述"
align="center"
prop="description"
width="300"
show-overflow-tooltip
/>
</XModal>
<el-table-column label="操作" align="center" width="150" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="handleAssignRule(scope.row)"
v-hasPermi="['bpm:task-assign-rule: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>
</template>
<script setup lang="ts">
// import
import * as DefinitionApi from '@/api/bpm/definition'
// import * as ModelApi from '@/api/bpm/model'
import { allSchemas } from './definition.data'
import { setConfAndFields2 } from '@/utils/formCreate'
import { DICT_TYPE } from '@/utils/dict'
const bpmnXML = ref(null)
const showBpmnOpen = ref(false)
const bpmnControlForm = ref({
prefix: 'flowable'
})
// const message = useMessage() //
const router = useRouter() //
<!-- 弹窗表单详情 -->
<Dialog title="表单详情" v-model="formDetailVisible" width="800">
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
</Dialog>
<!-- 弹窗流程模型图的预览 -->
<Dialog title="流程图" v-model="bpmnDetailVisible" width="800">
<my-process-viewer
key="designer"
v-model="bpmnXML"
:value="bpmnXML"
v-bind="bpmnControlForm"
:prefix="bpmnControlForm.prefix"
/>
</Dialog>
</template>
<script setup lang="ts" name="Form">
import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as DefinitionApi from '@/api/bpm/definition'
import { setConfAndFields2 } from '@/utils/formCreate'
const { push } = useRouter() //
const { query } = useRoute() //
// ========== ==========
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
key: query.key
})
const [registerTable] = useXTable({
allSchemas: allSchemas,
getListApi: DefinitionApi.getProcessDefinitionPageApi,
params: queryParams
})
//
/** 查询参数列表 */
const getList = async () => {
loading.value = true
try {
const data = await DefinitionApi.getProcessDefinitionPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 点击任务分配按钮 */
const handleAssignRule = (row) => {
push({
name: 'BpmTaskAssignRuleList',
query: {
modelId: row.id
}
})
}
/** 流程表单的详情按钮操作 */
const formDetailVisible = ref(false)
const formDetailPreview = ref({
rule: [],
@ -99,32 +145,25 @@ const handleFormDetail = async (row) => {
//
formDetailVisible.value = true
} else {
await router.push({
await push({
path: row.formCustomCreatePath
})
}
}
//
const handleBpmnDetail = (row) => {
// TODO
console.log(row)
DefinitionApi.getProcessDefinitionBpmnXMLApi(row).then((response) => {
console.log(response, 'response')
bpmnXML.value = response
//
showBpmnOpen.value = true
})
// message.success(' 2 ')
/** 流程图的详情按钮操作 */
const bpmnDetailVisible = ref(false)
const bpmnXML = ref(null)
const bpmnControlForm = ref({
prefix: 'flowable'
})
const handleBpmnDetail = async (row) => {
bpmnXML.value = await DefinitionApi.getProcessDefinitionBpmnXML(row.id)
bpmnDetailVisible.value = true
}
//
const handleAssignRule = (row) => {
router.push({
name: 'BpmTaskAssignRuleList',
query: {
processDefinitionId: row.id
}
})
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -0,0 +1,106 @@
<template>
<ContentWrap>
<!-- 表单设计器 -->
<fc-designer ref="designer" height="780px">
<template #handle>
<el-button round size="small" type="primary" @click="handleSave">
<Icon icon="ep:plus" class="mr-5px" /> 保存
</el-button>
</template>
</fc-designer>
</ContentWrap>
<!-- 表单保存的弹窗 -->
<Dialog title="保存表单" v-model="modelVisible" width="600">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
<el-form-item label="表单名" prop="name">
<el-input v-model="formData.name" placeholder="请输入表单名" />
</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="remark">
<el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { CommonStatusEnum } from '@/utils/constants'
import * as FormApi from '@/api/bpm/form'
import { encodeConf, encodeFields, setConfAndFields } from '@/utils/formCreate'
const { t } = useI18n() //
const message = useMessage() //
const { query } = useRoute() //
const designer = ref() //
const modelVisible = ref(false) //
const formLoading = ref(false) //
const formData = ref({
name: '',
status: CommonStatusEnum.ENABLE,
remark: ''
})
const formRules = reactive({
name: [{ required: true, message: '表单名不能为空', trigger: 'blur' }],
status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
/** 处理保存按钮 */
const handleSave = () => {
modelVisible.value = true
}
/** 提交表单 */
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as FormApi.FormVO
data.conf = encodeConf(designer) //
data.fields = encodeFields(designer) //
if (!data.id) {
await FormApi.createForm(data)
message.success(t('common.createSuccess'))
} else {
await FormApi.updateForm(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
} finally {
formLoading.value = false
}
}
/** 初始化 **/
onMounted(async () => {
//
const id = query.id as unknown as number
if (!id) {
return
}
//
const data = await FormApi.getForm(id)
formData.value = data
setConfAndFields(designer, data.conf, data.fields)
})
</script>

View File

@ -1,43 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
name: [required]
})
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: 'id',
primaryTitle: '表单编号',
action: true,
columns: [
{
title: '表单名',
field: 'name',
isSearch: true
},
{
title: t('common.status'),
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
dictClass: 'number'
},
{
title: '备注',
field: 'remark'
},
{
title: t('common.createTime'),
field: 'createTime',
formatter: 'formatDate',
isForm: false,
table: {
width: 180
}
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -1,157 +0,0 @@
<template>
<ContentWrap>
<!-- 表单设计器 -->
<fc-designer ref="designer" height="780px">
<template #handle>
<XButton type="primary" title="生成JSON" @click="showJson" />
<XButton type="primary" title="生成Options" @click="showOption" />
<XButton type="primary" :title="t('action.save')" @click="handleSave" />
</template>
</fc-designer>
<Dialog :title="dialogTitle" v-model="dialogVisible1" maxHeight="600">
<div ref="editor" v-if="dialogVisible1">
<XTextButton style="float: right" :title="t('common.copy')" @click="copy(formValue)" />
<el-scrollbar height="580">
<pre>
{{ formValue }}
</pre>
</el-scrollbar>
</div>
</Dialog>
<!-- 表单保存的弹窗 -->
<XModal v-model="dialogVisible" title="保存表单">
<el-form ref="formRef" :model="formValues" :rules="formRules" label-width="80px">
<el-form-item label="表单名" prop="name">
<el-input v-model="formValues.name" placeholder="请输入表单名" />
</el-form-item>
<el-form-item label="开启状态" prop="status">
<el-radio-group v-model="formValues.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="remark">
<el-input v-model="formValues.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-form>
<!-- 操作按钮 -->
<template #footer>
<!-- 按钮保存 -->
<XButton
type="primary"
:title="t('action.save')"
:loading="dialogLoading"
@click="submitForm"
/>
<!-- 按钮关闭 -->
<XButton :title="t('dialog.close')" @click="dialogVisible = false" />
</template>
</XModal>
</ContentWrap>
</template>
<script setup lang="ts" name="BpmFormEditor">
import { FormInstance } from 'element-plus'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { CommonStatusEnum } from '@/utils/constants'
import * as FormApi from '@/api/bpm/form'
import { encodeConf, encodeFields, setConfAndFields } from '@/utils/formCreate'
import { useClipboard } from '@vueuse/core'
const { t } = useI18n() //
const message = useMessage() //
const { query } = useRoute() //
const designer = ref() //
const type = ref(-1)
const formValue = ref('')
const dialogTitle = ref('')
const dialogVisible = ref(false) //
const dialogVisible1 = ref(false) //
const dialogLoading = ref(false) //
const formRef = ref<FormInstance>()
const formRules = reactive({
name: [{ required: true, message: '表单名不能为空', trigger: 'blur' }],
status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }]
})
const formValues = ref({
name: '',
status: CommonStatusEnum.ENABLE,
remark: ''
})
//
const handleSave = () => {
dialogVisible.value = true
}
//
const submitForm = async () => {
//
const elForm = unref(formRef)
if (!elForm) return
const valid = await elForm.validate()
if (!valid) return
//
dialogLoading.value = true
try {
const data = formValues.value as FormApi.FormVO
data.conf = encodeConf(designer) //
data.fields = encodeFields(designer) //
if (!data.id) {
await FormApi.createFormApi(data)
message.success(t('common.createSuccess'))
} else {
await FormApi.updateFormApi(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
} finally {
dialogLoading.value = false
}
}
const showJson = () => {
openModel('生成JSON')
type.value = 0
formValue.value = designer.value.getRule()
}
const showOption = () => {
openModel('生成Options')
type.value = 1
formValue.value = designer.value.getOption()
}
const openModel = (title: string) => {
dialogVisible1.value = true
dialogTitle.value = title
}
/** 复制 **/
const copy = async (text: string) => {
const { copy, copied, isSupported } = useClipboard({ source: text })
if (!isSupported) {
message.error(t('common.copyError'))
} else {
await copy()
if (unref(copied)) {
message.success(t('common.copySuccess'))
}
}
}
// ========== ==========
onMounted(() => {
//
const id = query.id as unknown as number
if (!id) {
return
}
//
FormApi.getFormApi(id).then((data) => {
formValues.value = data
setConfAndFields(designer, data.conf, data.fields)
})
})
</script>

View File

@ -1,93 +1,171 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<!-- 操作新增 -->
<template #toolbar_buttons>
<XButton
type="primary"
preIcon="ep:zoom-in"
:title="t('action.add')"
v-hasPermi="['system:post:create']"
@click="handleCreate()"
<content-wrap>
<!-- 搜索工作栏 -->
<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"
/>
</template>
<template #actionbtns_default="{ row }">
<!-- 操作修改 -->
<XTextButton
preIcon="ep:edit"
:title="t('action.edit')"
v-hasPermi="['bpm:form:update']"
@click="handleUpdate(row.id)"
/>
<!-- 操作详情 -->
<XTextButton
preIcon="ep:view"
:title="t('action.detail')"
v-hasPermi="['bpm:form:query']"
@click="handleDetail(row.id)"
/>
<!-- 操作删除 -->
<XTextButton
preIcon="ep:delete"
:title="t('action.del')"
v-hasPermi="['bpm:form:delete']"
@click="deleteData(row.id)"
/>
</template>
</XTable>
<!-- 表单详情的弹窗 -->
<XModal v-model="detailOpen" width="800" title="表单详情">
<form-create :rule="detailPreview.rule" :option="detailPreview.option" v-if="detailOpen" />
</XModal>
</ContentWrap>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button type="primary" @click="openForm()" v-hasPermi="['bpm:form:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</el-form-item>
</el-form>
</content-wrap>
<!-- 列表 -->
<content-wrap>
<el-table v-loading="loading" :data="list">
<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="remark" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm(scope.row.id)"
v-hasPermi="['bpm:form:update']"
>
编辑
</el-button>
<el-button link @click="openDetail(scope.row.id)" v-hasPermi="['bpm:form:query']">
详情
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['bpm:form:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</content-wrap>
<!-- 表单详情的弹窗 -->
<Dialog title="表单详情" v-model="detailVisible" width="800">
<form-create :rule="detailData.rule" :option="detailData.option" />
</Dialog>
</template>
<script setup lang="ts" name="BpmForm">
// import
<script setup lang="ts" name="Form">
import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as FormApi from '@/api/bpm/form'
import { allSchemas } from './form.data'
// import
import { setConfAndFields2 } from '@/utils/formCreate'
const message = useMessage() //
const { t } = useI18n() //
const { push } = useRouter() //
//
const [registerTable, { deleteData }] = useXTable({
allSchemas: allSchemas,
getListApi: FormApi.getFormPageApi,
deleteApi: FormApi.deleteFormApi
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: null
})
const queryFormRef = ref() //
//
const handleCreate = () => {
push({
name: 'bpmFormEditor'
})
/** 查询参数列表 */
const getList = async () => {
loading.value = true
try {
const data = await FormApi.getFormPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
//
const handleUpdate = async (rowId: number) => {
await push({
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const openForm = (id?: number) => {
push({
name: 'bpmFormEditor',
query: {
id: rowId
id
}
})
}
//
const detailOpen = ref(false)
const detailPreview = ref({
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await FormApi.deleteForm(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 详情操作 */
const detailVisible = ref(false)
const detailData = ref({
rule: [],
option: {}
})
const handleDetail = async (rowId: number) => {
const openDetail = async (rowId: number) => {
//
const data = await FormApi.getFormApi(rowId)
setConfAndFields2(detailPreview, data.conf, data.fields)
const data = await FormApi.getForm(rowId)
setConfAndFields2(detailData, data.conf, data.fields)
//
detailOpen.value = true
detailVisible.value = true
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -0,0 +1,130 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="组名" prop="name">
<el-input v-model="formData.name" placeholder="请输入组名" />
</el-form-item>
<el-form-item label="描述">
<el-input type="textarea" v-model="formData.name" placeholder="请输入描述" />
</el-form-item>
<el-form-item label="成员" prop="memberUserIds">
<el-select v-model="formData.memberUserIds" multiple placeholder="请选择成员">
<el-option
v-for="user in userList"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { CommonStatusEnum } from '@/utils/constants'
import * as UserGroupApi from '@/api/bpm/userGroup'
import * as UserApi from '@/api/system/user'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
name: undefined,
description: undefined,
memberUserIds: undefined,
status: CommonStatusEnum.ENABLE
})
const formRules = reactive({
name: [{ required: true, message: '组名不能为空', trigger: 'blur' }],
description: [{ required: true, message: '描述不能为空', trigger: 'blur' }],
memberUserIds: [{ required: true, message: '成员不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
const userList = ref([]) //
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
modelVisible.value = true
modelTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await UserGroupApi.getUserGroup(id)
} finally {
formLoading.value = false
}
}
//
userList.value = await UserApi.getSimpleUserList()
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as unknown as UserGroupApi.UserGroupVO
if (formType.value === 'create') {
await UserGroupApi.createUserGroup(data)
message.success(t('common.createSuccess'))
} else {
await UserGroupApi.updateUserGroup(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: undefined,
description: undefined,
memberUserIds: undefined,
status: CommonStatusEnum.ENABLE
}
formRef.value?.resetFields()
}
</script>

View File

@ -1,64 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
name: [required],
description: [required],
memberUserIds: [required],
status: [required]
})
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: 'id',
primaryTitle: '编号',
action: true,
searchSpan: 8,
columns: [
{
title: '组名',
field: 'name',
isSearch: true
},
{
title: '成员',
field: 'memberUserIds',
table: {
slots: {
default: 'memberUserIds_default'
}
}
},
{
title: '描述',
field: 'description'
},
{
title: t('common.status'),
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
dictClass: 'number',
isSearch: true
},
{
title: t('common.createTime'),
field: 'createTime',
formatter: 'formatDate',
isForm: false,
isSearch: true,
search: {
show: true,
itemRender: {
name: 'XDataTimePicker'
}
},
table: {
width: 180
}
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -1,182 +1,184 @@
<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="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')"
@click="openForm('create')"
v-hasPermi="['bpm:user-group:create']"
@click="handleCreate()"
/>
</template>
<template #memberUserIds_default="{ row }">
<span v-for="userId in row.memberUserIds" :key="userId">
{{ getUserNickname(userId) }} &nbsp;
</span>
</template>
<template #actionbtns_default="{ row }">
<!-- 操作修改 -->
<XTextButton
preIcon="ep:edit"
:title="t('action.edit')"
v-hasPermi="['bpm:user-group:update']"
@click="handleUpdate(row.id)"
/>
<!-- 操作详情 -->
<XTextButton
preIcon="ep:view"
:title="t('action.detail')"
v-hasPermi="['bpm:user-group:query']"
@click="handleDetail(row.id)"
/>
<!-- 操作删除 -->
<XTextButton
preIcon="ep:delete"
:title="t('action.del')"
v-hasPermi="['bpm:user-group:delete']"
@click="deleteData(row.id)"
/>
</template>
</XTable>
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<XModal v-model="dialogVisible" :title="dialogTitle" :mask-closable="false">
<!-- 对话框(添加 / 修改) -->
<Form
v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema"
:rules="rules"
ref="formRef"
>
<template #memberUserIds="form">
<el-select v-model="form.memberUserIds" multiple>
<el-option v-for="item in users" :key="item.id" :label="item.nickname" :value="item.id" />
</el-select>
</template>
</Form>
<!-- 对话框(详情) -->
<Descriptions
v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema"
:data="detailData"
>
<template #memberUserIds="{ row }">
<span v-for="userId in row.memberUserIds" :key="userId">
{{ getUserNickname(userId) }} &nbsp;
</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<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="description" />
<el-table-column label="成员" align="center">
<template #default="scope">
<span v-for="userId in scope.row.memberUserIds" :key="userId" class="pr-5px">
{{ userList.find((user) => user.id === userId)?.nickname }}
</span>
</template>
</el-table-column>
<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="createTime"
:formatter="dateFormatter"
/>
<!-- 按钮关闭 -->
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="dialogVisible = false" />
</template>
</XModal>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['bpm:user-group:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['bpm:user-group: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>
<!-- 表单弹窗添加/修改 -->
<UserGroupForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
// import
<script setup lang="ts" name="UserGroup">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as UserGroupApi from '@/api/bpm/userGroup'
import { getListSimpleUsersApi, UserVO } from '@/api/system/user'
import { allSchemas, rules } from './group.data'
import { FormExpose } from '@/components/Form'
const { t } = useI18n() //
import * as UserApi from '@/api/system/user'
import UserGroupForm from './UserGroupForm.vue'
const message = useMessage() //
//
const [registerTable, { reload, deleteData }] = useXTable({
allSchemas: allSchemas,
getListApi: UserGroupApi.getUserGroupPageApi,
deleteApi: UserGroupApi.deleteUserGroupApi
const { t } = useI18n() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: null,
status: null,
createTime: []
})
//
const users = ref<UserVO[]>([])
const queryFormRef = ref() //
const userList = ref([]) //
const getUserNickname = (userId) => {
for (const user of users.value) {
if (user.id === userId) {
return user.nickname
}
/** 查询参数列表 */
const getList = async () => {
loading.value = true
try {
const data = await UserGroupApi.getUserGroupPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
return '未知(' + userId + ')'
}
// ========== 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 handleQuery = () => {
queryParams.pageNo = 1
getList()
}
//
const handleCreate = () => {
setDialogTile('create')
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
//
const handleUpdate = async (rowId: number) => {
setDialogTile('update')
//
const res = await UserGroupApi.getUserGroupApi(rowId)
unref(formRef)?.setValues(res)
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
//
const handleDetail = async (rowId: number) => {
setDialogTile('detail')
detailData.value = await UserGroupApi.getUserGroupApi(rowId)
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await UserGroupApi.deleteUserGroup(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
//
const submitForm = async () => {
const elForm = unref(formRef)?.getElFormRef()
if (!elForm) return
elForm.validate(async (valid) => {
if (valid) {
actionLoading.value = true
//
try {
const data = unref(formRef)?.formModel as UserGroupApi.UserGroupVO
if (actionType.value === 'create') {
await UserGroupApi.createUserGroupApi(data)
message.success(t('common.createSuccess'))
} else {
await UserGroupApi.updateUserGroupApi(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
} finally {
actionLoading.value = false
//
await reload()
}
}
})
}
// ========== ==========
onMounted(() => {
getListSimpleUsersApi().then((data) => {
users.value = data
})
/** 初始化 **/
onMounted(async () => {
await getList()
//
userList.value = await UserApi.getSimpleUserList()
})
</script>

View File

@ -0,0 +1,227 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible" width="600">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="110px"
v-loading="formLoading"
>
<el-form-item label="流程标识" prop="key">
<el-input
v-model="formData.key"
placeholder="请输入流标标识"
style="width: 330px"
:disabled="!!formData.id"
/>
<el-tooltip
v-if="!formData.id"
class="item"
effect="light"
content="新建后,流程标识不可修改!"
placement="top"
>
<i style="padding-left: 5px" class="el-icon-question"></i>
</el-tooltip>
<el-tooltip v-else class="item" effect="light" content="流程标识不可修改!" placement="top">
<i style="padding-left: 5px" class="el-icon-question"></i>
</el-tooltip>
</el-form-item>
<el-form-item label="流程名称" prop="name">
<el-input
v-model="formData.name"
placeholder="请输入流程名称"
:disabled="!!formData.id"
clearable
/>
</el-form-item>
<el-form-item v-if="formData.id" label="流程分类" prop="category">
<el-select
v-model="formData.category"
placeholder="请选择流程分类"
clearable
style="width: 100%"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="流程描述" prop="description">
<el-input type="textarea" v-model="formData.description" clearable />
</el-form-item>
<div v-if="formData.id">
<el-form-item label="表单类型" prop="formType">
<el-radio-group v-model="formData.formType">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="formData.formType === 10" label="流程表单" prop="formId">
<el-select v-model="formData.formId" clearable style="width: 100%">
<el-option
v-for="form in formList"
:key="form.id"
:label="form.name"
:value="form.id"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="formData.formType === 20"
label="表单提交路由"
prop="formCustomCreatePath"
>
<el-input
v-model="formData.formCustomCreatePath"
placeholder="请输入表单提交路由"
style="width: 330px"
/>
<el-tooltip
class="item"
effect="light"
content="自定义表单的提交路径,使用 Vue 的路由地址例如说bpm/oa/leave/create"
placement="top"
>
<i style="padding-left: 5px" class="el-icon-question"></i>
</el-tooltip>
</el-form-item>
<el-form-item
v-if="formData.formType === 20"
label="表单查看路由"
prop="formCustomViewPath"
>
<el-input
v-model="formData.formCustomViewPath"
placeholder="请输入表单查看路由"
style="width: 330px"
/>
<el-tooltip
class="item"
effect="light"
content="自定义表单的查看路径,使用 Vue 的路由地址例如说bpm/oa/leave/view"
placement="top"
>
<i style="padding-left: 5px" class="el-icon-question"></i>
</el-tooltip>
</el-form-item>
</div>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { ElMessageBox } from 'element-plus'
import * as ModelApi from '@/api/bpm/model'
import * as FormApi from '@/api/bpm/form'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
formType: 10,
name: '',
category: undefined,
description: '',
formId: '',
formCustomCreatePath: '',
formCustomViewPath: ''
})
const formRules = reactive({
category: [{ required: true, message: '参数分类不能为空', trigger: 'blur' }],
name: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }],
key: [{ required: true, message: '参数键名不能为空', trigger: 'blur' }],
value: [{ required: true, message: '参数键值不能为空', trigger: 'blur' }],
visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
const formList = ref([]) //
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
modelVisible.value = true
modelTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await ModelApi.getModel(id)
} finally {
formLoading.value = false
}
}
//
formList.value = await FormApi.getSimpleFormList()
}
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 ModelApi.ModelVO
if (formType.value === 'create') {
await ModelApi.createModel(data)
//
await ElMessageBox.alert(
'<strong>新建模型成功!</strong>后续需要执行如下 4 个步骤:' +
'<div>1. 点击【修改流程】按钮,配置流程的分类、表单信息</div>' +
'<div>2. 点击【设计流程】按钮,绘制流程图</div>' +
'<div>3. 点击【分配规则】按钮,设置每个用户任务的审批人</div>' +
'<div>4. 点击【发布流程】按钮,完成流程的最终发布</div>' +
'另外,每次流程修改后,都需要点击【发布流程】按钮,才能正式生效!!!',
'重要提示',
{
dangerouslyUseHTMLString: true,
type: 'success'
}
)
} else {
await ModelApi.updateModel(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
formType: 10,
name: '',
category: undefined,
description: '',
formId: '',
formCustomCreatePath: '',
formCustomViewPath: ''
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,137 @@
<template>
<Dialog title="导入流程" v-model="modelVisible" width="400">
<div>
<el-upload
ref="uploadRef"
:action="importUrl"
:headers="uploadHeaders"
:data="formData"
name="bpmnFile"
v-model:file-list="fileList"
:drag="true"
:auto-upload="false"
accept=".bpmn, .xml"
:limit="1"
:on-exceed="handleExceed"
:on-success="submitFormSuccess"
:on-error="submitFormError"
:disabled="formLoading"
>
<Icon class="el-icon--upload" icon="ep:upload-filled" />
<div class="el-upload__text"> 将文件拖到此处 <em>点击上传</em> </div>
<template #tip>
<div class="el-upload__tip" style="color: red">
提示仅允许导入bpmxml格式文件
</div>
<div>
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px">
<el-form-item label="流程标识" prop="key">
<el-input
v-model="formData.key"
placeholder="请输入流标标识"
style="width: 250px"
/>
</el-form-item>
<el-form-item label="流程名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入流程名称" clearable />
</el-form-item>
<el-form-item label="流程描述" prop="description">
<el-input type="textarea" v-model="formData.description" clearable />
</el-form-item>
</el-form>
</div>
</template>
</el-upload>
</div>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { getAccessToken, getTenantId } from '@/utils/auth'
const message = useMessage() //
const modelVisible = ref(false) //
const formLoading = ref(false) //
const formData = ref({
key: '',
name: '',
description: ''
})
const formRules = reactive({
key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
const uploadRef = ref() // Ref
const importUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/bpm/model/import'
const uploadHeaders = ref() // Header
const fileList = ref([]) //
/** 打开弹窗 */
const open = async () => {
modelVisible.value = true
resetForm()
}
defineExpose({ open }) // open
/** 重置表单 */
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
if (fileList.value.length == 0) {
message.error('请上传文件')
return
}
//
uploadHeaders.value = {
Authorization: 'Bearer ' + getAccessToken(),
'tenant-id': getTenantId()
}
formLoading.value = true
uploadRef.value!.submit()
}
/** 文件上传成功 */
const emit = defineEmits(['success']) // success
const submitFormSuccess = async (response: any): Promise<void> => {
if (response.code !== 0) {
message.error(response.msg)
formLoading.value = false
return
}
//
message.success('导入流程成功!请点击【设计流程】按钮,进行编辑保存后,才可以进行【发布流程】')
//
emit('success')
}
/** 上传错误提示 */
const submitFormError = (): void => {
message.error('导入流程失败,请您重新上传!')
formLoading.value = false
}
/** 重置表单 */
const resetForm = () => {
//
formLoading.value = false
uploadRef.value?.clearFiles()
//
formData.value = {
key: '',
name: '',
description: ''
}
formRef.value?.resetFields()
}
/** 文件数超出提示 */
const handleExceed = (): void => {
message.error('最多只能上传一个文件!')
}
</script>

View File

@ -0,0 +1,102 @@
<template>
<ContentWrap>
<!-- 流程设计器负责绘制流程等 -->
<my-process-designer
key="designer"
v-if="xmlString !== undefined"
v-model="xmlString"
:value="xmlString"
v-bind="controlForm"
keyboard
ref="processDesigner"
@init-finished="initModeler"
:additionalModel="controlForm.additionalModel"
@save="save"
/>
<!-- 流程属性器负责编辑每个流程节点的属性 -->
<my-properties-panel
key="penal"
:bpmnModeler="modeler"
:prefix="controlForm.prefix"
class="process-panel"
:model="model"
/>
</ContentWrap>
</template>
<script setup lang="ts">
//
import CustomContentPadProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/content-pad'
//
import CustomPaletteProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/palette'
import * as ModelApi from '@/api/bpm/model'
const router = useRouter() //
const { query } = useRoute() //
const message = useMessage() //
const xmlString = ref(undefined) // BPMN XML
const modeler = ref(null) // BPMN Modeler
const controlForm = ref({
simulation: true,
labelEditing: false,
labelVisible: false,
prefix: 'flowable',
headerButtonSize: 'mini',
additionalModel: [CustomContentPadProvider, CustomPaletteProvider]
})
const model = ref<ModelApi.ModelVO>() //
/** 初始化 modeler */
const initModeler = (item) => {
setTimeout(() => {
modeler.value = item
}, 10)
}
/** 添加/修改模型 */
const save = async (bpmnXml) => {
const data = {
...model.value,
bpmnXml: bpmnXml // bpmnXml
} as unknown as ModelApi.ModelVO
//
if (data.id) {
await ModelApi.updateModel(data)
message.success('修改成功')
} else {
await ModelApi.createModel(data)
message.success('新增成功')
}
//
close()
}
/** 关闭按钮 */
const close = () => {
router.push({ path: '/bpm/manager/model' })
}
/** 初始化 */
onMounted(async () => {
const modelId = query.modelId as unknown as number
if (!modelId) {
message.error('缺少模型 modelId 编号')
return
}
//
const data = await ModelApi.getModel(modelId)
xmlString.value = data.bpmnXml
model.value = {
...data,
bpmnXml: undefined // bpmnXml
}
})
</script>
<style lang="scss">
.process-panel__container {
position: absolute;
right: 60px;
top: 90px;
}
</style>

View File

@ -1,353 +1,324 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<template #toolbar_buttons>
<!-- 操作新增 -->
<XButton
type="primary"
preIcon="ep:zoom-in"
title="新建流程"
v-hasPermi="['bpm:model:create']"
@click="handleCreate"
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="流程标识" prop="key">
<el-input
v-model="queryParams.key"
placeholder="请输入流程标识"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
<!-- 操作导入 -->
<XButton
type="warning"
preIcon="ep:upload"
:title="'导入流程'"
@click="handleImport"
style="margin-left: 10px"
</el-form-item>
<el-form-item label="流程名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入流程名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</template>
<!-- 流程名称 -->
<template #name_default="{ row }">
<XTextButton :title="row.name" @click="handleBpmnDetail(row.id)" />
</template>
<!-- 流程分类 -->
<template #category_default="{ row }">
<DictTag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="Number(row?.category)" />
</template>
<!-- 表单信息 -->
<template #formId_default="{ row }">
<XTextButton
v-if="row.formType === 10"
:title="forms.find((form) => form.id === row.formId)?.name || row.formId"
@click="handleFormDetail(row)"
/>
<XTextButton v-else :title="row.formCustomCreatePath" @click="handleFormDetail(row)" />
</template>
<!-- 流程版本 -->
<template #version_default="{ row }">
<el-tag v-if="row.processDefinition">v{{ row.processDefinition.version }}</el-tag>
<el-tag type="warning" v-else>未部署</el-tag>
</template>
<!-- 激活状态 -->
<template #status_default="{ row }">
<el-switch
v-if="row.processDefinition"
v-model="row.processDefinition.suspensionState"
:active-value="1"
:inactive-value="2"
@change="handleChangeState(row)"
/>
</template>
<!-- 操作 -->
<template #actionbtns_default="{ row }">
<XTextButton
preIcon="ep:edit"
title="修改流程"
v-hasPermi="['bpm:model:update']"
@click="handleUpdate(row.id)"
/>
<XTextButton
preIcon="ep:setting"
title="设计流程"
v-hasPermi="['bpm:model:update']"
@click="handleDesign(row)"
/>
<XTextButton
preIcon="ep:user"
title="分配规则"
v-hasPermi="['bpm:task-assign-rule:query']"
@click="handleAssignRule(row)"
/>
<XTextButton
preIcon="ep:position"
title="发布流程"
v-hasPermi="['bpm:model:deploy']"
@click="handleDeploy(row)"
/>
<XTextButton
preIcon="ep:aim"
title="流程定义"
v-hasPermi="['bpm:process-definition:query']"
@click="handleDefinitionList(row)"
/>
<!-- 操作删除 -->
<XTextButton
preIcon="ep:delete"
:title="t('action.del')"
v-hasPermi="['bpm:model:delete']"
@click="handleDelete(row.id)"
/>
</template>
</XTable>
<!-- 对话框(添加 / 修改流程) -->
<XModal v-model="dialogVisible" :title="dialogTitle" width="600">
<el-form
:loading="dialogLoading"
el-form
ref="saveFormRef"
:model="saveForm"
:rules="rules"
label-width="110px"
>
<el-form-item label="流程标识" prop="key">
<el-input
v-model="saveForm.key"
placeholder="请输入流标标识"
style="width: 330px"
:disabled="!!saveForm.id"
/>
<el-tooltip
v-if="!saveForm.id"
class="item"
effect="light"
content="新建后,流程标识不可修改!"
placement="top"
>
<i style="padding-left: 5px" class="el-icon-question"></i>
</el-tooltip>
<el-tooltip
v-else
class="item"
effect="light"
content="流程标识不可修改!"
placement="top"
>
<i style="padding-left: 5px" class="el-icon-question"></i>
</el-tooltip>
</el-form-item>
<el-form-item label="流程名称" prop="name">
<el-input
v-model="saveForm.name"
placeholder="请输入流程名称"
:disabled="!!saveForm.id"
clearable
/>
</el-form-item>
<el-form-item v-if="saveForm.id" label="流程分类" prop="category">
<el-select
v-model="saveForm.category"
placeholder="请选择流程分类"
clearable
style="width: 100%"
>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="流程描述" prop="description">
<el-input type="textarea" v-model="saveForm.description" clearable />
</el-form-item>
<div v-if="saveForm.id">
<el-form-item label="表单类型" prop="formType">
<el-radio-group v-model="saveForm.formType">
<el-radio
v-for="dict in getDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE)"
:key="parseInt(dict.value)"
:label="parseInt(dict.value)"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="saveForm.formType === 10" label="流程表单" prop="formId">
<el-select v-model="saveForm.formId" clearable style="width: 100%">
<el-option v-for="form in forms" :key="form.id" :label="form.name" :value="form.id" />
</el-select>
</el-form-item>
<el-form-item
v-if="saveForm.formType === 20"
label="表单提交路由"
prop="formCustomCreatePath"
>
<el-input
v-model="saveForm.formCustomCreatePath"
placeholder="请输入表单提交路由"
style="width: 330px"
/>
<el-tooltip
class="item"
effect="light"
content="自定义表单的提交路径,使用 Vue 的路由地址例如说bpm/oa/leave/create"
placement="top"
>
<i style="padding-left: 5px" class="el-icon-question"></i>
</el-tooltip>
</el-form-item>
<el-form-item
v-if="saveForm.formType === 20"
label="表单查看路由"
prop="formCustomViewPath"
>
<el-input
v-model="saveForm.formCustomViewPath"
placeholder="请输入表单查看路由"
style="width: 330px"
/>
<el-tooltip
class="item"
effect="light"
content="自定义表单的查看路径,使用 Vue 的路由地址例如说bpm/oa/leave/view"
placement="top"
>
<i style="padding-left: 5px" class="el-icon-question"></i>
</el-tooltip>
</el-form-item>
</div>
</el-form>
<template #footer>
<!-- 按钮保存 -->
<XButton
type="primary"
:loading="dialogLoading"
@click="submitForm"
:title="t('action.save')"
/>
<!-- 按钮关闭 -->
<XButton
:loading="dialogLoading"
@click="dialogVisible = false"
:title="t('dialog.close')"
/>
</template>
</XModal>
<!-- 导入流程 -->
<XModal v-model="importDialogVisible" width="400" title="导入流程">
<div>
<el-upload
ref="uploadRef"
:action="importUrl"
:headers="uploadHeaders"
:drag="true"
:limit="1"
:multiple="true"
:show-file-list="true"
:disabled="uploadDisabled"
:on-exceed="handleExceed"
:on-success="handleFileSuccess"
:on-error="excelUploadError"
:auto-upload="false"
accept=".bpmn, .xml"
name="bpmnFile"
:data="importForm"
</el-form-item>
<el-form-item label="流程分类" prop="category">
<el-select
v-model="queryParams.category"
placeholder="请选择流程分类"
clearable
class="!w-240px"
>
<Icon class="el-icon--upload" icon="ep:upload-filled" />
<div class="el-upload__text"> 将文件拖到此处 <em>点击上传</em> </div>
<template #tip>
<div class="el-upload__tip" style="color: red">
提示仅允许导入bpmxml格式文件
</div>
<div>
<el-form
ref="importFormRef"
:model="importForm"
:rules="rules"
label-width="120px"
status-icon
>
<el-form-item label="流程标识" prop="key">
<el-input
v-model="importForm.key"
placeholder="请输入流标标识"
style="width: 250px"
/>
</el-form-item>
<el-form-item label="流程名称" prop="name">
<el-input v-model="importForm.name" placeholder="请输入流程名称" clearable />
</el-form-item>
<el-form-item label="流程描述" prop="description">
<el-input type="textarea" v-model="importForm.description" clearable />
</el-form-item>
</el-form>
</div>
</template>
</el-upload>
</div>
<template #footer>
<!-- 按钮保存 -->
<XButton
type="warning"
preIcon="ep:upload-filled"
:title="t('action.save')"
@click="submitFileForm"
/>
<XButton title="取 消" @click="uploadClose" />
</template>
</XModal>
<!-- 表单详情的弹窗 -->
<XModal v-model="formDetailVisible" width="800" title="表单详情" :show-footer="false">
<form-create
:rule="formDetailPreview.rule"
:option="formDetailPreview.option"
v-if="formDetailVisible"
/>
</XModal>
<!-- 流程模型图的预览 -->
<XModal title="流程图" v-model="showBpmnOpen" width="80%" height="90%">
<my-process-viewer
key="designer"
v-model="bpmnXML"
:value="bpmnXML"
v-bind="bpmnControlForm"
:prefix="bpmnControlForm.prefix"
/>
</XModal>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['bpm:model:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新建流程
</el-button>
<el-button type="success" plain @click="openImportForm" v-hasPermi="['bpm:model:import']">
<Icon icon="ep:upload" class="mr-5px" /> 导入流程
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="流程标识" align="center" prop="key" width="200" />
<el-table-column label="流程名称" align="center" prop="name" width="200">
<template #default="scope">
<el-button type="text" @click="handleBpmnDetail(scope.row)">
<span>{{ scope.row.name }}</span>
</el-button>
</template>
</el-table-column>
<el-table-column label="流程分类" align="center" prop="category" width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" />
</template>
</el-table-column>
<el-table-column label="表单信息" align="center" prop="formType" width="200">
<template #default="scope">
<el-button
v-if="scope.row.formType === 10"
type="text"
@click="handleFormDetail(scope.row)"
>
<span>{{ scope.row.formName }}</span>
</el-button>
<el-button
v-else-if="scope.row.formType === 20"
type="text"
@click="handleFormDetail(scope.row)"
>
<span>{{ scope.row.formCustomCreatePath }}</span>
</el-button>
<label v-else>暂无表单</label>
</template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="最新部署的流程定义" align="center">
<el-table-column
label="流程版本"
align="center"
prop="processDefinition.version"
width="100"
>
<template #default="scope">
<el-tag v-if="scope.row.processDefinition">
v{{ scope.row.processDefinition.version }}
</el-tag>
<el-tag v-else type="warning">未部署</el-tag>
</template>
</el-table-column>
<el-table-column
label="激活状态"
align="center"
prop="processDefinition.version"
width="85"
>
<template #default="scope">
<el-switch
v-if="scope.row.processDefinition"
v-model="scope.row.processDefinition.suspensionState"
:active-value="1"
:inactive-value="2"
@change="handleChangeState(scope.row)"
/>
</template>
</el-table-column>
<el-table-column label="部署时间" align="center" prop="deploymentTime" width="180">
<template #default="scope">
<span v-if="scope.row.processDefinition">
{{ formatDate(scope.row.processDefinition.deploymentTime) }}
</span>
</template>
</el-table-column>
</el-table-column>
<el-table-column label="操作" align="center" width="240" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['bpm:model:update']"
>
修改流程
</el-button>
<el-button
link
type="primary"
@click="handleDesign(scope.row)"
v-hasPermi="['bpm:model:update']"
>
设计流程
</el-button>
<el-button
link
type="primary"
@click="handleAssignRule(scope.row)"
v-hasPermi="['bpm:task-assign-rule:query']"
>
分配规则
</el-button>
<el-button
link
type="primary"
@click="handleDeploy(scope.row)"
v-hasPermi="['bpm:model:deploy']"
>
发布流程
</el-button>
<el-button
link
type="primary"
v-hasPermi="['bpm:process-definition:query']"
@click="handleDefinitionList(scope.row)"
>
流程定义
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['bpm:model: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>
<!-- 表单弹窗添加/修改流程 -->
<ModelForm ref="formRef" @success="getList" />
<!-- 表单弹窗导入流程 -->
<ModelImportForm ref="importFormRef" @success="getList" />
<!-- 弹窗表单详情 -->
<Dialog title="表单详情" v-model="formDetailVisible" width="800">
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
</Dialog>
<!-- 弹窗流程模型图的预览 -->
<Dialog title="流程图" v-model="bpmnDetailVisible" width="800">
<my-process-viewer
key="designer"
v-model="bpmnXML"
:value="bpmnXML"
v-bind="bpmnControlForm"
:prefix="bpmnControlForm.prefix"
/>
</Dialog>
</template>
<script setup lang="ts">
// import
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import { FormInstance, UploadInstance } from 'element-plus'
// import
import { getAccessToken, getTenantId } from '@/utils/auth'
import * as FormApi from '@/api/bpm/form'
<script setup lang="ts" name="Form">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter, formatDate } from '@/utils/formatTime'
import * as ModelApi from '@/api/bpm/model'
import { allSchemas, rules } from './model.data'
import * as FormApi from '@/api/bpm/form'
import ModelForm from './ModelForm.vue'
import ModelImportForm from '@/views/bpm/model/ModelImportForm.vue'
import { setConfAndFields2 } from '@/utils/formCreate'
const { t } = useI18n() //
const message = useMessage() //
const router = useRouter() //
const { t } = useI18n() //
const { push } = useRouter() //
const showBpmnOpen = ref(false)
const bpmnXML = ref(null)
const bpmnControlForm = ref({
prefix: 'flowable'
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
key: undefined,
name: undefined,
category: undefined
})
// ========== ==========
const [registerTable, { reload }] = useXTable({
allSchemas: allSchemas,
getListApi: ModelApi.getModelPageApi
})
const forms = ref() //
const queryFormRef = ref() //
//
/** 查询参数列表 */
const getList = async () => {
loading.value = true
try {
const data = await ModelApi.getModelPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 添加/修改操作 */
const importFormRef = ref()
const openImportForm = () => {
importFormRef.value.open()
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await ModelApi.deleteModel(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 更新状态操作 */
const handleChangeState = async (row) => {
const state = row.processDefinition.suspensionState
try {
//
const id = row.id
const statusState = state === 1 ? '激活' : '挂起'
const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
await message.confirm(content)
//
await ModelApi.updateModelState(id, state)
//
await getList()
} catch {
//
row.processDefinition.suspensionState = state === 1 ? 2 : 1
}
}
/** 设计流程 */
const handleDesign = (row) => {
console.log(row, '设计流程')
router.push({
push({
name: 'modelEditor',
query: {
modelId: row.id
@ -355,9 +326,32 @@ const handleDesign = (row) => {
})
}
//
/** 发布流程 */
const handleDeploy = async (row) => {
try {
//
await message.confirm('是否部署该流程!!')
//
await ModelApi.deployModel(row.id)
message.success(t('部署成功'))
//
await getList()
} catch {}
}
/** 点击任务分配按钮 */
const handleAssignRule = (row) => {
push({
name: 'BpmTaskAssignRuleList',
query: {
modelId: row.id
}
})
}
/** 跳转到指定流程定义列表 */
const handleDefinitionList = (row) => {
router.push({
push({
name: 'BpmProcessDefinitionList',
query: {
key: row.key
@ -365,7 +359,7 @@ const handleDefinitionList = (row) => {
})
}
//
/** 流程表单的详情按钮操作 */
const formDetailVisible = ref(false)
const formDetailPreview = ref({
rule: [],
@ -374,222 +368,31 @@ const formDetailPreview = ref({
const handleFormDetail = async (row) => {
if (row.formType == 10) {
//
const data = await FormApi.getFormApi(row.formId)
const data = await FormApi.getForm(row.formId)
setConfAndFields2(formDetailPreview, data.conf, data.fields)
//
formDetailVisible.value = true
} else {
await router.push({
await push({
path: row.formCustomCreatePath
})
}
}
//
const handleBpmnDetail = (row) => {
// TODO
console.log(row)
ModelApi.getModelApi(row).then((response) => {
console.log(response, 'response')
bpmnXML.value = response.bpmnXml
//
showBpmnOpen.value = true
})
// message.success(' 2 ')
}
//
const handleAssignRule = (row) => {
router.push({
name: 'BpmTaskAssignRuleList',
query: {
modelId: row.id
}
})
}
// ========== / ==========
const dialogVisible = ref(false)
const dialogTitle = ref('新建模型')
const dialogLoading = ref(false)
const saveForm = ref()
const saveFormRef = ref<FormInstance>()
//
const setDialogTile = async (type: string) => {
dialogTitle.value = t('action.' + type)
dialogVisible.value = true
}
//
const handleCreate = async () => {
resetForm()
await setDialogTile('create')
}
//
const handleUpdate = async (rowId: number) => {
resetForm()
await setDialogTile('edit')
//
saveForm.value = await ModelApi.getModelApi(rowId)
if (saveForm.value.category == null) {
saveForm.value.category = 1
} else {
saveForm.value.category = Number(saveForm.value.category)
}
}
//
const submitForm = async () => {
//
const elForm = unref(saveFormRef)
if (!elForm) return
const valid = await elForm.validate()
if (!valid) return
//
dialogLoading.value = true
try {
const data = saveForm.value as ModelApi.ModelVO
if (!data.id) {
await ModelApi.createModelApi(data)
message.success(t('common.createSuccess'))
} else {
await ModelApi.updateModelApi(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
} finally {
//
await reload()
dialogLoading.value = false
}
}
//
const resetForm = () => {
saveForm.value = {
formType: 10,
name: '',
courseSort: '',
description: '',
formId: '',
formCustomCreatePath: '',
formCustomViewPath: ''
}
saveFormRef.value?.resetFields()
}
// ========== / / ==========
//
const handleDelete = (rowId) => {
message.delConfirm('是否删除该流程!!').then(async () => {
await ModelApi.deleteModelApi(rowId)
message.success(t('common.delSuccess'))
//
reload()
})
}
//
const handleChangeState = (row) => {
const id = row.id
const state = row.processDefinition.suspensionState
const statusState = state === 1 ? '激活' : '挂起'
const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
message
.confirm(content)
.then(async () => {
await ModelApi.updateModelStateApi(id, state)
message.success(t('部署成功'))
//
reload()
})
.catch(() => {
//
row.processDefinition.suspensionState = state === 1 ? 2 : 1
})
}
//
const handleDeploy = (row) => {
message.confirm('是否部署该流程!!').then(async () => {
await ModelApi.deployModelApi(row.id)
message.success(t('部署成功'))
//
reload()
})
}
// ========== ==========
const uploadRef = ref<UploadInstance>()
let importUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/bpm/model/import'
const uploadHeaders = ref()
const importDialogVisible = ref(false)
const uploadDisabled = ref(false)
const importFormRef = ref<FormInstance>()
const importForm = ref({
key: '',
name: '',
description: ''
/** 流程图的详情按钮操作 */
const bpmnDetailVisible = ref(false)
const bpmnXML = ref(null)
const bpmnControlForm = ref({
prefix: 'flowable'
})
//
const handleImport = () => {
importDialogVisible.value = true
}
//
const handleExceed = (): void => {
message.error('最多只能上传一个文件!')
}
//
const excelUploadError = (): void => {
message.error('导入流程失败,请您重新上传!')
const handleBpmnDetail = async (row) => {
const data = await ModelApi.getModel(row.id)
bpmnXML.value = data.bpmnXml || ''
bpmnDetailVisible.value = true
}
//
const submitFileForm = () => {
uploadHeaders.value = {
Authorization: 'Bearer ' + getAccessToken(),
'tenant-id': getTenantId()
}
uploadDisabled.value = true
uploadRef.value!.submit()
}
//
const handleFileSuccess = async (response: any): Promise<void> => {
if (response.code !== 0) {
message.error(response.msg)
return
}
//
uploadClose()
//
message.success('导入流程成功!请点击【设计流程】按钮,进行编辑保存后,才可以进行【发布流程】')
await reload()
}
//
const uploadClose = () => {
//
importDialogVisible.value = false
//
uploadDisabled.value = false
uploadRef.value!.clearFiles()
//
importForm.value = {
key: '',
name: '',
description: ''
}
importFormRef.value?.resetFields()
}
// ========== ==========
/** 初始化 **/
onMounted(() => {
//
FormApi.getSimpleFormsApi().then((data) => {
forms.value = data
})
getList()
})
</script>

View File

@ -1,106 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
key: [required],
name: [required],
category: [required],
formType: [required],
formId: [required],
formCustomCreatePath: [required],
formCustomViewPath: [required]
})
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'key',
primaryType: null,
action: true,
actionWidth: '540px',
columns: [
{
title: '流程标识',
field: 'key',
isSearch: true,
table: {
width: 120
}
},
{
title: '流程名称',
field: 'name',
isSearch: true,
table: {
width: 120,
slots: {
default: 'name_default'
}
}
},
{
title: '流程分类',
field: 'category',
dictType: DICT_TYPE.BPM_MODEL_CATEGORY,
dictClass: 'number',
isSearch: true,
table: {
slots: {
default: 'category_default'
}
}
},
{
title: '表单信息',
field: 'formId',
table: {
width: 180,
slots: {
default: 'formId_default'
}
}
},
{
title: '最新部署的流程定义',
field: 'processDefinition',
isForm: false,
table: {
children: [
{
title: '流程版本',
field: 'version',
slots: {
default: 'version_default'
},
width: 80
},
{
title: '激活状态',
field: 'status',
slots: {
default: 'status_default'
},
width: 80
},
{
title: '部署时间',
field: 'processDefinition.deploymentTime',
formatter: 'formatDate',
width: 180
}
]
}
},
{
title: t('common.createTime'),
field: 'createTime',
isForm: false,
formatter: 'formatDate',
table: {
width: 180
}
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -1,204 +0,0 @@
<template>
<div class="app-container">
<!-- 流程设计器负责绘制流程等 -->
<!-- <myProcessDesigner -->
<my-process-designer
:key="`designer-${reloadIndex}`"
v-if="xmlString !== undefined"
v-model="xmlString"
:value="xmlString"
v-bind="controlForm"
keyboard
ref="processDesigner"
@init-finished="initModeler"
:additionalModel="controlForm.additionalModel"
@save="save"
/>
<!-- 流程属性器负责编辑每个流程节点的属性 -->
<!-- <MyProcessPalette -->
<my-properties-panel
:key="`penal-${reloadIndex}`"
:bpmnModeler="modeler"
:prefix="controlForm.prefix"
class="process-panel"
:model="model"
/>
</div>
</template>
<script setup lang="ts">
// import { translations } from '@/components/bpmnProcessDesigner/src/translations'
//
import CustomContentPadProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/content-pad'
//
import CustomPaletteProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/palette'
// import xmlObj2json from "./utils/xml2json";
// import myProcessDesigner from '@/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue'
// import MyProcessPalette from '@/components/bpmnProcessDesigner/package/palette/ProcessPalette.vue'
import { createModelApi, getModelApi, updateModelApi, ModelVO } from '@/api/bpm/model'
const router = useRouter()
const message = useMessage()
//
// import MyProcessPanel from "../package/process-panel/ProcessPanel";
const xmlString = ref(undefined) // BPMN XML
const modeler = ref(null)
const reloadIndex = ref(0)
// const controlDrawerVisible = ref(false)
// const translationsSelf = translations
const controlForm = ref({
simulation: true,
labelEditing: false,
labelVisible: false,
prefix: 'flowable',
headerButtonSize: 'mini',
additionalModel: [CustomContentPadProvider, CustomPaletteProvider]
})
// const addis = ref({
// CustomContentPadProvider,
// CustomPaletteProvider
// })
//
const model = ref<ModelVO>()
onMounted(() => {
// modelId
const modelId = router.currentRoute.value.query && router.currentRoute.value.query.modelId
console.log(modelId, 'modelId')
if (modelId) {
// let data = '4b4909d8-97e7-11ec-8e20-862bc1a4a054'
getModelApi(modelId as unknown as number).then((data) => {
console.log(data, 'response')
xmlString.value = data.bpmnXml
model.value = {
...data,
bpmnXml: undefined // bpmnXml
}
// this.controlForm.processId = data.key
// xmlString.value =
// '<?xml version="1.0" encoding="UTF-8"?>\n<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="diagram_Process_1645980650311" targetNamespace="http://activiti.org/bpmn"><bpmn2:process id="flowable_01" name="flowable" isExecutable="true"><bpmn2:startEvent id="Event_1iruxim"><bpmn2:outgoing>Flow_0804gmo</bpmn2:outgoing></bpmn2:startEvent><bpmn2:userTask id="task01" name="task01"><bpmn2:incoming>Flow_0804gmo</bpmn2:incoming><bpmn2:outgoing>Flow_0cx479x</bpmn2:outgoing></bpmn2:userTask><bpmn2:sequenceFlow id="Flow_0804gmo" sourceRef="Event_1iruxim" targetRef="task01" /><bpmn2:endEvent id="Event_1mdsccz"><bpmn2:incoming>Flow_0cx479x</bpmn2:incoming></bpmn2:endEvent><bpmn2:sequenceFlow id="Flow_0cx479x" sourceRef="task01" targetRef="Event_1mdsccz" /></bpmn2:process><bpmndi:BPMNDiagram id="BPMNDiagram_1"><bpmndi:BPMNPlane id="flowable_01_di" bpmnElement="flowable_01"><bpmndi:BPMNEdge id="Flow_0cx479x_di" bpmnElement="Flow_0cx479x"><di:waypoint x="440" y="350" /><di:waypoint x="492" y="350" /></bpmndi:BPMNEdge><bpmndi:BPMNEdge id="Flow_0804gmo_di" bpmnElement="Flow_0804gmo"><di:waypoint x="288" y="350" /><di:waypoint x="340" y="350" /></bpmndi:BPMNEdge><bpmndi:BPMNShape id="Event_1iruxim_di" bpmnElement="Event_1iruxim"><dc:Bounds x="252" y="332" width="36" height="36" /></bpmndi:BPMNShape><bpmndi:BPMNShape id="task01_di" bpmnElement="task01"><dc:Bounds x="340" y="310" width="100" height="80" /></bpmndi:BPMNShape><bpmndi:BPMNShape id="Event_1mdsccz_di" bpmnElement="Event_1mdsccz"><dc:Bounds x="492" y="332" width="36" height="36" /></bpmndi:BPMNShape></bpmndi:BPMNPlane></bpmndi:BPMNDiagram></bpmn2:definitions>'
// model.value = {
// key: 'flowable_01',
// name: 'flowable',
// description: 'ooxx',
// category: '1',
// formType: 10,
// formId: 11,
// formCustomCreatePath: null,
// formCustomViewPath: null,
// id: '4b4909d8-97e7-11ec-8e20-862bc1a4a054',
// createTime: 1645978019795,
// bpmnXml: undefined // bpmnXml
// }
// console.log(modeler.value, 'modeler11111111')
})
}
})
const initModeler = (item) => {
setTimeout(() => {
modeler.value = item
console.log(item, 'initModeler方法modeler')
console.log(modeler.value, 'initModeler方法modeler')
// controlForm.value.prefix = '2222'
}, 10)
}
const save = (bpmnXml) => {
const data: ModelVO = {
...(model.value ?? ({} as ModelVO)),
bpmnXml: bpmnXml // bpmnXml
}
console.log(data, 'data')
//
if (data.id) {
updateModelApi(data).then((response) => {
console.log(response, 'response')
message.success('修改成功')
//
close()
})
return
}
//
createModelApi(data).then((response) => {
console.log(response, 'response1')
message.success('保存成功')
//
close()
})
}
/** 关闭按钮 */
const close = () => {
router.push({ path: '/bpm/manager/model' })
}
</script>
<style lang="scss">
//body {
// overflow: hidden;
// margin: 0;
// box-sizing: border-box;
//}
//.app {
// width: 100%;
// height: 100%;
// box-sizing: border-box;
// display: inline-grid;
// grid-template-columns: 100px auto max-content;
//}
.demo-control-bar {
position: fixed;
right: 8px;
bottom: 8px;
z-index: 1;
.open-control-dialog {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
font-size: 32px;
background: rgba(64, 158, 255, 1);
color: #ffffff;
cursor: pointer;
}
}
// TODO faq
//.info-tip {
// position: fixed;
// top: 40px;
// right: 500px;
// z-index: 10;
// color: #999999;
//}
.control-form {
.el-radio {
width: 100%;
line-height: 32px;
}
}
.element-overlays {
box-sizing: border-box;
padding: 8px;
background: rgba(0, 0, 0, 0.6);
border-radius: 4px;
color: #fafafa;
}
.my-process-designer {
height: calc(100vh - 84px);
}
.process-panel__container {
position: absolute;
right: 0;
top: 55px;
height: calc(100vh - 84px);
}
</style>

View File

@ -72,7 +72,7 @@ const [registerTable] = useXTable({
params: {
suspensionState: 1
},
getListApi: DefinitionApi.getProcessDefinitionListApi,
getListApi: DefinitionApi.getProcessDefinitionList,
isList: true
})
@ -99,7 +99,7 @@ const handleSelect = async (row) => {
setConfAndFields2(detailForm, row.formConf, row.formFields)
//
DefinitionApi.getProcessDefinitionBpmnXMLApi(row.id).then((response) => {
DefinitionApi.getProcessDefinitionBpmnXML(row.id).then((response) => {
bpmnXML.value = response
})
//

View File

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

View File

@ -0,0 +1,247 @@
<template>
<Dialog title="修改任务规则" v-model="modelVisible" width="600">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
<el-form-item label="任务名称" prop="taskDefinitionName">
<el-input v-model="formData.taskDefinitionName" placeholder="请输入流标标识" disabled />
</el-form-item>
<el-form-item label="任务标识" prop="taskDefinitionKey">
<el-input v-model="formData.taskDefinitionKey" placeholder="请输入任务标识" disabled />
</el-form-item>
<el-form-item label="规则类型" prop="type">
<el-select v-model="formData.type" clearable style="width: 100%">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_RULE_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item v-if="formData.type === 10" label="指定角色" prop="roleIds">
<el-select v-model="formData.roleIds" multiple clearable style="width: 100%">
<el-option
v-for="item in roleOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item
label="指定部门"
prop="deptIds"
span="24"
v-if="formData.type === 20 || formData.type === 21"
>
<el-tree-select
ref="treeRef"
v-model="formData.deptIds"
node-key="id"
show-checkbox
:props="defaultProps"
:data="deptTreeOptions"
empty-text="加载中,请稍后"
multiple
/>
</el-form-item>
<el-form-item label="指定岗位" prop="postIds" span="24" v-if="formData.type === 22">
<el-select v-model="formData.postIds" multiple clearable style="width: 100%">
<el-option
v-for="item in postOptions"
:key="parseInt(item.id)"
:label="item.name"
:value="parseInt(item.id)"
/>
</el-select>
</el-form-item>
<el-form-item
label="指定用户"
prop="userIds"
span="24"
v-if="formData.type === 30 || formData.type === 31 || formData.type === 32"
>
<el-select v-model="formData.userIds" multiple clearable style="width: 100%">
<el-option
v-for="item in userOptions"
:key="parseInt(item.id)"
:label="item.nickname"
:value="parseInt(item.id)"
/>
</el-select>
</el-form-item>
<el-form-item label="指定用户组" prop="userGroupIds" v-if="formData.type === 40">
<el-select v-model="formData.userGroupIds" multiple clearable style="width: 100%">
<el-option
v-for="item in userGroupOptions"
:key="parseInt(item.id)"
:label="item.name"
:value="parseInt(item.id)"
/>
</el-select>
</el-form-item>
<el-form-item label="指定脚本" prop="scripts" v-if="formData.type === 50">
<el-select v-model="formData.scripts" multiple clearable style="width: 100%">
<el-option
v-for="dict in taskAssignScriptDictDatas"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-form>
<!-- 操作按钮 -->
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { handleTree, defaultProps } from '@/utils/tree'
import * as TaskAssignRuleApi from '@/api/bpm/taskAssignRule'
import * as RoleApi from '@/api/system/role'
import * as DeptApi from '@/api/system/dept'
import * as PostApi from '@/api/system/post'
import * as UserApi from '@/api/system/user'
import * as UserGroupApi from '@/api/bpm/userGroup'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const formLoading = ref(false) // 12
const formData = ref({
type: Number(undefined),
modelId: '',
options: [],
roleIds: [],
deptIds: [],
postIds: [],
userIds: [],
userGroupIds: [],
scripts: []
})
const formRules = reactive({
type: [{ required: true, message: '规则类型不能为空', trigger: 'change' }],
roleIds: [{ required: true, message: '指定角色不能为空', trigger: 'change' }],
deptIds: [{ required: true, message: '指定部门不能为空', trigger: 'change' }],
postIds: [{ required: true, message: '指定岗位不能为空', trigger: 'change' }],
userIds: [{ required: true, message: '指定用户不能为空', trigger: 'change' }],
userGroupIds: [{ required: true, message: '指定用户组不能为空', trigger: 'change' }],
scripts: [{ required: true, message: '指定脚本不能为空', trigger: 'change' }]
})
const formRef = ref() // Ref
const roleOptions = ref<RoleApi.RoleVO[]>([]) //
const deptOptions = ref<DeptApi.DeptVO[]>([]) //
const deptTreeOptions = ref() //
const postOptions = ref<PostApi.PostVO[]>([]) //
const userOptions = ref<UserApi.UserVO[]>([]) //
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) //
const taskAssignScriptDictDatas = getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_SCRIPT)
/** 打开弹窗 */
const open = async (modelId: string, row: TaskAssignRuleApi.TaskAssignVO) => {
// 1.
resetForm()
// 2.
formData.value = {
...row,
modelId: modelId,
options: [],
roleIds: [],
deptIds: [],
postIds: [],
userIds: [],
userGroupIds: [],
scripts: []
}
// options roleIds
if (row.type === 10) {
formData.value.roleIds.push(...row.options)
} else if (row.type === 20 || row.type === 21) {
formData.value.deptIds.push(...row.options)
} else if (row.type === 22) {
formData.value.postIds.push(...row.options)
} else if (row.type === 30 || row.type === 31 || row.type === 32) {
formData.value.userIds.push(...row.options)
} else if (row.type === 40) {
formData.value.userGroupIds.push(...row.options)
} else if (row.type === 50) {
formData.value.scripts.push(...row.options)
}
//
modelVisible.value = true
//
roleOptions.value = await RoleApi.getSimpleRoleList()
//
deptOptions.value = await DeptApi.getSimpleDeptList()
deptTreeOptions.value = handleTree(deptOptions.value, 'id')
//
postOptions.value = await PostApi.getSimplePostList()
//
userOptions.value = await UserApi.getSimpleUserList()
//
userGroupOptions.value = await UserGroupApi.getSimpleUserGroupList()
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
const form = {
...formData.value,
taskDefinitionName: undefined
}
// roleIds options
if (form.type === 10) {
form.options = form.roleIds
} else if (form.type === 20 || form.type === 21) {
form.options = form.deptIds
} else if (form.type === 22) {
form.options = form.postIds
} else if (form.type === 30 || form.type === 31 || form.type === 32) {
form.options = form.userIds
} else if (form.type === 40) {
form.options = form.userGroupIds
} else if (form.type === 50) {
form.options = form.scripts
}
form.roleIds = undefined
form.deptIds = undefined
form.postIds = undefined
form.userIds = undefined
form.userGroupIds = undefined
form.scripts = undefined
//
formLoading.value = true
try {
const data = form as unknown as TaskAssignRuleApi.TaskAssignVO
if (!data.id) {
await TaskAssignRuleApi.createTaskAssignRule(data)
message.success(t('common.createSuccess'))
} else {
await TaskAssignRuleApi.updateTaskAssignRule(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formRef.value?.resetFields()
}
</script>

View File

@ -1,186 +1,73 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable" ref="xGrid">
<template #options_default="{ row }">
<span :key="option" v-for="option in row.options">
<el-tag>
{{ getAssignRuleOptionName(row.type, option) }}
<el-table v-loading="loading" :data="list">
<el-table-column label="任务名" align="center" prop="taskDefinitionName" />
<el-table-column label="任务标识" align="center" prop="taskDefinitionKey" />
<el-table-column label="规则类型" align="center" prop="type">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_TASK_ASSIGN_RULE_TYPE" :value="scope.row.type" />
</template>
</el-table-column>
<el-table-column label="规则范围" align="center" prop="options">
<template #default="scope">
<el-tag class="mr-5px" :key="option" v-for="option in scope.row.options">
{{ getAssignRuleOptionName(scope.row.type, option) }}
</el-tag>
&nbsp;
</span>
</template>
<!-- 操作 -->
<template #actionbtns_default="{ row }" v-if="modelId">
<!-- 操作修改 -->
<XTextButton
preIcon="ep:edit"
:title="t('action.edit')"
v-hasPermi="['bpm:task-assign-rule:update']"
@click="handleUpdate(row)"
/>
</template>
</XTable>
<!-- 添加/修改弹窗 -->
<XModal v-model="dialogVisible" title="修改任务规则" width="800" height="35%">
<el-form ref="formRef" :model="formData" :rules="rules" label-width="80px">
<el-form-item label="任务名称" prop="taskDefinitionName">
<el-input v-model="formData.taskDefinitionName" placeholder="请输入流标标识" disabled />
</el-form-item>
<el-form-item label="任务标识" prop="taskDefinitionKey">
<el-input v-model="formData.taskDefinitionKey" placeholder="请输入任务标识" disabled />
</el-form-item>
<el-form-item label="规则类型" prop="type">
<el-select v-model="formData.type" clearable style="width: 100%">
<el-option
v-for="dict in getDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_RULE_TYPE)"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
</el-select>
</el-form-item>
<el-form-item v-if="formData.type === 10" label="指定角色" prop="roleIds">
<el-select v-model="formData.roleIds" multiple clearable style="width: 100%">
<el-option
v-for="item in roleOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item
label="指定部门"
prop="deptIds"
span="24"
v-if="formData.type === 20 || formData.type === 21"
>
<el-tree-select
ref="treeRef"
v-model="formData.deptIds"
node-key="id"
show-checkbox
:props="defaultProps"
:data="deptTreeOptions"
empty-text="加载中,请稍后"
multiple
/>
</el-form-item>
<el-form-item label="指定岗位" prop="postIds" span="24" v-if="formData.type === 22">
<el-select v-model="formData.postIds" multiple clearable style="width: 100%">
<el-option
v-for="item in postOptions"
:key="parseInt(item.id)"
:label="item.name"
:value="parseInt(item.id)"
/>
</el-select>
</el-form-item>
<el-form-item
label="指定用户"
prop="userIds"
span="24"
v-if="formData.type === 30 || formData.type === 31 || formData.type === 32"
>
<el-select v-model="formData.userIds" multiple clearable style="width: 100%">
<el-option
v-for="item in userOptions"
:key="parseInt(item.id)"
:label="item.nickname"
:value="parseInt(item.id)"
/>
</el-select>
</el-form-item>
<el-form-item label="指定用户组" prop="userGroupIds" v-if="formData.type === 40">
<el-select v-model="formData.userGroupIds" multiple clearable style="width: 100%">
<el-option
v-for="item in userGroupOptions"
:key="parseInt(item.id)"
:label="item.name"
:value="parseInt(item.id)"
/>
</el-select>
</el-form-item>
<el-form-item label="指定脚本" prop="scripts" v-if="formData.type === 50">
<el-select v-model="formData.scripts" multiple clearable style="width: 100%">
<el-option
v-for="dict in taskAssignScriptDictDatas"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
</el-select>
</el-form-item>
</el-form>
<!-- 操作按钮 -->
<template #footer>
<!-- 按钮保存 -->
<XButton
type="primary"
:title="t('action.save')"
:loading="actionLoading"
@click="submitForm"
/>
<!-- 按钮关闭 -->
<XButton
:loading="actionLoading"
:title="t('dialog.close')"
@click="dialogVisible = false"
/>
</template>
</XModal>
</template>
</el-table-column>
<el-table-column v-if="queryParams.modelId" label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm(scope.row)"
v-hasPermi="['bpm:task-assign-rule:update']"
>
修改
</el-button>
</template>
</el-table-column>
</el-table>
</ContentWrap>
<!-- 添加/修改弹窗 -->
<TaskAssignRuleForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="TaskAssignRule">
// import
import { FormInstance } from 'element-plus'
// import
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as TaskAssignRuleApi from '@/api/bpm/taskAssignRule'
import { listSimpleRolesApi } from '@/api/system/role'
import { listSimplePostsApi } from '@/api/system/post'
import { getListSimpleUsersApi } from '@/api/system/user'
import { listSimpleUserGroupsApi } from '@/api/bpm/userGroup'
import { listSimpleDeptApi } from '@/api/system/dept'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import { handleTree, defaultProps } from '@/utils/tree'
import { allSchemas, rules, idShowActionClick } from './taskAssignRule.data'
import * as RoleApi from '@/api/system/role'
import * as DeptApi from '@/api/system/dept'
import * as PostApi from '@/api/system/post'
import * as UserApi from '@/api/system/user'
import * as UserGroupApi from '@/api/bpm/userGroup'
import TaskAssignRuleForm from './TaskAssignRuleForm.vue'
const { query } = useRoute() //
const { t } = useI18n() //
const message = useMessage() //
const { query } = useRoute()
const xGrid = ref()
// ========== ==========
const roleOptions = ref() //
const deptOptions = ref() //
const deptTreeOptions = ref()
const postOptions = ref() //
const userOptions = ref() //
const userGroupOptions = ref() //
const taskAssignScriptDictDatas = getDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_SCRIPT)
// modelId
const modelId = query.modelId
// processDefinitionId
const processDefinitionId = query.processDefinitionId
let isShow = idShowActionClick(modelId)
//
const loading = ref(true) //
const list = ref([]) //
const queryParams = reactive({
modelId: modelId,
processDefinitionId: processDefinitionId
})
const [registerTable, { reload }] = useXTable({
allSchemas: allSchemas,
params: queryParams,
getListApi: TaskAssignRuleApi.getTaskAssignRuleList,
isList: true
modelId: query.modelId, // modelId
processDefinitionId: query.processDefinitionId // processDefinitionId
})
const roleOptions = ref<RoleApi.RoleVO[]>([]) //
const deptOptions = ref<DeptApi.DeptVO[]>([]) //
const postOptions = ref<PostApi.PostVO[]>([]) //
const userOptions = ref<UserApi.UserVO[]>([]) //
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) //
const taskAssignScriptDictDatas = getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_SCRIPT)
//
/** 查询参数列表 */
const getList = async () => {
loading.value = true
try {
list.value = await TaskAssignRuleApi.getTaskAssignRuleList(queryParams)
} finally {
loading.value = false
}
}
/** 翻译规则范围 */
// TODO ts
const getAssignRuleOptionName = (type, option) => {
if (type === 10) {
for (const roleOption of roleOptions.value) {
@ -223,136 +110,24 @@ const getAssignRuleOptionName = (type, option) => {
return '未知(' + option + ')'
}
// ========== ==========
//
const actionLoading = ref(false) //
const dialogVisible = ref(false) //
const formRef = ref<FormInstance>()
const formData = ref() //
//
const submitForm = async () => {
//
const elForm = unref(formRef)
if (!elForm) return
const valid = await elForm.validate()
if (!valid) return
//
let form = {
...formData.value,
taskDefinitionName: undefined
}
// roleIds options
if (form.type === 10) {
form.options = form.roleIds
} else if (form.type === 20 || form.type === 21) {
form.options = form.deptIds
} else if (form.type === 22) {
form.options = form.postIds
} else if (form.type === 30 || form.type === 31 || form.type === 32) {
form.options = form.userIds
} else if (form.type === 40) {
form.options = form.userGroupIds
} else if (form.type === 50) {
form.options = form.scripts
}
form.roleIds = undefined
form.deptIds = undefined
form.postIds = undefined
form.userIds = undefined
form.userGroupIds = undefined
form.scripts = undefined
//
actionLoading.value = true
//
try {
const data = form as TaskAssignRuleApi.TaskAssignVO
//
if (!data.id) {
await TaskAssignRuleApi.createTaskAssignRule(data)
message.success(t('common.createSuccess'))
//
} else {
await TaskAssignRuleApi.updateTaskAssignRule(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
} finally {
actionLoading.value = false
//
await reload()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (row: TaskAssignRuleApi.TaskAssignVO) => {
formRef.value.open(queryParams.modelId, row)
}
//
const handleUpdate = (row) => {
// 1.
formData.value = {}
// 2.
formData.value = {
...row,
modelId: modelId,
options: [],
roleIds: [],
deptIds: [],
postIds: [],
userIds: [],
userGroupIds: [],
scripts: []
}
// options roleIds
if (row.type === 10) {
formData.value.roleIds.push(...row.options)
} else if (row.type === 20 || row.type === 21) {
formData.value.deptIds.push(...row.options)
} else if (row.type === 22) {
formData.value.postIds.push(...row.options)
} else if (row.type === 30 || row.type === 31 || row.type === 32) {
formData.value.userIds.push(...row.options)
} else if (row.type === 40) {
formData.value.userGroupIds.push(...row.options)
} else if (row.type === 50) {
formData.value.scripts.push(...row.options)
}
//
dialogVisible.value = true
actionLoading.value = false
}
// ========== ==========
onMounted(() => {
/** 初始化 */
onMounted(async () => {
await getList()
//
roleOptions.value = []
listSimpleRolesApi().then((data) => {
roleOptions.value.push(...data)
})
roleOptions.value = await RoleApi.getSimpleRoleList()
//
deptOptions.value = []
deptTreeOptions.value = []
listSimpleDeptApi().then((data) => {
deptOptions.value.push(...data)
deptTreeOptions.value.push(...handleTree(data, 'id'))
})
deptOptions.value = await DeptApi.getSimpleDeptList()
//
postOptions.value = []
listSimplePostsApi().then((data) => {
postOptions.value.push(...data)
})
postOptions.value = await PostApi.getSimplePostList()
//
userOptions.value = []
getListSimpleUsersApi().then((data) => {
userOptions.value.push(...data)
})
userOptions.value = await UserApi.getSimpleUserList()
//
userGroupOptions.value = []
listSimpleUserGroupsApi().then((data) => {
userGroupOptions.value.push(...data)
})
if (!isShow) {
setTimeout(() => {
xGrid.value.Ref.hideColumn('actionbtns')
}, 100)
}
userGroupOptions.value = await UserGroupApi.getSimpleUserGroupList()
})
</script>

View File

@ -1,54 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
// 表单校验
export const rules = reactive({
type: [{ required: true, message: '规则类型不能为空', trigger: 'change' }],
roleIds: [{ required: true, message: '指定角色不能为空', trigger: 'change' }],
deptIds: [{ required: true, message: '指定部门不能为空', trigger: 'change' }],
postIds: [{ required: true, message: '指定岗位不能为空', trigger: 'change' }],
userIds: [{ required: true, message: '指定用户不能为空', trigger: 'change' }],
userGroupIds: [{ required: true, message: '指定用户组不能为空', trigger: 'change' }],
scripts: [{ required: true, message: '指定脚本不能为空', trigger: 'change' }]
})
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: null,
action: true,
actionWidth: '200px',
columns: [
{
title: '任务名',
field: 'taskDefinitionName'
},
{
title: '任务标识',
field: 'taskDefinitionKey'
},
{
title: '规则类型',
field: 'type',
dictType: DICT_TYPE.BPM_TASK_ASSIGN_RULE_TYPE,
dictClass: 'number'
},
{
title: '规则范围',
field: 'options',
table: {
slots: {
default: 'options_default'
}
}
}
]
})
export const idShowActionClick = (modelId?: any) => {
if (modelId) {
return true
} else {
return false
}
}
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -0,0 +1,65 @@
<template>
<Dialog title="详情" v-model="modelVisible" :scroll="true" :max-height="500" width="800">
<el-descriptions border :column="1">
<el-descriptions-item label="日志主键" min-width="120">
{{ detailData.id }}
</el-descriptions-item>
<el-descriptions-item label="链路追踪">
{{ detailData.traceId }}
</el-descriptions-item>
<el-descriptions-item label="应用名">
{{ detailData.applicationName }}
</el-descriptions-item>
<el-descriptions-item label="用户信息">
{{ detailData.userId }}
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="detailData.userType" />
</el-descriptions-item>
<el-descriptions-item label="用户 IP">
{{ detailData.userIp }}
</el-descriptions-item>
<el-descriptions-item label="用户 UA">
{{ detailData.userAgent }}
</el-descriptions-item>
<el-descriptions-item label="请求信息">
{{ detailData.requestMethod }} {{ detailData.requestUrl }}
</el-descriptions-item>
<el-descriptions-item label="请求参数">
{{ detailData.requestParams }}
</el-descriptions-item>
<el-descriptions-item label="请求时间">
{{ formatDate(detailData.beginTime) }} ~ {{ formatDate(detailData.endTime) }}
</el-descriptions-item>
<el-descriptions-item label="请求耗时">{{ detailData.duration }} ms</el-descriptions-item>
<el-descriptions-item label="操作结果">
<div v-if="detailData.resultCode === 0">正常</div>
<div v-else-if="detailData.resultCode > 0"
>失败 | {{ detailData.resultCode }} | {{ detailData.resultMsg }}</div
>
</el-descriptions-item>
</el-descriptions>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE } from '@/utils/dict'
import { formatDate } from '@/utils/formatTime'
import * as ApiAccessLog from '@/api/infra/apiAccessLog'
const modelVisible = ref(false) //
const detailLoading = ref(false) //
const detailData = ref() //
/** 打开弹窗 */
const open = async (data: ApiAccessLog.ApiAccessLogVO) => {
modelVisible.value = true
//
detailLoading.value = true
try {
detailData.value = data
} finally {
detailLoading.value = false
}
}
defineExpose({ open }) // open
</script>

View File

@ -1,74 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: 'id',
primaryTitle: '日志编号',
action: true,
actionWidth: '80px',
columns: [
{
title: '链路追踪',
field: 'traceId',
isTable: false
},
{
title: '用户编号',
field: 'userId',
isSearch: true
},
{
title: '用户类型',
field: 'userType',
dictType: DICT_TYPE.USER_TYPE,
dictClass: 'number',
isSearch: true
},
{
title: '应用名',
field: 'applicationName',
isSearch: true
},
{
title: '请求方法名',
field: 'requestMethod'
},
{
title: '请求地址',
field: 'requestUrl',
isSearch: true
},
{
title: '请求时间',
field: 'beginTime',
formatter: 'formatDate',
search: {
show: true,
itemRender: {
name: 'XDataTimePicker'
}
}
},
{
title: '执行时长',
field: 'duration',
table: {
slots: {
default: 'duration_default'
}
}
},
{
title: '操作结果',
field: 'resultCode',
isSearch: true,
table: {
slots: {
default: 'resultCode_default'
}
}
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -1,62 +1,220 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<template #duration_default="{ row }">
<span>{{ row.duration + 'ms' }}</span>
</template>
<template #resultCode_default="{ row }">
<span>{{ row.resultCode === 0 ? '成功' : '失败(' + row.resultMsg + ')' }}</span>
</template>
<template #actionbtns_default="{ row }">
<!-- 操作详情 -->
<XTextButton
preIcon="ep:view"
:title="t('action.detail')"
v-hasPermi="['infra:api-access-log:query']"
@click="handleDetail(row)"
<content-wrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="用户编号" prop="userId">
<el-input
v-model="queryParams.userId"
placeholder="请输入用户编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</template>
</XTable>
</ContentWrap>
<XModal v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(详情) -->
<Descriptions :schema="allSchemas.detailSchema" :data="detailData">
<template #duration="{ row }">
<span>{{ row.duration + 'ms' }}</span>
</template>
<template #resultCode="{ row }">
<span>{{ row.resultCode === 0 ? '成功' : '失败(' + row.resultMsg + ')' }}</span>
</template>
</Descriptions>
<!-- 操作按钮 -->
<template #footer>
<XButton :title="t('dialog.close')" @click="dialogVisible = false" />
</template>
</XModal>
</el-form-item>
<el-form-item label="用户类型" prop="userType">
<el-select
v-model="queryParams.userType"
placeholder="请选择用户类型"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getDictOptions(DICT_TYPE.USER_TYPE)"
:key="parseInt(dict.value)"
:label="dict.label"
:value="parseInt(dict.value)"
/>
</el-select>
</el-form-item>
<el-form-item label="应用名" prop="applicationName">
<el-input
v-model="queryParams.applicationName"
placeholder="请输入应用名"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="请求时间" prop="beginTime">
<el-date-picker
v-model="queryParams.beginTime"
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 label="执行时长" prop="duration">
<el-input
v-model="queryParams.duration"
placeholder="请输入执行时长"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="结果码" prop="resultCode">
<el-input
v-model="queryParams.resultCode"
placeholder="请输入结果码"
clearable
@keyup.enter="handleQuery"
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="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['infra:api-error-log:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</content-wrap>
<!-- 列表 -->
<content-wrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="日志编号" align="center" prop="id" />
<el-table-column label="用户编号" align="center" prop="userId" />
<el-table-column label="用户类型" align="center" prop="userType">
<template #default="scope">
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" />
</template>
</el-table-column>
<el-table-column label="应用名" align="center" prop="applicationName" />
<el-table-column label="请求方法" align="center" prop="requestMethod" width="80" />
<el-table-column label="请求地址" align="center" prop="requestUrl" width="250" />
<el-table-column label="请求时间" align="center" prop="beginTime" width="180">
<template #default="scope">
<span>{{ formatDate(scope.row.beginTime) }}</span>
</template>
</el-table-column>
<el-table-column label="执行时长" align="center" prop="duration" width="180">
<template #default="scope">
<span>{{ scope.row.duration }} ms</span>
</template>
</el-table-column>
<el-table-column label="操作结果" align="center" prop="status">
<template #default="scope">
<span>{{
scope.row.resultCode === 0 ? '成功' : '失败(' + scope.row.resultMsg + ')'
}}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openDetail(scope.row)"
v-hasPermi="['infra:api-access-log:query']"
>
详细
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</content-wrap>
<!-- 表单弹窗详情 -->
<ApiAccessLogDetail ref="detailRef" />
</template>
<script setup lang="ts" name="ApiAccessLog">
import { allSchemas } from './apiAccessLog.data'
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
import download from '@/utils/download'
import { formatDate } from '@/utils/formatTime'
import * as ApiAccessLogApi from '@/api/infra/apiAccessLog'
import ApiAccessLogDetail from './ApiAccessLogDetail.vue'
const { t } = useI18n() //
const message = useMessage() //
//
const [registerTable] = useXTable({
allSchemas: allSchemas,
topActionSlots: false,
getListApi: ApiAccessLogApi.getApiAccessLogPageApi
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
userId: null,
userType: null,
applicationName: null,
requestUrl: null,
duration: null,
resultCode: null,
beginTime: []
})
// ========== ==========
const detailData = ref() // Ref
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const queryFormRef = ref() //
const exportLoading = ref(false) //
//
const handleDetail = (row: ApiAccessLogApi.ApiAccessLogVO) => {
//
detailData.value = row
dialogTitle.value = t('action.detail')
dialogVisible.value = true
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ApiAccessLogApi.getApiAccessLogPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 详情操作 */
const detailRef = ref()
const openDetail = (data: ApiAccessLogApi.ApiAccessLogVO) => {
detailRef.value.open(data)
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await ApiAccessLogApi.exportApiAccessLog(queryParams)
download.excel(data, 'API 访问日志.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -0,0 +1,79 @@
<template>
<Dialog title="详情" v-model="modelVisible" :scroll="true" :max-height="500" width="800">
<el-descriptions border :column="1">
<el-descriptions-item label="日志主键" min-width="120">
{{ detailData.id }}
</el-descriptions-item>
<el-descriptions-item label="链路追踪">
{{ detailData.traceId }}
</el-descriptions-item>
<el-descriptions-item label="应用名">
{{ detailData.applicationName }}
</el-descriptions-item>
<el-descriptions-item label="用户编号">
{{ detailData.userId }}
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="detailData.userType" />
</el-descriptions-item>
<el-descriptions-item label="用户 IP">
{{ detailData.userIp }}
</el-descriptions-item>
<el-descriptions-item label="用户 UA">
{{ detailData.userAgent }}
</el-descriptions-item>
<el-descriptions-item label="请求信息">
{{ detailData.requestMethod }} {{ detailData.requestUrl }}
</el-descriptions-item>
<el-descriptions-item label="请求参数">
{{ detailData.requestParams }}
</el-descriptions-item>
<el-descriptions-item label="异常时间">
{{ formatDate(detailData.exceptionTime) }}
</el-descriptions-item>
<el-descriptions-item label="异常名">
{{ detailData.exceptionName }}
</el-descriptions-item>
<el-descriptions-item label="异常堆栈" v-if="detailData.exceptionStackTrace">
<el-input
type="textarea"
:readonly="true"
:autosize="{ maxRows: 20 }"
v-model="detailData.exceptionStackTrace"
/>
</el-descriptions-item>
<el-descriptions-item label="处理状态">
<dict-tag
:type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS"
:value="detailData.processStatus"
/>
</el-descriptions-item>
<el-descriptions-item label="处理人" v-if="detailData.processUserId">
{{ detailData.processUserId }}
</el-descriptions-item>
<el-descriptions-item label="处理时间" v-if="detailData.processTime">
{{ formatDate(detailData.processTime) }}
</el-descriptions-item>
</el-descriptions>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE } from '@/utils/dict'
import { formatDate } from '@/utils/formatTime'
import * as ApiErrorLog from '@/api/infra/apiErrorLog'
const modelVisible = ref(false) //
const detailLoading = ref(false) //
const detailData = ref() //
/** 打开弹窗 */
const open = async (data: ApiErrorLog.ApiErrorLogVO) => {
modelVisible.value = true
//
detailLoading.value = true
try {
detailData.value = data
} finally {
detailLoading.value = false
}
}
defineExpose({ open }) // open
</script>

View File

@ -1,76 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: 'id',
primaryTitle: '日志编号',
action: true,
actionWidth: '300',
columns: [
{
title: '链路追踪',
field: 'traceId',
isTable: false
},
{
title: '用户编号',
field: 'userId',
isSearch: true
},
{
title: '用户类型',
field: 'userType',
dictType: DICT_TYPE.USER_TYPE,
isSearch: true
},
{
title: '应用名',
field: 'applicationName',
isSearch: true
},
{
title: '请求方法名',
field: 'requestMethod'
},
{
title: '请求地址',
field: 'requestUrl',
isSearch: true
},
{
title: '异常发生时间',
field: 'exceptionTime',
formatter: 'formatDate',
search: {
show: true,
itemRender: {
name: 'XDataTimePicker'
}
}
},
{
title: '异常名',
field: 'exceptionName'
},
{
title: '处理状态',
field: 'processStatus',
dictType: DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS,
dictClass: 'number',
isSearch: true
},
{
title: '处理人',
field: 'processUserId',
isTable: false
},
{
title: '处理时间',
field: 'processTime',
formatter: 'formatDate',
isTable: false
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -1,99 +1,248 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<!-- 操作导出 -->
<template #toolbar_buttons>
<XButton
type="warning"
preIcon="ep:download"
:title="t('action.export')"
@click="exportList('错误数据.xls')"
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="用户编号" prop="userId">
<el-input
v-model="queryParams.userId"
placeholder="请输入用户编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</template>
<template #duration_default="{ row }">
<span>{{ row.duration + 'ms' }}</span>
</template>
<template #resultCode_default="{ row }">
<span>{{ row.resultCode === 0 ? '成功' : '失败(' + row.resultMsg + ')' }}</span>
</template>
<template #actionbtns_default="{ row }">
<!-- 操作详情 -->
<XTextButton
preIcon="ep:view"
:title="t('action.detail')"
v-hasPermi="['infra:api-access-log:query']"
@click="handleDetail(row)"
</el-form-item>
<el-form-item label="用户类型" prop="userType">
<el-select
v-model="queryParams.userType"
placeholder="请选择用户类型"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.USER_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="应用名" prop="applicationName">
<el-input
v-model="queryParams.applicationName"
placeholder="请输入应用名"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
<XTextButton
preIcon="ep:cpu"
title="已处理"
v-if="row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT"
v-hasPermi="['infra:api-error-log:update-status']"
@click="handleProcessClick(row, InfraApiErrorLogProcessStatusEnum.DONE, '已处理')"
</el-form-item>
<el-form-item label="异常时间" prop="exceptionTime">
<el-date-picker
v-model="queryParams.exceptionTime"
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"
/>
<XTextButton
preIcon="ep:mute-notification"
title="已忽略"
v-if="row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT"
v-hasPermi="['infra:api-error-log:update-status']"
@click="handleProcessClick(row, InfraApiErrorLogProcessStatusEnum.IGNORE, '已忽略')"
/>
</template>
</XTable>
</el-form-item>
<el-form-item label="处理状态" prop="processStatus">
<el-select
v-model="queryParams.processStatus"
placeholder="请选择处理状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['infra:api-error-log: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 :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="userId" />
<el-table-column label="用户类型" align="center" prop="userType">
<template #default="scope">
<dict-tag :type="DICT_TYPE.USER_TYPE" :value="scope.row.userType" />
</template>
</el-table-column>
<el-table-column label="应用名" align="center" prop="applicationName" width="200" />
<el-table-column label="请求方法" align="center" prop="requestMethod" width="80" />
<el-table-column label="请求地址" align="center" prop="requestUrl" width="180" />
<el-table-column
label="异常发生时间"
align="center"
prop="exceptionTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="异常名" align="center" prop="exceptionName" width="180" />
<el-table-column label="处理状态" align="center" prop="processStatus">
<template #default="scope">
<dict-tag
:type="DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS"
:value="scope.row.processStatus"
/>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200">
<template #default="scope">
<el-button
link
type="primary"
@click="openDetail(scope.row)"
v-hasPermi="['infra:api-error-log:query']"
>
详细
</el-button>
<el-button
link
type="primary"
v-if="scope.row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT"
@click="handleProcess(scope.row.id, InfraApiErrorLogProcessStatusEnum.DONE)"
v-hasPermi="['infra:api-error-log:update-status']"
>
已处理
</el-button>
<el-button
link
type="primary"
v-if="scope.row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT"
@click="handleProcess(scope.row.id, InfraApiErrorLogProcessStatusEnum.IGNORE)"
v-hasPermi="['infra:api-error-log:update-status']"
>
已忽略
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗详情 -->
<ApiErrorLogDetail ref="detailRef" />
</template>
<script setup lang="ts" name="ApiErrorLog">
import { allSchemas } from './apiErrorLog.data'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as ApiErrorLogApi from '@/api/infra/apiErrorLog'
import ApiErrorLogDetail from './ApiErrorLogDetail.vue'
import { InfraApiErrorLogProcessStatusEnum } from '@/utils/constants'
const { t } = useI18n() //
const message = useMessage()
const message = useMessage() //
// ========== ==========
const [registerTable, { reload, exportList }] = useXTable({
allSchemas: allSchemas,
getListApi: ApiErrorLogApi.getApiErrorLogPageApi,
exportListApi: ApiErrorLogApi.exportApiErrorLogApi
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
userId: null,
userType: null,
applicationName: null,
requestUrl: null,
processStatus: null,
exceptionTime: []
})
// ========== ==========
const detailData = ref() // Ref
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const queryFormRef = ref() //
const exportLoading = ref(false) //
//
const handleDetail = (row: ApiErrorLogApi.ApiErrorLogVO) => {
//
detailData.value = row
dialogTitle.value = t('action.detail')
dialogVisible.value = true
/** 查询参数列表 */
const getList = async () => {
loading.value = true
try {
const data = await ApiErrorLogApi.getApiErrorLogPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
//
const handleProcessClick = (
row: ApiErrorLogApi.ApiErrorLogVO,
processSttatus: number,
type: string
) => {
message
.confirm('确认标记为' + type + '?', t('common.reminder'))
.then(async () => {
await ApiErrorLogApi.updateApiErrorLogPageApi(row.id, processSttatus)
message.success(t('common.updateSuccess'))
})
.finally(async () => {
//
await reload()
})
.catch(() => {})
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 详情操作 */
const detailRef = ref()
const openDetail = (data: ApiErrorLogApi.ApiErrorLogVO) => {
detailRef.value.open(data)
}
/** 处理已处理 / 已忽略的操作 **/
const handleProcess = async (id: number, processStatus: number) => {
try {
//
const type = processStatus === InfraApiErrorLogProcessStatusEnum.DONE ? '已处理' : '已忽略'
await message.confirm('确认标记为' + type + '?')
//
await ApiErrorLogApi.updateApiErrorLogPage(id, processStatus)
await message.success(type)
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await ApiErrorLogApi.exportApiErrorLog(queryParams)
download.excel(data, '异常日志.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -3,77 +3,99 @@
<el-row>
<el-col>
<div class="mb-2 float-right">
<el-button size="small" @click="setJson"> 导入JSON</el-button>
<el-button size="small" @click="setOption"> 导入Options</el-button>
<el-button size="small" type="primary" @click="showJson">生成JSON</el-button>
<el-button size="small" type="success" @click="showOption">生成Options</el-button>
<el-button size="small" type="primary" @click="showJson">生成 JSON</el-button>
<el-button size="small" type="success" @click="showOption">生成O ptions</el-button>
<el-button size="small" type="danger" @click="showTemplate">生成组件</el-button>
<!-- <el-button size="small" @click="changeLocale">中英切换</el-button> -->
</div>
</el-col>
<!-- 表单设计器 -->
<el-col>
<fc-designer ref="designer" height="780px" />
</el-col>
</el-row>
<Dialog :title="dialogTitle" v-model="dialogVisible" maxHeight="600">
<div ref="editor" v-if="dialogVisible">
<XTextButton style="float: right" :title="t('common.copy')" @click="copy(formValue)" />
<el-scrollbar height="580">
<div v-highlight>
<code class="hljs">
{{ formValue }}
</code>
</div>
</el-scrollbar>
</div>
<span style="color: red" v-if="err">输入内容格式有误!</span>
</Dialog>
</ContentWrap>
<!-- 弹窗表单预览 -->
<Dialog :title="dialogTitle" v-model="dialogVisible" max-height="600">
<div ref="editor" v-if="dialogVisible">
<el-button style="float: right" @click="copy(formData)">
{{ t('common.copy') }}
</el-button>
<el-scrollbar height="580">
<div v-highlight>
<code class="hljs">
{{ formData }}
</code>
</div>
</el-scrollbar>
</div>
</Dialog>
</template>
<script setup lang="ts" name="Build">
import formCreate from '@form-create/element-ui'
import { useClipboard } from '@vueuse/core'
const { t } = useI18n() //
const message = useMessage() //
const { t } = useI18n()
const message = useMessage()
const designer = ref()
const dialogVisible = ref(false)
const dialogTitle = ref('')
const err = ref(false)
const type = ref(-1)
const formValue = ref('')
const designer = ref() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formType = ref(-1) // 0 - JSON1 - Options2 -
const formData = ref('') //
/** 打开弹窗 */
const openModel = (title: string) => {
dialogVisible.value = true
dialogTitle.value = title
}
const setJson = () => {
openModel('导入JSON--未实现')
}
const setOption = () => {
openModel('导入Options--未实现')
}
/** 生成 JSON */
const showJson = () => {
openModel('生成JSON')
type.value = 0
formValue.value = designer.value.getRule()
openModel('生成 JSON')
formType.value = 0
formData.value = designer.value.getRule()
}
/** 生成 Options */
const showOption = () => {
openModel('生成Options')
type.value = 1
formValue.value = designer.value.getOption()
openModel('生成 Options')
formType.value = 1
formData.value = designer.value.getOption()
}
/** 生成组件 */
const showTemplate = () => {
openModel('生成组件')
type.value = 2
formValue.value = makeTemplate()
formType.value = 2
formData.value = makeTemplate()
}
const makeTemplate = () => {
const rule = designer.value.getRule()
const opt = designer.value.getOption()
return `<template>
<form-create
v-model="fapi"
:rule="rule"
:option="option"
@submit="onSubmit"
></form-create>
</template>
<script setup lang=ts>
import formCreate from "@form-create/element-ui";
const faps = ref(null)
const rule = ref('')
const option = ref('')
const init = () => {
rule.value = formCreate.parseJson('${formCreate.toJson(rule).replaceAll('\\', '\\\\')}')
option.value = formCreate.parseJson('${JSON.stringify(opt)}')
}
const onSubmit = (formData) => {
//todo
}
init()
<\/script>`
}
// const changeLocale = () => {
// console.info('changeLocale')
// }
/** 复制 **/
const copy = async (text: string) => {
@ -87,31 +109,4 @@ const copy = async (text: string) => {
}
}
}
const makeTemplate = () => {
const rule = designer.value.getRule()
const opt = designer.value.getOption()
return `<template>
<form-create
v-model="fapi"
:rule="rule"
:option="option"
@submit="onSubmit"
></form-create>
</template>
<script setup lang=ts>
import formCreate from "@form-create/element-ui";
const faps = ref(null)
const rule = ref('')
const option = ref('')
const init = () => {
rule.value = formCreate.parseJson('${formCreate.toJson(rule).replaceAll('\\', '\\\\')}')
option.value = formCreate.parseJson('${JSON.stringify(opt)}')
}
const onSubmit = (formData) => {
//todo
}
init()
<\/script>`
}
</script>

View File

@ -6,7 +6,7 @@ import { useForm } from '@/hooks/web/useForm'
import { FormSchema } from '@/types/form'
import { CodegenTableVO } from '@/api/infra/codegen/types'
import { getIntDictOptions } from '@/utils/dict'
import { listSimpleMenusApi } from '@/api/system/menu'
import { getSimpleMenusList } from '@/api/system/menu'
import { handleTree, defaultProps } from '@/utils/tree'
import { PropType } from 'vue'
@ -21,7 +21,7 @@ const templateTypeOptions = getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_T
const sceneOptions = getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE)
const menuOptions = ref<any>([]) //
const getTree = async () => {
const res = await listSimpleMenusApi()
const res = await getSimpleMenusList()
menuOptions.value = handleTree(res)
}

View File

@ -41,10 +41,8 @@
<vxe-column field="comment" title="表描述" />
</vxe-table>
<template #footer>
<div class="dialog-footer">
<XButton type="primary" :title="t('action.import')" @click="handleImportTable()" />
<XButton :title="t('dialog.close')" @click="handleClose()" />
</div>
<XButton type="primary" :title="t('action.import')" @click="handleImportTable()" />
<XButton :title="t('dialog.close')" @click="handleClose()" />
</template>
</XModal>
</template>
@ -52,7 +50,7 @@
import { VxeTableInstance } from 'vxe-table'
import type { DatabaseTableVO } from '@/api/infra/codegen/types'
import { getSchemaTableListApi, createCodegenListApi } from '@/api/infra/codegen'
import { getDataSourceConfigListApi, DataSourceConfigVO } from '@/api/infra/dataSourceConfig'
import { getDataSourceConfigList, DataSourceConfigVO } from '@/api/infra/dataSourceConfig'
const { t } = useI18n() //
const message = useMessage() //
@ -63,13 +61,13 @@ const dbLoading = ref(true)
const queryParams = reactive({
name: undefined,
comment: undefined,
dataSourceConfigId: 0
dataSourceConfigId: 0 as number | undefined
})
const dataSourceConfigs = ref<DataSourceConfigVO[]>([])
const show = async () => {
const res = await getDataSourceConfigListApi()
const res = await getDataSourceConfigList()
dataSourceConfigs.value = res
queryParams.dataSourceConfigId = dataSourceConfigs.value[0].id
queryParams.dataSourceConfigId = dataSourceConfigs.value[0].id as number
visible.value = true
await getList()
}

View File

@ -35,10 +35,8 @@
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</template>
</Dialog>
</template>

View File

@ -21,10 +21,8 @@
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</template>
</Dialog>
</template>

View File

@ -23,10 +23,8 @@
</template>
</el-upload>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitFileForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
<el-button @click="submitFileForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</template>
</Dialog>
</template>

View File

@ -93,10 +93,8 @@
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</template>
</Dialog>
</template>

View File

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

View File

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

View File

@ -0,0 +1,157 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible">
<el-form
ref="formRef"
:model="formData"
:rules="rules"
label-width="120px"
v-loading="formLoading"
>
<el-form-item label="名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称" />
</el-form-item>
<el-form-item label="微信号" prop="account">
<template #label>
<span>
<el-tooltip
content="在微信公众平台mp.weixin.qq.com的菜单 [设置与开发 - 公众号设置 - 账号详情] 中能找到「微信号」"
placement="top"
>
<Icon icon="ep:question-filled" style="vertical-align: middle" />
</el-tooltip>
微信号
</span>
</template>
<el-input v-model="formData.account" placeholder="请输入微信号" />
</el-form-item>
<el-form-item label="appId" prop="appId">
<template #label>
<span>
<el-tooltip
content="在微信公众平台mp.weixin.qq.com的菜单 [设置与开发 - 公众号设置 - 基本设置] 中能找到「开发者ID(AppID)」"
placement="top"
>
<Icon icon="ep:question-filled" style="vertical-align: middle" />
</el-tooltip>
appId
</span>
</template>
<el-input v-model="formData.appId" placeholder="请输入公众号 appId" />
</el-form-item>
<el-form-item label="appSecret" prop="appSecret">
<template #label>
<span>
<el-tooltip
content="在微信公众平台mp.weixin.qq.com的菜单 [设置与开发 - 公众号设置 - 基本设置] 中能找到「开发者密码(AppSecret)」"
placement="top"
>
<Icon icon="ep:question-filled" style="vertical-align: middle" />
</el-tooltip>
appSecret
</span>
</template>
<el-input v-model="formData.appSecret" placeholder="请输入公众号 appSecret" />
</el-form-item>
<el-form-item label="token" prop="token">
<el-input v-model="formData.token" placeholder="请输入公众号token" />
</el-form-item>
<el-form-item label="消息加解密密钥" prop="aesKey">
<el-input v-model="formData.aesKey" placeholder="请输入消息加解密密钥" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input type="textarea" 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="modelVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import * as AccountApi from '@/api/mp/account'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
name: '',
account: '',
appId: '',
appSecret: '',
token: '',
aesKey: '',
remark: ''
})
const rules = reactive({
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
account: [{ required: true, message: '公众号账号不能为空', trigger: 'blur' }],
appId: [{ required: true, message: '公众号 appId 不能为空', trigger: 'blur' }],
appSecret: [{ required: true, message: '公众号密钥不能为空', trigger: 'blur' }],
token: [{ required: true, message: '公众号 token 不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
modelVisible.value = true
modelTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await AccountApi.getAccount(id)
} finally {
formLoading.value = false
}
}
}
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
if (formType.value === 'create') {
await AccountApi.createAccount(data)
message.success(t('common.createSuccess'))
} else {
await AccountApi.updateAccount(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 表单重置 */
const resetForm = () => {
formData.value = {
id: undefined,
name: '',
account: '',
appId: '',
appSecret: '',
token: '',
aesKey: '',
remark: ''
}
formRef.value?.resetFields()
}
</script>

View File

@ -1,3 +1,192 @@
<template>
<span>开发中</span>
<!-- 搜索工作栏 -->
<content-wrap>
<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>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" />搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" />重置</el-button>
<el-button type="primary" @click="openForm('create')" v-hasPermi="['mp:account:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</el-form-item>
</el-form>
</content-wrap>
<!-- 列表 -->
<content-wrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="名称" align="center" prop="name" />
<el-table-column label="微信号" align="center" prop="account" width="180" />
<el-table-column label="appId" align="center" prop="appId" width="180" />
<el-table-column label="服务器地址(URL)" align="center" prop="appId" width="360">
<template #default="scope">
{{ 'http://服务端地址/mp/open/' + scope.row.appId }}
</template>
</el-table-column>
<el-table-column label="二维码" align="center" prop="qrCodeUrl">
<template #default="scope">
<img
v-if="scope.row.qrCodeUrl"
:src="scope.row.qrCodeUrl"
alt="二维码"
style="height: 100px; display: inline-block"
/>
<el-button
link
type="primary"
@click="handleGenerateQrCode(scope.row)"
v-hasPermi="['mp:account:qr-code']"
>
生成二维码
</el-button>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['mp:account:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['mp:account:delete']"
>
删除
</el-button>
<el-button
link
type="danger"
@click="handleCleanQuota(scope.row)"
v-hasPermi="['mp:account:clear-quota']"
>
清空 API 配额
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</content-wrap>
<!-- 对话框(添加 / 修改) -->
<AccountForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="MpAccount">
import * as AccountApi from '@/api/mp/account'
import AccountForm from './AccountForm.vue'
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: null,
account: null,
appId: null
})
const queryFormRef = ref() //
/** 查询列表 */
const getList = async () => {
loading.value = true
//
let params = { ...queryParams }
//
const data = await AccountApi.getAccountPage(params)
list.value = data.list
total.value = data.total
loading.value = false
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id) => {
try {
//
await message.delConfirm()
//
await AccountApi.deleteAccount(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 生成二维码的按钮操作 */
const handleGenerateQrCode = async (row) => {
try {
//
await message.confirm('是否确认生成公众号账号编号为"' + row.name + '"的二维码?')
//
await AccountApi.generateAccountQrCode(row.id)
message.success('生成二维码成功')
//
await getList()
} catch {}
}
/** 清空二维码 API 配额的按钮操作 */
const handleCleanQuota = async (row) => {
try {
// API
await message.confirm('是否确认清空生成公众号账号编号为"' + row.name + '"的 API 配额?')
// API
await AccountApi.clearAccountQuota(row.id)
message.success('清空 API 配额成功')
} catch {}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

Binary file not shown.

After

(image error) Size: 15 KiB

View File

@ -0,0 +1,71 @@
<!--
微信消息 - 定位
-->
<template>
<div>
<el-link
type="primary"
target="_blank"
:href="
'https://map.qq.com/?type=marker&isopeninfowin=1&markertype=1&pointx=' +
locationY +
'&pointy=' +
locationX +
'&name=' +
label +
'&ref=yudao'
"
>
<el-col>
<el-row>
<img
:src="
'https://apis.map.qq.com/ws/staticmap/v2/?zoom=10&markers=color:blue|label:A|' +
locationX +
',' +
locationY +
'&key=' +
qqMapKey +
'&size=250*180'
"
/>
</el-row>
<el-row>
<el-icon><Location /></el-icon>
<Icon icon="ep:location" />
{{ label }}
</el-row>
</el-col>
</el-link>
</div>
</template>
<script setup lang="ts" name="WxLocation">
const props = defineProps({
locationX: {
required: true,
type: Number
},
locationY: {
required: true,
type: Number
},
label: {
//
required: true,
type: String
},
qqMapKey: {
// QQ https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
required: false,
type: String,
default: 'TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E' //
}
})
defineExpose({
locationX: props.locationX,
locationY: props.locationY,
label: props.label,
qqMapKey: props.qqMapKey
})
</script>

View File

@ -0,0 +1,101 @@
.avue-card{
&__item{
margin-bottom: 16px;
border: 1px solid #e8e8e8;
background-color: #fff;
box-sizing: border-box;
color: rgba(0,0,0,.65);
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5;
list-style: none;
font-feature-settings: "tnum";
cursor: pointer;
height:200px;
&:hover{
border-color: rgba(0,0,0,.09);
box-shadow: 0 2px 8px rgba(0,0,0,.09);
}
&--add{
border:1px dashed #000;
width: 100%;
color: rgba(0,0,0,.45);
background-color: #fff;
border-color: #d9d9d9;
border-radius: 2px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
i{
margin-right: 10px;
}
&:hover{
color: #40a9ff;
background-color: #fff;
border-color: #40a9ff;
}
}
}
&__body{
display: flex;
padding: 24px;
}
&__detail{
flex:1
}
&__avatar{
width: 48px;
height: 48px;
border-radius: 48px;
overflow: hidden;
margin-right: 12px;
img{
width: 100%;
height: 100%;
}
}
&__title{
color: rgba(0,0,0,.85);
margin-bottom: 12px;
font-size: 16px;
&:hover{
color:#1890ff;
}
}
&__info{
color: rgba(0,0,0,.45);
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
height: 64px;
}
&__menu{
display: flex;
justify-content:space-around;
height: 50px;
background: #f7f9fa;
color: rgba(0,0,0,.45);
text-align: center;
line-height: 50px;
&:hover{
color:#1890ff;
}
}
}
/** joolun 额外加的 */
.avue-comment__main {
flex: unset!important;
border-radius: 5px!important;
margin: 0 8px!important;
}
.avue-comment__header {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
.avue-comment__body {
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
}

View File

@ -0,0 +1,88 @@
/* 来自 https://github.com/nmxiaowei/avue/blob/master/styles/src/element-ui/comment.scss */
.avue-comment{
margin-bottom: 30px;
display: flex;
align-items: flex-start;
&--reverse{
flex-direction:row-reverse;
.avue-comment__main{
&:before,&:after{
left: auto;
right: -8px;
border-width: 8px 0 8px 8px;
}
&:before{
border-left-color: #dedede;
}
&:after{
border-left-color: #f8f8f8;
margin-right: 1px;
margin-left: auto;
}
}
}
&__avatar{
width: 48px;
height: 48px;
border-radius: 50%;
border: 1px solid transparent;
box-sizing: border-box;
vertical-align: middle;
}
&__header{
padding: 5px 15px;
background: #f8f8f8;
border-bottom: 1px solid #eee;
display: flex;
align-items: center;
justify-content: space-between;
}
&__author{
font-weight: 700;
font-size: 14px;
color: #999;
}
&__main{
flex:1;
margin: 0 20px;
position: relative;
border: 1px solid #dedede;
border-radius: 2px;
&:before,&:after{
position: absolute;
top: 10px;
left: -8px;
right: 100%;
width: 0;
height: 0;
display: block;
content: " ";
border-color: transparent;
border-style: solid solid outset;
border-width: 8px 8px 8px 0;
pointer-events: none;
}
&:before {
border-right-color: #dedede;
z-index: 1;
}
&:after{
border-right-color: #f8f8f8;
margin-left: 1px;
z-index: 2;
}
}
&__body{
padding: 15px;
overflow: hidden;
background: #fff;
font-family: Segoe UI,Lucida Grande,Helvetica,Arial,Microsoft YaHei,FreeSans,Arimo,Droid Sans,wenquanyi micro hei,Hiragino Sans GB,Hiragino Sans GB W3,FontAwesome,sans-serif;color: #333;
font-size: 14px;
}
blockquote{
margin:0;
font-family: Georgia,Times New Roman,Times,Kai,Kaiti SC,KaiTi,BiauKai,FontAwesome,serif;
padding: 1px 0 1px 15px;
border-left: 4px solid #ddd;
}
}

View File

@ -0,0 +1,338 @@
<!--
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
芋道源码
移除暂时用不到的 websocket
代码优化补充注释提升阅读性
-->
<template>
<div class="msg-main">
<div class="msg-div" :id="'msg-div' + nowStr">
<!-- 加载更多 -->
<div v-loading="loading"></div>
<div v-if="!loading">
<div class="el-table__empty-block" v-if="loadMore" @click="loadingMore"
><span class="el-table__empty-text">点击加载更多</span></div
>
<div class="el-table__empty-block" v-if="!loadMore"
><span class="el-table__empty-text">没有更多了</span></div
>
</div>
<!-- 消息列表 -->
<div class="execution" v-for="item in list" :key="item.id">
<div class="avue-comment" :class="item.sendFrom === 2 ? 'avue-comment--reverse' : ''">
<div class="avatar-div">
<img
:src="item.sendFrom === 1 ? user.avatar : mp.avatar"
class="avue-comment__avatar"
/>
<div class="avue-comment__author">{{
item.sendFrom === 1 ? user.nickname : mp.nickname
}}</div>
</div>
<div class="avue-comment__main">
<div class="avue-comment__header">
<div class="avue-comment__create_time">{{ parseTime(item.createTime) }}</div>
</div>
<div
class="avue-comment__body"
:style="item.sendFrom === 2 ? 'background: #6BED72;' : ''"
>
<!-- 事件区域 -->
<div v-if="item.type === 'event' && item.event === 'subscribe'">
<el-tag type="success" size="mini">关注</el-tag>
</div>
<div v-else-if="item.type === 'event' && item.event === 'unsubscribe'">
<el-tag type="danger" size="mini">取消关注</el-tag>
</div>
<div v-else-if="item.type === 'event' && item.event === 'CLICK'">
<el-tag size="mini">点击菜单</el-tag>{{ item.eventKey }}
</div>
<div v-else-if="item.type === 'event' && item.event === 'VIEW'">
<el-tag size="mini">点击菜单链接</el-tag>{{ item.eventKey }}
</div>
<div v-else-if="item.type === 'event' && item.event === 'scancode_waitmsg'">
<el-tag size="mini">扫码结果</el-tag>{{ item.eventKey }}
</div>
<div v-else-if="item.type === 'event' && item.event === 'scancode_push'">
<el-tag size="mini">扫码结果</el-tag>{{ item.eventKey }}
</div>
<div v-else-if="item.type === 'event' && item.event === 'pic_sysphoto'">
<el-tag size="mini">系统拍照发图</el-tag>
</div>
<div v-else-if="item.type === 'event' && item.event === 'pic_photo_or_album'">
<el-tag size="mini">拍照或者相册</el-tag>
</div>
<div v-else-if="item.type === 'event' && item.event === 'pic_weixin'">
<el-tag size="mini">微信相册</el-tag>
</div>
<div v-else-if="item.type === 'event' && item.event === 'location_select'">
<el-tag size="mini">选择地理位置</el-tag>
</div>
<div v-else-if="item.type === 'event'">
<el-tag type="danger" size="mini">未知事件类型</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>
<div v-else-if="item.type === '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'"
style="text-align: center"
>
<wx-video-player :url="item.mediaUrl" />
</div>
<div v-else-if="item.type === '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
:label="item.label"
:location-y="item.locationY"
:location-x="item.locationX"
/>
</div>
<div v-else-if="item.type === 'news'" style="width: 300px">
<!-- TODO 芋艿待测试详情页也存在类似的情况 -->
<wx-news :articles="item.articles" />
</div>
<div v-else-if="item.type === 'music'">
<wx-music
:title="item.title"
:description="item.description"
:thumb-media-url="item.thumbMediaUrl"
:music-url="item.musicUrl"
:hq-music-url="item.hqMusicUrl"
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="msg-send" v-loading="sendLoading">
<wx-reply-select ref="replySelect" :objData="objData" />
<el-button type="success" size="small" class="send-but" @click="sendMsg">发送(S)</el-button>
</div>
</div>
</template>
<script>
import { getMessagePage, sendMessage } from '@/api/mp/message'
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 { getUser } from '@/api/mp/mpuser'
export default {
name: 'WxMsg',
components: {
WxReplySelect,
WxVideoPlayer,
WxVoicePlayer,
WxNews,
WxLocation,
WxMusic
},
props: {
userId: {
type: Number,
required: true
}
},
data() {
return {
nowStr: new Date().getTime(), // :id="'msg-div' + nowStr"
loading: false, //
loadMore: true, //
list: [], //
queryParams: {
pageNo: 1, //
pageSize: 14, //
accountId: undefined
},
user: {
// 使
nickname: '用户',
avatar: require('@/assets/images/profile.jpg'),
accountId: 0 //
},
mp: {
nickname: '公众号',
avatar: require('@/assets/images/wechat.png')
},
// ========= =========
sendLoading: false, //
objData: {
//
type: 'text'
}
}
},
created() {
//
getUser(this.userId).then((response) => {
this.user.nickname =
response.data.nickname && response.data.nickname.length > 0
? response.data.nickname
: this.user.nickname
this.user.avatar =
response.data.avatar && this.user.avatar.length > 0
? response.data.avatar
: this.user.avatar
this.user.accountId = response.data.accountId
//
this.queryParams.accountId = response.data.accountId
this.objData.accountId = response.data.accountId
//
console.log(this.queryParams)
this.refreshChange()
})
},
methods: {
sendMsg() {
if (!this.objData) {
return
}
//
if (this.objData.type === 'news' && this.objData.articles.length > 1) {
this.objData.articles = [this.objData.articles[0]]
this.$message({
showClose: true,
message: '图文消息条数限制在 1 条以内,已默认发送第一条',
type: 'success'
})
}
//
this.sendLoading = true
sendMessage(
Object.assign(
{
userId: this.userId
},
{
...this.objData
}
)
)
.then((response) => {
this.sendLoading = false
//
this.list = [...this.list, ...[response.data]]
this.scrollToBottom()
// objData
this.$refs['replySelect'].deleteObj() // tab
})
.catch(() => {
this.sendLoading = false
})
},
loadingMore() {
this.queryParams.pageNo++
this.getPage(this.queryParams)
},
getPage(page, params) {
this.loading = true
getMessagePage(
Object.assign(
{
pageNo: page.pageNo,
pageSize: page.pageSize,
userId: this.userId,
accountId: page.accountId
},
params
)
).then((response) => {
//
const msgDiv = document.getElementById('msg-div' + this.nowStr)
let scrollHeight = 0
if (msgDiv) {
scrollHeight = msgDiv.scrollHeight
}
//
const data = response.data.list.reverse()
this.list = [...data, ...this.list]
this.loading = false
if (data.length < this.queryParams.pageSize || data.length === 0) {
this.loadMore = false
}
this.queryParams.pageNo = page.pageNo
this.queryParams.pageSize = page.pageSize
//
if (this.queryParams.pageNo === 1) {
//
this.scrollToBottom()
} else if (data.length !== 0) {
//
this.$nextTick(() => {
if (scrollHeight !== 0) {
msgDiv.scrollTop =
document.getElementById('msg-div' + this.nowStr).scrollHeight - scrollHeight - 100
}
})
}
})
},
/**
* 刷新回调
*/
refreshChange() {
this.getPage(this.queryParams)
},
/** 定位到消息底部 */
scrollToBottom: function () {
this.$nextTick(() => {
let div = document.getElementById('msg-div' + this.nowStr)
div.scrollTop = div.scrollHeight
})
}
}
}
</script>
<style lang="scss" scoped>
/* 因为 joolun 实现依赖 avue 组件,该页面使用了 comment.scss、card.scc */
@import './comment.scss';
@import './card.scss';
.msg-main {
margin-top: -30px;
padding: 10px;
}
.msg-div {
height: 50vh;
overflow: auto;
background-color: #eaeaea;
margin-left: 10px;
margin-right: 10px;
}
.msg-send {
padding: 10px;
}
.avatar-div {
text-align: center;
width: 80px;
}
.send-but {
float: right;
margin-top: 8px !important;
}
</style>

View File

@ -0,0 +1,60 @@
<!--
微信消息 - 音乐
-->
<template>
<div>
<el-link
type="success"
:underline="false"
target="_blank"
:href="hqMusicUrl ? hqMusicUrl : musicUrl"
>
<div
class="avue-card__body"
style="padding: 10px; background-color: #fff; border-radius: 5px"
>
<div class="avue-card__avatar">
<img :src="thumbMediaUrl" alt="" />
</div>
<div class="avue-card__detail">
<div class="avue-card__title" style="margin-bottom: unset">{{ title }}</div>
<div class="avue-card__info" style="height: unset">{{ description }}</div>
</div>
</div>
</el-link>
</div>
</template>
<script setup lang="ts" name="WxMusic">
const props = defineProps({
title: {
required: false,
type: String
},
description: {
required: false,
type: String
},
musicUrl: {
required: false,
type: String
},
hqMusicUrl: {
required: false,
type: String
},
thumbMediaUrl: {
required: true,
type: String
}
})
defineExpose({
musicUrl: props.musicUrl
})
</script>
<style lang="scss" scoped>
/* 因为 joolun 实现依赖 avue 组件,该页面使用了 card.scc */
@import '../wx-msg/card.scss';
</style>

View File

@ -0,0 +1,107 @@
<!--
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
微信消息 - 图文
芋道源码
代码优化补充注释提升阅读性
-->
<template>
<div class="news-home">
<div v-for="(article, index) in articles" :key="index" class="news-div">
<!-- 头条 -->
<a target="_blank" :href="article.url" v-if="index === 0">
<div class="news-main">
<div class="news-content">
<el-image
class="material-img"
style="width: 100%; height: 120px"
:src="article.picUrl"
/>
<div class="news-content-title">
<span>{{ article.title }}</span>
</div>
</div>
</div>
</a>
<!-- 二条/三条等等 -->
<a target="_blank" :href="article.url" v-else>
<div class="news-main-item">
<div class="news-content-item">
<div class="news-content-item-title">{{ article.title }}</div>
<div class="news-content-item-img">
<img class="material-img" :src="article.picUrl" height="100%" />
</div>
</div>
</div>
</a>
</div>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
articles: {
type: Array,
default: () => null
}
})
defineExpose({
articles: props.articles
})
</script>
<style lang="scss" scoped>
.news-home {
background-color: #ffffff;
width: 100%;
margin: auto;
}
.news-main {
width: 100%;
margin: auto;
}
.news-content {
background-color: #acadae;
width: 100%;
position: relative;
}
.news-content-title {
display: inline-block;
font-size: 12px;
color: #ffffff;
position: absolute;
left: 0;
bottom: 0;
background-color: black;
width: 98%;
padding: 1%;
opacity: 0.65;
white-space: normal;
box-sizing: unset !important;
}
.news-main-item {
background-color: #ffffff;
padding: 5px 0;
border-top: 1px solid #eaeaea;
}
.news-content-item {
position: relative;
}
.news-content-item-title {
display: inline-block;
font-size: 10px;
width: 70%;
margin-left: 1%;
white-space: normal;
}
.news-content-item-img {
display: inline-block;
width: 25%;
background-color: #acadae;
margin-right: 1%;
}
.material-img {
width: 100%;
}
</style>

View File

@ -0,0 +1,634 @@
<!--&lt;!&ndash;-->
<!-- - Copyright (C) 2018-2019-->
<!-- - All rights reserved, Designed By www.joolun.com-->
<!-- 芋道源码-->
<!-- 移除多余的 rep 为前缀的变量 message 消息更简单-->
<!-- 代码优化补充注释提升阅读性-->
<!-- 优化消息的临时缓存策略发送消息时只清理被发送消息的 tab不会强制切回到 text 输入-->
<!-- 支持发送视频消息时支持新建视频-->
<!--&ndash;&gt;-->
<!--<template>-->
<!-- <el-tabs type="border-card" v-model="objData.type" @tab-click="handleClick">-->
<!-- &lt;!&ndash; 类型 1文本 &ndash;&gt;-->
<!-- <el-tab-pane name="text">-->
<!-- <span slot="label"><i class="el-icon-document"></i> 文本</span>-->
<!-- <el-input-->
<!-- type="textarea"-->
<!-- :rows="5"-->
<!-- placeholder="请输入内容"-->
<!-- v-model="objData.content"-->
<!-- @input="inputContent"-->
<!-- />-->
<!-- </el-tab-pane>-->
<!-- &lt;!&ndash; 类型 2图片 &ndash;&gt;-->
<!-- <el-tab-pane name="image">-->
<!-- <span slot="label"><i class="el-icon-picture"></i> 图片</span>-->
<!-- <el-row>-->
<!-- &lt;!&ndash; 情况一已经选择好素材或者上传好图片 &ndash;&gt;-->
<!-- <div class="select-item" v-if="objData.url">-->
<!-- <img class="material-img" :src="objData.url" />-->
<!-- <p class="item-name" v-if="objData.name">{{ objData.name }}</p>-->
<!-- <el-row class="ope-row">-->
<!-- <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" />-->
<!-- </el-row>-->
<!-- </div>-->
<!-- &lt;!&ndash; 情况二未做完上述操作 &ndash;&gt;-->
<!-- <div v-else>-->
<!-- <el-row style="text-align: center">-->
<!-- &lt;!&ndash; 选择素材 &ndash;&gt;-->
<!-- <el-col :span="12" class="col-select">-->
<!-- <el-button type="success" @click="openMaterial">-->
<!-- 素材库选择<i class="el-icon-circle-check el-icon&#45;&#45;right"></i>-->
<!-- </el-button>-->
<!-- <el-dialog-->
<!-- title="选择图片"-->
<!-- v-model:visible="dialogImageVisible"-->
<!-- width="90%"-->
<!-- append-to-body-->
<!-- >-->
<!-- <wx-material-select :obj-data="objData" @selectMaterial="selectMaterial" />-->
<!-- </el-dialog>-->
<!-- </el-col>-->
<!-- &lt;!&ndash; 文件上传 &ndash;&gt;-->
<!-- <el-col :span="12" class="col-add">-->
<!-- <el-upload-->
<!-- :action="actionUrl"-->
<!-- :headers="headers"-->
<!-- multiple-->
<!-- :limit="1"-->
<!-- :file-list="fileList"-->
<!-- :data="uploadData"-->
<!-- :before-upload="beforeImageUpload"-->
<!-- :on-success="handleUploadSuccess"-->
<!-- >-->
<!-- <el-button type="primary">上传图片</el-button>-->
<!-- <div slot="tip" class="el-upload__tip"-->
<!-- >支持 bmp/png/jpeg/jpg/gif 格式大小不超过 2M</div-->
<!-- >-->
<!-- </el-upload>-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- </div>-->
<!-- </el-row>-->
<!-- </el-tab-pane>-->
<!-- &lt;!&ndash; 类型 3语音 &ndash;&gt;-->
<!-- <el-tab-pane name="voice">-->
<!-- <span slot="label"><i class="el-icon-phone"></i> 语音</span>-->
<!-- <el-row>-->
<!-- <div class="select-item2" v-if="objData.url">-->
<!-- <p class="item-name">{{ objData.name }}</p>-->
<!-- <div class="item-infos">-->
<!-- <wx-voice-player :url="objData.url" />-->
<!-- </div>-->
<!-- <el-row class="ope-row">-->
<!-- <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" />-->
<!-- </el-row>-->
<!-- </div>-->
<!-- <div v-else>-->
<!-- <el-row style="text-align: center">-->
<!-- &lt;!&ndash; 选择素材 &ndash;&gt;-->
<!-- <el-col :span="12" class="col-select">-->
<!-- <el-button type="success" @click="openMaterial">-->
<!-- 素材库选择<i class="el-icon-circle-check el-icon&#45;&#45;right"></i>-->
<!-- </el-button>-->
<!-- <el-dialog-->
<!-- title="选择语音"-->
<!-- v-model:visible="dialogVoiceVisible"-->
<!-- width="90%"-->
<!-- append-to-body-->
<!-- >-->
<!-- <WxMaterialSelect :objData="objData" @selectMaterial="selectMaterial" />-->
<!-- </el-dialog>-->
<!-- </el-col>-->
<!-- &lt;!&ndash; 文件上传 &ndash;&gt;-->
<!-- <el-col :span="12" class="col-add">-->
<!-- <el-upload-->
<!-- :action="actionUrl"-->
<!-- :headers="headers"-->
<!-- multiple-->
<!-- :limit="1"-->
<!-- :file-list="fileList"-->
<!-- :data="uploadData"-->
<!-- :before-upload="beforeVoiceUpload"-->
<!-- :on-success="handleUploadSuccess"-->
<!-- >-->
<!-- <el-button type="primary">点击上传</el-button>-->
<!-- <div slot="tip" class="el-upload__tip"-->
<!-- >格式支持 mp3/wma/wav/amr文件大小不超过 2M播放长度不超过 60s</div-->
<!-- >-->
<!-- </el-upload>-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- </div>-->
<!-- </el-row>-->
<!-- </el-tab-pane>-->
<!-- &lt;!&ndash; 类型 4视频 &ndash;&gt;-->
<!-- <el-tab-pane name="video">-->
<!-- <span slot="label"><i class="el-icon-share"></i> 视频</span>-->
<!-- <el-row>-->
<!-- <el-input v-model="objData.title" placeholder="请输入标题" @input="inputContent" />-->
<!-- <div style="margin: 20px 0"></div>-->
<!-- <el-input v-model="objData.description" placeholder="请输入描述" @input="inputContent" />-->
<!-- <div style="margin: 20px 0"></div>-->
<!-- <div style="text-align: center">-->
<!-- <wx-video-player v-if="objData.url" :url="objData.url" />-->
<!-- </div>-->
<!-- <div style="margin: 20px 0"></div>-->
<!-- <el-row style="text-align: center">-->
<!-- &lt;!&ndash; 选择素材 &ndash;&gt;-->
<!-- <el-col :span="12">-->
<!-- <el-button type="success" @click="openMaterial">-->
<!-- 素材库选择<i class="el-icon-circle-check el-icon&#45;&#45;right"></i>-->
<!-- </el-button>-->
<!-- <el-dialog-->
<!-- title="选择视频"-->
<!-- v-model:visible="dialogVideoVisible"-->
<!-- width="90%"-->
<!-- append-to-body-->
<!-- >-->
<!-- <wx-material-select :objData="objData" @selectMaterial="selectMaterial" />-->
<!-- </el-dialog>-->
<!-- </el-col>-->
<!-- &lt;!&ndash; 文件上传 &ndash;&gt;-->
<!-- <el-col :span="12">-->
<!-- <el-upload-->
<!-- :action="actionUrl"-->
<!-- :headers="headers"-->
<!-- multiple-->
<!-- :limit="1"-->
<!-- :file-list="fileList"-->
<!-- :data="uploadData"-->
<!-- :before-upload="beforeVideoUpload"-->
<!-- :on-success="handleUploadSuccess"-->
<!-- >-->
<!-- <el-button type="primary"-->
<!-- >新建视频<i class="el-icon-upload el-icon&#45;&#45;right"></i-->
<!-- ></el-button>-->
<!-- </el-upload>-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- </el-row>-->
<!-- </el-tab-pane>-->
<!-- &lt;!&ndash; 类型 5图文 &ndash;&gt;-->
<!-- <el-tab-pane name="news">-->
<!-- <span slot="label"><i class="el-icon-news"></i> 图文</span>-->
<!-- <el-row>-->
<!-- <div class="select-item" v-if="objData.articles">-->
<!-- <wx-news :articles="objData.articles" />-->
<!-- <el-row class="ope-row">-->
<!-- <el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" />-->
<!-- </el-row>-->
<!-- </div>-->
<!-- &lt;!&ndash; 选择素材 &ndash;&gt;-->
<!-- <div v-if="!objData.content">-->
<!-- <el-row style="text-align: center">-->
<!-- <el-col :span="24">-->
<!-- <el-button type="success" @click="openMaterial"-->
<!-- >{{ newsType === '1' ? '选择已发布图文' : '选择草稿箱图文'-->
<!-- }}<i class="el-icon-circle-check el-icon&#45;&#45;right"></i-->
<!-- ></el-button>-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- </div>-->
<!-- <el-dialog title="选择图文" v-model:visible="dialogNewsVisible" width="90%" append-to-body>-->
<!-- <wx-material-select-->
<!-- :objData="objData"-->
<!-- @selectMaterial="selectMaterial"-->
<!-- :newsType="newsType"-->
<!-- />-->
<!-- </el-dialog>-->
<!-- </el-row>-->
<!-- </el-tab-pane>-->
<!-- &lt;!&ndash; 类型 6音乐 &ndash;&gt;-->
<!-- <el-tab-pane name="music">-->
<!-- <span slot="label"><i class="el-icon-service"></i> 音乐</span>-->
<!-- <el-row>-->
<!-- <el-col :span="6">-->
<!-- <div class="thumb-div">-->
<!-- <img style="width: 100px" v-if="objData.thumbMediaUrl" :src="objData.thumbMediaUrl" />-->
<!-- <i v-else class="el-icon-plus avatar-uploader-icon"></i>-->
<!-- <div class="thumb-but">-->
<!-- <el-upload-->
<!-- :action="actionUrl"-->
<!-- :headers="headers"-->
<!-- multiple-->
<!-- :limit="1"-->
<!-- :file-list="fileList"-->
<!-- :data="uploadData"-->
<!-- :before-upload="beforeThumbImageUpload"-->
<!-- :on-success="handleUploadSuccess"-->
<!-- >-->
<!-- <el-button slot="trigger" size="mini" type="text">本地上传</el-button>-->
<!-- <el-button size="mini" type="text" @click="openMaterial" style="margin-left: 5px"-->
<!-- >素材库选择</el-button-->
<!-- >-->
<!-- </el-upload>-->
<!-- </div>-->
<!-- </div>-->
<!-- <el-dialog-->
<!-- title="选择图片"-->
<!-- v-model:visible="dialogThumbVisible"-->
<!-- width="80%"-->
<!-- append-to-body-->
<!-- >-->
<!-- <wx-material-select-->
<!-- :objData="{ type: 'image', accountId: objData.accountId }"-->
<!-- @selectMaterial="selectMaterial"-->
<!-- />-->
<!-- </el-dialog>-->
<!-- </el-col>-->
<!-- <el-col :span="18">-->
<!-- <el-input v-model="objData.title" placeholder="请输入标题" @input="inputContent" />-->
<!-- <div style="margin: 20px 0"></div>-->
<!-- <el-input v-model="objData.description" placeholder="请输入描述" @input="inputContent" />-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- <div style="margin: 20px 0"></div>-->
<!-- <el-input v-model="objData.musicUrl" placeholder="请输入音乐链接" @input="inputContent" />-->
<!-- <div style="margin: 20px 0"></div>-->
<!-- <el-input-->
<!-- v-model="objData.hqMusicUrl"-->
<!-- placeholder="请输入高质量音乐链接"-->
<!-- @input="inputContent"-->
<!-- />-->
<!-- </el-tab-pane>-->
<!-- </el-tabs>-->
<!--</template>-->
<!--<script>-->
<!--import WxNews from '@/views/mp/components/wx-news/main.vue'-->
<!--import WxMaterialSelect from '@/views/mp/components/wx-material-select/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 { getAccessToken } from '@/utils/auth'-->
<!--export default {-->
<!-- name: 'WxReplySelect',-->
<!-- components: {-->
<!-- WxNews,-->
<!-- WxMaterialSelect,-->
<!-- WxVoicePlayer,-->
<!-- WxVideoPlayer-->
<!-- },-->
<!-- props: {-->
<!-- objData: {-->
<!-- // -->
<!-- type: Object, // Object 便-->
<!-- required: true-->
<!-- },-->
<!-- newsType: {-->
<!-- // 12稿-->
<!-- type: String,-->
<!-- default: '1'-->
<!-- }-->
<!-- },-->
<!-- data() {-->
<!-- return {-->
<!-- tempPlayerObj: {-->
<!-- type: '2'-->
<!-- },-->
<!-- tempObj: new Map().set(-->
<!-- // tab -->
<!-- this.objData.type, // -->
<!-- Object.assign({}, this.objData)-->
<!-- ), // -->
<!-- // ========== ==========-->
<!-- dialogNewsVisible: false, // -->
<!-- dialogImageVisible: false, // -->
<!-- dialogVoiceVisible: false, // -->
<!-- dialogVideoVisible: false, // -->
<!-- dialogThumbVisible: false, // -->
<!-- // ========== ==========-->
<!-- fileList: [], // -->
<!-- uploadData: {-->
<!-- accountId: undefined,-->
<!-- type: this.objData.type,-->
<!-- title: '',-->
<!-- introduction: ''-->
<!-- },-->
<!-- actionUrl: process.env.VUE_APP_BASE_API + '/admin-api/mp/material/upload-temporary',-->
<!-- headers: { Authorization: 'Bearer ' + getAccessToken() } // -->
<!-- }-->
<!-- },-->
<!-- methods: {-->
<!-- beforeThumbImageUpload(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) {-->
<!-- this.$message.error('上传图片格式不对!')-->
<!-- return false-->
<!-- }-->
<!-- const isLt = file.size / 1024 / 1024 < 2-->
<!-- if (!isLt) {-->
<!-- this.$message.error('上传图片大小不能超过 2M!')-->
<!-- return false-->
<!-- }-->
<!-- this.uploadData.accountId = this.objData.accountId-->
<!-- return true-->
<!-- },-->
<!-- beforeVoiceUpload(file) {-->
<!-- // -->
<!-- const isType =-->
<!-- file.type === 'audio/mp3' ||-->
<!-- file.type === 'audio/mpeg' ||-->
<!-- file.type === 'audio/wma' ||-->
<!-- file.type === 'audio/wav' ||-->
<!-- file.type === 'audio/amr'-->
<!-- if (!isType) {-->
<!-- this.$message.error('上传语音格式不对!' + file.type)-->
<!-- return false-->
<!-- }-->
<!-- // -->
<!-- const isLt = file.size / 1024 / 1024 < 2-->
<!-- if (!isLt) {-->
<!-- this.$message.error('上传语音大小不能超过 2M!')-->
<!-- return false-->
<!-- }-->
<!-- this.uploadData.accountId = this.objData.accountId-->
<!-- return true-->
<!-- },-->
<!-- 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) {-->
<!-- this.$message.error('上传图片格式不对!')-->
<!-- return false-->
<!-- }-->
<!-- // -->
<!-- const isLt = file.size / 1024 / 1024 < 2-->
<!-- if (!isLt) {-->
<!-- this.$message.error('上传图片大小不能超过 2M!')-->
<!-- return false-->
<!-- }-->
<!-- this.uploadData.accountId = this.objData.accountId-->
<!-- return true-->
<!-- },-->
<!-- beforeVideoUpload(file) {-->
<!-- // -->
<!-- const isType = file.type === 'video/mp4'-->
<!-- if (!isType) {-->
<!-- this.$message.error('上传视频格式不对!')-->
<!-- return false-->
<!-- }-->
<!-- // -->
<!-- const isLt = file.size / 1024 / 1024 < 10-->
<!-- if (!isLt) {-->
<!-- this.$message.error('上传视频大小不能超过 10M!')-->
<!-- return false-->
<!-- }-->
<!-- this.uploadData.accountId = this.objData.accountId-->
<!-- return true-->
<!-- },-->
<!-- handleUploadSuccess(response, file, fileList) {-->
<!-- if (response.code !== 0) {-->
<!-- this.$message.error('上传出错:' + response.msg)-->
<!-- return false-->
<!-- }-->
<!-- // -->
<!-- this.fileList = []-->
<!-- this.uploadData.title = ''-->
<!-- this.uploadData.introduction = ''-->
<!-- // -->
<!-- let item = response.data-->
<!-- this.selectMaterial(item)-->
<!-- },-->
<!-- /**-->
<!-- * 切换消息类型的 tab-->
<!-- *-->
<!-- * @param tab tab-->
<!-- */-->
<!-- handleClick(tab) {-->
<!-- // -->
<!-- this.uploadData.type = this.objData.type-->
<!-- if (this.uploadData.type === 'music') {-->
<!-- // -->
<!-- this.uploadData.type = 'thumb'-->
<!-- }-->
<!-- // tempObj objData-->
<!-- let tempObjItem = this.tempObj.get(this.objData.type)-->
<!-- if (tempObjItem) {-->
<!-- this.objData.content = tempObjItem.content ? tempObjItem.content : null-->
<!-- this.objData.mediaId = tempObjItem.mediaId ? tempObjItem.mediaId : null-->
<!-- this.objData.url = tempObjItem.url ? tempObjItem.url : null-->
<!-- this.objData.name = tempObjItem.url ? tempObjItem.name : null-->
<!-- this.objData.title = tempObjItem.title ? tempObjItem.title : null-->
<!-- this.objData.description = tempObjItem.description ? tempObjItem.description : null-->
<!-- return-->
<!-- }-->
<!-- // objData -->
<!-- // 使 $set input -->
<!-- this.$set(this.objData, 'content', '')-->
<!-- this.$delete(this.objData, 'mediaId')-->
<!-- this.$delete(this.objData, 'url')-->
<!-- this.$set(this.objData, 'title', '')-->
<!-- this.$set(this.objData, 'description', '')-->
<!-- },-->
<!-- /**-->
<!-- * 选择素材将设置设置到 objData 变量-->
<!-- *-->
<!-- * @param item 素材-->
<!-- */-->
<!-- selectMaterial(item) {-->
<!-- // -->
<!-- this.closeMaterial()-->
<!-- // tempObjItem -->
<!-- let tempObjItem = {}-->
<!-- tempObjItem.type = this.objData.type-->
<!-- if (this.objData.type === 'news') {-->
<!-- tempObjItem.articles = item.content.newsItem-->
<!-- this.objData.articles = item.content.newsItem-->
<!-- } else if (this.objData.type === 'music') {-->
<!-- // -->
<!-- tempObjItem.thumbMediaId = item.mediaId-->
<!-- this.objData.thumbMediaId = item.mediaId-->
<!-- tempObjItem.thumbMediaUrl = item.url-->
<!-- this.objData.thumbMediaUrl = item.url-->
<!-- // titleintroductionmusicUrlhqMusicUrl objData tempObjItem-->
<!-- tempObjItem.title = this.objData.title || ''-->
<!-- tempObjItem.introduction = this.objData.introduction || ''-->
<!-- tempObjItem.musicUrl = this.objData.musicUrl || ''-->
<!-- tempObjItem.hqMusicUrl = this.objData.hqMusicUrl || ''-->
<!-- } else if (this.objData.type === 'image' || this.objData.type === 'voice') {-->
<!-- tempObjItem.mediaId = item.mediaId-->
<!-- this.objData.mediaId = item.mediaId-->
<!-- tempObjItem.url = item.url-->
<!-- this.objData.url = item.url-->
<!-- tempObjItem.name = item.name-->
<!-- this.objData.name = item.name-->
<!-- } else if (this.objData.type === 'video') {-->
<!-- tempObjItem.mediaId = item.mediaId-->
<!-- this.objData.mediaId = item.mediaId-->
<!-- tempObjItem.url = item.url-->
<!-- this.objData.url = item.url-->
<!-- tempObjItem.name = item.name-->
<!-- this.objData.name = item.name-->
<!-- // titleintroduction item tempObjItem titleintroduction-->
<!-- if (item.title) {-->
<!-- this.objData.title = item.title || ''-->
<!-- tempObjItem.title = item.title || ''-->
<!-- }-->
<!-- if (item.introduction) {-->
<!-- this.objData.description = item.introduction || '' // 使 description使 introduction-->
<!-- tempObjItem.description = item.introduction || ''-->
<!-- }-->
<!-- } else if (this.objData.type === 'text') {-->
<!-- this.objData.content = item.content || ''-->
<!-- }-->
<!-- // -->
<!-- this.tempObj.set(this.objData.type, tempObjItem)-->
<!-- },-->
<!-- openMaterial() {-->
<!-- if (this.objData.type === 'news') {-->
<!-- this.dialogNewsVisible = true-->
<!-- } else if (this.objData.type === 'image') {-->
<!-- this.dialogImageVisible = true-->
<!-- } else if (this.objData.type === 'voice') {-->
<!-- this.dialogVoiceVisible = true-->
<!-- } else if (this.objData.type === 'video') {-->
<!-- this.dialogVideoVisible = true-->
<!-- } else if (this.objData.type === 'music') {-->
<!-- this.dialogThumbVisible = true-->
<!-- }-->
<!-- },-->
<!-- closeMaterial() {-->
<!-- this.dialogNewsVisible = false-->
<!-- this.dialogImageVisible = false-->
<!-- this.dialogVoiceVisible = false-->
<!-- this.dialogVideoVisible = false-->
<!-- this.dialogThumbVisible = false-->
<!-- },-->
<!-- deleteObj() {-->
<!-- if (this.objData.type === 'news') {-->
<!-- this.$delete(this.objData, 'articles')-->
<!-- } else if (this.objData.type === 'image') {-->
<!-- this.objData.mediaId = null-->
<!-- this.$delete(this.objData, 'url')-->
<!-- this.objData.name = null-->
<!-- } else if (this.objData.type === 'voice') {-->
<!-- this.objData.mediaId = null-->
<!-- this.$delete(this.objData, 'url')-->
<!-- this.objData.name = null-->
<!-- } else if (this.objData.type === 'video') {-->
<!-- this.objData.mediaId = null-->
<!-- this.$delete(this.objData, 'url')-->
<!-- this.objData.name = null-->
<!-- this.objData.title = null-->
<!-- this.objData.description = null-->
<!-- } else if (this.objData.type === 'music') {-->
<!-- this.objData.thumbMediaId = null-->
<!-- this.objData.thumbMediaUrl = null-->
<!-- this.objData.title = null-->
<!-- this.objData.description = null-->
<!-- this.objData.musicUrl = null-->
<!-- this.objData.hqMusicUrl = null-->
<!-- } else if (this.objData.type === 'text') {-->
<!-- this.objData.content = null-->
<!-- }-->
<!-- // -->
<!-- this.tempObj.set(this.objData.type, Object.assign({}, this.objData))-->
<!-- },-->
<!-- /**-->
<!-- * 输入时缓存每次 objData tempObj -->
<!-- *-->
<!-- * why不确定为什么 v-model="objData.content" 不能自动缓存所以通过这样的方式-->
<!-- */-->
<!-- inputContent(str) {-->
<!-- // -->
<!-- this.tempObj.set(this.objData.type, Object.assign({}, this.objData))-->
<!-- }-->
<!-- }-->
<!--}-->
<!--</script>-->
<!--<style lang="scss" scoped>-->
<!--.public-account-management {-->
<!-- .el-input {-->
<!-- width: 70%;-->
<!-- margin-right: 2%;-->
<!-- }-->
<!--}-->
<!--.pagination {-->
<!-- text-align: right;-->
<!-- margin-right: 25px;-->
<!--}-->
<!--.select-item {-->
<!-- width: 280px;-->
<!-- padding: 10px;-->
<!-- margin: 0 auto 10px auto;-->
<!-- border: 1px solid #eaeaea;-->
<!--}-->
<!--.select-item2 {-->
<!-- padding: 10px;-->
<!-- margin: 0 auto 10px auto;-->
<!-- border: 1px solid #eaeaea;-->
<!--}-->
<!--.ope-row {-->
<!-- padding-top: 10px;-->
<!-- text-align: center;-->
<!--}-->
<!--.item-name {-->
<!-- font-size: 12px;-->
<!-- overflow: hidden;-->
<!-- text-overflow: ellipsis;-->
<!-- white-space: nowrap;-->
<!-- text-align: center;-->
<!--}-->
<!--.el-form-item__content {-->
<!-- line-height: unset !important;-->
<!--}-->
<!--.col-select {-->
<!-- border: 1px solid rgb(234, 234, 234);-->
<!-- padding: 50px 0px;-->
<!-- height: 160px;-->
<!-- width: 49.5%;-->
<!--}-->
<!--.col-select2 {-->
<!-- border: 1px solid rgb(234, 234, 234);-->
<!-- padding: 50px 0px;-->
<!-- height: 160px;-->
<!--}-->
<!--.col-add {-->
<!-- border: 1px solid rgb(234, 234, 234);-->
<!-- padding: 50px 0px;-->
<!-- height: 160px;-->
<!-- width: 49.5%;-->
<!-- float: right;-->
<!--}-->
<!--.avatar-uploader-icon {-->
<!-- border: 1px solid #d9d9d9;-->
<!-- font-size: 28px;-->
<!-- color: #8c939d;-->
<!-- width: 100px !important;-->
<!-- height: 100px !important;-->
<!-- line-height: 100px !important;-->
<!-- text-align: center;-->
<!--}-->
<!--.material-img {-->
<!-- width: 100%;-->
<!--}-->
<!--.thumb-div {-->
<!-- display: inline-block;-->
<!-- text-align: center;-->
<!--}-->
<!--.item-infos {-->
<!-- width: 30%;-->
<!-- margin: auto;-->
<!--}-->
<!--</style>-->

View File

@ -0,0 +1,117 @@
<!--
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
微信消息 - 视频
芋道源码
bug 修复
1joolun 的做法使用 mediaId 从微信公众号下载对应的 mp4 素材从而播放内容
存在的问题mediaId 有效期是 3 超过时间后无法播放
2重构后的做法后端接收到微信公众号的视频消息后将视频消息的 media_id 的文件内容保存到文件服务器中这样前端可以直接使用 URL 播放
体验优化弹窗关闭后自动暂停视频的播放
-->
<template>
<div>
<!-- 提示 -->
<div @click="playVideo()">
<el-icon>
<VideoPlay />
</el-icon>
<p>点击播放视频</p>
</div>
<!-- 弹窗播放 -->
<el-dialog
title="视频播放"
v-model:visible="dialogVideo"
width="40%"
append-to-body
@close="closeDialog"
>
<video-player
v-if="playerOptions.sources[0].src"
class="video-player vjs-custom-skin"
ref="videoPlayerRef"
:playsinline="true"
:options="playerOptions"
@play="onPlayerPlay($event)"
@pause="onPlayerPause($event)"
/>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="WxVideoPlayer">
// videoPlayer https://juejin.cn/post/6923056942281654285
import { videoPlayer } from 'vue-video-player'
import 'video.js/dist/video-js.css'
import 'vue-video-player/src/custom-theme.css'
import { VideoPlay } from '@element-plus/icons-vue'
const props = defineProps({
url: {
// https://www.iocoder.cn/xxx.mp4
type: String,
required: true
}
})
const videoPlayerRef = ref()
const dialogVideo = ref(false)
const playerOptions = reactive({
playbackRates: [0.5, 1.0, 1.5, 2.0], //
autoplay: false, // true,
muted: false, //
loop: false, //
preload: 'auto', // <video> auto ,
language: 'zh-CN',
aspectRatio: '16:9', // 使 - "16:9""4:3"
fluid: true, // trueVideo.js player
sources: [
{
type: 'video/mp4',
src: '' //
}
],
poster: '', //
width: document.documentElement.clientWidth,
notSupportedMessage: '此视频暂无法播放,请稍后再试', // Video.js
controlBar: {
timeDivider: true,
durationDisplay: true,
remainingTimeDisplay: false,
fullscreenToggle: true //
}
})
const playVideo = () => {
dialogVideo.value = true
playerOptions.sources[0].src = props.url
}
const closeDialog = () => {
//
// videoPlayerRef.player.pause()
}
// onPlayerPlay(player) {},
// // // eslint-disable-next-line @typescript-eslint/no-unused-vars
// // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
// onPlayerPause(player) {}
// methods: {
// playVideo() {
// this.dialogVideo = true
// //
// this.playerOptions.sources[0]['src'] = this.url
// },
// closeDialog() {
// //
// this.$refs.videoPlayer.player.pause()
// },
//
// //todo player
//
// // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
// onPlayerPlay(player) {},
// // // eslint-disable-next-line @typescript-eslint/no-unused-vars
// // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
// onPlayerPause(player) {}
// }
</script>

View File

@ -0,0 +1,97 @@
<!--
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
微信消息 - 语音
芋道源码
bug 修复
1joolun 的做法使用 mediaId 从微信公众号下载对应的 mp4 素材从而播放内容
存在的问题mediaId 有效期是 3 超过时间后无法播放
2重构后的做法后端接收到微信公众号的视频消息后将视频消息的 media_id 的文件内容保存到文件服务器中这样前端可以直接使用 URL 播放
代码优化 props 中的 objData 调成为 data 中对应的属性并补充相关注释
-->
<template>
<div class="wx-voice-div" @click="playVoice">
<el-icon>
<Icon v-if="playing !== true" icon="ep:video-play" />
<Icon v-else icon="ep:video-pause" />
<span class="amr-duration" v-if="duration">{{ duration }} </span>
</el-icon>
<div v-if="content">
<el-tag type="success" size="mini">语音识别</el-tag>
{{ content }}
</div>
</div>
</template>
<script setup lang="ts" name="WxVoicePlayer">
// amr amr https://www.npmjs.com/package/benz-amr-recorder
import BenzAMRRecorder from 'benz-amr-recorder'
const props = defineProps({
url: {
type: String, // https://www.iocoder.cn/xxx.amr
required: true
},
content: {
type: String, //
required: false
}
})
const amr = ref()
const playing = ref(false)
const duration = ref()
/** 处理点击,播放或暂停 */
const playVoice = () => {
// BenzAMRRecorder
if (amr.value === undefined) {
amrInit()
return
}
//
if (amr.value.isPlaying()) {
amrStop()
} else {
amrPlay()
}
}
/** 音频初始化 */
const amrInit = () => {
amr.value = new BenzAMRRecorder()
//
amr.value.initWithUrl(props.url).then(function () {
amrPlay()
duration.value = amr.value.getDuration()
})
//
amr.value.onEnded(function () {
playing.value = false
})
}
/** 音频播放 */
const amrPlay = () => {
playing.value = true
amr.value.play()
}
/** 音频暂停 */
const amrStop = () => {
playing.value = false
amr.value.stop()
}
// TODO
</script>
<style lang="scss" scoped>
.wx-voice-div {
padding: 5px;
background-color: #eaeaea;
border-radius: 10px;
}
.amr-duration {
font-size: 11px;
margin-left: 5px;
}
</style>

View File

@ -1,3 +1,392 @@
<template>
<span>开发中</span>
<!-- 搜索工作栏 -->
<content-wrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
: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" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
</el-form-item>
</el-form>
</content-wrap>
<!-- 列表 -->
<content-wrap>
<div class="waterfall" v-loading="loading">
<div
class="waterfall-item"
v-show="item.content && item.content.newsItem"
v-for="item in list"
:key="item.articleId"
>
<wx-news :articles="item.content.newsItem" />
<!-- 操作 -->
<el-row justify="center" class="ope-row">
<el-button
type="danger"
circle
@click="handleDelete(item)"
v-hasPermi="['mp:free-publish:delete']"
>
<Icon icon="ep:delete" />
</el-button>
</el-row>
</div>
</div>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</content-wrap>
</template>
<script setup lang="ts" name="freePublish">
import { getFreePublishPage, deleteFreePublish } from '@/api/mp/freePublish'
import * as MpAccountApi from '@/api/mp/account'
import WxNews from '@/views/mp/components/wx-news/main.vue'
const message = useMessage() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
currentPage: 1, //
pageNo: 1, //
accountId: undefined //
})
const queryFormRef = ref() //
const accountList = ref<MpAccountApi.AccountVO[]>([]) //
/** 查询列表 */
const getList = async () => {
//
if (!queryParams.accountId) {
message.error('未选中公众号,无法查询已发表图文')
return false
}
// TODO await
loading.value = true
getFreePublishPage(queryParams)
.then((data) => {
console.log(data)
// thumbUrl picUrl wx-news
data.list.forEach((item) => {
console.log(item)
const newsItem = item.content.newsItem
newsItem.forEach((article) => {
article.picUrl = article.thumbUrl
})
})
list.value = data.list
total.value = data.total
})
.finally(() => {
loading.value = false
})
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
//
if (accountList.value.length > 0) {
// @ts-ignore
queryParams.accountId = accountList.value[0].id
}
handleQuery()
}
/** 删除按钮操作 */
const handleDelete = async (item) => {
{
// TODO await
const articleId = item.articleId
const accountId = queryParams.accountId
message
.confirm('删除后用户将无法访问此页面,确定删除?')
.then(function () {
return deleteFreePublish(accountId, articleId)
})
.then(() => {
getList()
message.success('删除成功')
})
.catch(() => {})
}
}
onMounted(async () => {
accountList.value = await MpAccountApi.getSimpleAccountList()
//
if (accountList.value.length > 0) {
// @ts-ignore
queryParams.accountId = accountList.value[0].id
}
await getList()
})
</script>
<style lang="scss" scoped>
.pagination {
float: right;
margin-right: 25px;
}
.add_but {
padding: 10px;
}
.ope-row {
margin-top: 5px;
text-align: center;
border-top: 1px solid #eaeaea;
padding-top: 5px;
}
.item-name {
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
.el-upload__tip {
margin-left: 5px;
}
/* 新增图文 */
.left {
display: inline-block;
width: 35%;
vertical-align: top;
margin-top: 200px;
}
.right {
display: inline-block;
width: 60%;
margin-top: -40px;
}
.avatar-uploader {
width: 20%;
display: inline-block;
}
.avatar-uploader .el-upload {
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
text-align: unset !important;
}
.avatar-uploader .el-upload:hover {
border-color: #165dff;
}
.avatar-uploader-icon {
border: 1px solid #d9d9d9;
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
}
.avatar {
width: 230px;
height: 120px;
}
.avatar1 {
width: 120px;
height: 120px;
}
.digest {
width: 60%;
display: inline-block;
vertical-align: top;
}
/*新增图文*/
/*瀑布流样式*/
.waterfall {
width: 100%;
column-gap: 10px;
column-count: 5;
margin: 0 auto;
}
.waterfall-item {
padding: 10px;
margin-bottom: 10px;
break-inside: avoid;
border: 1px solid #eaeaea;
}
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;
}
}
/*瀑布流样式*/
.news-main {
background-color: #ffffff;
width: 100%;
margin: auto;
height: 120px;
}
.news-content {
background-color: #acadae;
width: 100%;
height: 120px;
position: relative;
}
.news-content-title {
display: inline-block;
font-size: 15px;
color: #ffffff;
position: absolute;
left: 0px;
bottom: 0px;
background-color: black;
width: 98%;
padding: 1%;
opacity: 0.65;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
height: 25px;
}
.news-main-item {
background-color: #ffffff;
padding: 5px 0px;
border-top: 1px solid #eaeaea;
width: 100%;
margin: auto;
}
.news-content-item {
position: relative;
margin-left: -3px;
}
.news-content-item-title {
display: inline-block;
font-size: 12px;
width: 70%;
}
.news-content-item-img {
display: inline-block;
width: 25%;
background-color: #acadae;
}
.input-tt {
padding: 5px;
}
.activeAddNews {
border: 5px solid #2bb673;
}
.news-main-plus {
width: 280px;
text-align: center;
margin: auto;
height: 50px;
}
.icon-plus {
margin: 10px;
font-size: 25px;
}
.select-item {
width: 60%;
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.father .child {
display: none;
text-align: center;
position: relative;
bottom: 25px;
}
.father:hover .child {
display: block;
}
.thumb-div {
display: inline-block;
width: 30%;
text-align: center;
}
.thumb-but {
margin: 5px;
}
.material-img {
width: 100%;
height: 100%;
}
</style>

View File

@ -1,3 +1,261 @@
<template>
<span>开发中</span>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
: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 label="消息类型" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择消息类型" class="!w-240px">
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.MP_MESSAGE_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="用户标识" prop="openid">
<el-input
v-model="queryParams.openid"
placeholder="请输入用户标识"
clearable
:v-on="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
style="width: 240px"
value-format="yyyy-MM-dd HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="['00:00:00', '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-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column
label="发送时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="消息类型" align="center" prop="type" width="80" />
<el-table-column label="发送方" align="center" prop="sendFrom" width="80">
<template #default="scope">
<el-tag v-if="scope.row.sendFrom === 1" type="success">粉丝</el-tag>
<el-tag v-else type="info">公众号</el-tag>
</template>
</el-table-column>
<el-table-column label="用户标识" align="center" prop="openid" width="300" />
<el-table-column label="内容" prop="content">
<template #default="scope">
<!-- 事件区域 -->
<div v-if="scope.row.type === 'event' && scope.row.event === 'subscribe'">
<el-tag type="success">关注</el-tag>
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'unsubscribe'">
<el-tag type="danger">取消关注</el-tag>
</div>
<div v-else-if="scope.row.type === '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'">
<el-tag>点击菜单链接</el-tag>{{ scope.row.eventKey }}
</div>
<div v-else-if="scope.row.type === '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'">
<el-tag>扫码结果</el-tag>{{ scope.row.eventKey }}
</div>
<div v-else-if="scope.row.type === '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'">
<el-tag>拍照或者相册</el-tag>
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_weixin'">
<el-tag>微信相册</el-tag>
</div>
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'location_select'">
<el-tag>选择地理位置</el-tag>
</div>
<div v-else-if="scope.row.type === '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'">
<wx-voice-player :url="scope.row.mediaUrl" :content="scope.row.recognition" />
</div>
<div v-else-if="scope.row.type === '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'">
<wx-video-player :url="scope.row.mediaUrl" style="margin-top: 10px" />
</div>
<div v-else-if="scope.row.type === '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
: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
:title="scope.row.title"
:description="scope.row.description"
:thumb-media-url="scope.row.thumbMediaUrl"
:music-url="scope.row.musicUrl"
:hq-music-url="scope.row.hqMusicUrl"
/>
</div>
<div v-else-if="scope.row.type === 'news'">
<wx-news :articles="scope.row.articles" />
</div>
<div v-else>
<el-tag type="danger">未知消息类型</el-tag>
</div>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button
link
type="primary"
@click="handleSend(scope.row)"
v-hasPermi="['mp:message:send']"
>
消息
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 发送消息的弹窗 -->
<el-dialog title="粉丝消息列表" v-model:visible="open" width="50%">
<wx-msg :user-id="userId" v-if="open" />
</el-dialog>
</ContentWrap>
</template>
<script setup lang="ts" name="MpMessage">
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
// import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
// 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 * as MpMessageApi from '@/api/mp/message'
const message = useMessage() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
openid: null,
accountId: null,
type: null,
createTime: []
})
const queryFormRef = ref() //
// TODO
const open = ref(false) //
const userId = ref(0) //
const accountList = ref<MpAccountApi.AccountVO[]>([]) //
/** 查询参数列表 */
const getList = async () => {
//
if (!queryParams.accountId) {
await message.error('未选中公众号,无法查询消息')
return
}
try {
loading.value = true
const data = await MpMessageApi.getMessagePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = async () => {
queryFormRef.value.resetFields()
//
if (accountList.value.length > 0) {
// @ts-ignore
queryParams.accountId = accountList.value[0].id
}
handleQuery()
}
const handleSend = async (row) => {
userId.value = row.userId
open.value = true
}
/** 初始化 **/
onMounted(async () => {
accountList.value = await MpAccountApi.getSimpleAccountList()
//
if (accountList.value.length > 0) {
// @ts-ignore
queryParams.accountId = accountList.value[0].id
}
await getList()
})
</script>

View File

@ -0,0 +1,91 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="80px"
v-loading="formLoading"
>
<el-form-item label="标签名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入标签名称" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import * as MpTagApi from '@/api/mp/tag'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
accountId: -1,
name: ''
})
const formRules = reactive({
name: [{ required: true, message: '请输入标签名称', trigger: 'blur' }]
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, accountId: number, id?: number) => {
modelVisible.value = true
modelTitle.value = t('action.' + type)
formType.value = type
resetForm()
formData.value.accountId = accountId
//
if (id) {
formLoading.value = true
try {
formData.value = await MpTagApi.getTag(id)
} finally {
formLoading.value = false
}
}
}
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 MpTagApi.TagVO
if (formType.value === 'create') {
await MpTagApi.createTag(data)
message.success(t('common.createSuccess'))
} else {
await MpTagApi.updateTag(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
accountId: -1,
name: ''
}
formRef.value?.resetFields()
}
</script>

183
src/views/mp/tag/index.vue Normal file
View File

@ -0,0 +1,183 @@
<template>
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
: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="parseInt(item.id)"
:label="item.name"
:value="parseInt(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"
/>
</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" plain @click="openForm('create')" v-hasPermi="['mp:tag:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button type="success" plain @click="handleSync" v-hasPermi="['mp:tag:sync']">
<Icon icon="ep:refresh" class="mr-5px" /> 同步
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<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="count" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['mp:tag:update']"
>
修改
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['mp:tag: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>
<!-- 表单弹窗添加/修改 -->
<TagForm ref="formRef" @success="getList" />
</template>
<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'
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
accountId: undefined,
name: null
})
const queryFormRef = ref() //
const accountList = ref<MpAccountApi.AccountVO[]>([]) //
/** 查询参数列表 */
const getList = async () => {
//
if (!queryParams.accountId) {
await message.error('未选中公众号,无法查询标签')
return
}
try {
loading.value = true
const data = await MpTagApi.getTagPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
//
if (accountList.value.length > 0) {
// @ts-ignore
queryParams.accountId = accountList.value[0].id
}
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, queryParams.accountId, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await MpTagApi.deleteTag(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 同步操作 */
const handleSync = async () => {
try {
await message.confirm('是否确认同步标签?')
// @ts-ignore
await MpTagApi.syncTag(queryParams.accountId)
message.success('同步标签成功')
await getList()
} catch {}
}
/** 初始化 **/
onMounted(async () => {
accountList.value = await MpAccountApi.getSimpleAccountList()
//
if (accountList.value.length > 0) {
// @ts-ignore
queryParams.accountId = accountList.value[0].id
}
await getList()
})
</script>

View File

@ -15,10 +15,8 @@
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</template>
</Dialog>
</template>

View File

@ -0,0 +1,174 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="80px"
v-loading="formLoading"
>
<el-form-item label="上级部门" prop="parentId">
<el-tree-select
v-model="formData.parentId"
:data="deptTree"
:props="{ value: 'id', label: 'name', children: 'children' }"
value-key="deptId"
placeholder="请选择上级部门"
check-strictly
default-expand-all
/>
</el-form-item>
<el-form-item label="部门名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入部门名称" />
</el-form-item>
<el-form-item label="显示排序" prop="sort">
<el-input-number v-model="formData.sort" controls-position="right" :min="0" />
</el-form-item>
<el-form-item label="负责人" prop="leaderUserId">
<el-select
v-model="formData.leaderUserId"
placeholder="请输入负责人"
clearable
style="width: 100%"
>
<el-option
v-for="item in userList"
:key="item.id"
:label="item.nickname"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="联系电话" prop="phone">
<el-input v-model="formData.phone" placeholder="请输入联系电话" maxlength="11" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="formData.email" placeholder="请输入邮箱" maxlength="50" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="formData.status" placeholder="请选择状态" clearable>
<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>
<template #footer>
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { handleTree } from '@/utils/tree'
import * as DeptApi from '@/api/system/dept'
import * as UserApi from '@/api/system/user'
import { CommonStatusEnum } from '@/utils/constants'
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
title: '',
parentId: undefined,
name: undefined,
sort: undefined,
leaderUserId: undefined,
phone: undefined,
email: undefined,
status: CommonStatusEnum.ENABLE
})
const formRules = reactive({
parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }],
name: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
sort: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }],
email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
phone: [
{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
]
})
const formRef = ref() // Ref
const deptTree = ref() //
const userList = ref<UserApi.UserVO[]>([]) //
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
modelVisible.value = true
modelTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await DeptApi.getDeptApi(id)
} finally {
formLoading.value = false
}
}
//
userList.value = await UserApi.getSimpleUserList()
//
await getTree()
}
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 DeptApi.DeptVO
if (formType.value === 'create') {
await DeptApi.createDeptApi(data)
message.success(t('common.createSuccess'))
} else {
await DeptApi.updateDeptApi(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
title: '',
parentId: undefined,
name: undefined,
sort: undefined,
leaderUserId: undefined,
phone: undefined,
email: undefined,
status: CommonStatusEnum.ENABLE
}
formRef.value?.resetFields()
}
/** 获得部门树 */
const getTree = async () => {
deptTree.value = []
const data = await DeptApi.getSimpleDeptList()
let dept: Tree = { id: 0, name: '顶级部门', children: [] }
dept.children = handleTree(data)
deptTree.value.push(dept)
}
</script>

View File

@ -1,84 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
const { t } = useI18n() // 国际化
// 表单校验
export const rules = reactive({
name: [required],
sort: [required],
// email: [required],
email: [
{ required: true, message: t('profile.rules.mail'), trigger: 'blur' },
{
type: 'email',
message: t('profile.rules.truemail'),
trigger: ['blur', 'change']
}
],
phone: [
{
len: 11,
trigger: 'blur',
message: '请输入正确的手机号码'
}
]
})
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: null,
action: true,
columns: [
{
title: '上级部门',
field: 'parentId',
isTable: false
},
{
title: '部门名称',
field: 'name',
isSearch: true,
table: {
treeNode: true,
align: 'left'
}
},
{
title: '负责人',
field: 'leaderUserId',
table: {
slots: {
default: 'leaderUserId_default'
}
}
},
{
title: '联系电话',
field: 'phone'
},
{
title: '邮箱',
field: 'email',
isTable: false
},
{
title: '显示排序',
field: 'sort'
},
{
title: t('common.status'),
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
dictClass: 'number',
isSearch: true
},
{
title: t('common.createTime'),
field: 'createTime',
formatter: 'formatDate',
isForm: false
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -1,189 +1,188 @@
<template>
<!-- 搜索工作栏 -->
<ContentWrap>
<!-- 列表 -->
<XTable ref="xGrid" @register="registerTable" show-overflow>
<template #toolbar_buttons>
<!-- 操作新增 -->
<XButton
type="primary"
preIcon="ep:zoom-in"
:title="t('action.add')"
v-hasPermi="['system:dept:create']"
@click="handleCreate()"
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="部门名称" prop="title">
<el-input
v-model="queryParams.name"
placeholder="请输入部门名称"
clearable
class="!w-240px"
/>
<XButton title="展开所有" @click="xGrid?.Ref.setAllTreeExpand(true)" />
<XButton title="关闭所有" @click="xGrid?.Ref.clearTreeExpand()" />
</template>
<template #leaderUserId_default="{ row }">
<span>{{ userNicknameFormat(row) }}</span>
</template>
<template #actionbtns_default="{ row }">
<!-- 操作修改 -->
<XTextButton
preIcon="ep:edit"
:title="t('action.edit')"
v-hasPermi="['system:dept:update']"
@click="handleUpdate(row.id)"
/>
<!-- 操作删除 -->
<XTextButton
preIcon="ep:delete"
:title="t('action.del')"
v-hasPermi="['system:dept:delete']"
@click="deleteData(row.id)"
/>
</template>
</XTable>
</ContentWrap>
<!-- 添加或修改菜单对话框 -->
<XModal id="deptModel" v-model="dialogVisible" :title="dialogTitle">
<!-- 对话框(添加 / 修改) -->
<Form ref="formRef" :schema="allSchemas.formSchema" :rules="rules">
<template #parentId="form">
<el-tree-select
node-key="id"
v-model="form['parentId']"
:props="defaultProps"
:data="deptOptions"
:default-expanded-keys="[100]"
check-strictly
/>
</template>
<template #leaderUserId="form">
<el-select v-model="form['leaderUserId']">
</el-form-item>
<el-form-item label="部门状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择不么你状态"
clearable
class="!w-240px"
>
<el-option
v-for="item in userOption"
:key="item.id"
:label="item.nickname"
:value="item.id"
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</template>
</Form>
<template #footer>
<!-- 按钮保存 -->
<XButton
v-if="['create', 'update'].includes(actionType)"
type="primary"
:loading="actionLoading"
@click="submitForm()"
:title="t('action.save')"
</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"
plain
@click="openForm('create')"
v-hasPermi="['system:dept:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button type="danger" plain @click="toggleExpandAll">
<Icon icon="ep:sort" class="mr-5px" /> 展开/折叠
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table
v-loading="loading"
:data="list"
row-key="id"
v-if="refreshTable"
:default-expand-all="isExpandAll"
>
<el-table-column prop="name" label="部门名称" width="260" />
<el-table-column prop="leader" label="负责人" width="120">
<template #default="scope">
{{ userList.find((user) => user.id === scope.row.leaderUserId)?.nickname }}
</template>
</el-table-column>
<el-table-column prop="sort" label="排序" width="200" />
<el-table-column prop="status" label="状态" width="100">
<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="createTime"
width="180"
:formatter="dateFormatter"
/>
<!-- 按钮关闭 -->
<XButton :loading="actionLoading" @click="dialogVisible = false" :title="t('dialog.close')" />
</template>
</XModal>
<el-table-column label="操作" align="center" class-name="fixed-width">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['system:dept:update']"
>
修改
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['system:dept:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<DeptForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="Dept">
import { handleTree, defaultProps } from '@/utils/tree'
import type { FormExpose } from '@/components/Form'
import { allSchemas, rules } from './dept.data'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { handleTree } from '@/utils/tree'
import * as DeptApi from '@/api/system/dept'
import { getListSimpleUsersApi, UserVO } from '@/api/system/user'
const { t } = useI18n() //
import DeptForm from './DeptForm.vue'
import * as UserApi from '@/api/system/user'
const message = useMessage() //
//
const xGrid = ref<any>() // Grid Ref
const treeConfig = {
transform: true,
rowField: 'id',
parentField: 'parentId',
expandAll: true
}
const { t } = useI18n() //
//
const dialogVisible = ref(false) //
const dialogTitle = ref('edit') //
const actionType = ref('') //
const actionLoading = ref(false) //
const formRef = ref<FormExpose>() // Ref
const deptOptions = ref() //
const userOption = ref<UserVO[]>([])
const getUserList = async () => {
const res = await getListSimpleUsersApi()
userOption.value = res
}
// []
const getTree = async () => {
deptOptions.value = []
const res = await DeptApi.listSimpleDeptApi()
let dept: Tree = { id: 0, name: '顶级部门', children: [] }
dept.children = handleTree(res)
deptOptions.value.push(dept)
}
const [registerTable, { reload, deleteData }] = useXTable({
allSchemas: allSchemas,
treeConfig: treeConfig,
getListApi: DeptApi.getDeptPageApi,
deleteApi: DeptApi.deleteDeptApi
const loading = ref(true) //
const list = ref() //
const queryParams = reactive({
title: '',
name: undefined,
status: undefined,
pageNo: 1,
pageSize: 100
})
// ========== / ==========
const queryFormRef = ref() //
const isExpandAll = ref(true) //
const refreshTable = ref(true) //
const userList = ref<UserApi.UserVO[]>([]) //
//
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 DeptApi.getDeptPageApi(queryParams)
list.value = handleTree(data)
} finally {
loading.value = false
}
}
//
const handleCreate = async () => {
setDialogTile('create')
}
//
const handleUpdate = async (rowId: number) => {
setDialogTile('update')
//
const res = await DeptApi.getDeptApi(rowId)
await nextTick()
unref(formRef)?.setValues(res)
}
// /
const submitForm = async () => {
const elForm = unref(formRef)?.getElFormRef()
if (!elForm) return
elForm.validate(async (valid) => {
if (valid) {
actionLoading.value = true
//
try {
const data = unref(formRef)?.formModel as DeptApi.DeptVO
if (actionType.value === 'create') {
await DeptApi.createDeptApi(data)
message.success(t('common.createSuccess'))
} else if (actionType.value === 'update') {
await DeptApi.updateDeptApi(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
} finally {
actionLoading.value = false
await getTree()
await reload()
}
}
/** 展开/折叠操作 */
const toggleExpandAll = () => {
refreshTable.value = false
isExpandAll.value = !isExpandAll.value
console.log(isExpandAll.value)
nextTick(() => {
refreshTable.value = true
})
}
const userNicknameFormat = (row) => {
if (!row || !row.leaderUserId) {
return '未设置'
}
for (const user of userOption.value) {
if (row.leaderUserId === user.id) {
return user.nickname
}
}
return '未知【' + row.leaderUserId + '】'
/** 搜索按钮操作 */
const handleQuery = () => {
getList()
}
// ========== ==========
/** 重置按钮操作 */
const resetQuery = () => {
queryParams.pageNo = 1
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await DeptApi.deleteDeptApi(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 初始化 **/
onMounted(async () => {
await getUserList()
await getTree()
await getList()
//
userList.value = await UserApi.getSimpleUserList()
})
</script>

View File

@ -51,10 +51,8 @@
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</template>
</Dialog>
</template>

View File

@ -33,10 +33,8 @@
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</template>
</Dialog>
</template>

View File

@ -21,10 +21,8 @@
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</template>
</Dialog>
</template>

View File

@ -99,7 +99,7 @@
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<el-table-column label="操作" align="center" class-name="small-paddingfixed-width">
<template #default="scope">
<el-button
link

View File

@ -0,0 +1,49 @@
<template>
<Dialog title="详情" v-model="modelVisible" width="800">
<el-descriptions border :column="1">
<el-descriptions-item label="日志编号" min-width="120">
{{ detailData.id }}
</el-descriptions-item>
<el-descriptions-item label="操作类型">
<dict-tag :type="DICT_TYPE.SYSTEM_LOGIN_TYPE" :value="detailData.logType" />
</el-descriptions-item>
<el-descriptions-item label="用户名称">
{{ detailData.username }}
</el-descriptions-item>
<el-descriptions-item label="登录地址">
{{ detailData.userIp }}
</el-descriptions-item>
<el-descriptions-item label="浏览器">
{{ detailData.userAgent }}
</el-descriptions-item>
<el-descriptions-item label="登陆结果">
<dict-tag :type="DICT_TYPE.SYSTEM_LOGIN_RESULT" :value="detailData.result" />
</el-descriptions-item>
<el-descriptions-item label="登录日期">
{{ formatDate(detailData.createTime) }}
</el-descriptions-item>
</el-descriptions>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE } from '@/utils/dict'
import { formatDate } from '@/utils/formatTime'
import * as LoginLogApi from '@/api/system/loginLog'
const modelVisible = ref(false) //
const detailLoading = ref(false) //
const detailData = ref() //
/** 打开弹窗 */
const open = async (data: LoginLogApi.LoginLogVO) => {
modelVisible.value = true
//
detailLoading.value = true
try {
detailData.value = data
} finally {
detailLoading.value = false
}
}
defineExpose({ open }) // open
</script>

View File

@ -1,53 +1,175 @@
<template>
<ContentWrap>
<!-- 列表 -->
<XTable @register="registerTable">
<!-- 操作导出 -->
<template #toolbar_buttons>
<XButton
type="warning"
preIcon="ep:download"
:title="t('action.export')"
@click="exportList('登录列表.xls')"
<content-wrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="用户名称" prop="username">
<el-input
v-model="queryParams.username"
placeholder="请输入用户名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</template>
<template #actionbtns_default="{ row }">
<!-- 操作详情 -->
<XTextButton preIcon="ep:view" :title="t('action.detail')" @click="handleDetail(row)" />
</template>
</XTable>
</ContentWrap>
<!-- 弹窗 -->
<XModal id="postModel" v-model="dialogVisible" :title="dialogTitle">
<!-- 表单详情 -->
<Descriptions :schema="allSchemas.detailSchema" :data="detailData" />
<template #footer>
<!-- 按钮关闭 -->
<XButton :title="t('dialog.close')" @click="dialogVisible = false" />
</template>
</XModal>
</el-form-item>
<el-form-item label="登录地址" prop="userIp">
<el-input
v-model="queryParams.userIp"
placeholder="请输入登录地址"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</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="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['infra:config:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</content-wrap>
<!-- 列表 -->
<content-wrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="日志编号" align="center" prop="id" />
<el-table-column label="操作类型" align="center" prop="logType">
<template #default="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_LOGIN_TYPE" :value="scope.row.logType" />
</template>
</el-table-column>
<el-table-column label="用户名称" align="center" prop="username" width="180" />
<el-table-column label="登录地址" align="center" prop="userIp" width="180" />
<el-table-column label="浏览器" align="center" prop="userAgent" />
<el-table-column label="登陆结果" align="center" prop="result">
<template #default="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_LOGIN_RESULT" :value="scope.row.result" />
</template>
</el-table-column>
<el-table-column
label="登录日期"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openDetail(scope.row)"
v-hasPermi="['infra:config:query']"
>
详情
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</content-wrap>
<!-- 表单弹窗详情 -->
<LoginLogDetail ref="detailRef" />
</template>
<script setup lang="ts" name="Loginlog">
// import
import { allSchemas } from './loginLog.data'
import { getLoginLogPageApi, exportLoginLogApi, LoginLogVO } from '@/api/system/loginLog'
<script setup lang="ts" name="LoginLog">
import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import * as LoginLogApi from '@/api/system/loginLog'
import LoginLogDetail from './LoginLogDetail.vue'
const message = useMessage() //
const { t } = useI18n() //
//
const [registerTable, { exportList }] = useXTable({
allSchemas: allSchemas,
getListApi: getLoginLogPageApi,
exportListApi: exportLoginLogApi
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
username: undefined,
userIp: undefined,
createTime: []
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
//
const detailData = ref() // Ref
const dialogVisible = ref(false) //
const dialogTitle = ref(t('action.detail')) //
//
const handleDetail = async (row: LoginLogVO) => {
//
detailData.value = row
dialogVisible.value = true
/** 查询参数列表 */
const getList = async () => {
loading.value = true
try {
const data = await LoginLogApi.getLoginLogPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 详情操作 */
const detailRef = ref()
const openDetail = (data: LoginLogApi.LoginLogVO) => {
detailRef.value.open(data)
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await LoginLogApi.exportLoginLog(queryParams)
download.excel(data, '登录日志.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -1,53 +0,0 @@
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
// CrudSchema
const crudSchemas = reactive<VxeCrudSchema>({
primaryKey: 'id',
primaryType: 'id',
primaryTitle: '日志编号',
action: true,
actionWidth: '100px',
columns: [
{
title: '日志类型',
field: 'logType',
dictType: DICT_TYPE.SYSTEM_LOGIN_TYPE,
dictClass: 'number'
},
{
title: '用户名称',
field: 'username',
isSearch: true
},
{
title: '登录地址',
field: 'userIp',
isSearch: true
},
{
title: '浏览器',
field: 'userAgent'
},
{
title: '登陆结果',
field: 'result',
dictType: DICT_TYPE.SYSTEM_LOGIN_RESULT,
dictClass: 'number'
},
{
title: '登录日期',
field: 'createTime',
formatter: 'formatDate',
table: {
width: 150
},
search: {
show: true,
itemRender: {
name: 'XDataTimePicker'
}
}
}
]
})
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)

View File

@ -26,10 +26,8 @@
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</div>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</template>
</Dialog>
</template>

View File

@ -0,0 +1,253 @@
<template>
<Dialog :title="modelTitle" v-model="modelVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="上级菜单">
<el-tree-select
node-key="id"
v-model="formData.parentId"
:props="defaultProps"
:data="menuTree"
:default-expanded-keys="[0]"
check-strictly
/>
</el-form-item>
<el-form-item label="菜单名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入菜单名称" clearable />
</el-form-item>
<el-form-item label="菜单类型" prop="type">
<el-radio-group v-model="formData.type">
<el-radio-button
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_MENU_TYPE)"
:key="dict.label"
:label="dict.value"
>
{{ dict.label }}
</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="菜单图标" v-if="formData.type !== 3">
<IconSelect v-model="formData.icon" clearable />
</el-form-item>
<el-form-item label="路由地址" prop="path" v-if="formData.type !== 3">
<template #label>
<Tooltip
titel="路由地址"
message="访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头"
/>
</template>
<el-input v-model="formData.path" placeholder="请输入路由地址" clearable />
</el-form-item>
<el-form-item label="组件地址" prop="component" v-if="formData.type === 2">
<el-input v-model="formData.component" placeholder="例如说system/user/index" clearable />
</el-form-item>
<el-form-item label="组件名字" prop="componentName" v-if="formData.type === 2">
<el-input v-model="formData.componentName" placeholder="例如说SystemUser" clearable />
</el-form-item>
<el-form-item label="权限标识" prop="permission" v-if="formData.type !== 1">
<template #label>
<Tooltip
titel="权限标识"
message="Controller 方法上的权限字符,如:@PreAuthorize(`@ss.hasPermission('system:user:list')`)"
/>
</template>
<el-input v-model="formData.permission" placeholder="请输入权限标识" clearable />
</el-form-item>
<el-form-item label="显示排序" prop="sort">
<el-input-number v-model="formData.sort" controls-position="right" :min="0" clearable />
</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.label"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="显示状态" prop="visible" v-if="formData.type !== 3">
<template #label>
<Tooltip titel="显示状态" message="选择隐藏时,路由将不会出现在侧边栏,但仍然可以访问" />
</template>
<el-radio-group v-model="formData.visible">
<el-radio border key="true" :label="true">显示</el-radio>
<el-radio border key="false" :label="false">隐藏</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="总是显示" prop="alwaysShow" v-if="formData.type !== 3">
<template #label>
<Tooltip
titel="总是显示"
message="选择不是时,当该菜单只有一个子菜单时,不展示自己,直接展示子菜单"
/>
</template>
<el-radio-group v-model="formData.alwaysShow">
<el-radio border key="true" :label="true">总是</el-radio>
<el-radio border key="false" :label="false">不是</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="缓存状态" prop="keepAlive" v-if="formData.type === 2">
<template #label>
<Tooltip
titel="缓存状态"
message="选择缓存时,则会被 `keep-alive` 缓存,必须填写「组件名称」字段"
/>
</template>
<el-radio-group v-model="formData.keepAlive">
<el-radio border key="true" :label="true">缓存</el-radio>
<el-radio border key="false" :label="false">不缓存</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="modelVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as MenuApi from '@/api/system/menu'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants'
import { handleTree, defaultProps } from '@/utils/tree'
const { wsCache } = useCache()
const { t } = useI18n() //
const message = useMessage() //
const modelVisible = ref(false) //
const modelTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: 0,
name: '',
permission: '',
type: SystemMenuTypeEnum.DIR,
sort: Number(undefined),
parentId: 0,
path: '',
icon: '',
component: '',
componentName: '',
status: CommonStatusEnum.ENABLE,
visible: true,
keepAlive: true,
alwaysShow: true
})
const formRules = reactive({
name: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }],
sort: [{ required: true, message: '菜单顺序不能为空', trigger: 'blur' }],
path: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, id?: number, parentId?: number) => {
modelVisible.value = true
modelTitle.value = t('action.' + type)
formType.value = type
resetForm()
if (parentId) {
formData.value.parentId = parentId
}
//
if (id) {
formLoading.value = true
try {
formData.value = await MenuApi.getMenuApi(id)
} finally {
formLoading.value = false
}
}
//
await getTree()
}
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 {
if (
formData.value.type === SystemMenuTypeEnum.DIR ||
formData.value.type === SystemMenuTypeEnum.MENU
) {
if (!isExternal(formData.value.path)) {
if (formData.value.parentId === 0 && formData.value.path.charAt(0) !== '/') {
message.error('路径必须以 / 开头')
return
} else if (formData.value.parentId !== 0 && formData.value.path.charAt(0) === '/') {
message.error('路径不能以 / 开头')
return
}
}
}
const data = formData.value as unknown as MenuApi.MenuVO
if (formType.value === 'create') {
await MenuApi.createMenu(data)
message.success(t('common.createSuccess'))
} else {
await MenuApi.updateMenu(data)
message.success(t('common.updateSuccess'))
}
modelVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
//
wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
}
}
/** 获取下拉框[上级菜单]的数据 */
const menuTree = ref<Tree[]>([]) //
const getTree = async () => {
menuTree.value = []
const res = await MenuApi.getSimpleMenusList()
let menu: Tree = { id: 0, name: '主类目', children: [] }
menu.children = handleTree(res)
menuTree.value.push(menu)
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: 0,
name: '',
permission: '',
type: SystemMenuTypeEnum.DIR,
sort: Number(undefined),
parentId: 0,
path: '',
icon: '',
component: '',
componentName: '',
status: CommonStatusEnum.ENABLE,
visible: true,
keepAlive: true,
alwaysShow: true
}
formRef.value?.resetFields()
}
/** 判断 path 是不是外部的 HTTP 等链接 */
const isExternal = (path: string) => {
return /^(https?:|mailto:|tel:)/.test(path)
}
</script>

Some files were not shown because too many files have changed in this diff Show More