commit
5e705776b8
3
.env
3
.env
|
@ -15,3 +15,6 @@ VITE_APP_CAPTCHA_ENABLE=true
|
|||
|
||||
# 验证码的开关
|
||||
VITE_APP_CAPTCHA_ENABLE=true
|
||||
|
||||
# 百度统计
|
||||
VITE_APP_BAIDU_CODE = a1ff8825baa73c3a78eb96aa40325abc
|
||||
|
|
2
.env.dev
2
.env.dev
|
@ -16,7 +16,7 @@ VITE_API_BASEPATH=/dev-api
|
|||
VITE_API_URL=/admin-api
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/dist-dev/
|
||||
VITE_BASE_PATH=/
|
||||
|
||||
# 是否删除debugger
|
||||
VITE_DROP_DEBUGGER=false
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"private": false,
|
||||
"scripts": {
|
||||
"i": "pnpm install",
|
||||
"dev": "pnpm vite",
|
||||
"dev": "vite --mode base",
|
||||
"front": "vite --mode front",
|
||||
"ts:check": "vue-tsc --noEmit",
|
||||
"build:pro": "node --max_old_space_size=8000 ./node_modules/vite/bin/vite.js build --mode pro",
|
||||
|
|
|
@ -22,6 +22,6 @@ export const getLeave = async (id: number) => {
|
|||
}
|
||||
|
||||
// 获得请假申请分页
|
||||
export const getLeavePage = async (params) => {
|
||||
export const getLeavePage = async (params: PageParam) => {
|
||||
return await request.get({ url: '/bpm/oa/leave/page', params })
|
||||
}
|
||||
|
|
|
@ -6,39 +6,3 @@ import request from '@/config/axios'
|
|||
export const getCache = () => {
|
||||
return request.get({ url: '/infra/redis/get-monitor-info' })
|
||||
}
|
||||
|
||||
// 获取模块
|
||||
export const getKeyDefineList = () => {
|
||||
return request.get({ url: '/infra/redis/get-key-define-list' })
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取redis key列表
|
||||
*/
|
||||
export const getKeyList = (keyTemplate: string) => {
|
||||
return request.get({
|
||||
url: '/infra/redis/get-key-list',
|
||||
params: {
|
||||
keyTemplate
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取缓存内容
|
||||
export const getKeyValue = (key: string) => {
|
||||
return request.get({ url: '/infra/redis/get-key-value?key=' + key })
|
||||
}
|
||||
|
||||
// 根据键名删除缓存
|
||||
export const deleteKey = (key: string) => {
|
||||
return request.delete({ url: '/infra/redis/delete-key?key=' + key })
|
||||
}
|
||||
|
||||
export const deleteKeys = (keyTemplate: string) => {
|
||||
return request.delete({
|
||||
url: '/infra/redis/delete-keys?',
|
||||
params: {
|
||||
keyTemplate
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -174,12 +174,3 @@ export interface RedisCommandStatsVO {
|
|||
calls: number
|
||||
usec: number
|
||||
}
|
||||
|
||||
export interface RedisKeyInfo {
|
||||
keyTemplate: string
|
||||
keyType: string
|
||||
valueType: string
|
||||
timeoutType: number
|
||||
timeout: number
|
||||
memo: string
|
||||
}
|
||||
|
|
|
@ -2,15 +2,11 @@ import request from '@/config/axios'
|
|||
import { getRefreshToken } from '@/utils/auth'
|
||||
import type { UserLoginVO } from './types'
|
||||
|
||||
export interface CodeImgResult {
|
||||
captchaOnOff: boolean
|
||||
img: string
|
||||
uuid: string
|
||||
}
|
||||
export interface SmsCodeVO {
|
||||
mobile: string
|
||||
scene: number
|
||||
}
|
||||
|
||||
export interface SmsLoginVO {
|
||||
mobile: string
|
||||
code: string
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
// 获得授权信息
|
||||
export const getAuthorize = (clientId: string) => {
|
||||
return request.get({ url: '/system/oauth2/authorize?clientId=' + clientId })
|
||||
}
|
||||
|
||||
// 发起授权
|
||||
export const authorize = (
|
||||
responseType: string,
|
||||
clientId: string,
|
||||
redirectUri: string,
|
||||
state: string,
|
||||
autoApprove: boolean,
|
||||
checkedScopes: string[],
|
||||
uncheckedScopes: string[]
|
||||
) => {
|
||||
// 构建 scopes
|
||||
const scopes = {}
|
||||
for (const scope of checkedScopes) {
|
||||
scopes[scope] = true
|
||||
}
|
||||
for (const scope of uncheckedScopes) {
|
||||
scopes[scope] = false
|
||||
}
|
||||
// 发起请求
|
||||
return request.post({
|
||||
url: '/system/oauth2/authorize',
|
||||
headers: {
|
||||
'Content-type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
params: {
|
||||
response_type: responseType,
|
||||
client_id: clientId,
|
||||
redirect_uri: redirectUri,
|
||||
state: state,
|
||||
auto_approve: autoApprove,
|
||||
scope: JSON.stringify(scopes)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -26,17 +26,3 @@ export type UserVO = {
|
|||
loginIp: string
|
||||
loginDate: string
|
||||
}
|
||||
|
||||
export type UserInfoVO = {
|
||||
permissions: []
|
||||
roles: []
|
||||
user: {
|
||||
avatar: string
|
||||
id: number
|
||||
nickname: string
|
||||
}
|
||||
}
|
||||
|
||||
export type TentantNameVO = {
|
||||
name: string
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
/**
|
||||
* 商品品牌
|
||||
*/
|
||||
export interface BrandVO {
|
||||
/**
|
||||
* 品牌编号
|
||||
*/
|
||||
id?: number
|
||||
/**
|
||||
* 品牌名称
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* 品牌图片
|
||||
*/
|
||||
picUrl: string
|
||||
/**
|
||||
* 品牌排序
|
||||
*/
|
||||
sort?: number
|
||||
/**
|
||||
* 品牌描述
|
||||
*/
|
||||
description?: string
|
||||
/**
|
||||
* 开启状态
|
||||
*/
|
||||
status: number
|
||||
}
|
||||
|
||||
// 创建商品品牌
|
||||
export const createBrand = (data: BrandVO) => {
|
||||
return request.post({ url: '/product/brand/create', data })
|
||||
}
|
||||
|
||||
// 更新商品品牌
|
||||
export const updateBrand = (data: BrandVO) => {
|
||||
return request.put({ url: '/product/brand/update', data })
|
||||
}
|
||||
|
||||
// 删除商品品牌
|
||||
export const deleteBrand = (id: number) => {
|
||||
return request.delete({ url: `/product/brand/delete?id=${id}` })
|
||||
}
|
||||
|
||||
// 获得商品品牌
|
||||
export const getBrand = (id: number) => {
|
||||
return request.get({ url: `/product/brand/get?id=${id}` })
|
||||
}
|
||||
|
||||
// 获得商品品牌列表
|
||||
export const getBrandParam = (params: PageParam) => {
|
||||
return request.get({ url: '/product/brand/page', params })
|
||||
}
|
|
@ -33,9 +33,15 @@
|
|||
/>
|
||||
<el-table-column label="操作" width="90px">
|
||||
<template #default="scope">
|
||||
<el-button type="text" @click="openFieldForm(scope, scope.$index)">编辑</el-button>
|
||||
<el-button type="primary" link @click="openFieldForm(scope, scope.$index)"
|
||||
>编辑</el-button
|
||||
>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button type="text" style="color: #ff4d4f" @click="removeField(scope, scope.$index)"
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
style="color: #ff4d4f"
|
||||
@click="removeField(scope, scope.$index)"
|
||||
>移除</el-button
|
||||
>
|
||||
</template>
|
||||
|
@ -97,7 +103,10 @@
|
|||
<el-table-column label="枚举值名称" prop="name" min-width="100px" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="90px">
|
||||
<template #default="scope">
|
||||
<el-button type="text" @click="openFieldOptionForm(scope, scope.$index, 'enum')"
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="openFieldOptionForm(scope, scope.$index, 'enum')"
|
||||
>编辑</el-button
|
||||
>
|
||||
<el-divider direction="vertical" />
|
||||
|
@ -126,7 +135,10 @@
|
|||
<el-table-column label="约束配置" prop="config" min-width="100px" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="90px">
|
||||
<template #default="scope">
|
||||
<el-button type="text" @click="openFieldOptionForm(scope, scope.$index, 'constraint')"
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="openFieldOptionForm(scope, scope.$index, 'constraint')"
|
||||
>编辑</el-button
|
||||
>
|
||||
<el-divider direction="vertical" />
|
||||
|
@ -154,7 +166,10 @@
|
|||
<el-table-column label="属性值" prop="value" min-width="100px" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="90px">
|
||||
<template #default="scope">
|
||||
<el-button type="text" @click="openFieldOptionForm(scope, scope.$index, 'property')"
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="openFieldOptionForm(scope, scope.$index, 'property')"
|
||||
>编辑</el-button
|
||||
>
|
||||
<el-divider direction="vertical" />
|
||||
|
|
|
@ -7,9 +7,9 @@ export const template = (isTaskListener) => {
|
|||
<el-table-column label="监听器类型" min-width="100px" show-overflow-tooltip :formatter="row => listenerTypeObject[row.listenerType]" />
|
||||
<el-table-column label="操作" width="90px">
|
||||
<template #default="scope">
|
||||
<el-button size="small" type="text" @click="openListenerForm(scope, scope.$index)">编辑</el-button>
|
||||
<el-button size="small" type="primary" link @click="openListenerForm(scope, scope.$index)">编辑</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button size="small" type="text" style="color: #ff4d4f" @click="removeListener(scope, scope.$index)">移除</el-button>
|
||||
<el-button size="small" type="primary" link style="color: #ff4d4f" @click="removeListener(scope, scope.$index)">移除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
@ -125,9 +125,9 @@ export const template = (isTaskListener) => {
|
|||
<el-table-column label="字段值/表达式" min-width="100px" show-overflow-tooltip :formatter="row => row.string || row.expression" />
|
||||
<el-table-column label="操作" width="100px">
|
||||
<template #default="scope">
|
||||
<el-button size="small" type="text" @click="openListenerFieldForm(scope, scope.$index)">编辑</el-button>
|
||||
<el-button size="small" type="primary" link @click="openListenerFieldForm(scope, scope.$index)">编辑</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button size="small" type="text" style="color: #ff4d4f" @click="removeListenerField(scope, scope.$index)">移除</el-button>
|
||||
<el-button size="small" type="primary" link style="color: #ff4d4f" @click="removeListenerField(scope, scope.$index)">移除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
</el-table>
|
||||
|
||||
<el-dialog
|
||||
v-model="modelVisible"
|
||||
v-model="dialogVisible"
|
||||
:title="modelConfig.title"
|
||||
:close-on-click-modal="false"
|
||||
width="400px"
|
||||
|
@ -39,7 +39,7 @@
|
|||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="modelVisible = false">取 消</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="addNewObject">保 存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
@ -49,7 +49,7 @@
|
|||
const message = useMessage()
|
||||
const signalList = ref<any[]>([])
|
||||
const messageList = ref<any[]>([])
|
||||
const modelVisible = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const modelType = ref('')
|
||||
const modelObjectForm = ref<any>({})
|
||||
const rootElements = ref()
|
||||
|
@ -85,7 +85,7 @@ const initDataList = () => {
|
|||
const openModel = (type) => {
|
||||
modelType.value = type
|
||||
modelObjectForm.value = {}
|
||||
modelVisible.value = true
|
||||
dialogVisible.value = true
|
||||
}
|
||||
const addNewObject = () => {
|
||||
if (modelType.value === 'message') {
|
||||
|
@ -101,7 +101,7 @@ const addNewObject = () => {
|
|||
const signalRef = bpmnInstances().moddle.create('bpmn:Signal', modelObjectForm.value)
|
||||
rootElements.value.push(signalRef)
|
||||
}
|
||||
modelVisible.value = false
|
||||
dialogVisible.value = false
|
||||
initDataList()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import axios, {
|
||||
AxiosError,
|
||||
AxiosInstance,
|
||||
AxiosRequestHeaders,
|
||||
AxiosResponse,
|
||||
AxiosError,
|
||||
InternalAxiosRequestConfig
|
||||
} from 'axios'
|
||||
|
||||
|
@ -230,7 +230,8 @@ const handleAuthorized = () => {
|
|||
wsCache.clear()
|
||||
removeToken()
|
||||
isRelogin.show = false
|
||||
window.location.href = import.meta.env.VITE_BASE_PATH
|
||||
// 干掉token后再走一次路由让它过router.beforeEach的校验
|
||||
window.location.href = window.location.href
|
||||
})
|
||||
}
|
||||
return Promise.reject(t('sys.api.timeoutMessage'))
|
||||
|
|
|
@ -352,6 +352,7 @@ export default {
|
|||
login: {
|
||||
backSignIn: '返回',
|
||||
signInFormTitle: '登录',
|
||||
ssoFormTitle: '三方授权',
|
||||
mobileSignInFormTitle: '手机登录',
|
||||
qrSignInFormTitle: '二维码登录',
|
||||
signUpFormTitle: '注册',
|
||||
|
|
|
@ -52,6 +52,8 @@ import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'
|
|||
import hljs from 'highlight.js' //导入代码高亮文件
|
||||
import 'highlight.js/styles/github.css' //导入代码高亮样式 新版
|
||||
|
||||
import '@/plugins/tongji' // 百度统计
|
||||
|
||||
import Logger from '@/utils/Logger'
|
||||
|
||||
// 本地开发模式 全局引入 element-plus 样式,加快第一次进入速度
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import router from '@/router'
|
||||
|
||||
// 用于 router push
|
||||
window._hmt = window._hmt || []
|
||||
// HM_ID
|
||||
const HM_ID = import.meta.env.VITE_APP_BAIDU_CODE
|
||||
;(function () {
|
||||
// 有值的时候,才开启
|
||||
if (!HM_ID) {
|
||||
return
|
||||
}
|
||||
const hm = document.createElement('script')
|
||||
hm.src = 'https://hm.baidu.com/hm.js?' + HM_ID
|
||||
const s = document.getElementsByTagName('script')[0]
|
||||
s.parentNode.insertBefore(hm, s)
|
||||
})()
|
||||
|
||||
router.afterEach(function (to) {
|
||||
if (!HM_ID) {
|
||||
return
|
||||
}
|
||||
_hmt.push(['_trackPageview', to.fullPath])
|
||||
})
|
|
@ -1,11 +1,11 @@
|
|||
import type { App } from 'vue'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import remainingRouter from './modules/remaining'
|
||||
|
||||
// 创建路由实例
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(), // createWebHashHistory URL带#,createWebHistory URL不带#
|
||||
history: createWebHistory(), // createWebHashHistory URL带#,createWebHistory URL不带#
|
||||
strict: true,
|
||||
routes: remainingRouter as RouteRecordRaw[],
|
||||
scrollBehavior: () => ({ left: 0, top: 0 })
|
||||
|
|
|
@ -116,7 +116,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||
{
|
||||
path: 'type/data/:dictType',
|
||||
component: () => import('@/views/system/dict/data/index.vue'),
|
||||
name: 'data',
|
||||
name: 'SystemDictData',
|
||||
meta: {
|
||||
title: '字典数据',
|
||||
noCache: true,
|
||||
|
@ -140,7 +140,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||
{
|
||||
path: 'edit',
|
||||
component: () => import('@/views/infra/codegen/EditTable.vue'),
|
||||
name: 'EditTable',
|
||||
name: 'InfraCodegenEditTable',
|
||||
meta: {
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
|
@ -163,7 +163,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||
{
|
||||
path: 'job-log',
|
||||
component: () => import('@/views/infra/job/logger/index.vue'),
|
||||
name: 'JobLog',
|
||||
name: 'InfraJobLog',
|
||||
meta: {
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
|
@ -185,6 +185,16 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||
noTagsView: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/sso',
|
||||
component: () => import('@/views/Login/Login.vue'),
|
||||
name: 'SSOLogin',
|
||||
meta: {
|
||||
hidden: true,
|
||||
title: t('router.login'),
|
||||
noTagsView: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/403',
|
||||
component: () => import('@/views/Error/403.vue'),
|
||||
|
@ -226,7 +236,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||
{
|
||||
path: '/manager/form/edit',
|
||||
component: () => import('@/views/bpm/form/editor/index.vue'),
|
||||
name: 'bpmFormEditor',
|
||||
name: 'BpmFormEditor',
|
||||
meta: {
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
|
@ -238,7 +248,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||
{
|
||||
path: '/manager/model/edit',
|
||||
component: () => import('@/views/bpm/model/editor/index.vue'),
|
||||
name: 'modelEditor',
|
||||
name: 'BpmModelEditor',
|
||||
meta: {
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
|
@ -250,7 +260,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||
{
|
||||
path: '/manager/definition',
|
||||
component: () => import('@/views/bpm/definition/index.vue'),
|
||||
name: 'BpmProcessDefinitionList',
|
||||
name: 'BpmProcessDefinition',
|
||||
meta: {
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
|
@ -262,7 +272,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||
{
|
||||
path: '/manager/task-assign-rule',
|
||||
component: () => import('@/views/bpm/taskAssignRule/index.vue'),
|
||||
name: 'BpmTaskAssignRuleList',
|
||||
name: 'BpmTaskAssignRule',
|
||||
meta: {
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
|
@ -305,18 +315,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||
title: '发起 OA 请假',
|
||||
activeMenu: 'bpm/oa/leave/create'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/bpm/oa/leave/detail',
|
||||
component: () => import('@/views/bpm/oa/leave/detail.vue'),
|
||||
name: 'OALeaveDetail',
|
||||
meta: {
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
canTo: true,
|
||||
title: '查看 OA 请假',
|
||||
activeMenu: 'bpm/oa/leave/detail'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -331,7 +329,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||
{
|
||||
path: 'value/:propertyId(\\d+)',
|
||||
component: () => import('@/views/mall/product/property/value/index.vue'),
|
||||
name: 'PropertyValue',
|
||||
name: 'ProductPropertyValue',
|
||||
meta: { title: '商品属性值', icon: '', activeMenu: '/product/property' }
|
||||
}
|
||||
]
|
||||
|
|
|
@ -25,13 +25,12 @@ declare module '@vue/runtime-core' {
|
|||
Echart: typeof import('./../components/Echart/src/Echart.vue')['default']
|
||||
Editor: typeof import('./../components/Editor/src/Editor.vue')['default']
|
||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||
ElAutoResizer: typeof import('element-plus/es')['ElAutoResizer']
|
||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||
ElBadge: typeof import('element-plus/es')['ElBadge']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||
|
@ -71,11 +70,9 @@ declare module '@vue/runtime-core' {
|
|||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
|
||||
ElSpace: typeof import('element-plus/es')['ElSpace']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTableV2: typeof import('element-plus/es')['ElTableV2']
|
||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
|
|
|
@ -6,6 +6,7 @@ export {}
|
|||
declare global {
|
||||
const DICT_TYPE: typeof import('@/utils/dict')['DICT_TYPE']
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
|
|
|
@ -112,7 +112,6 @@ export enum DICT_TYPE {
|
|||
|
||||
// ========== INFRA 模块 ==========
|
||||
INFRA_BOOLEAN_STRING = 'infra_boolean_string',
|
||||
INFRA_REDIS_TIMEOUT_TYPE = 'infra_redis_timeout_type',
|
||||
INFRA_JOB_STATUS = 'infra_job_status',
|
||||
INFRA_JOB_LOG_STATUS = 'infra_job_log_status',
|
||||
INFRA_API_ERROR_LOG_PROCESS_STATUS = 'infra_api_error_log_process_status',
|
||||
|
|
|
@ -9,19 +9,19 @@
|
|||
>
|
||||
<!-- 左上角的 logo + 系统标题 -->
|
||||
<div class="flex items-center relative text-white">
|
||||
<img src="@/assets/imgs/logo.png" alt="" class="w-48px h-48px mr-10px" />
|
||||
<img alt="" class="w-48px h-48px mr-10px" src="@/assets/imgs/logo.png" />
|
||||
<span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
|
||||
</div>
|
||||
<!-- 左边的背景图 + 欢迎语 -->
|
||||
<div class="flex justify-center items-center h-[calc(100%-60px)]">
|
||||
<TransitionGroup
|
||||
appear
|
||||
tag="div"
|
||||
enter-active-class="animate__animated animate__bounceInLeft"
|
||||
tag="div"
|
||||
>
|
||||
<img src="@/assets/svgs/login-box-bg.svg" key="1" alt="" class="w-350px" />
|
||||
<div class="text-3xl text-white" key="2">{{ t('login.welcome') }}</div>
|
||||
<div class="mt-5 font-normal text-white text-14px" key="3">
|
||||
<img key="1" alt="" class="w-350px" src="@/assets/svgs/login-box-bg.svg" />
|
||||
<div key="2" class="text-3xl text-white">{{ t('login.welcome') }}</div>
|
||||
<div key="3" class="mt-5 font-normal text-white text-14px">
|
||||
{{ t('login.message') }}
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
|
@ -31,7 +31,7 @@
|
|||
<!-- 右上角的主题、语言选择 -->
|
||||
<div class="flex justify-between items-center text-white @2xl:justify-end @xl:justify-end">
|
||||
<div class="flex items-center @2xl:hidden @xl:hidden">
|
||||
<img src="@/assets/imgs/logo.png" alt="" class="w-48px h-48px mr-10px" />
|
||||
<img alt="" class="w-48px h-48px mr-10px" src="@/assets/imgs/logo.png" />
|
||||
<span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-end items-center space-x-10px">
|
||||
|
@ -52,20 +52,23 @@
|
|||
<QrCodeForm class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" />
|
||||
<!-- 注册 -->
|
||||
<RegisterForm class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" />
|
||||
<!-- 三方登录 -->
|
||||
<SSOLoginVue class="p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)" />
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import { underlineToHump } from '@/utils'
|
||||
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
|
||||
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
||||
import { LoginForm, MobileForm, RegisterForm, QrCodeForm } from './components'
|
||||
|
||||
import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue } from './components'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
|
|
|
@ -137,7 +137,7 @@ import { useIcon } from '@/hooks/web/useIcon'
|
|||
import * as authUtil from '@/utils/auth'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import * as LoginApi from '@/api/login'
|
||||
import { LoginStateEnum, useLoginState, useFormValid } from './useLogin'
|
||||
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage()
|
||||
|
@ -240,7 +240,12 @@ const handleLogin = async (params) => {
|
|||
if (!redirect.value) {
|
||||
redirect.value = '/'
|
||||
}
|
||||
push({ path: redirect.value || permissionStore.addRouters[0].path })
|
||||
// 判断是否为SSO登录
|
||||
if (redirect.value.indexOf('sso') !== -1) {
|
||||
window.location.href = window.location.href.replace('/login?redirect=', '')
|
||||
} else {
|
||||
push({ path: redirect.value || permissionStore.addRouters[0].path })
|
||||
}
|
||||
} catch {
|
||||
loginLoading.value = false
|
||||
} finally {
|
||||
|
@ -291,6 +296,7 @@ onMounted(() => {
|
|||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.login-code {
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
|
|
|
@ -16,7 +16,8 @@ const getFormTitle = computed(() => {
|
|||
[LoginStateEnum.LOGIN]: t('sys.login.signInFormTitle'),
|
||||
[LoginStateEnum.REGISTER]: t('sys.login.signUpFormTitle'),
|
||||
[LoginStateEnum.MOBILE]: t('sys.login.mobileSignInFormTitle'),
|
||||
[LoginStateEnum.QR_CODE]: t('sys.login.qrSignInFormTitle')
|
||||
[LoginStateEnum.QR_CODE]: t('sys.login.qrSignInFormTitle'),
|
||||
[LoginStateEnum.SSO]: t('sys.login.ssoFormTitle')
|
||||
}
|
||||
return titleObj[unref(getLoginState)]
|
||||
})
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
<template>
|
||||
<div v-show="ssoVisible" class="form-cont">
|
||||
<!-- 应用名 -->
|
||||
<LoginFormTitle style="width: 100%" />
|
||||
<el-tabs class="form" style="float: none" value="uname">
|
||||
<el-tab-pane :label="client.name" name="uname" />
|
||||
</el-tabs>
|
||||
<div>
|
||||
<el-form :model="formData" class="login-form">
|
||||
<!-- 授权范围的选择 -->
|
||||
此第三方应用请求获得以下权限:
|
||||
<el-form-item prop="scopes">
|
||||
<el-checkbox-group v-model="formData.scopes">
|
||||
<el-checkbox
|
||||
v-for="scope in queryParams.scopes"
|
||||
:key="scope"
|
||||
:label="scope"
|
||||
style="display: block; margin-bottom: -10px"
|
||||
>
|
||||
{{ formatScope(scope) }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<!-- 下方的登录按钮 -->
|
||||
<el-form-item class="w-1/1">
|
||||
<el-button
|
||||
:loading="formLoading"
|
||||
class="w-6/10"
|
||||
type="primary"
|
||||
@click.prevent="handleAuthorize(true)"
|
||||
>
|
||||
<span v-if="!formLoading">同意授权</span>
|
||||
<span v-else>授 权 中...</span>
|
||||
</el-button>
|
||||
<el-button class="w-3/10" @click.prevent="handleAuthorize(false)">拒绝</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" name="SSOLogin" setup>
|
||||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import * as OAuth2Api from '@/api/login/oauth2'
|
||||
import { LoginStateEnum, useLoginState } from './useLogin'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
const route = useRoute() // 路由
|
||||
const { currentRoute } = useRouter() // 路由
|
||||
const { getLoginState, setLoginState } = useLoginState()
|
||||
|
||||
const client = ref({
|
||||
// 客户端信息
|
||||
name: '',
|
||||
logo: ''
|
||||
})
|
||||
const queryParams = reactive({
|
||||
// URL 上的 client_id、scope 等参数
|
||||
responseType: '',
|
||||
clientId: '',
|
||||
redirectUri: '',
|
||||
state: '',
|
||||
scopes: [] // 优先从 query 参数获取;如果未传递,从后端获取
|
||||
})
|
||||
const ssoVisible = computed(() => unref(getLoginState) === LoginStateEnum.SSO) // 是否展示 SSO 登录的表单
|
||||
const formData = reactive({
|
||||
scopes: [] // 已选中的 scope 数组
|
||||
})
|
||||
const formLoading = ref(false) // 表单是否提交中
|
||||
|
||||
/** 初始化授权信息 */
|
||||
const init = async () => {
|
||||
// 防止在没有登录的情况下循环弹窗
|
||||
if (typeof route.query.client_id === 'undefined') return
|
||||
// 解析参数
|
||||
// 例如说【自动授权不通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read%20user.write
|
||||
// 例如说【自动授权通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read
|
||||
queryParams.responseType = route.query.response_type as string
|
||||
queryParams.clientId = route.query.client_id as string
|
||||
queryParams.redirectUri = route.query.redirect_uri as string
|
||||
queryParams.state = route.query.state as string
|
||||
if (route.query.scope) {
|
||||
queryParams.scopes = (route.query.scope as string).split(' ')
|
||||
}
|
||||
|
||||
// 如果有 scope 参数,先执行一次自动授权,看看是否之前都授权过了。
|
||||
if (queryParams.scopes.length > 0) {
|
||||
const data = await doAuthorize(true, queryParams.scopes, [])
|
||||
if (data) {
|
||||
location.href = data
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 获取授权页的基本信息
|
||||
const data = await OAuth2Api.getAuthorize(queryParams.clientId)
|
||||
client.value = data.client
|
||||
// 解析 scope
|
||||
let scopes
|
||||
// 1.1 如果 params.scope 非空,则过滤下返回的 scopes
|
||||
if (queryParams.scopes.length > 0) {
|
||||
scopes = []
|
||||
for (const scope of data.scopes) {
|
||||
if (queryParams.scopes.indexOf(scope.key) >= 0) {
|
||||
scopes.push(scope)
|
||||
}
|
||||
}
|
||||
// 1.2 如果 params.scope 为空,则使用返回的 scopes 设置它
|
||||
} else {
|
||||
scopes = data.scopes
|
||||
for (const scope of scopes) {
|
||||
queryParams.scopes.push(scope.key)
|
||||
}
|
||||
}
|
||||
// 生成已选中的 checkedScopes
|
||||
for (const scope of scopes) {
|
||||
if (scope.value) {
|
||||
formData.scopes.push(scope.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理授权的提交 */
|
||||
const handleAuthorize = async (approved) => {
|
||||
// 计算 checkedScopes + uncheckedScopes
|
||||
let checkedScopes
|
||||
let uncheckedScopes
|
||||
if (approved) {
|
||||
// 同意授权,按照用户的选择
|
||||
checkedScopes = formData.scopes
|
||||
uncheckedScopes = queryParams.scopes.filter((item) => checkedScopes.indexOf(item) === -1)
|
||||
} else {
|
||||
// 拒绝,则都是取消
|
||||
checkedScopes = []
|
||||
uncheckedScopes = queryParams.scopes
|
||||
}
|
||||
// 提交授权的请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = await doAuthorize(false, checkedScopes, uncheckedScopes)
|
||||
if (!data) {
|
||||
return
|
||||
}
|
||||
location.href = data
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 调用授权 API 接口 */
|
||||
const doAuthorize = (autoApprove, checkedScopes, uncheckedScopes) => {
|
||||
return OAuth2Api.authorize(
|
||||
queryParams.responseType,
|
||||
queryParams.clientId,
|
||||
queryParams.redirectUri,
|
||||
queryParams.state,
|
||||
autoApprove,
|
||||
checkedScopes,
|
||||
uncheckedScopes
|
||||
)
|
||||
}
|
||||
|
||||
/** 格式化 scope 文本 */
|
||||
const formatScope = (scope) => {
|
||||
// 格式化 scope 授权范围,方便用户理解。
|
||||
// 这里仅仅是一个 demo,可以考虑录入到字典数据中,例如说字典类型 "system_oauth2_scope",它的每个 scope 都是一条字典数据。
|
||||
switch (scope) {
|
||||
case 'user.read':
|
||||
return '访问你的个人信息'
|
||||
case 'user.write':
|
||||
return '修改你的个人信息'
|
||||
default:
|
||||
return scope
|
||||
}
|
||||
}
|
||||
|
||||
/** 监听当前路由为 SSOLogin 时,进行数据的初始化 */
|
||||
watch(
|
||||
() => currentRoute.value,
|
||||
(route: RouteLocationNormalizedLoaded) => {
|
||||
if (route.name === 'SSOLogin') {
|
||||
setLoginState(LoginStateEnum.SSO)
|
||||
init()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
|
@ -3,5 +3,6 @@ import MobileForm from './MobileForm.vue'
|
|||
import LoginFormTitle from './LoginFormTitle.vue'
|
||||
import RegisterForm from './RegisterForm.vue'
|
||||
import QrCodeForm from './QrCodeForm.vue'
|
||||
import SSOLoginVue from './SSOLogin.vue'
|
||||
|
||||
export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm }
|
||||
export { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm, SSOLoginVue }
|
||||
|
|
|
@ -5,7 +5,8 @@ export enum LoginStateEnum {
|
|||
REGISTER,
|
||||
RESET_PASSWORD,
|
||||
MOBILE,
|
||||
QR_CODE
|
||||
QR_CODE,
|
||||
SSO
|
||||
}
|
||||
|
||||
const currentState = ref(LoginStateEnum.LOGIN)
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<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)">
|
||||
<el-button type="primary" link @click="handleBpmnDetail(scope.row)">
|
||||
<span>{{ scope.row.name }}</span>
|
||||
</el-button>
|
||||
</template>
|
||||
|
@ -23,7 +23,7 @@
|
|||
>
|
||||
<span>{{ scope.row.formName }}</span>
|
||||
</el-button>
|
||||
<el-button v-else type="text" @click="handleFormDetail(scope.row)">
|
||||
<el-button v-else type="primary" link @click="handleFormDetail(scope.row)">
|
||||
<span>{{ scope.row.formCustomCreatePath }}</span>
|
||||
</el-button>
|
||||
</template>
|
||||
|
@ -93,7 +93,7 @@
|
|||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Form">
|
||||
<script setup lang="ts" name="BpmProcessDefinition">
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
|
|
|
@ -83,12 +83,11 @@
|
|||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Form">
|
||||
<script setup lang="ts" name="BpmForm">
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as FormApi from '@/api/bpm/form'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const { push } = useRouter() // 路由
|
||||
|
@ -130,7 +129,7 @@ const resetQuery = () => {
|
|||
/** 添加/修改操作 */
|
||||
const openForm = (id?: number) => {
|
||||
push({
|
||||
name: 'bpmFormEditor',
|
||||
name: 'BpmFormEditor',
|
||||
query: {
|
||||
id
|
||||
}
|
||||
|
|
|
@ -111,7 +111,7 @@
|
|||
<UserGroupForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="UserGroup">
|
||||
<script setup lang="ts" name="BpmUserGroup">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as UserGroupApi from '@/api/bpm/userGroup'
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts" name="BpmModelEditor">
|
||||
// 自定义元素选中时的弹出菜单(修改 默认任务 为 用户任务)
|
||||
import CustomContentPadProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/content-pad'
|
||||
// 自定义左侧菜单(修改 默认任务 为 用户任务)
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
<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)">
|
||||
<el-button type="primary" link @click="handleBpmnDetail(scope.row)">
|
||||
<span>{{ scope.row.name }}</span>
|
||||
</el-button>
|
||||
</template>
|
||||
|
@ -224,7 +224,7 @@
|
|||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Form">
|
||||
<script setup lang="ts" name="BpmModel">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter, formatDate } from '@/utils/formatTime'
|
||||
import * as ModelApi from '@/api/bpm/model'
|
||||
|
@ -319,7 +319,7 @@ const handleChangeState = async (row) => {
|
|||
/** 设计流程 */
|
||||
const handleDesign = (row) => {
|
||||
push({
|
||||
name: 'modelEditor',
|
||||
name: 'BpmModelEditor',
|
||||
query: {
|
||||
modelId: row.id
|
||||
}
|
||||
|
@ -352,7 +352,7 @@ const handleAssignRule = (row) => {
|
|||
/** 跳转到指定流程定义列表 */
|
||||
const handleDefinitionList = (row) => {
|
||||
push({
|
||||
name: 'BpmProcessDefinitionList',
|
||||
name: 'BpmProcessDefinition',
|
||||
query: {
|
||||
key: row.key
|
||||
}
|
||||
|
|
|
@ -1,56 +1,107 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<Form :schema="allSchemas.formSchema" :rules="rules" ref="formRef" />
|
||||
<!-- 按钮:保存 -->
|
||||
<XButton
|
||||
type="primary"
|
||||
:title="t('action.save')"
|
||||
:loading="actionLoading"
|
||||
@click="submitForm"
|
||||
/>
|
||||
</ContentWrap>
|
||||
<Dialog title="发起 OA 请假流程" v-model="modelVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="80px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="请假类型" prop="type">
|
||||
<el-select v-model="formData.type" placeholder="请选择请假类型" clearable>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_OA_LEAVE_TYPE)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker
|
||||
clearable
|
||||
v-model="formData.startTime"
|
||||
type="datetime"
|
||||
value-format="x"
|
||||
placeholder="请选择开始时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker
|
||||
clearable
|
||||
v-model="formData.endTime"
|
||||
type="datetime"
|
||||
value-format="x"
|
||||
placeholder="请选择结束时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="原因" prop="reason">
|
||||
<el-input v-model="formData.reason" 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 { FormExpose } from '@/components/Form'
|
||||
// import XEUtils from 'xe-utils'
|
||||
|
||||
// 业务相关的 import
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import * as LeaveApi from '@/api/bpm/leave'
|
||||
import { rules, allSchemas } from './leave.data'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { push } = useRouter() // 路由
|
||||
|
||||
// 表单参数
|
||||
const actionLoading = ref(false) // 按钮 Loading
|
||||
const formRef = ref<FormExpose>() // 表单 Ref
|
||||
const modelVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formData = ref({
|
||||
type: undefined,
|
||||
reason: undefined,
|
||||
startTime: undefined,
|
||||
endTime: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
type: [{ required: true, message: '请假类型不能为空', trigger: 'blur' }],
|
||||
reason: [{ required: true, message: '请假原因不能为空', trigger: 'change' }],
|
||||
startTime: [{ required: true, message: '请假开始时间不能为空', trigger: 'change' }],
|
||||
endTime: [{ required: true, message: '请假结束时间不能为空', trigger: 'change' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
// 提交按钮
|
||||
/** 打开弹窗 */
|
||||
const open = async () => {
|
||||
modelVisible.value = true
|
||||
resetForm()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
const elForm = unref(formRef)?.getElFormRef()
|
||||
if (!elForm) return
|
||||
elForm.validate(async (valid) => {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
// 设置提交中
|
||||
actionLoading.value = true
|
||||
const data = unref(formRef)?.formModel as LeaveApi.LeaveVO
|
||||
// data.startTime = XEUtils.toDateString(data.startTime, 'yyyy-MM-dd HH:mm:ss')
|
||||
// data.endTime = XEUtils.toDateString(data.endTime, 'yyyy-MM-dd HH:mm:ss')
|
||||
data.startTime = Date.parse(new Date(data.startTime).toString()).toString()
|
||||
data.endTime = Date.parse(new Date(data.endTime).toString()).toString()
|
||||
// 添加的提交
|
||||
await LeaveApi.createLeave(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
// 关闭窗口
|
||||
push('/bpm/oa/leave')
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
}
|
||||
})
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
// 提交请求
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown as LeaveApi.LeaveVO
|
||||
await LeaveApi.createLeave(data)
|
||||
message.success('新增成功')
|
||||
modelVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
type: undefined,
|
||||
reason: undefined,
|
||||
startTime: undefined,
|
||||
endTime: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,42 +1,40 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 详情 -->
|
||||
<Descriptions :schema="allSchemas.detailSchema" :data="formData" />
|
||||
<el-button @click="routerReturn" type="primary">返回</el-button>
|
||||
</ContentWrap>
|
||||
<Dialog title="详情" v-model="modelVisible" :scroll="true" :max-height="200">
|
||||
<el-descriptions border :column="1">
|
||||
<el-descriptions-item label="请假类型">
|
||||
<dict-tag :type="DICT_TYPE.BPM_OA_LEAVE_TYPE" :value="detailData.type" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="开始时间">
|
||||
{{ formatDate(detailData.startTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="结束时间">
|
||||
{{ formatDate(detailData.endTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="原因">
|
||||
{{ detailData.reason }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 业务相关的 import
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import * as LeaveApi from '@/api/bpm/leave'
|
||||
import { allSchemas } from '@/views/bpm/oa/leave/leave.data'
|
||||
import { useRouter } from 'vue-router'
|
||||
const router = useRouter()
|
||||
const { query } = useRoute() // 查询参数
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const id = ref() // 请假编号
|
||||
// 表单参数
|
||||
const formData = ref({
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
type: undefined,
|
||||
reason: undefined
|
||||
})
|
||||
const modelVisible = ref(false) // 弹窗的是否展示
|
||||
const detailLoading = ref(false) // 表单的加载中
|
||||
const detailData = ref() // 详情数据
|
||||
|
||||
const routerReturn = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
id.value = query.id
|
||||
if (!id.value) {
|
||||
message.error('未传递 id 参数,无法查看 OA 请假信息')
|
||||
return
|
||||
/** 打开弹窗 */
|
||||
const open = async (data: LeaveApi.LeaveVO) => {
|
||||
modelVisible.value = true
|
||||
// 设置数据
|
||||
detailLoading.value = true
|
||||
try {
|
||||
detailData.value = data
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
// 获得请假信息
|
||||
LeaveApi.getLeave(id.value).then((data) => {
|
||||
formData.value = data
|
||||
})
|
||||
})
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
</script>
|
||||
|
|
|
@ -1,83 +1,236 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<XTable @register="registerTable">
|
||||
<template #toolbar_buttons>
|
||||
<!-- 操作:发起请假 -->
|
||||
<XButton type="primary" preIcon="ep:plus" title="发起请假" @click="handleCreate()" />
|
||||
</template>
|
||||
<template #actionbtns_default="{ row }">
|
||||
<!-- 操作: 取消请假 -->
|
||||
<XTextButton
|
||||
preIcon="ep:delete"
|
||||
title="取消请假"
|
||||
v-hasPermi="['bpm:oa-leave:create']"
|
||||
v-if="row.result === 1"
|
||||
@click="cancelLeave(row)"
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="请假类型" prop="type">
|
||||
<el-select
|
||||
v-model="queryParams.type"
|
||||
placeholder="请选择请假类型"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_OA_LEAVE_TYPE)"
|
||||
: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"
|
||||
/>
|
||||
<!-- 操作: 详情 -->
|
||||
<XTextButton preIcon="ep:view" :title="t('action.detail')" @click="handleDetail(row)" />
|
||||
<!-- 操作: 审批进度 -->
|
||||
<XTextButton preIcon="ep:edit-pen" title="审批进度" @click="handleProcessDetail(row)" />
|
||||
</template>
|
||||
</XTable>
|
||||
</el-form-item>
|
||||
<el-form-item label="结果" prop="result">
|
||||
<el-select v-model="queryParams.result" placeholder="请选择结果" clearable class="!w-240px">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="原因" prop="reason">
|
||||
<el-input
|
||||
v-model="queryParams.reason"
|
||||
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="handleCreate()">
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 发起请假
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 全局相关的 import
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
// 业务相关的 import
|
||||
import { allSchemas } from './leave.data'
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="申请编号" align="center" prop="id" />
|
||||
<el-table-column label="状态" align="center" prop="result">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="开始时间"
|
||||
align="center"
|
||||
prop="startTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="结束时间"
|
||||
align="center"
|
||||
prop="endTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="请假类型" align="center" prop="type">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_OA_LEAVE_TYPE" :value="scope.row.type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="原因" align="center" prop="reason" />
|
||||
<el-table-column
|
||||
label="申请时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center" width="200">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="handleDetail(scope.row)"
|
||||
v-hasPermi="['bpm:oa-leave:query']"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="handleProcessDetail(scope.row)"
|
||||
v-hasPermi="['bpm:oa-leave:query']"
|
||||
>
|
||||
进度
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="cancelLeave(scope.row)"
|
||||
v-hasPermi="['bpm:oa-leave:create']"
|
||||
v-if="scope.row.result === 1"
|
||||
>
|
||||
取消
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:详情 -->
|
||||
<LeaveDetail ref="detailRef" />
|
||||
<!-- 表单弹窗:添加 -->
|
||||
<LeaveForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script setup lang="ts" name="BpmOALeave">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as LeaveApi from '@/api/bpm/leave'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
import LeaveDetail from './detail.vue'
|
||||
import LeaveForm from './create.vue'
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { push } = useRouter() // 路由
|
||||
const router = useRouter() // 路由
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const [registerTable, { reload }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: LeaveApi.getLeavePage
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
type: undefined,
|
||||
result: undefined,
|
||||
reason: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
// 发起请假
|
||||
const handleCreate = () => {
|
||||
push({
|
||||
name: 'OALeaveCreate'
|
||||
})
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await LeaveApi.getLeavePage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 取消请假
|
||||
const cancelLeave = (row) => {
|
||||
ElMessageBox.prompt('请输入取消原因', '取消流程', {
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加操作 */
|
||||
const formRef = ref()
|
||||
const handleCreate = () => {
|
||||
formRef.value.open()
|
||||
}
|
||||
|
||||
/** 详情操作 */
|
||||
const detailRef = ref()
|
||||
const handleDetail = (data: LeaveApi.LeaveVO) => {
|
||||
detailRef.value.open(data)
|
||||
}
|
||||
|
||||
/** 取消请假操作 */
|
||||
const cancelLeave = async (row) => {
|
||||
// 二次确认
|
||||
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
|
||||
inputErrorMessage: '取消原因不能为空'
|
||||
}).then(async ({ value }) => {
|
||||
await ProcessInstanceApi.cancelProcessInstance(row.id, value)
|
||||
message.success('取消成功')
|
||||
reload()
|
||||
})
|
||||
// 发起取消
|
||||
await ProcessInstanceApi.cancelProcessInstance(row.id, value)
|
||||
message.success('取消成功')
|
||||
// 刷新列表
|
||||
await getList()
|
||||
}
|
||||
|
||||
// 详情
|
||||
const handleDetail = (row) => {
|
||||
push({
|
||||
name: 'OALeaveDetail',
|
||||
query: {
|
||||
id: row.id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 审批进度
|
||||
/** 审批进度 */
|
||||
const handleProcessDetail = (row) => {
|
||||
push({
|
||||
router.push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
query: {
|
||||
id: row.processInstanceId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// 表单校验
|
||||
export const rules = reactive({
|
||||
startTime: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
|
||||
endTime: [{ required: true, message: '结束时间不能为空', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '请假类型不能为空', trigger: 'change' }]
|
||||
})
|
||||
|
||||
// crudSchemas
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: 'id',
|
||||
primaryTitle: '申请编号',
|
||||
action: true,
|
||||
actionWidth: '260',
|
||||
searchSpan: 8,
|
||||
columns: [
|
||||
{
|
||||
title: t('common.status'),
|
||||
field: 'result',
|
||||
dictType: DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT,
|
||||
dictClass: 'number',
|
||||
isSearch: true,
|
||||
isForm: false
|
||||
},
|
||||
{
|
||||
title: t('common.startTimeText'),
|
||||
field: 'startTime',
|
||||
formatter: 'formatDay',
|
||||
table: {
|
||||
width: 180
|
||||
},
|
||||
detail: {
|
||||
dateFormat: 'YYYY-MM-DD'
|
||||
},
|
||||
form: {
|
||||
component: 'DatePicker'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('common.endTimeText'),
|
||||
field: 'endTime',
|
||||
formatter: 'formatDay',
|
||||
table: {
|
||||
width: 180
|
||||
},
|
||||
detail: {
|
||||
dateFormat: 'YYYY-MM-DD'
|
||||
},
|
||||
form: {
|
||||
component: 'DatePicker'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '请假类型',
|
||||
field: 'type',
|
||||
dictType: DICT_TYPE.BPM_OA_LEAVE_TYPE,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '原因',
|
||||
field: 'reason',
|
||||
isSearch: true,
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 4
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '申请时间',
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
table: {
|
||||
width: 180
|
||||
},
|
||||
isSearch: true,
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
},
|
||||
isForm: false
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
|
@ -46,7 +46,7 @@
|
|||
<ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts" name="BpmProcessInstanceCreate">
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import * as DefinitionApi from '@/api/bpm/definition'
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
|
|
|
@ -96,7 +96,7 @@
|
|||
<TaskUpdateAssigneeForm ref="taskUpdateAssigneeFormRef" @success="getDetail" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts" name="BpmProcessInstanceDetail">
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { setConfAndFields2 } from '@/utils/formCreate'
|
||||
import type { ApiAttrs } from '@form-create/element-ui/types/config'
|
||||
|
|
|
@ -1,64 +1,211 @@
|
|||
<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="processDefinitionId">
|
||||
<el-input
|
||||
v-model="queryParams.processDefinitionId"
|
||||
placeholder="请输入流程定义的编号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="流程分类" prop="category">
|
||||
<el-select
|
||||
v-model="queryParams.category"
|
||||
placeholder="请选择流程分类"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<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="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="结果" prop="result">
|
||||
<el-select v-model="queryParams.result" placeholder="请选择结果" clearable class="!w-240px">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT)"
|
||||
: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="发起流程"
|
||||
plain
|
||||
v-hasPermi="['bpm:process-instance:query']"
|
||||
@click="handleCreate"
|
||||
/>
|
||||
</template>
|
||||
<!-- 流程分类 -->
|
||||
<template #category_default="{ row }">
|
||||
<DictTag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="Number(row?.category)" />
|
||||
</template>
|
||||
<!-- 当前审批任务 -->
|
||||
<template #tasks_default="{ row }">
|
||||
<el-button v-for="task in row.tasks" :key="task.id" link>
|
||||
<span>{{ task.name }}</span>
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 发起流程
|
||||
</el-button>
|
||||
</template>
|
||||
<!-- 操作 -->
|
||||
<template #actionbtns_default="{ row }">
|
||||
<XTextButton
|
||||
preIcon="ep:view"
|
||||
:title="t('action.detail')"
|
||||
v-hasPermi="['bpm:process-instance:cancel']"
|
||||
@click="handleDetail(row)"
|
||||
/>
|
||||
<XTextButton
|
||||
preIcon="ep:delete"
|
||||
title="取消"
|
||||
v-if="row.result === 1"
|
||||
v-hasPermi="['bpm:process-instance:query']"
|
||||
@click="handleCancel(row)"
|
||||
/>
|
||||
</template>
|
||||
</XTable>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="流程编号" align="center" prop="id" width="300px" />
|
||||
<el-table-column label="流程名称" align="center" prop="name" />
|
||||
<el-table-column label="流程分类" align="center" prop="category">
|
||||
<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="tasks">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" v-for="task in scope.row.tasks" :key="task.id" link>
|
||||
<span>{{ task.name }}</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结果" prop="result">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_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"
|
||||
prop="endTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-hasPermi="['bpm:process-instance:cancel']"
|
||||
@click="handleDetail(scope.row)"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
v-if="scope.row.result === 1"
|
||||
v-hasPermi="['bpm:process-instance:query']"
|
||||
@click="handleCancel(scope.row)"
|
||||
>
|
||||
取消
|
||||
</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
|
||||
<script setup lang="ts" name="BpmProcessInstance">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
|
||||
// 业务相关的 import
|
||||
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
|
||||
import { allSchemas } from './process.data'
|
||||
|
||||
const router = useRouter() // 路由
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// ========== 列表相关 ==========
|
||||
const [registerTable, { reload }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: ProcessInstanceApi.getMyProcessInstancePage
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: '',
|
||||
processDefinitionId: undefined,
|
||||
category: undefined,
|
||||
status: undefined,
|
||||
result: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ProcessInstanceApi.getMyProcessInstancePage(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 handleCreate = () => {
|
||||
|
@ -67,7 +214,7 @@ const handleCreate = () => {
|
|||
})
|
||||
}
|
||||
|
||||
// 列表操作
|
||||
/** 查看详情 */
|
||||
const handleDetail = (row) => {
|
||||
router.push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
|
@ -78,16 +225,23 @@ const handleDetail = (row) => {
|
|||
}
|
||||
|
||||
/** 取消按钮操作 */
|
||||
const handleCancel = (row) => {
|
||||
ElMessageBox.prompt('请输入取消原因', '取消流程', {
|
||||
const handleCancel = async (row) => {
|
||||
// 二次确认
|
||||
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
|
||||
inputErrorMessage: '取消原因不能为空'
|
||||
}).then(async ({ value }) => {
|
||||
await ProcessInstanceApi.cancelProcessInstance(row.id, value)
|
||||
message.success('取消成功')
|
||||
reload()
|
||||
})
|
||||
// 发起取消
|
||||
await ProcessInstanceApi.cancelProcessInstance(row.id, value)
|
||||
message.success('取消成功')
|
||||
// 刷新列表
|
||||
await getList()
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// CrudSchema
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: null,
|
||||
primaryTitle: '编号',
|
||||
action: true,
|
||||
actionWidth: '200px',
|
||||
columns: [
|
||||
{
|
||||
title: '编号',
|
||||
field: 'id',
|
||||
table: {
|
||||
width: 320
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '流程名',
|
||||
field: 'name',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '所属流程',
|
||||
field: 'processDefinitionId',
|
||||
isSearch: true,
|
||||
isTable: false
|
||||
},
|
||||
{
|
||||
title: '流程分类',
|
||||
field: 'category',
|
||||
dictType: DICT_TYPE.BPM_MODEL_CATEGORY,
|
||||
dictClass: 'number',
|
||||
isSearch: true,
|
||||
table: {
|
||||
slots: {
|
||||
default: 'category_default'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '当前审批任务',
|
||||
field: 'tasks',
|
||||
table: {
|
||||
width: 140,
|
||||
slots: {
|
||||
default: 'tasks_default'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('common.status'),
|
||||
field: 'status',
|
||||
dictType: DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '结果',
|
||||
field: 'result',
|
||||
dictType: DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT,
|
||||
dictClass: 'number',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '提交时间',
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
table: {
|
||||
width: 180
|
||||
},
|
||||
isForm: false,
|
||||
isSearch: true,
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '结束时间',
|
||||
field: 'endTime',
|
||||
formatter: 'formatDate',
|
||||
table: {
|
||||
width: 180
|
||||
},
|
||||
isForm: false
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
|
@ -74,7 +74,7 @@
|
|||
<!-- 表单弹窗:详情 -->
|
||||
<TaskDetail ref="detailRef" @success="getList" />
|
||||
</template>
|
||||
<script setup lang="tsx">
|
||||
<script setup lang="tsx" name="BpmTodoTask">
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
|
|
|
@ -1,32 +1,117 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<XTable @register="registerTable">
|
||||
<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:edit-pen" title="审批进度" @click="handleAudit(row)" />
|
||||
</template>
|
||||
</XTable>
|
||||
<!-- 搜索工作栏 -->
|
||||
<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="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-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="任务编号" align="center" prop="id" width="300px" />
|
||||
<el-table-column label="任务名称" align="center" prop="name" />
|
||||
<el-table-column label="所属流程" align="center" prop="processInstance.name" />
|
||||
<el-table-column label="流程发起人" align="center" prop="processInstance.startUserNickname" />
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="任务状态" prop="suspensionState">
|
||||
<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">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" @click="handleAudit(scope.row)">审批进度</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 { allSchemas } from './todo.data'
|
||||
<script setup lang="tsx" name="BpmDoneTask">
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
const { push } = useRouter() // 路由
|
||||
import * as TaskApi from '@/api/bpm/task'
|
||||
|
||||
const { push } = useRouter() // 路由
|
||||
|
||||
const [registerTable] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
topActionSlots: false,
|
||||
getListApi: TaskApi.getTodoTaskPage
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: '',
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
// 处理审批按钮
|
||||
/** 查询任务列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await TaskApi.getTodoTaskPage(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 handleAudit = (row) => {
|
||||
push({
|
||||
name: 'BpmProcessInstanceDetail',
|
||||
|
@ -35,4 +120,9 @@ const handleAudit = (row) => {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
// crudSchemas
|
||||
const crudSchemas = reactive<VxeCrudSchema>({
|
||||
primaryKey: 'id',
|
||||
primaryType: null,
|
||||
action: true,
|
||||
searchSpan: 8,
|
||||
columns: [
|
||||
{
|
||||
title: '任务编号',
|
||||
field: 'id',
|
||||
table: {
|
||||
width: 320
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '任务名称',
|
||||
field: 'name',
|
||||
isSearch: true
|
||||
},
|
||||
{
|
||||
title: '所属流程',
|
||||
field: 'processInstance.name'
|
||||
},
|
||||
{
|
||||
title: '流程发起人',
|
||||
field: 'processInstance.startUserNickname'
|
||||
},
|
||||
{
|
||||
title: t('common.createTime'),
|
||||
field: 'createTime',
|
||||
formatter: 'formatDate',
|
||||
table: {
|
||||
width: 180
|
||||
},
|
||||
isSearch: true,
|
||||
search: {
|
||||
show: true,
|
||||
itemRender: {
|
||||
name: 'XDataTimePicker'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '任务状态',
|
||||
field: 'suspensionState',
|
||||
table: {
|
||||
slots: {
|
||||
default: 'suspensionState_default'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
export const { allSchemas } = useVxeCrudSchemas(crudSchemas)
|
|
@ -32,7 +32,7 @@
|
|||
<!-- 添加/修改弹窗 -->
|
||||
<TaskAssignRuleForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script setup lang="ts" name="TaskAssignRule">
|
||||
<script setup lang="ts" name="BpmTaskAssignRule">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import * as TaskAssignRuleApi from '@/api/bpm/taskAssignRule'
|
||||
import * as RoleApi from '@/api/system/role'
|
||||
|
|
|
@ -139,7 +139,7 @@
|
|||
<!-- 表单弹窗:详情 -->
|
||||
<ApiAccessLogDetail ref="detailRef" />
|
||||
</template>
|
||||
<script setup lang="ts" name="ApiAccessLog">
|
||||
<script setup lang="ts" name="InfraApiAccessLog">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import download from '@/utils/download'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
|
|
|
@ -158,14 +158,13 @@
|
|||
<ApiErrorLogDetail ref="detailRef" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ApiErrorLog">
|
||||
<script setup lang="ts" name="InfraApiErrorLog">
|
||||
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 message = useMessage() // 消息弹窗
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts" name="Build">
|
||||
<script setup lang="ts" name="InfraBuild">
|
||||
import formCreate from '@form-create/element-ui'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
const { t } = useI18n() // 国际化
|
||||
|
|
|
@ -142,7 +142,7 @@
|
|||
<!-- 弹窗:预览代码 -->
|
||||
<PreviewCode ref="previewRef" />
|
||||
</template>
|
||||
<script setup lang="ts" name="Codegen">
|
||||
<script setup lang="ts" name="InfraCodegen">
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as CodegenApi from '@/api/infra/codegen'
|
||||
|
|
|
@ -137,7 +137,7 @@
|
|||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ConfigForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script setup lang="ts" name="Config">
|
||||
<script setup lang="ts" name="InfraConfig">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
<!-- 表单弹窗:添加/修改 -->
|
||||
<DataSourceConfigForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script setup lang="ts" name="DataSourceConfig">
|
||||
<script setup lang="ts" name="InfraDataSourceConfig">
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as DataSourceConfigApi from '@/api/infra/dataSourceConfig'
|
||||
import DataSourceConfigForm from './DataSourceConfigForm.vue'
|
||||
|
|
|
@ -2,46 +2,38 @@
|
|||
<doc-alert title="数据库文档" url="https://doc.iocoder.cn/db-doc/" />
|
||||
|
||||
<ContentWrap title="数据库文档">
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="mb-10px">
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:download"
|
||||
:title="t('action.export') + ' HTML'"
|
||||
@click="handleExport('HTML')"
|
||||
/>
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:download"
|
||||
:title="t('action.export') + ' Word'"
|
||||
@click="handleExport('Word')"
|
||||
/>
|
||||
<XButton
|
||||
type="primary"
|
||||
preIcon="ep:download"
|
||||
:title="t('action.export') + ' Markdown'"
|
||||
@click="handleExport('Markdown')"
|
||||
/>
|
||||
<el-button type="primary" plain @click="handleExport('HTML')">
|
||||
<Icon icon="ep:download" /> 导出 HTML
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="handleExport('Word')">
|
||||
<Icon icon="ep:download" /> 导出 Word
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="handleExport('Markdown')">
|
||||
<Icon icon="ep:download" /> 导出 Markdown
|
||||
</el-button>
|
||||
</div>
|
||||
<IFrame v-if="!loding" v-loading="loding" :src="src" />
|
||||
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts" name="DbDoc">
|
||||
<script setup lang="ts" name="InfraDBDoc">
|
||||
import download from '@/utils/download'
|
||||
|
||||
import * as DbDocApi from '@/api/infra/dbDoc'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const src = ref('')
|
||||
const loding = ref(true)
|
||||
const loading = ref(true) // 是否加载中
|
||||
const src = ref('') // HTML 的地址
|
||||
|
||||
/** 页面加载 */
|
||||
const init = async () => {
|
||||
const res = await DbDocApi.exportHtml()
|
||||
let blob = new Blob([res], { type: 'text/html' })
|
||||
let blobUrl = window.URL.createObjectURL(blob)
|
||||
src.value = blobUrl
|
||||
loding.value = false
|
||||
try {
|
||||
const data = await DbDocApi.exportHtml()
|
||||
const blob = new Blob([data], { type: 'text/html' })
|
||||
src.value = window.URL.createObjectURL(blob)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理导出 */
|
||||
const handleExport = async (type: string) => {
|
||||
if (type === 'HTML') {
|
||||
|
@ -57,6 +49,8 @@ const handleExport = async (type: string) => {
|
|||
download.markdown(res, '数据库文档.md')
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await init()
|
||||
})
|
||||
|
|
|
@ -3,10 +3,24 @@
|
|||
<doc-alert title="多数据源(读写分离)" url="https://doc.iocoder.cn/dynamic-datasource/" />
|
||||
|
||||
<ContentWrap>
|
||||
<IFrame :src="src" />
|
||||
<IFrame v-if="!loading" :src="url" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts" name="Druid">
|
||||
const BASE_URL = import.meta.env.VITE_BASE_URL
|
||||
const src = ref(BASE_URL + '/druid/index.html')
|
||||
<script setup lang="ts" name="InfraDruid">
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
|
||||
const loading = ref(true) // 是否加载中
|
||||
const url = ref(import.meta.env.VITE_BASE_URL + '/druid/index.html')
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await ConfigApi.getConfigKey('url.druid')
|
||||
if (data && data.length > 0) {
|
||||
url.value = data
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -2,17 +2,19 @@
|
|||
<Dialog title="上传文件" v-model="dialogVisible">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:limit="1"
|
||||
accept=".jpg, .png, .gif"
|
||||
:auto-upload="false"
|
||||
drag
|
||||
:headers="headers"
|
||||
:action="url"
|
||||
:data="data"
|
||||
:disabled="formLoading"
|
||||
:headers="uploadHeaders"
|
||||
v-model:file-list="fileList"
|
||||
drag
|
||||
accept=".jpg, .png, .gif"
|
||||
:limit="1"
|
||||
:on-success="submitFormSuccess"
|
||||
:on-exceed="handleExceed"
|
||||
:on-error="submitFormError"
|
||||
:on-change="handleFileChange"
|
||||
:on-progress="handleFileUploadProgress"
|
||||
:on-success="handleFileSuccess"
|
||||
:auto-upload="false"
|
||||
:disabled="formLoading"
|
||||
>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em> </div>
|
||||
|
@ -29,44 +31,47 @@
|
|||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { Dialog } from '@/components/Dialog'
|
||||
import { getAccessToken } from '@/utils/auth'
|
||||
import { getAccessToken, getTenantId } from '@/utils/auth'
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const url = import.meta.env.VITE_UPLOAD_URL
|
||||
const headers = { Authorization: 'Bearer ' + getAccessToken() }
|
||||
const uploadHeaders = ref() // 上传 Header 头
|
||||
const fileList = ref([]) // 文件列表
|
||||
const data = ref({ path: '' })
|
||||
const uploadRef = ref()
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async () => {
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
/** 处理上传的文件发生变化 */
|
||||
const handleFileChange = (file) => {
|
||||
data.value.path = file.name
|
||||
}
|
||||
|
||||
/** 处理文件上传中 */
|
||||
const handleFileUploadProgress = () => {
|
||||
formLoading.value = true // 禁止修改
|
||||
}
|
||||
|
||||
/** 发起文件上传 */
|
||||
/** 提交表单 */
|
||||
const submitFileForm = () => {
|
||||
if (fileList.value.length == 0) {
|
||||
message.error('请上传文件')
|
||||
return
|
||||
}
|
||||
// 提交请求
|
||||
uploadHeaders.value = {
|
||||
Authorization: 'Bearer ' + getAccessToken(),
|
||||
'tenant-id': getTenantId()
|
||||
}
|
||||
unref(uploadRef)?.submit()
|
||||
}
|
||||
|
||||
/** 文件上传成功处理 */
|
||||
const handleFileSuccess = () => {
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitFormSuccess = () => {
|
||||
// 清理
|
||||
dialogVisible.value = false
|
||||
formLoading.value = false
|
||||
|
@ -75,4 +80,22 @@ const handleFileSuccess = () => {
|
|||
message.success(t('common.createSuccess'))
|
||||
emit('success')
|
||||
}
|
||||
|
||||
/** 上传错误提示 */
|
||||
const submitFormError = (): void => {
|
||||
message.error('上传失败,请您重新上传!')
|
||||
formLoading.value = false
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
// 重置上传状态和文件
|
||||
formLoading.value = false
|
||||
uploadRef.value?.clearFiles()
|
||||
}
|
||||
|
||||
/** 文件数超出提示 */
|
||||
const handleExceed = (): void => {
|
||||
message.error('最多只能上传一个文件!')
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
<template>
|
||||
<doc-alert title="上传下载" url="https://doc.iocoder.cn/file/"/>
|
||||
|
||||
<doc-alert title="上传下载" url="https://doc.iocoder.cn/file/" />
|
||||
<!-- 搜索 -->
|
||||
<ContentWrap>
|
||||
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="文件路径" prop="path">
|
||||
<el-input
|
||||
v-model="queryParams.path"
|
||||
|
@ -33,7 +38,7 @@
|
|||
<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">
|
||||
<el-button type="primary" plain @click="openForm">
|
||||
<Icon icon="ep:upload" class="mr-5px" /> 上传文件
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
@ -86,11 +91,11 @@
|
|||
<!-- 表单弹窗:添加/修改 -->
|
||||
<FileForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script setup lang="ts" name="Config">
|
||||
<script setup lang="ts" name="InfraFile">
|
||||
import { fileSizeFormatter } from '@/utils'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as FileApi from '@/api/infra/file'
|
||||
import FileUploadForm from './FileForm.vue'
|
||||
import FileForm from './FileForm.vue'
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
|
|
|
@ -3,17 +3,29 @@
|
|||
|
||||
<!-- 搜索 -->
|
||||
<ContentWrap>
|
||||
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true">
|
||||
<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="storage">
|
||||
<el-select v-model="queryParams.storage" placeholder="请选择存储器" clearable>
|
||||
<el-select
|
||||
v-model="queryParams.storage"
|
||||
placeholder="请选择存储器"
|
||||
clearable
|
||||
class="!w-240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)"
|
||||
:key="dict.value"
|
||||
|
@ -30,6 +42,7 @@
|
|||
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>
|
||||
|
@ -113,7 +126,7 @@
|
|||
<!-- 表单弹窗:添加/修改 -->
|
||||
<FileConfigForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script setup lang="ts" name="Config">
|
||||
<script setup lang="ts" name="InfraFileConfig">
|
||||
import * as FileConfigApi from '@/api/infra/fileConfig'
|
||||
import FileConfigForm from './FileConfigForm.vue'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
|
|
|
@ -147,7 +147,7 @@
|
|||
<!-- 表单弹窗:查看 -->
|
||||
<JobDetail ref="detailRef" />
|
||||
</template>
|
||||
<script setup lang="ts" name="Job">
|
||||
<script setup lang="ts" name="InfraJob">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { checkPermi } from '@/utils/permission'
|
||||
import JobForm from './JobForm.vue'
|
||||
|
|
|
@ -121,7 +121,7 @@
|
|||
<!-- 表单弹窗:查看 -->
|
||||
<JobLogDetail ref="detailRef" />
|
||||
</template>
|
||||
<script setup lang="ts" name="JobLog">
|
||||
<script setup lang="ts" name="InfraJobLog">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
<el-scrollbar height="calc(100vh - 88px - 40px - 50px)">
|
||||
<el-row>
|
||||
<!-- 基本信息 -->
|
||||
<el-col :span="24" class="card-box" shadow="hover">
|
||||
<el-card>
|
||||
<el-descriptions title="基本信息" :column="6" border>
|
||||
|
@ -47,106 +48,33 @@
|
|||
</el-descriptions>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<!-- 命令统计 -->
|
||||
<el-col :span="12" class="mt-3">
|
||||
<el-card :gutter="12" shadow="hover">
|
||||
<div ref="commandStatsRef" class="h-88"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<!-- 内存使用量统计 -->
|
||||
<el-col :span="12" class="mt-3">
|
||||
<el-card class="ml-3" :gutter="12" shadow="hover">
|
||||
<div ref="usedmemory" class="h-88"></div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row class="mt-3">
|
||||
<el-col :span="24" class="card-box" shadow="hover">
|
||||
<el-card>
|
||||
<el-table
|
||||
v-loading="keyListLoad"
|
||||
:data="keyList"
|
||||
row-key="id"
|
||||
@row-click="openKeyTemplate"
|
||||
>
|
||||
<el-table-column prop="keyTemplate" label="Key 模板" width="200" />
|
||||
<el-table-column prop="keyType" label="Key 类型" width="100" />
|
||||
<el-table-column prop="valueType" label="Value 类型" />
|
||||
<el-table-column prop="timeoutType" label="超时时间" width="200">
|
||||
<template #default="{ row }">
|
||||
<DictTag :type="DICT_TYPE.INFRA_REDIS_TIMEOUT_TYPE" :value="row?.timeoutType" />
|
||||
<span v-if="row?.timeout > 0">({{ row?.timeout / 1000 }} 秒)</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="memo" label="备注" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-scrollbar>
|
||||
<XModal v-model="dialogVisible" :title="keyTemplate + ' 模板'">
|
||||
<el-row>
|
||||
<el-col :span="14" class="mt-3">
|
||||
<el-card shadow="always">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>键名列表</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-table :data="cacheKeys" style="width: 100%" @row-click="handleKeyValue">
|
||||
<el-table-column label="缓存键名" align="center" :show-overflow-tooltip="true">
|
||||
<template #default="{ row }">
|
||||
{{ row }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="right" width="60">
|
||||
<template #default="{ row }">
|
||||
<XTextButton preIcon="ep:delete" @click="handleDeleteKey(row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="10" class="mt-3">
|
||||
<el-card shadow="always">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>缓存内容</span>
|
||||
<XTextButton
|
||||
preIcon="ep:refresh"
|
||||
title="清理全部"
|
||||
@click="handleDeleteKeys(keyTemplate)"
|
||||
class="float-right p-1"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="1">
|
||||
<el-descriptions-item label="缓存键名:">{{ cacheForm.key }}</el-descriptions-item>
|
||||
<el-descriptions-item label="缓存内容:">{{ cacheForm.value }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="Redis">
|
||||
<script setup lang="ts" name="InfraRedis">
|
||||
import * as echarts from 'echarts'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
|
||||
import * as RedisApi from '@/api/infra/redis'
|
||||
import { RedisKeyInfo, RedisMonitorInfoVO } from '@/api/infra/redis/types'
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
import { RedisMonitorInfoVO } from '@/api/infra/redis/types'
|
||||
|
||||
const cache = ref<RedisMonitorInfoVO>()
|
||||
const keyListLoad = ref(true)
|
||||
const keyList = ref<RedisKeyInfo[]>([])
|
||||
|
||||
// 基本信息
|
||||
const readRedisInfo = async () => {
|
||||
const data = await RedisApi.getCache()
|
||||
cache.value = data
|
||||
loadEchartOptions(data.commandStats)
|
||||
const redisKeysInfo = await RedisApi.getKeyDefineList()
|
||||
keyList.value = redisKeysInfo
|
||||
keyListLoad.value = false //加载完成
|
||||
}
|
||||
// 图表
|
||||
const commandStatsRef = ref<HTMLElement>()
|
||||
|
@ -241,40 +169,8 @@ const loadEchartOptions = (stats) => {
|
|||
]
|
||||
})
|
||||
}
|
||||
const dialogVisible = ref(false)
|
||||
const keyTemplate = ref('')
|
||||
const cacheKeys = ref()
|
||||
const cacheForm = ref<{
|
||||
key: string
|
||||
value: string
|
||||
}>({
|
||||
key: '',
|
||||
value: ''
|
||||
})
|
||||
const openKeyTemplate = async (row: RedisKeyInfo) => {
|
||||
keyTemplate.value = row.keyTemplate
|
||||
cacheKeys.value = await RedisApi.getKeyList(row.keyTemplate)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
const handleDeleteKey = async (row) => {
|
||||
RedisApi.deleteKey(row)
|
||||
message.success(t('common.delSuccess'))
|
||||
}
|
||||
const handleDeleteKeys = async (row) => {
|
||||
RedisApi.deleteKeys(row)
|
||||
message.success(t('common.delSuccess'))
|
||||
}
|
||||
const handleKeyValue = async (row) => {
|
||||
const res = await RedisApi.getKeyValue(row)
|
||||
cacheForm.value = res
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
readRedisInfo()
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
.redis {
|
||||
height: 600px;
|
||||
max-height: 860px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,10 +1,25 @@
|
|||
<template>
|
||||
<doc-alert title="服务监控" url="https://doc.iocoder.cn/server-monitor/" />
|
||||
|
||||
<ContentWrap>
|
||||
<IFrame :src="src" />
|
||||
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts" name="AdminServer">
|
||||
const BASE_URL = import.meta.env.VITE_BASE_URL
|
||||
const src = ref(BASE_URL + '/admin/applications')
|
||||
<script setup lang="ts" name="InfraAdminServer">
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
|
||||
const loading = ref(true) // 是否加载中
|
||||
const src = ref(import.meta.env.VITE_BASE_URL + '/admin/applications')
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await ConfigApi.getConfigKey('url.spring-boot-admin')
|
||||
if (data && data.length > 0) {
|
||||
src.value = data
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -1,9 +1,25 @@
|
|||
<template>
|
||||
<doc-alert title="服务监控" url="https://doc.iocoder.cn/server-monitor/" />
|
||||
|
||||
<ContentWrap>
|
||||
<IFrame :src="src" />
|
||||
<IFrame v-if="!loading" v-loading="loading" :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts" name="Skywalking">
|
||||
<script setup lang="ts" name="InfraSkyWalking">
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
|
||||
const loading = ref(true) // 是否加载中
|
||||
const src = ref('http://skywalking.shop.iocoder.cn')
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await ConfigApi.getConfigKey('url.skywalking')
|
||||
if (data && data.length > 0) {
|
||||
src.value = data
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -5,8 +5,22 @@
|
|||
<IFrame :src="src" />
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup lang="ts" name="Swagger">
|
||||
const BASE_URL = import.meta.env.VITE_BASE_URL
|
||||
// const src = ref(BASE_URL + '/doc.html')
|
||||
const src = ref(BASE_URL + '/swagger-ui')
|
||||
<script setup lang="ts" name="InfraSwagger">
|
||||
import * as ConfigApi from '@/api/infra/config'
|
||||
|
||||
const loading = ref(true) // 是否加载中
|
||||
const src = ref(import.meta.env.VITE_BASE_URL + '/doc.html') // Knife4j UI
|
||||
// const src = ref(import.meta.env.VITE_BASE_URL + '/swagger-ui') // Swagger UI
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await ConfigApi.getConfigKey('url.swagger')
|
||||
if (data && data.length > 0) {
|
||||
src.value = data
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||
<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-item label="品牌图片" prop="picUrl">
|
||||
<UploadImg v-model="formData.picUrl" :limit="1" :is-show-tip="false" />
|
||||
</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="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="品牌描述">
|
||||
<el-input v-model="formData.description" type="textarea" placeholder="请输入品牌描述" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts" name="ProductBrandForm">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import * as ProductBrandApi from '@/api/mall/product/brand'
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
name: '',
|
||||
picUrl: '',
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
description: ''
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '品牌名称不能为空', trigger: 'blur' }],
|
||||
picUrl: [{ required: true, message: '品牌图片不能为空', trigger: 'blur' }],
|
||||
sort: [{ required: true, message: '品牌排序不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await ProductBrandApi.getBrand(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 ProductBrandApi.BrandVO
|
||||
if (formType.value === 'create') {
|
||||
await ProductBrandApi.createBrand(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
await ProductBrandApi.updateBrand(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
name: '',
|
||||
picUrl: '',
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
description: ''
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,177 @@
|
|||
<template>
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<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"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['product:brand:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" row-key="id" default-expand-all>
|
||||
<el-table-column label="品牌名称" prop="name" sortable />
|
||||
<el-table-column label="品牌图片" align="center" prop="picUrl">
|
||||
<template #default="scope">
|
||||
<img v-if="scope.row.picUrl" :src="scope.row.picUrl" alt="品牌图片" class="h-100px" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="品牌排序" align="center" prop="sort" />
|
||||
<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"
|
||||
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="['product:brand:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['product:brand: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>
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<BrandForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script setup lang="ts" name="ProductBrand">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as ProductBrandApi from '@/api/mall/product/brand'
|
||||
import BrandForm from './BrandForm.vue'
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref<any[]>([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: undefined,
|
||||
status: undefined,
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await ProductBrandApi.getBrandParam(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
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: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await ProductBrandApi.deleteBrand(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
|
@ -50,7 +50,7 @@
|
|||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts" name="ProductCategory">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import { handleTree } from '@/utils/tree'
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
<!-- 表单弹窗:添加/修改 -->
|
||||
<PropertyForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script setup lang="ts" name="Config">
|
||||
<script setup lang="ts" name="ProductProperty">
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as PropertyApi from '@/api/mall/product/property'
|
||||
import PropertyForm from './PropertyForm.vue'
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
<!-- 表单弹窗:添加/修改 -->
|
||||
<ValueForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script setup lang="ts" name="Config">
|
||||
<script setup lang="ts" name="ProductPropertyValue">
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as PropertyApi from '@/api/mall/product/property'
|
||||
import ValueForm from './ValueForm.vue'
|
||||
|
|
|
@ -1,3 +1,423 @@
|
|||
<template>
|
||||
<span>开发中</span>
|
||||
<doc-alert title="自动回复" url="https://doc.iocoder.cn/mp/auto-reply/" />
|
||||
|
||||
<!-- 搜索工作栏 -->
|
||||
<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>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- tab 切换 -->
|
||||
<ContentWrap>
|
||||
<el-tabs v-model="type" @tab-change="handleTabChange">
|
||||
<!-- 操作工具栏 -->
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="handleAdd"
|
||||
v-hasPermi="['mp:auto-reply:create']"
|
||||
v-if="type !== '1' || list.length <= 0"
|
||||
>
|
||||
<Icon icon="ep:plus" />新增
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- tab 项 -->
|
||||
<el-tab-pane name="1">
|
||||
<template #label>
|
||||
<span><Icon icon="ep:star-off" /> 关注时回复</span>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="2">
|
||||
<template #label>
|
||||
<span><Icon icon="ep:chat-line-round" /> 消息回复</span>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="3">
|
||||
<template #label>
|
||||
<span><Icon icon="ep:news" /> 关键词回复</span>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<!-- 列表 -->
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column
|
||||
label="请求消息类型"
|
||||
align="center"
|
||||
prop="requestMessageType"
|
||||
v-if="type === '2'"
|
||||
/>
|
||||
<el-table-column label="关键词" align="center" prop="requestKeyword" v-if="type === '3'" />
|
||||
<el-table-column label="匹配类型" align="center" prop="requestMatch" v-if="type === '3'">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH" :value="scope.row.requestMatch" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="回复消息类型" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.MP_MESSAGE_TYPE" :value="scope.row.responseMessageType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="回复内容" align="center">
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.responseMessageType === 'text'">{{ scope.row.responseContent }}</div>
|
||||
<div v-else-if="scope.row.responseMessageType === 'voice'">
|
||||
<WxVoicePlayer :url="scope.row.responseMediaUrl" />
|
||||
</div>
|
||||
<div v-else-if="scope.row.responseMessageType === 'image'">
|
||||
<a target="_blank" :href="scope.row.responseMediaUrl">
|
||||
<img :src="scope.row.responseMediaUrl" style="width: 100px" />
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
scope.row.responseMessageType === 'video' ||
|
||||
scope.row.responseMessageType === 'shortvideo'
|
||||
"
|
||||
>
|
||||
<WxVideoPlayer :url="scope.row.responseMediaUrl" style="margin-top: 10px" />
|
||||
</div>
|
||||
<div v-else-if="scope.row.responseMessageType === 'news'">
|
||||
<WxNews :articles="scope.row.responseArticles" />
|
||||
</div>
|
||||
<div v-else-if="scope.row.responseMessageType === 'music'">
|
||||
<WxMusic
|
||||
:title="scope.row.responseTitle"
|
||||
:description="scope.row.responseDescription"
|
||||
:thumb-media-url="scope.row.responseThumbMediaUrl"
|
||||
:music-url="scope.row.responseMusicUrl"
|
||||
:hq-music-url="scope.row.responseHqMusicUrl"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="创建时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
:formatter="dateFormatter"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['mp:auto-reply:update']"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['mp:auto-reply:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 添加或修改自动回复的对话框 -->
|
||||
<el-dialog :title="title" v-model="open" width="800px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="消息类型" prop="requestMessageType" v-if="type === '2'">
|
||||
<el-select v-model="form.requestMessageType" placeholder="请选择">
|
||||
<template v-for="dict in getDictOptions(DICT_TYPE.MP_MESSAGE_TYPE)" :key="dict.value">
|
||||
<el-option
|
||||
v-if="requestMessageTypes.includes(dict.value)"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</template>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="匹配类型" prop="requestMatch" v-if="type === '3'">
|
||||
<el-select v-model="form.requestMatch" placeholder="请选择匹配类型" clearable>
|
||||
<el-option
|
||||
v-for="dict in getDictOptions(DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="关键词" prop="requestKeyword" v-if="type === '3'">
|
||||
<el-input v-model="form.requestKeyword" placeholder="请输入内容" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="回复消息">
|
||||
<WxReplySelect :objData="objData" v-if="hackResetWxReplySelect" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确 定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup name="MpAutoReply">
|
||||
import { ref, reactive, onMounted, nextTick } from 'vue'
|
||||
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
|
||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
|
||||
import WxMusic from '@/views/mp/components/wx-music/main.vue'
|
||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
|
||||
import { getSimpleAccountList } from '@/api/mp/account'
|
||||
import {
|
||||
createAutoReply,
|
||||
deleteAutoReply,
|
||||
getAutoReply,
|
||||
getAutoReplyPage,
|
||||
updateAutoReply
|
||||
} from '@/api/mp/autoReply'
|
||||
|
||||
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const queryFormRef = ref()
|
||||
const formRef = ref()
|
||||
|
||||
// tab 类型(1、关注时回复;2、消息回复;3、关键词回复)
|
||||
const type = ref('3')
|
||||
// 允许选择的请求消息类型
|
||||
const requestMessageTypes = ['text', 'image', 'voice', 'video', 'shortvideo', 'location', 'link']
|
||||
// 遮罩层
|
||||
const loading = ref(true)
|
||||
// 显示搜索条件
|
||||
// const showSearch = ref(true)
|
||||
// 总条数
|
||||
const total = ref(0)
|
||||
// 自动回复列表
|
||||
const list = ref([])
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
accountId: undefined
|
||||
})
|
||||
|
||||
// 弹出层标题
|
||||
const title = ref('')
|
||||
// 是否显示弹出层
|
||||
const open = ref(false)
|
||||
// 表单参数
|
||||
const form = ref({})
|
||||
// 回复消息
|
||||
const objData = ref({
|
||||
type: 'text'
|
||||
})
|
||||
// 表单校验
|
||||
const rules = {
|
||||
requestKeyword: [{ required: true, message: '请求的关键字不能为空', trigger: 'blur' }],
|
||||
requestMatch: [{ required: true, message: '请求的关键字的匹配不能为空', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const hackResetWxReplySelect = ref(false) // 重置 WxReplySelect 组件,解决无法清除的问题
|
||||
|
||||
// 公众号账号列表
|
||||
const accountList = ref([])
|
||||
|
||||
onMounted(() => {
|
||||
getSimpleAccountList().then((data) => {
|
||||
accountList.value = data
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
queryParams.accountId = accountList.value[0].id
|
||||
}
|
||||
// 加载数据
|
||||
getList()
|
||||
})
|
||||
})
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
// 如果没有选中公众号账号,则进行提示。
|
||||
if (!queryParams.accountId) {
|
||||
message.error('未选中公众号,无法查询自动回复')
|
||||
return false
|
||||
}
|
||||
|
||||
loading.value = false
|
||||
// 处理查询参数
|
||||
let params = {
|
||||
...queryParams,
|
||||
type: type.value
|
||||
}
|
||||
// 执行查询
|
||||
getAutoReplyPage(params).then((data) => {
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields()
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
queryParams.accountId = accountList.value[0].id
|
||||
}
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
const handleTabChange = (tabName) => {
|
||||
type.value = tabName
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset()
|
||||
resetEditor()
|
||||
// 打开表单,并设置初始化
|
||||
open.value = true
|
||||
title.value = '新增自动回复'
|
||||
objData.value = {
|
||||
type: 'text',
|
||||
accountId: queryParams.accountId
|
||||
}
|
||||
}
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = (row) => {
|
||||
reset()
|
||||
resetEditor()
|
||||
console.log(row)
|
||||
|
||||
getAutoReply(row.id).then((data) => {
|
||||
// 设置属性
|
||||
form.value = { ...data }
|
||||
delete form.value['responseMessageType']
|
||||
delete form.value['responseContent']
|
||||
delete form.value['responseMediaId']
|
||||
delete form.value['responseMediaUrl']
|
||||
delete form.value['responseDescription']
|
||||
delete form.value['responseArticles']
|
||||
objData.value = {
|
||||
type: data.responseMessageType,
|
||||
accountId: queryParams.accountId,
|
||||
content: data.responseContent,
|
||||
mediaId: data.responseMediaId,
|
||||
url: data.responseMediaUrl,
|
||||
title: data.responseTitle,
|
||||
description: data.responseDescription,
|
||||
thumbMediaId: data.responseThumbMediaId,
|
||||
thumbMediaUrl: data.responseThumbMediaUrl,
|
||||
articles: data.responseArticles,
|
||||
musicUrl: data.responseMusicUrl,
|
||||
hqMusicUrl: data.responseHqMusicUrl
|
||||
}
|
||||
|
||||
// 打开表单
|
||||
open.value = true
|
||||
title.value = '修改自动回复'
|
||||
})
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
formRef.value?.validate((valid) => {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
|
||||
// 处理回复消息
|
||||
const form = { ...form.value }
|
||||
form.responseMessageType = objData.value.type
|
||||
form.responseContent = objData.value.content
|
||||
form.responseMediaId = objData.value.mediaId
|
||||
form.responseMediaUrl = objData.value.url
|
||||
form.responseTitle = objData.value.title
|
||||
form.responseDescription = objData.value.description
|
||||
form.responseThumbMediaId = objData.value.thumbMediaId
|
||||
form.responseThumbMediaUrl = objData.value.thumbMediaUrl
|
||||
form.responseArticles = objData.value.articles
|
||||
form.responseMusicUrl = objData.value.musicUrl
|
||||
form.responseHqMusicUrl = objData.value.hqMusicUrl
|
||||
|
||||
if (form.value.id !== undefined) {
|
||||
updateAutoReply(form).then(() => {
|
||||
message.success('修改成功')
|
||||
open.value = false
|
||||
getList()
|
||||
})
|
||||
} else {
|
||||
createAutoReply(form).then(() => {
|
||||
message.success('新增成功')
|
||||
open.value = false
|
||||
getList()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 表单重置
|
||||
const reset = () => {
|
||||
form.value = {
|
||||
id: undefined,
|
||||
accountId: queryParams.accountId,
|
||||
type: type.value,
|
||||
requestKeyword: undefined,
|
||||
requestMatch: type.value === '3' ? 1 : undefined,
|
||||
requestMessageType: undefined
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
// 取消按钮
|
||||
const cancel = () => {
|
||||
open.value = false
|
||||
reset()
|
||||
}
|
||||
|
||||
// 表单 Editor 重置
|
||||
const resetEditor = () => {
|
||||
hackResetWxReplySelect.value = false // 销毁组件
|
||||
nextTick(() => {
|
||||
hackResetWxReplySelect.value = true // 重建组件
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = async (row) => {
|
||||
await message.confirm('是否确认删除此数据?')
|
||||
await deleteAutoReply(row.id)
|
||||
await getList()
|
||||
message.success('删除成功')
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { QuillEditor } from '@vueup/vue-quill'
|
||||
import '@vueup/vue-quill/dist/vue-quill.snow.css'
|
||||
import { getAccessToken } from '@/utils/auth'
|
||||
import editorOptions from './quill-options'
|
||||
|
||||
const BASE_URL = import.meta.env.VITE_BASE_URL
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const props = defineProps({
|
||||
/* 公众号账号编号 */
|
||||
accountId: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
/* 编辑器的内容 */
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/* 图片大小 */
|
||||
maxSize: {
|
||||
type: Number,
|
||||
default: 4000 // kb
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['input'])
|
||||
|
||||
const myQuillEditorRef = ref()
|
||||
|
||||
const content = ref(props.value.replace(/data-src/g, 'src'))
|
||||
|
||||
const loading = ref(false) // 根据图片上传状态来确定是否显示loading动画,刚开始是false,不显示
|
||||
|
||||
const actionUrl = ref(BASE_URL + '/admin-api/mp/material/upload-news-image') // 这里写你要上传的图片服务器地址
|
||||
const headers = ref({ Authorization: 'Bearer ' + getAccessToken() }) // 设置上传的请求头部
|
||||
const uploadData = reactive({
|
||||
type: 'image', // TODO 芋艿:试试要不要换成 thumb
|
||||
accountId: props.accountId
|
||||
})
|
||||
|
||||
const onEditorChange = () => {
|
||||
//内容改变事件
|
||||
emit('input', content.value)
|
||||
}
|
||||
|
||||
// 富文本图片上传前
|
||||
const beforeUpload = () => {
|
||||
// 显示 loading 动画
|
||||
loading.value = true
|
||||
}
|
||||
|
||||
// 图片上传成功
|
||||
// 注意!由于微信公众号的图片有访问限制,所以会显示“此图片来自微信公众号,未经允许不可引用”
|
||||
const uploadSuccess = (res) => {
|
||||
// res为图片服务器返回的数据
|
||||
// 获取富文本组件实例
|
||||
const quill = myQuillEditorRef.value.quill
|
||||
// 如果上传成功
|
||||
const link = res.data
|
||||
if (link) {
|
||||
// 获取光标所在位置
|
||||
let length = quill.getSelection().index
|
||||
// 插入图片 res.info为服务器返回的图片地址
|
||||
quill.insertEmbed(length, 'image', link)
|
||||
// 调整光标到最后
|
||||
quill.setSelection(length + 1)
|
||||
} else {
|
||||
message.error('图片插入失败')
|
||||
}
|
||||
// loading 动画消失
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
// 富文本图片上传失败
|
||||
const uploadError = () => {
|
||||
// loading 动画消失
|
||||
loading.value = false
|
||||
message.error('图片插入失败')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="wxEditor">
|
||||
<div v-loading="loading" element-loading-text="请稍等,图片上传中">
|
||||
<!-- 图片上传组件辅助-->
|
||||
<el-upload
|
||||
class="avatar-uploader"
|
||||
name="file"
|
||||
:action="actionUrl"
|
||||
:headers="headers"
|
||||
:show-file-list="false"
|
||||
:data="uploadData"
|
||||
:on-success="uploadSuccess"
|
||||
:on-error="uploadError"
|
||||
:before-upload="beforeUpload"
|
||||
/>
|
||||
<QuillEditor
|
||||
class="editor"
|
||||
v-model="content"
|
||||
ref="quillEditorRef"
|
||||
:options="editorOptions"
|
||||
@change="onEditorChange($event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.editor {
|
||||
line-height: normal !important;
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.ql-snow .ql-tooltip[data-mode='link']::before {
|
||||
content: '请输入链接地址:';
|
||||
}
|
||||
|
||||
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
|
||||
border-right: 0;
|
||||
content: '保存';
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.ql-snow .ql-tooltip[data-mode='video']::before {
|
||||
content: '请输入视频地址:';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
|
||||
content: '14px';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='small']::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before {
|
||||
content: '10px';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='large']::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before {
|
||||
content: '18px';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='huge']::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before {
|
||||
content: '32px';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
|
||||
content: '文本';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before {
|
||||
content: '标题1';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before {
|
||||
content: '标题2';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before {
|
||||
content: '标题3';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before {
|
||||
content: '标题4';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before {
|
||||
content: '标题5';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before,
|
||||
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before {
|
||||
content: '标题6';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
|
||||
content: '标准字体';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='serif']::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before {
|
||||
content: '衬线字体';
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='monospace']::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before {
|
||||
content: '等宽字体';
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,45 @@
|
|||
const toolbarOptions = [
|
||||
['bold', 'italic', 'underline', 'strike'], // 加粗 斜体 下划线 删除线
|
||||
['blockquote', 'code-block'], // 引用 代码块
|
||||
[{ header: 1 }, { header: 2 }], // 1、2 级标题
|
||||
[{ list: 'ordered' }, { list: 'bullet' }], // 有序、无序列表
|
||||
[{ script: 'sub' }, { script: 'super' }], // 上标/下标
|
||||
[{ indent: '-1' }, { indent: '+1' }], // 缩进
|
||||
// [{'direction': 'rtl'}], // 文本方向
|
||||
[{ size: ['small', false, 'large', 'huge'] }], // 字体大小
|
||||
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
|
||||
[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
|
||||
[{ font: [] }], // 字体种类
|
||||
[{ align: [] }], // 对齐方式
|
||||
['clean'], // 清除文本格式
|
||||
['link', 'image', 'video'] // 链接、图片、视频
|
||||
]
|
||||
|
||||
export default {
|
||||
theme: 'snow',
|
||||
placeholder: '请输入文章内容',
|
||||
modules: {
|
||||
toolbar: {
|
||||
container: toolbarOptions,
|
||||
// container: "#toolbar",
|
||||
handlers: {
|
||||
image: function (value) {
|
||||
if (value) {
|
||||
// 触发input框选择图片文件
|
||||
document.querySelector('.avatar-uploader input').click()
|
||||
} else {
|
||||
this.quill.format('image', false)
|
||||
}
|
||||
},
|
||||
link: function (value) {
|
||||
if (value) {
|
||||
const href = prompt('注意!只支持公众号图文链接')
|
||||
this.quill.format('link', href)
|
||||
} else {
|
||||
this.quill.format('link', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,127 +5,125 @@
|
|||
① 移除 avue 组件,使用 ElementUI 原生组件
|
||||
-->
|
||||
<template>
|
||||
<!-- 类型:图片 -->
|
||||
<div v-if="objData.type === 'image'">
|
||||
<div class="waterfall" v-loading="loading">
|
||||
<div class="waterfall-item" v-for="item in list" :key="item.mediaId">
|
||||
<img class="material-img" :src="item.url" />
|
||||
<p class="item-name">{{ item.name }}</p>
|
||||
<el-row class="ope-row">
|
||||
<el-button type="success" @click="selectMaterialFun(item)"
|
||||
>选择
|
||||
<i class="el-icon-circle-check el-icon--right"></i>
|
||||
</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分页组件 -->
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getMaterialPageFun"
|
||||
/>
|
||||
</div>
|
||||
<!-- 类型:语音 -->
|
||||
<div v-else-if="objData.type === 'voice'">
|
||||
<!-- 列表 -->
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="编号" align="center" prop="mediaId" />
|
||||
<el-table-column label="文件名" align="center" prop="name" />
|
||||
<el-table-column label="语音" align="center">
|
||||
<template #default="scope">
|
||||
<wx-voice-player :url="scope.row.url" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="上传时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ formatDate(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
align="center"
|
||||
fixed="right"
|
||||
class-name="small-padding fixed-width"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-button type="text" icon="el-icon-circle-plus" @click="selectMaterialFun(scope.row)"
|
||||
>选择
|
||||
</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="getPage"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="objData.type === 'video'">
|
||||
<!-- 列表 -->
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="编号" align="center" prop="mediaId" />
|
||||
<el-table-column label="文件名" align="center" prop="name" />
|
||||
<el-table-column label="标题" align="center" prop="title" />
|
||||
<el-table-column label="介绍" align="center" prop="introduction" />
|
||||
<el-table-column label="视频" align="center">
|
||||
<template #default="scope">
|
||||
<wx-video-player :url="scope.row.url" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="上传时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ formatDate(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
align="center"
|
||||
fixed="right"
|
||||
class-name="small-padding fixed-width"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-button type="text" icon="el-icon-circle-plus" @click="selectMaterialFun(scope.row)"
|
||||
>选择
|
||||
</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="getMaterialPageFun"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="objData.type === 'news'">
|
||||
<div class="waterfall" v-loading="loading">
|
||||
<div class="waterfall-item" v-for="item in list" :key="item.mediaId">
|
||||
<div v-if="item.content && item.content.newsItem">
|
||||
<wx-news :articles="item.content.newsItem" />
|
||||
<div class="pb-30px">
|
||||
<!-- 类型:image -->
|
||||
<div v-if="objData.type === 'image'">
|
||||
<div class="waterfall" v-loading="loading">
|
||||
<div class="waterfall-item" v-for="item in list" :key="item.mediaId">
|
||||
<img class="material-img" :src="item.url" />
|
||||
<p class="item-name">{{ item.name }}</p>
|
||||
<el-row class="ope-row">
|
||||
<el-button type="success" @click="selectMaterialFun(item)">
|
||||
选择<i class="el-icon-circle-check el-icon--right"></i>
|
||||
选择 <Icon icon="ep:circle-check" />
|
||||
</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分页组件 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getMaterialPageFun"
|
||||
/>
|
||||
</div>
|
||||
<!-- 类型:voice -->
|
||||
<div v-else-if="objData.type === 'voice'">
|
||||
<!-- 列表 -->
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="编号" align="center" prop="mediaId" />
|
||||
<el-table-column label="文件名" align="center" prop="name" />
|
||||
<el-table-column label="语音" align="center">
|
||||
<template #default="scope">
|
||||
<WxVoicePlayer :url="scope.row.url" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="上传时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="selectMaterialFun(scope.row)"
|
||||
>选择<Icon icon="ep:plus" />
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页组件 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getPage"
|
||||
/>
|
||||
</div>
|
||||
<!-- 类型:video -->
|
||||
<div v-else-if="objData.type === 'video'">
|
||||
<!-- 列表 -->
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="编号" align="center" prop="mediaId" />
|
||||
<el-table-column label="文件名" align="center" prop="name" />
|
||||
<el-table-column label="标题" align="center" prop="title" />
|
||||
<el-table-column label="介绍" align="center" prop="introduction" />
|
||||
<el-table-column label="视频" align="center">
|
||||
<template #default="scope">
|
||||
<WxVideoPlayer :url="scope.row.url" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="上传时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
align="center"
|
||||
fixed="right"
|
||||
class-name="small-padding fixed-width"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link @click="selectMaterialFun(scope.row)"
|
||||
>选择<Icon icon="akar-icons:circle-plus" />
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页组件 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getMaterialPageFun"
|
||||
/>
|
||||
</div>
|
||||
<!-- 类型:news -->
|
||||
<div v-else-if="objData.type === 'news'">
|
||||
<div class="waterfall" v-loading="loading">
|
||||
<div class="waterfall-item" v-for="item in list" :key="item.mediaId">
|
||||
<div v-if="item.content && item.content.newsItem">
|
||||
<WxNews :articles="item.content.newsItem" />
|
||||
<el-row class="ope-row">
|
||||
<el-button type="success" @click="selectMaterialFun(item)">
|
||||
选择<Icon icon="ep:circle-check" />
|
||||
</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分页组件 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getMaterialPageFun"
|
||||
/>
|
||||
</div>
|
||||
<!-- 分页组件 -->
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getMaterialPageFun"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -136,7 +134,7 @@ import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
|
|||
import { getMaterialPage } from '@/api/mp/material'
|
||||
import { getFreePublishPage } from '@/api/mp/freePublish'
|
||||
import { getDraftPage } from '@/api/mp/draft'
|
||||
import { dateFormatter, formatDate } from '@/utils/formatTime'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -173,7 +171,7 @@ export default defineComponent({
|
|||
const newsTypeRef = ref(props.newsType)
|
||||
|
||||
const selectMaterialFun = (item) => {
|
||||
ctx.emit('selectMaterial', item)
|
||||
ctx.emit('select-material', item)
|
||||
}
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
|
@ -203,9 +201,10 @@ export default defineComponent({
|
|||
total.value = data.total
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const getFreePublishPageFun = async () => {
|
||||
let data = await getFreePublishPage(queryParams)
|
||||
data.list.foreach((item) => {
|
||||
data.list.forEach((item) => {
|
||||
const newsItem = item.content.newsItem
|
||||
newsItem.forEach((article) => {
|
||||
article.picUrl = article.thumbUrl
|
||||
|
@ -232,6 +231,7 @@ export default defineComponent({
|
|||
onMounted(async () => {
|
||||
getPage()
|
||||
})
|
||||
|
||||
return {
|
||||
handleQuery,
|
||||
dateFormatter,
|
||||
|
@ -239,7 +239,6 @@ export default defineComponent({
|
|||
getMaterialPageFun,
|
||||
getPage,
|
||||
formatDate,
|
||||
newsTypeRef,
|
||||
queryParams,
|
||||
objDataRef,
|
||||
list,
|
||||
|
@ -249,7 +248,6 @@ export default defineComponent({
|
|||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/*瀑布流样式*/
|
||||
.waterfall {
|
||||
|
|
|
@ -139,7 +139,7 @@ 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'
|
||||
import { getUser } from '@/api/mp/user'
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
|
|
@ -12,10 +12,7 @@
|
|||
<!-- 类型 1:文本 -->
|
||||
<el-tab-pane name="text">
|
||||
<template #label>
|
||||
<el-row align="middle">
|
||||
<icon icon="ep:document" />
|
||||
文本
|
||||
</el-row>
|
||||
<el-row align="middle"><Icon icon="ep:document" /> 文本</el-row>
|
||||
</template>
|
||||
<el-input
|
||||
type="textarea"
|
||||
|
@ -28,18 +25,15 @@
|
|||
<!-- 类型 2:图片 -->
|
||||
<el-tab-pane name="image">
|
||||
<template #label>
|
||||
<el-row align="middle">
|
||||
<icon icon="ep:picture" class="mr-5px" />
|
||||
图片
|
||||
</el-row>
|
||||
<el-row align="middle"><Icon icon="ep:picture" class="mr-5px" /> 图片</el-row>
|
||||
</template>
|
||||
<!-- 情况一:已经选择好素材、或者上传好图片 -->
|
||||
<div class="select-item" v-if="objDataRef.url">
|
||||
<img class="material-img" :src="objDataRef.url" />
|
||||
<p class="item-name" v-if="objDataRef.name">{{ objDataRef.name }}</p>
|
||||
<el-row class="ope-row">
|
||||
<el-row class="ope-row" justify="center">
|
||||
<el-button type="danger" circle @click="deleteObj">
|
||||
<icon icon="ep:delete" />
|
||||
<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
|
@ -48,11 +42,10 @@
|
|||
<!-- 选择素材 -->
|
||||
<el-col :span="12" class="col-select">
|
||||
<el-button type="success" @click="openMaterial">
|
||||
素材库选择
|
||||
<icon icon="ep:circle-check" />
|
||||
素材库选择 <Icon icon="ep:circle-check" />
|
||||
</el-button>
|
||||
<el-dialog title="选择图片" v-model="dialogImageVisible" width="90%" append-to-body>
|
||||
<wx-material-select :obj-data="objDataRef" @selectMaterial="selectMaterial" />
|
||||
<WxMaterialSelect :obj-data="objDataRef" @select-material="selectMaterial" />
|
||||
</el-dialog>
|
||||
</el-col>
|
||||
<!-- 文件上传 -->
|
||||
|
@ -70,10 +63,8 @@
|
|||
<el-button type="primary">上传图片</el-button>
|
||||
<template #tip>
|
||||
<span>
|
||||
<div class="el-upload__tip"
|
||||
>支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div
|
||||
></span
|
||||
>
|
||||
<div class="el-upload__tip">支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div>
|
||||
</span>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-col>
|
||||
|
@ -82,29 +73,25 @@
|
|||
<!-- 类型 3:语音 -->
|
||||
<el-tab-pane name="voice">
|
||||
<template #label>
|
||||
<el-row align="middle">
|
||||
<icon icon="ep:phone" />
|
||||
语音
|
||||
</el-row>
|
||||
<el-row align="middle"><Icon icon="ep:phone" /> 语音</el-row>
|
||||
</template>
|
||||
|
||||
<div class="select-item2" v-if="objDataRef.url">
|
||||
<p class="item-name">{{ objDataRef.name }}</p>
|
||||
<div class="item-infos">
|
||||
<wx-voice-player :url="objDataRef.url" />
|
||||
<WxVoicePlayer :url="objDataRef.url" />
|
||||
</div>
|
||||
<el-row class="ope-row">
|
||||
<el-button type="danger" icon="el-icon-delete" circle @click="deleteObj" />
|
||||
<el-row class="ope-row" justify="center">
|
||||
<el-button type="danger" circle @click="deleteObj"><Icon icon="ep:delete" /></el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-row v-else style="text-align: center">
|
||||
<!-- 选择素材 -->
|
||||
<el-col :span="12" class="col-select">
|
||||
<el-button type="success" @click="openMaterial">
|
||||
素材库选择<i class="el-icon-circle-check el-icon--right"></i>
|
||||
素材库选择<Icon icon="ep:circle-check" />
|
||||
</el-button>
|
||||
<el-dialog title="选择语音" v-model="dialogVoiceVisible" width="90%" append-to-body>
|
||||
<WxMaterialSelect :objData="objData" @selectMaterial="selectMaterial" />
|
||||
<WxMaterialSelect :objData="objData" @select-material="selectMaterial" />
|
||||
</el-dialog>
|
||||
</el-col>
|
||||
<!-- 文件上传 -->
|
||||
|
@ -121,8 +108,8 @@
|
|||
>
|
||||
<el-button type="primary">点击上传</el-button>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip"
|
||||
>格式支持 mp3/wma/wav/amr,文件大小不超过 2M,播放长度不超过 60s
|
||||
<div class="el-upload__tip">
|
||||
格式支持 mp3/wma/wav/amr,文件大小不超过 2M,播放长度不超过 60s
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
|
@ -132,10 +119,7 @@
|
|||
<!-- 类型 4:视频 -->
|
||||
<el-tab-pane name="video">
|
||||
<template #label>
|
||||
<el-row align="middle">
|
||||
<icon icon="ep:share" />
|
||||
视频
|
||||
</el-row>
|
||||
<el-row align="middle"><Icon icon="ep:share" /> 视频</el-row>
|
||||
</template>
|
||||
<el-row>
|
||||
<el-input
|
||||
|
@ -151,18 +135,17 @@
|
|||
@input="inputContent"
|
||||
/>
|
||||
<div style="text-align: center">
|
||||
<wx-video-player v-if="objDataRef.url" :url="objDataRef.url" />
|
||||
<WxVideoPlayer v-if="objDataRef.url" :url="objDataRef.url" />
|
||||
</div>
|
||||
<el-col>
|
||||
<el-row style="text-align: center" align="middle">
|
||||
<!-- 选择素材 -->
|
||||
<el-col :span="12">
|
||||
<el-button type="success" @click="openMaterial">
|
||||
素材库选择
|
||||
<icon icon="ep:circle-check" />
|
||||
素材库选择 <Icon icon="ep:circle-check" />
|
||||
</el-button>
|
||||
<el-dialog title="选择视频" v-model="dialogVideoVisible" width="90%" append-to-body>
|
||||
<wx-material-select :objData="objDataRef" @selectMaterial="selectMaterial" />
|
||||
<WxMaterialSelect :objData="objDataRef" @select-material="selectMaterial" />
|
||||
</el-dialog>
|
||||
</el-col>
|
||||
<!-- 文件上传 -->
|
||||
|
@ -177,10 +160,7 @@
|
|||
:before-upload="beforeVideoUpload"
|
||||
:on-success="handleUploadSuccess"
|
||||
>
|
||||
<el-button type="primary"
|
||||
>新建视频
|
||||
<icon icon="ep:upload" />
|
||||
</el-button>
|
||||
<el-button type="primary">新建视频 <Icon icon="ep:upload" /></el-button>
|
||||
</el-upload>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
@ -190,17 +170,14 @@
|
|||
<!-- 类型 5:图文 -->
|
||||
<el-tab-pane name="news">
|
||||
<template #label>
|
||||
<el-row align="middle">
|
||||
<icon icon="ep:reading" />
|
||||
图文
|
||||
</el-row>
|
||||
<el-row align="middle"><Icon icon="ep:reading" /> 图文</el-row>
|
||||
</template>
|
||||
<el-row>
|
||||
<div class="select-item" v-if="objDataRef.articles.size > 0">
|
||||
<wx-news :articles="objDataRef.articles" />
|
||||
<div class="select-item" v-if="objDataRef.articles?.length > 0">
|
||||
<WxNews :articles="objDataRef.articles" />
|
||||
<el-col class="ope-row">
|
||||
<el-button type="danger" circle @click="deleteObj">
|
||||
<icon icon="ep:delete" />
|
||||
<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</el-col>
|
||||
</div>
|
||||
|
@ -208,17 +185,17 @@
|
|||
<el-col :span="24" v-if="!objDataRef.content">
|
||||
<el-row style="text-align: center" align="middle">
|
||||
<el-col :span="24">
|
||||
<el-button type="success" @click="openMaterial"
|
||||
>{{ newsType === '1' ? '选择已发布图文' : '选择草稿箱图文' }}
|
||||
<el-button type="success" @click="openMaterial">
|
||||
{{ newsType === '1' ? '选择已发布图文' : '选择草稿箱图文' }}
|
||||
<icon icon="ep:circle-check" />
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
<el-dialog title="选择图文" v-model="dialogNewsVisible" width="90%" append-to-body>
|
||||
<wx-material-select
|
||||
<WxMaterialSelect
|
||||
:objData="objDataRef"
|
||||
@selectMaterial="selectMaterial"
|
||||
@select-material="selectMaterial"
|
||||
:newsType="newsType"
|
||||
/>
|
||||
</el-dialog>
|
||||
|
@ -227,10 +204,7 @@
|
|||
<!-- 类型 6:音乐 -->
|
||||
<el-tab-pane name="music">
|
||||
<template #label>
|
||||
<el-row align="middle">
|
||||
<icon icon="ep:service" />
|
||||
音乐
|
||||
</el-row>
|
||||
<el-row align="middle"><Icon icon="ep:service" />音乐</el-row>
|
||||
</template>
|
||||
<el-row align="middle" justify="center">
|
||||
<el-col :span="6">
|
||||
|
@ -259,7 +233,7 @@
|
|||
<template #trigger>
|
||||
<el-button type="text">本地上传</el-button>
|
||||
</template>
|
||||
<el-button type="text" @click="openMaterial" style="margin-left: 5px"
|
||||
<el-button type="primary" link @click="openMaterial" style="margin-left: 5px"
|
||||
>素材库选择
|
||||
</el-button>
|
||||
</el-upload>
|
||||
|
@ -268,9 +242,9 @@
|
|||
</el-col>
|
||||
</el-row>
|
||||
<el-dialog title="选择图片" v-model="dialogThumbVisible" width="80%" append-to-body>
|
||||
<wx-material-select
|
||||
<WxMaterialSelect
|
||||
:objData="{ type: 'image', accountId: objDataRef.accountId }"
|
||||
@selectMaterial="selectMaterial"
|
||||
@select-material="selectMaterial"
|
||||
/>
|
||||
</el-dialog>
|
||||
</el-col>
|
||||
|
@ -295,7 +269,6 @@
|
|||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="WxReplySelect">
|
||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
|
||||
|
@ -482,7 +455,7 @@ export default defineComponent({
|
|||
// 创建 tempObjItem 对象,并设置对应的值
|
||||
let tempObjItem = {
|
||||
type: '',
|
||||
articles: '',
|
||||
articles: [],
|
||||
thumbMediaId: '',
|
||||
thumbMediaUrl: '',
|
||||
introduction: '',
|
||||
|
@ -560,7 +533,7 @@ export default defineComponent({
|
|||
}
|
||||
const deleteObj = () => {
|
||||
if (objDataRef.type === 'news') {
|
||||
objDataRef.articles = ''
|
||||
objDataRef.articles = []
|
||||
} else if (objDataRef.type === 'image') {
|
||||
objDataRef.mediaId = null
|
||||
objDataRef.url = null
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<span class="amr-duration" v-if="duration">{{ duration }} 秒</span>
|
||||
</el-icon>
|
||||
<div v-if="content">
|
||||
<el-tag type="success" size="mini">语音识别</el-tag>
|
||||
<el-tag type="success" size="small">语音识别</el-tag>
|
||||
{{ content }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,813 @@
|
|||
<template>
|
||||
<span>开发中</span>
|
||||
<doc-alert title="公众号图文" url="https://doc.iocoder.cn/mp/article/" />
|
||||
|
||||
<!-- 搜索工作栏 -->
|
||||
<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="请选择公众号">
|
||||
<el-option
|
||||
v-for="item in accountList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
|
||||
<el-button type="primary" plain @click="handleAdd" v-hasPermi="['mp:draft:create']">
|
||||
<Icon icon="ep:plus" />新增
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<div class="waterfall" v-loading="loading">
|
||||
<template v-for="item in list" :key="item.articleId">
|
||||
<div class="waterfall-item" v-if="item.content && item.content.newsItem">
|
||||
<wx-news :articles="item.content.newsItem" />
|
||||
<!-- 操作按钮 -->
|
||||
<el-row class="ope-row">
|
||||
<el-button
|
||||
type="success"
|
||||
circle
|
||||
@click="handlePublish(item)"
|
||||
v-hasPermi="['mp:free-publish:submit']"
|
||||
>
|
||||
<Icon icon="fa:upload" />
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
circle
|
||||
@click="handleUpdate(item)"
|
||||
v-hasPermi="['mp:draft:update']"
|
||||
>
|
||||
<Icon icon="ep:edit" />
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
circle
|
||||
@click="handleDelete(item)"
|
||||
v-hasPermi="['mp:draft:delete']"
|
||||
>
|
||||
<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- 分页记录 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- TODO @Dhb52:迁移成独立路由 -->
|
||||
<div class="app-container">
|
||||
<!-- 添加或修改草稿对话框 -->
|
||||
<Teleport to="body">
|
||||
<el-dialog
|
||||
:title="operateMaterial === 'add' ? '新建图文' : '修改图文'"
|
||||
width="80%"
|
||||
top="20px"
|
||||
v-model="dialogNewsVisible"
|
||||
:before-close="dialogNewsClose"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="left">
|
||||
<div class="select-item">
|
||||
<div v-for="(news, index) in articlesAdd" :key="news.id">
|
||||
<div
|
||||
class="news-main father"
|
||||
v-if="index === 0"
|
||||
:class="{ activeAddNews: isActiveAddNews === index }"
|
||||
@click="activeNews(index)"
|
||||
>
|
||||
<div class="news-content">
|
||||
<img class="material-img" v-if="news.thumbUrl" :src="news.thumbUrl" />
|
||||
<div class="news-content-title">{{ news.title }}</div>
|
||||
</div>
|
||||
<div class="child" v-if="articlesAdd.length > 1">
|
||||
<el-button size="small" @click="downNews(index)"
|
||||
><Icon icon="ep:sort-down" />下移</el-button
|
||||
>
|
||||
<el-button v-if="operateMaterial === 'add'" size="small" @click="minusNews(index)"
|
||||
><Icon icon="ep:delete" />删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="news-main-item father"
|
||||
v-if="index > 0"
|
||||
:class="{ activeAddNews: isActiveAddNews === index }"
|
||||
@click="activeNews(index)"
|
||||
>
|
||||
<div class="news-content-item">
|
||||
<div class="news-content-item-title">{{ news.title }}</div>
|
||||
<div class="news-content-item-img">
|
||||
<img
|
||||
class="material-img"
|
||||
v-if="news.thumbUrl"
|
||||
:src="news.thumbUrl"
|
||||
height="100%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="child">
|
||||
<el-button
|
||||
v-if="articlesAdd.length > index + 1"
|
||||
size="small"
|
||||
@click="downNews(index)"
|
||||
><Icon icon="ep:sort-down" />下移
|
||||
</el-button>
|
||||
<el-button size="small" @click="upNews(index)"
|
||||
><Icon icon="ep:sort-up" />上移</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="operateMaterial === 'add'"
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="minusNews(index)"
|
||||
><Icon icon="ep:delete" />删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-row justify="center" class="ope-row">
|
||||
<el-button
|
||||
type="primary"
|
||||
circle
|
||||
@click="plusNews(item)"
|
||||
v-if="articlesAdd.length < 8 && operateMaterial === 'add'"
|
||||
>
|
||||
<Icon icon="ep:plus" />
|
||||
</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right" v-loading="addMaterialLoading" v-if="articlesAdd.length > 0">
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<!-- 标题、作者、原文地址 -->
|
||||
<el-input v-model="articlesAdd[isActiveAddNews].title" placeholder="请输入标题(必填)" />
|
||||
<el-input
|
||||
v-model="articlesAdd[isActiveAddNews].author"
|
||||
placeholder="请输入作者"
|
||||
style="margin-top: 5px"
|
||||
/>
|
||||
<el-input
|
||||
v-model="articlesAdd[isActiveAddNews].contentSourceUrl"
|
||||
placeholder="请输入原文地址"
|
||||
style="margin-top: 5px"
|
||||
/>
|
||||
<!-- 封面和摘要 -->
|
||||
<div class="input-tt">封面和摘要:</div>
|
||||
<div>
|
||||
<div class="thumb-div">
|
||||
<img
|
||||
class="material-img"
|
||||
v-if="articlesAdd[isActiveAddNews].thumbUrl"
|
||||
:src="articlesAdd[isActiveAddNews].thumbUrl"
|
||||
:class="isActiveAddNews === 0 ? 'avatar' : 'avatar1'"
|
||||
/>
|
||||
<Icon
|
||||
v-else
|
||||
icon="ep:plus"
|
||||
class="avatar-uploader-icon"
|
||||
:class="isActiveAddNews === 0 ? 'avatar' : 'avatar1'"
|
||||
/>
|
||||
<div class="thumb-but">
|
||||
<el-upload
|
||||
:action="actionUrl"
|
||||
:headers="headers"
|
||||
multiple
|
||||
:limit="1"
|
||||
:file-list="fileList"
|
||||
:data="uploadData"
|
||||
:before-upload="beforeThumbImageUpload"
|
||||
:on-success="handleUploadSuccess"
|
||||
>
|
||||
<template #trigger>
|
||||
<el-button size="small" type="primary">本地上传</el-button>
|
||||
</template>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="openMaterial"
|
||||
style="margin-left: 5px"
|
||||
>素材库选择</el-button
|
||||
>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
<Teleport to="body">
|
||||
<el-dialog title="选择图片" v-model="dialogImageVisible" width="80%">
|
||||
<WxMaterialSelect
|
||||
ref="materialSelectRef"
|
||||
:objData="{ type: 'image', accountId: queryParams.accountId }"
|
||||
@select-material="selectMaterial"
|
||||
/>
|
||||
</el-dialog>
|
||||
</Teleport>
|
||||
</div>
|
||||
<el-input
|
||||
:rows="8"
|
||||
type="textarea"
|
||||
v-model="articlesAdd[isActiveAddNews].digest"
|
||||
placeholder="请输入摘要"
|
||||
class="digest"
|
||||
maxlength="120"
|
||||
style="float: right"
|
||||
/>
|
||||
</div>
|
||||
<!--富文本编辑器组件-->
|
||||
<el-row>
|
||||
<wx-editor
|
||||
v-model="articlesAdd[isActiveAddNews].content"
|
||||
:account-id="uploadData.accountId"
|
||||
v-if="hackResetEditor"
|
||||
/>
|
||||
</el-row>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="dialogNewsVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitForm">提 交</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
<script setup name="MpDraft">
|
||||
import WxEditor from '@/views/mp/components/wx-editor/WxEditor.vue'
|
||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
|
||||
import { getAccessToken } from '@/utils/auth'
|
||||
import * as MpAccountApi from '@/api/mp/account'
|
||||
import * as MpDraftApi from '@/api/mp/draft'
|
||||
import * as MpFreePublishApi from '@/api/mp/freePublish'
|
||||
const message = useMessage() // 消息
|
||||
// 可以用改本地数据模拟,避免API调用超限
|
||||
// import drafts from './mock'
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
accountId: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const accountList = ref([]) // 公众号账号列表
|
||||
|
||||
// ========== 文件上传 ==========
|
||||
const materialSelectRef = ref()
|
||||
const BASE_URL = import.meta.env.VITE_BASE_URL
|
||||
const actionUrl = ref(BASE_URL + '/admin-api/mp/material/upload-permanent') // 上传永久素材的地址
|
||||
const headers = ref({ Authorization: 'Bearer ' + getAccessToken() }) // 设置上传的请求头部
|
||||
const fileList = ref([])
|
||||
const uploadData = reactive({
|
||||
type: 'image',
|
||||
accountId: 1
|
||||
})
|
||||
|
||||
// ========== 草稿新建 or 修改 ==========
|
||||
const dialogNewsVisible = ref(false)
|
||||
const addMaterialLoading = ref(false) // 添加草稿的 loading 标识
|
||||
const articlesAdd = ref([])
|
||||
const isActiveAddNews = ref(0)
|
||||
const dialogImageVisible = ref(false)
|
||||
const operateMaterial = ref('add')
|
||||
const articlesMediaId = ref('')
|
||||
const hackResetEditor = ref(false)
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
accountList.value = await MpAccountApi.getSimpleAccountList()
|
||||
// 选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
// @ts-ignore
|
||||
queryParams.accountId = accountList.value[0].id
|
||||
}
|
||||
await getList()
|
||||
})
|
||||
|
||||
// ======================== 列表查询 ========================
|
||||
/** 设置账号编号 */
|
||||
const setAccountId = (accountId) => {
|
||||
queryParams.accountId = accountId
|
||||
uploadData.accountId = accountId
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
// 如果没有选中公众号账号,则进行提示。
|
||||
if (!queryParams.accountId) {
|
||||
message.error('未选中公众号,无法查询草稿箱')
|
||||
return false
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const drafts = await MpDraftApi.getDraftPage(queryParams)
|
||||
drafts.list.forEach((item) => {
|
||||
const newsItem = item.content.newsItem
|
||||
// 将 thumbUrl 转成 picUrl,保证 wx-news 组件可以预览封面
|
||||
newsItem.forEach((article) => {
|
||||
article.picUrl = article.thumbUrl
|
||||
})
|
||||
})
|
||||
list.value = drafts.list
|
||||
total.value = drafts.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
// 默认选中第一个
|
||||
if (queryParams.accountId) {
|
||||
setAccountId(queryParams.accountId)
|
||||
}
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
setAccountId(accountList.value[0].id)
|
||||
}
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
// ======================== 新增/修改草稿 ========================
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
resetEditor()
|
||||
reset()
|
||||
// 打开表单,并设置初始化
|
||||
operateMaterial.value = 'add'
|
||||
dialogNewsVisible.value = true
|
||||
}
|
||||
|
||||
/** 更新按钮操作 */
|
||||
const handleUpdate = (item) => {
|
||||
resetEditor()
|
||||
reset()
|
||||
articlesMediaId.value = item.mediaId
|
||||
articlesAdd.value = JSON.parse(JSON.stringify(item.content.newsItem))
|
||||
// 打开表单,并设置初始化
|
||||
operateMaterial.value = 'edit'
|
||||
dialogNewsVisible.value = true
|
||||
}
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
// TODO @Dhb52: 参考别的模块写法,改成 await 方式
|
||||
addMaterialLoading.value = true
|
||||
if (operateMaterial.value === 'add') {
|
||||
MpDraftApi.createDraft(queryParams.accountId, articlesAdd.value)
|
||||
.then(() => {
|
||||
message.notifySuccess('新增成功')
|
||||
dialogNewsVisible.value = false
|
||||
getList()
|
||||
})
|
||||
.finally(() => {
|
||||
addMaterialLoading.value = false
|
||||
})
|
||||
} else {
|
||||
MpDraftApi.updateDraft(queryParams.accountId, articlesMediaId.value, articlesAdd.value)
|
||||
.then(() => {
|
||||
message.notifySuccess('更新成功')
|
||||
dialogNewsVisible.value = false
|
||||
getList()
|
||||
})
|
||||
.finally(() => {
|
||||
addMaterialLoading.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const dialogNewsClose = async (done) => {
|
||||
try {
|
||||
await message.confirm('修改内容可能还未保存,确定关闭吗?')
|
||||
reset()
|
||||
resetEditor()
|
||||
done()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// 表单重置
|
||||
const reset = () => {
|
||||
isActiveAddNews.value = 0
|
||||
articlesAdd.value = [buildEmptyArticle()]
|
||||
}
|
||||
|
||||
// 表单 Editor 重置
|
||||
const resetEditor = () => {
|
||||
hackResetEditor.value = false // 销毁组件
|
||||
nextTick(() => {
|
||||
hackResetEditor.value = true // 重建组件
|
||||
})
|
||||
}
|
||||
|
||||
// 将图文向下移动
|
||||
const downNews = (index) => {
|
||||
let temp = articlesAdd.value[index]
|
||||
articlesAdd.value[index] = articlesAdd.value[index + 1]
|
||||
articlesAdd.value[index + 1] = temp
|
||||
isActiveAddNews.value = index + 1
|
||||
}
|
||||
|
||||
// 将图文向上移动
|
||||
const upNews = (index) => {
|
||||
let temp = articlesAdd.value[index]
|
||||
articlesAdd.value[index] = articlesAdd.value[index - 1]
|
||||
articlesAdd.value[index - 1] = temp
|
||||
isActiveAddNews.value = index - 1
|
||||
}
|
||||
|
||||
// 选中指定 index 的图文
|
||||
const activeNews = (index) => {
|
||||
resetEditor()
|
||||
isActiveAddNews.value = index
|
||||
}
|
||||
|
||||
// 删除指定 index 的图文
|
||||
const minusNews = async (index) => {
|
||||
try {
|
||||
await message.confirm('确定删除该图文吗?')
|
||||
articlesAdd.value.splice(index, 1)
|
||||
if (isActiveAddNews.value === index) {
|
||||
isActiveAddNews.value = 0
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// 添加一个图文
|
||||
const plusNews = () => {
|
||||
articlesAdd.value.push(buildEmptyArticle())
|
||||
isActiveAddNews.value = articlesAdd.value.length - 1
|
||||
}
|
||||
|
||||
// 创建空的 article
|
||||
const buildEmptyArticle = () => {
|
||||
return {
|
||||
title: '',
|
||||
thumbMediaId: '',
|
||||
author: '',
|
||||
digest: '',
|
||||
showCoverPic: '',
|
||||
content: '',
|
||||
contentSourceUrl: '',
|
||||
needOpenComment: '',
|
||||
onlyFansCanComment: '',
|
||||
thumbUrl: ''
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 文件上传 ========================
|
||||
const beforeThumbImageUpload = (file) => {
|
||||
addMaterialLoading.value = true
|
||||
const isType =
|
||||
file.type === 'image/jpeg' ||
|
||||
file.type === 'image/png' ||
|
||||
file.type === 'image/gif' ||
|
||||
file.type === 'image/bmp' ||
|
||||
file.type === 'image/jpg'
|
||||
if (!isType) {
|
||||
message.error('上传图片格式不对!')
|
||||
addMaterialLoading.value = false
|
||||
return false
|
||||
}
|
||||
const isLt = file.size / 1024 / 1024 < 2
|
||||
if (!isLt) {
|
||||
message.error('上传图片大小不能超过 2M!')
|
||||
addMaterialLoading.value = false
|
||||
return false
|
||||
}
|
||||
// 校验通过
|
||||
return true
|
||||
}
|
||||
|
||||
const handleUploadSuccess = (response, file, fileList) => {
|
||||
addMaterialLoading.value = false
|
||||
if (response.code !== 0) {
|
||||
message.error('上传出错:' + response.msg)
|
||||
return false
|
||||
}
|
||||
|
||||
// 重置上传文件的表单
|
||||
fileList.value = []
|
||||
|
||||
// 设置草稿的封面字段
|
||||
articlesAdd.value[isActiveAddNews.value].thumbMediaId = response.data.mediaId
|
||||
articlesAdd.value[isActiveAddNews.value].thumbUrl = response.data.url
|
||||
}
|
||||
|
||||
// 选择 or 上传完素材,设置回草稿
|
||||
const selectMaterial = (item) => {
|
||||
dialogImageVisible.value = false
|
||||
articlesAdd.value[isActiveAddNews.value].thumbMediaId = item.mediaId
|
||||
articlesAdd.value[isActiveAddNews.value].thumbUrl = item.url
|
||||
}
|
||||
|
||||
// 打开素材选择
|
||||
const openMaterial = () => {
|
||||
dialogImageVisible.value = true
|
||||
try {
|
||||
materialSelectRef.value.queryParams.accountId = queryParams.accountId // 强制设置下 accountId,避免二次查询不对
|
||||
materialSelectRef.value.handleQuery() // 刷新列表,失败也无所谓
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// ======================== 草稿箱发布 ========================
|
||||
const handlePublish = async (item) => {
|
||||
const accountId = queryParams.accountId
|
||||
const mediaId = item.mediaId
|
||||
const content =
|
||||
'你正在通过发布的方式发表内容。 发布不占用群发次数,一天可多次发布。已发布内容不会推送给用户,也不会展示在公众号主页中。 发布后,你可以前往发表记录获取链接,也可以将发布内容添加到自定义菜单、自动回复、话题和页面模板中。'
|
||||
try {
|
||||
await message.confirm(content)
|
||||
await MpFreePublishApi.submitFreePublish(accountId, mediaId)
|
||||
message.notifySuccess('发布成功')
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (item) => {
|
||||
const accountId = queryParams.accountId
|
||||
const mediaId = item.mediaId
|
||||
try {
|
||||
await message.confirm('此操作将永久删除该草稿, 是否继续?')
|
||||
await MpDraftApi.deleteDraft(accountId, mediaId)
|
||||
message.notifySuccess('删除成功')
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
</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>
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
export default {
|
||||
list: [
|
||||
{
|
||||
mediaId: 'r6ryvl6LrxBU0miaST4Y-q-G9pdsmZw0OYG4FzHQkKfpLfEwIH51wy2bxisx8PvW',
|
||||
content: {
|
||||
newsItem: [
|
||||
{
|
||||
title: '我是标题(OOO)',
|
||||
author: '我是作者',
|
||||
digest: '我是摘要',
|
||||
content: '我是内容',
|
||||
contentSourceUrl: 'https://www.iocoder.cn',
|
||||
thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn',
|
||||
showCoverPic: 0,
|
||||
needOpenComment: 0,
|
||||
onlyFansCanComment: 0,
|
||||
url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9XaFphcmtJVFh3VEc4Q1MxQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuN2QxTE56SFBCYXc2RE9NcUxIeS1CQjJuUHhTWjBlN2VOeGRpRi1fZUhwN1FNQjdrQV9yRU9EU0hibHREZmZoVW5acnZrN3ZjaWsxejR3RGpKczBzTHFIM0dFNFZWVkpBc0dWWlAzUEhlVmpnfn4%3D&chksm=1f6354802814dd969ef83c0f3babe555c614270b30bc383beaf7ffd13b0257f0fe5ced9af694#rd',
|
||||
thumbUrl:
|
||||
'http://test.yudao.iocoder.cn/r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn.png'
|
||||
},
|
||||
{
|
||||
title: '我是标题(XXX)',
|
||||
author: '我是作者',
|
||||
digest: '我是摘要',
|
||||
content: '我是内容',
|
||||
contentSourceUrl: 'https://www.iocoder.cn',
|
||||
thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn',
|
||||
showCoverPic: 0,
|
||||
needOpenComment: 0,
|
||||
onlyFansCanComment: 0,
|
||||
url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9yTlYwOEs1clpwcE5OUEhCQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuN0NSMjFqN3N1aUZMbFNVLTZHN2ZDME9qOGp2THk2RFNlSTlKZ3Y1czFVZDdQQm5IeUg3dEppSUtpQUh5SExOOTRkT3dHNUdBdHdWSWlOendlREV3dS1jUEVQbFpiVTZmVW5iRWhZcGdkNTFRfn4%3D&chksm=1f6354802814dd96a403151cd44c7da4eecf0e475d25423e46ecd795b513bafd829a75daef9b#rd',
|
||||
thumbUrl:
|
||||
'http://test.yudao.iocoder.cn/r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn.png'
|
||||
}
|
||||
]
|
||||
},
|
||||
updateTime: 1673655730
|
||||
},
|
||||
{
|
||||
mediaId: 'r6ryvl6LrxBU0miaST4Y-jGpXnO73ihN0lsNXknCRQHapp2xgHMRxHKG50LituFe',
|
||||
content: {
|
||||
newsItem: [
|
||||
{
|
||||
title: '我是标题(修改)',
|
||||
author: '我是作者',
|
||||
digest: '我是摘要',
|
||||
content: '我是内容',
|
||||
contentSourceUrl: 'https://www.iocoder.cn',
|
||||
thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn',
|
||||
showCoverPic: 0,
|
||||
needOpenComment: 0,
|
||||
onlyFansCanComment: 0,
|
||||
url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl95WVFXYndIZnZJd0t5cjgvQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuN1dlNURPbWswbEF4RDd5dVJTdjQ4cm9Cc0Q1TWhpMUh6SE1hVEE3ZHljaHhlZjZYSGF5N2JNSHpDTlh6ajNZbkpGTGpTcUQ4M3NMdW41ZUpXNFZZQ1VKbVlaMVp5ekxEV1czREdsY1dOYTZnfn4%3D&chksm=1f6354be2814dda8e6238037c2ebd52b1c8e80e93249a861ad80e4d40e5ca7207233475ca689#rd',
|
||||
thumbUrl:
|
||||
'http://test.yudao.iocoder.cn/r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn.png'
|
||||
}
|
||||
]
|
||||
},
|
||||
updateTime: 1673655584
|
||||
},
|
||||
{
|
||||
mediaId: 'r6ryvl6LrxBU0miaST4Y-v5SrbNCPpD6M_p3TmSrYwTjKogs-0DMJgmjMyNZPeMO',
|
||||
content: {
|
||||
newsItem: [
|
||||
{
|
||||
title: '1321',
|
||||
author: '3232',
|
||||
digest: '1333',
|
||||
content: '<p>444</p>',
|
||||
contentSourceUrl: 'http://www.iocoder.cn',
|
||||
thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-tlQmcl3RdC-Jcgns6IQtf7zenGy3b86WLT7GzUcrb1T',
|
||||
showCoverPic: 0,
|
||||
needOpenComment: 0,
|
||||
onlyFansCanComment: 0,
|
||||
url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9jelJiaDAzbmdpSkJOZ2M2QWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuNDNXVVc2ZDRYeTY0Zm1weXR6dE9vQWh1TzEwbEpUVnRfVzJyaGFDNXBkZ0ZXM2JFOTNaRHNhOHRUeFdEanhMeS01X01kMUNWQ1BpRER3cjYwTl9pMnpFLUJhZXFucVVfM1pDUXlTUEl1S25nfn4%3D&chksm=1f6354bc2814ddaa56a90ad5bc3d078601c8d1589ba01827a8170587bc830ff9747b5f59c3a0#rd',
|
||||
thumbUrl:
|
||||
'http://mmbiz.qpic.cn/mmbiz_png/btUmCVHwbJUoicwBiacjVeQbu6QxgBVrukfSJXz509boa21SpH8OVHAqXCJiaiaAaHQJNxwwsa0gHRXVr0G5EZYamw/0?wx_fmt=png'
|
||||
}
|
||||
]
|
||||
},
|
||||
updateTime: 1673628969
|
||||
},
|
||||
{
|
||||
mediaId: 'r6ryvl6LrxBU0miaST4Y-vdWrisK5EZbk4Y3tzh8P0PG0eEUbnQrh0BcsEb3WNP0',
|
||||
content: {
|
||||
newsItem: [
|
||||
{
|
||||
title: 'tudou',
|
||||
author: 'haha',
|
||||
digest: '312',
|
||||
content: '<p>132312</p>',
|
||||
contentSourceUrl: 'http://www.iocoder.cn',
|
||||
thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-pgFtUNLu1foMSAMkoOsrQrTZ8EtTMssBLfTtzP0dfjG',
|
||||
showCoverPic: 0,
|
||||
needOpenComment: 0,
|
||||
onlyFansCanComment: 0,
|
||||
url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9qdkJ1ZjBoUmg2Uk9TS3RlQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuNVg2aTJsaC1fMkU2eXNacUplN3VDTTZFZkhtMjhuTUZvWkxsNDBRSXExY2tiVXRHb09TaHgtREhzY3doZ0JYeC1TSTZ5eWZldXJsOWtfbV8yMi1aYkcyZ2pOY0haM0Ntb3VSWEtxUGVFRlNBfn4%3D&chksm=1f6354ba2814ddacf0184b24d310483641ef190b1faac098c285eb416c70017e2f54decfa1af#rd',
|
||||
thumbUrl:
|
||||
'http://test.yudao.iocoder.cn/r6ryvl6LrxBU0miaST4Y-pgFtUNLu1foMSAMkoOsrQrTZ8EtTMssBLfTtzP0dfjG.png'
|
||||
}
|
||||
]
|
||||
},
|
||||
updateTime: 1673628760
|
||||
},
|
||||
{
|
||||
mediaId: 'r6ryvl6LrxBU0miaST4Y-u9kTIm1DhWZDdXyxsxUVv2Z5DAB99IPxkIRTUUD206k',
|
||||
content: {
|
||||
newsItem: [
|
||||
{
|
||||
title: '12',
|
||||
author: '333',
|
||||
digest: '123',
|
||||
content: '123',
|
||||
contentSourceUrl: 'https://www.iocoder.cn',
|
||||
thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-jVixJGgnBnkBPRbuVptOW0CHYuQFyiOVNtamctS8xU8',
|
||||
showCoverPic: 0,
|
||||
needOpenComment: 0,
|
||||
onlyFansCanComment: 0,
|
||||
url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9qVVhpSDZUaFJWTzBBWWRVQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuNWRnTDJWYmF2NER0clV1bThmQ0xUR3hqQnJkZ3BJSUNmNDJmc0lCZ1dadkVnZ3Z5bkN4YWtVUjhoaWZWYzZURUR4NnpMd0Y4Z3U5aUdib0lkMzI4Rjg3SG9JX2FycTMxbUctOHplaTlQVVhnfn4%3D&chksm=1f6354b62814dda076c778af33f06580165d8aa81f7798d55cfabb1886b5c74d9b2124a3535c#rd',
|
||||
thumbUrl:
|
||||
'http://test.yudao.iocoder.cn/r6ryvl6LrxBU0miaST4Y-jVixJGgnBnkBPRbuVptOW0CHYuQFyiOVNtamctS8xU8.jpg'
|
||||
}
|
||||
]
|
||||
},
|
||||
updateTime: 1673626494
|
||||
},
|
||||
{
|
||||
mediaId: 'r6ryvl6LrxBU0miaST4Y-sO24upobaENDmeByfBTfaozB3aOqSMAV0lGy-UkHXE7',
|
||||
content: {
|
||||
newsItem: [
|
||||
{
|
||||
title: '我是标题',
|
||||
author: '我是作者',
|
||||
digest: '我是摘要',
|
||||
content: '我是内容',
|
||||
contentSourceUrl: 'https://www.iocoder.cn',
|
||||
thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn',
|
||||
showCoverPic: 0,
|
||||
needOpenComment: 0,
|
||||
onlyFansCanComment: 0,
|
||||
url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9LT2dqRnpMNUpsR0hjYWtBQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuNGNmazZTdlE5WkxvU0tfX2V5cjV2WjJiR0xjQUhyREFSZWo2eWNrUW9EYVh6ZkpWRXBLR3FmTEV6YldBMno3Q2ZvVXBSdzlaVDc3aFhndEpQWUwzWmFMUWt0YVVURE1VZ1FsQTdPMlRtc3JBfn4%3D&chksm=1f6354aa2814ddbcc2637382f963a8742993ac38ebcebe6e3411df5ac82ac7bbdb391be6494a#rd',
|
||||
thumbUrl:
|
||||
'http://test.yudao.iocoder.cn/r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn.png'
|
||||
}
|
||||
]
|
||||
},
|
||||
updateTime: 1673534279
|
||||
}
|
||||
],
|
||||
total: 6
|
||||
}
|
|
@ -59,7 +59,7 @@
|
|||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="freePublish">
|
||||
<script setup lang="ts" name="MpFreePublish">
|
||||
import * as FreePublishApi from '@/api/mp/freePublish'
|
||||
import * as MpAccountApi from '@/api/mp/account'
|
||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||
|
|
|
@ -1,3 +1,528 @@
|
|||
<template>
|
||||
<span>开发中</span>
|
||||
<doc-alert title="公众号素材" url="https://doc.iocoder.cn/mp/material/" />
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<el-select v-model="queryParams.accountId" placeholder="请选择公众号" class="!w-240px">
|
||||
<el-option
|
||||
v-for="item in accountList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<ContentWrap>
|
||||
<el-tabs v-model="type" @tab-change="handleTabChange">
|
||||
<!-- tab 1:图片 -->
|
||||
<el-tab-pane name="image">
|
||||
<template #label>
|
||||
<span><Icon icon="ep:picture" />图片</span>
|
||||
</template>
|
||||
<div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
|
||||
<el-upload
|
||||
:action="actionUrl"
|
||||
:headers="headers"
|
||||
multiple
|
||||
:limit="1"
|
||||
:file-list="fileList"
|
||||
:data="uploadData"
|
||||
:before-upload="beforeImageUpload"
|
||||
:on-success="handleUploadSuccess"
|
||||
>
|
||||
<el-button type="primary" plain>点击上传</el-button>
|
||||
<template #tip>
|
||||
<span class="el-upload__tip" style="margin-left: 5px">
|
||||
支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M
|
||||
</span>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
<div class="waterfall" v-loading="loading">
|
||||
<div class="waterfall-item" v-for="item in list" :key="item.id">
|
||||
<a target="_blank" :href="item.url">
|
||||
<img class="material-img" :src="item.url" />
|
||||
<div class="item-name">{{ item.name }}</div>
|
||||
</a>
|
||||
<el-row class="ope-row" justify="center">
|
||||
<el-button
|
||||
type="danger"
|
||||
circle
|
||||
@click="handleDelete(item)"
|
||||
v-hasPermi="['mp:material:delete']"
|
||||
>
|
||||
<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分页组件 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- tab 2:语音 -->
|
||||
<el-tab-pane name="voice">
|
||||
<template #label>
|
||||
<span><Icon icon="ep:microphone" />语音</span>
|
||||
</template>
|
||||
<div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
|
||||
<el-upload
|
||||
:action="actionUrl"
|
||||
:headers="headers"
|
||||
multiple
|
||||
:limit="1"
|
||||
:file-list="fileList"
|
||||
:data="uploadData"
|
||||
:on-success="handleUploadSuccess"
|
||||
:before-upload="beforeVoiceUpload"
|
||||
>
|
||||
<el-button type="primary" plain>点击上传</el-button>
|
||||
<template #tip>
|
||||
<span class="el-upload__tip" style="margin-left: 5px">
|
||||
格式支持 mp3/wma/wav/amr,文件大小不超过 2M,播放长度不超过 60s
|
||||
</span>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
<el-table :data="list" stripe border v-loading="loading" style="margin-top: 10px">
|
||||
<el-table-column label="编号" align="center" prop="mediaId" />
|
||||
<el-table-column label="文件名" align="center" prop="name" />
|
||||
<el-table-column label="语音" align="center">
|
||||
<template #default="scope">
|
||||
<WxVoicePlayer :url="scope.row.url" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="上传时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ formatDate(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link plain @click="handleDownload(scope.row)">
|
||||
<Icon icon="ep:download" />下载
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
plain
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['mp:material:delete']"
|
||||
>
|
||||
<Icon icon="ep:delete" />删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页组件 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- tab 3:视频 -->
|
||||
<el-tab-pane name="video">
|
||||
<template #label>
|
||||
<span><Icon icon="ep:video-play" /> 视频</span>
|
||||
</template>
|
||||
<div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
|
||||
<el-button type="primary" plain @click="handleAddVideo">新建视频</el-button>
|
||||
</div>
|
||||
<!-- 新建视频的弹窗 -->
|
||||
<el-dialog
|
||||
title="新建视频"
|
||||
v-model="dialogVideoVisible"
|
||||
width="600px"
|
||||
v-loading="addMaterialLoading"
|
||||
>
|
||||
<el-upload
|
||||
:action="actionUrl"
|
||||
:headers="headers"
|
||||
multiple
|
||||
:limit="1"
|
||||
:file-list="fileList"
|
||||
:data="uploadData"
|
||||
:before-upload="beforeVideoUpload"
|
||||
:on-success="handleUploadSuccess"
|
||||
ref="uploadVideoRef"
|
||||
:auto-upload="false"
|
||||
>
|
||||
<template #trigger>
|
||||
<el-button size="small" type="primary">选择视频</el-button>
|
||||
</template>
|
||||
<span class="el-upload__tip" style="margin-left: 10px"
|
||||
>格式支持 MP4,文件大小不超过 10MB</span
|
||||
>
|
||||
</el-upload>
|
||||
<el-form :model="uploadData" :rules="uploadRules" ref="uploadFormRef" label-width="80px">
|
||||
<el-row>
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input
|
||||
v-model="uploadData.title"
|
||||
placeholder="标题将展示在相关播放页面,建议填写清晰、准确、生动的标题"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-form-item label="描述" prop="introduction">
|
||||
<el-input
|
||||
:rows="3"
|
||||
type="textarea"
|
||||
v-model="uploadData.introduction"
|
||||
placeholder="介绍语将展示在相关播放页面,建议填写简洁明确、有信息量的内容"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancelVideo">取 消</el-button>
|
||||
<el-button type="primary" @click="submitVideo">提 交</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-table :data="list" stripe border v-loading="loading" style="margin-top: 10px">
|
||||
<el-table-column label="编号" align="center" prop="mediaId" />
|
||||
<el-table-column label="文件名" align="center" prop="name" />
|
||||
<el-table-column label="标题" align="center" prop="title" />
|
||||
<el-table-column label="介绍" align="center" prop="introduction" />
|
||||
<el-table-column label="视频" align="center">
|
||||
<template #default="scope">
|
||||
<WxVideoPlayer :url="scope.row.url" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="上传时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ formatDate(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link plain @click="handleDownload(scope.row)"
|
||||
><Icon icon="ep:download" />下载</el-button
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
plain
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['mp:material:delete']"
|
||||
>
|
||||
<Icon icon="ep:delete" />删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页组件 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup name="MpMaterial">
|
||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
|
||||
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
|
||||
import { getSimpleAccountList } from '@/api/mp/account'
|
||||
import { getMaterialPage, deletePermanentMaterial } from '@/api/mp/material'
|
||||
import { getAccessToken } from '@/utils/auth'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
|
||||
const BASE_URL = import.meta.env.VITE_BASE_URL
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const queryFormRef = ref()
|
||||
const uploadFormRef = ref()
|
||||
const uploadVideoRef = ref()
|
||||
|
||||
const type = ref('image')
|
||||
// 遮罩层
|
||||
const loading = ref(false)
|
||||
// 总条数
|
||||
const total = ref(0)
|
||||
// 数据列表
|
||||
const list = ref([])
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
accountId: undefined,
|
||||
permanent: true
|
||||
})
|
||||
|
||||
const actionUrl = BASE_URL + '/admin-api/mp/material/upload-permanent'
|
||||
const headers = { Authorization: 'Bearer ' + getAccessToken() }
|
||||
const fileList = ref([])
|
||||
const uploadData = reactive({
|
||||
type: 'image',
|
||||
title: '',
|
||||
introduction: ''
|
||||
})
|
||||
|
||||
// === 视频上传,独有变量 ===
|
||||
const dialogVideoVisible = ref(false)
|
||||
const addMaterialLoading = ref(false)
|
||||
const uploadRules = reactive({
|
||||
// 视频上传的校验规则
|
||||
title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
|
||||
introduction: [{ required: true, message: '请输入描述', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
// 公众号账号列表
|
||||
const accountList = ref([])
|
||||
|
||||
onMounted(() => {
|
||||
getSimpleAccountList().then((data) => {
|
||||
accountList.value = data
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
setAccountId(accountList.value[0].id)
|
||||
}
|
||||
// 加载数据
|
||||
getList()
|
||||
})
|
||||
})
|
||||
|
||||
// ======================== 列表查询 ========================
|
||||
/** 设置账号编号 */
|
||||
const setAccountId = (accountId) => {
|
||||
queryParams.accountId = accountId
|
||||
uploadData.accountId = accountId
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = () => {
|
||||
// 如果没有选中公众号账号,则进行提示。
|
||||
if (!queryParams.accountId) {
|
||||
message.error('未选中公众号,无法查询草稿箱')
|
||||
return false
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
getMaterialPage({
|
||||
...queryParams,
|
||||
type: type.value
|
||||
})
|
||||
.then((data) => {
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
// 默认选中第一个
|
||||
if (queryParams.accountId) {
|
||||
setAccountId(queryParams.accountId)
|
||||
}
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields()
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
setAccountId(accountList.value[0].id)
|
||||
}
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
const handleTabChange = (tabName) => {
|
||||
// 设置 type
|
||||
uploadData.type = tabName
|
||||
// 从第一页开始查询
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
// ======================== 文件上传 ========================
|
||||
const beforeImageUpload = (file) => {
|
||||
const isType =
|
||||
file.type === 'image/jpeg' ||
|
||||
file.type === 'image/png' ||
|
||||
file.type === 'image/gif' ||
|
||||
file.type === 'image/bmp' ||
|
||||
file.type === 'image/jpg'
|
||||
if (!isType) {
|
||||
message.error('上传图片格式不对!')
|
||||
return false
|
||||
}
|
||||
const isLt = file.size / 1024 / 1024 < 2
|
||||
if (!isLt) {
|
||||
message.error('上传图片大小不能超过 2M!')
|
||||
return false
|
||||
}
|
||||
loading.value = true
|
||||
return true
|
||||
}
|
||||
|
||||
const beforeVoiceUpload = (file) => {
|
||||
const isType =
|
||||
file.type === 'audio/mp3' ||
|
||||
file.type === 'audio/wma' ||
|
||||
file.type === 'audio/wav' ||
|
||||
file.type === 'audio/amr'
|
||||
const isLt = file.size / 1024 / 1024 < 2
|
||||
if (!isType) {
|
||||
message.error('上传语音格式不对!')
|
||||
return false
|
||||
}
|
||||
if (!isLt) {
|
||||
message.error('上传语音大小不能超过 2M!')
|
||||
return false
|
||||
}
|
||||
loading.value = true
|
||||
return true
|
||||
}
|
||||
|
||||
const beforeVideoUpload = (file) => {
|
||||
const isType = file.type === 'video/mp4'
|
||||
if (!isType) {
|
||||
message.error('上传视频格式不对!')
|
||||
return false
|
||||
}
|
||||
const isLt = file.size / 1024 / 1024 < 10
|
||||
if (!isLt) {
|
||||
message.error('上传视频大小不能超过 10M!')
|
||||
return false
|
||||
}
|
||||
addMaterialLoading.value = true
|
||||
return true
|
||||
}
|
||||
|
||||
const handleUploadSuccess = (response, file, fileList) => {
|
||||
loading.value = false
|
||||
addMaterialLoading.value = false
|
||||
if (response.code !== 0) {
|
||||
message.error('上传出错:' + response.msg)
|
||||
return false
|
||||
}
|
||||
|
||||
// 清空上传时的各种数据
|
||||
dialogVideoVisible.value = false
|
||||
fileList.value = []
|
||||
uploadData.title = ''
|
||||
uploadData.introduction = ''
|
||||
|
||||
// 加载数据
|
||||
getList()
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
const handleDownload = (row) => {
|
||||
window.open(row.url, '_blank')
|
||||
}
|
||||
|
||||
// 提交 video 新建的表单
|
||||
const submitVideo = () => {
|
||||
uploadFormRef.value.validate((valid) => {
|
||||
if (!valid) {
|
||||
return false
|
||||
}
|
||||
uploadVideoRef.value.submit()
|
||||
})
|
||||
}
|
||||
|
||||
const handleAddVideo = () => {
|
||||
resetVideo()
|
||||
dialogVideoVisible.value = true
|
||||
}
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancelVideo = () => {
|
||||
dialogVideoVisible.value = false
|
||||
resetVideo()
|
||||
}
|
||||
|
||||
/** 表单重置 */
|
||||
const resetVideo = () => {
|
||||
fileList.value = []
|
||||
uploadData.title = ''
|
||||
uploadData.introduction = ''
|
||||
uploadFormRef.value?.resetFields()
|
||||
}
|
||||
|
||||
// ======================== 其它操作 ========================
|
||||
const handleDelete = async (item) => {
|
||||
await message.confirm('此操作将永久删除该文件, 是否继续?')
|
||||
await deletePermanentMaterial(item.id)
|
||||
message.alertSuccess('删除成功')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/*瀑布流样式*/
|
||||
.waterfall {
|
||||
width: 100%;
|
||||
column-gap: 10px;
|
||||
column-count: 5;
|
||||
margin-top: 10px; /* 芋道源码:增加 10px,避免顶着上面 */
|
||||
}
|
||||
.waterfall-item {
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
break-inside: avoid;
|
||||
border: 1px solid #eaeaea;
|
||||
}
|
||||
.material-img {
|
||||
width: 100%;
|
||||
}
|
||||
p {
|
||||
line-height: 30px;
|
||||
}
|
||||
@media (min-width: 992px) and (max-width: 1300px) {
|
||||
.waterfall {
|
||||
column-count: 3;
|
||||
}
|
||||
p {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
.waterfall {
|
||||
column-count: 2;
|
||||
}
|
||||
p {
|
||||
color: orange;
|
||||
}
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.waterfall {
|
||||
column-count: 1;
|
||||
}
|
||||
}
|
||||
/*瀑布流样式*/
|
||||
</style>
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -1,3 +1,782 @@
|
|||
<template>
|
||||
<span>开发中</span>
|
||||
<doc-alert title="公众号菜单" url="https://doc.iocoder.cn/mp/menu/" />
|
||||
<!-- 搜索工作栏 -->
|
||||
<ContentWrap>
|
||||
<el-form class="-mb-15px" ref="queryFormRef" :inline="true" label-width="68px">
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<el-select v-model="accountId" placeholder="请选择公众号" class="!w-240px">
|
||||
<el-option
|
||||
v-for="item in accountList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<div class="public-account-management clearfix" v-loading="loading">
|
||||
<!--左边配置菜单-->
|
||||
<div class="left">
|
||||
<div class="weixin-hd">
|
||||
<div class="weixin-title">{{ name }}</div>
|
||||
</div>
|
||||
<div class="weixin-menu menu_main clearfix">
|
||||
<div class="menu_bottom" v-for="(item, i) of menuList" :key="i">
|
||||
<!-- 一级菜单 -->
|
||||
<div @click="menuClick(i, item)" class="menu_item" :class="{ active: isActive === i }"
|
||||
><Icon icon="ep:fold" color="black" />{{ item.name }}
|
||||
</div>
|
||||
<!-- 以下为二级菜单-->
|
||||
<div class="submenu" v-if="isSubMenuFlag === i">
|
||||
<div class="subtitle menu_bottom" v-for="(subItem, k) in item.children" :key="k">
|
||||
<div
|
||||
class="menu_subItem"
|
||||
v-if="item.children"
|
||||
:class="{ active: isSubMenuActive === i + '' + k }"
|
||||
@click="subMenuClick(subItem, i, k)"
|
||||
>
|
||||
{{ subItem.name }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 二级菜单加号, 当长度 小于 5 才显示二级菜单的加号 -->
|
||||
<div
|
||||
class="menu_bottom menu_addicon"
|
||||
v-if="!item.children || item.children.length < 5"
|
||||
@click="addSubMenu(i, item)"
|
||||
>
|
||||
<Icon icon="ep:plus" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 一级菜单加号 -->
|
||||
<div class="menu_bottom menu_addicon" v-if="menuList.length < 3" @click="addMenu">
|
||||
<Icon icon="ep:plus" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="save_div">
|
||||
<el-button
|
||||
class="save_btn"
|
||||
type="success"
|
||||
@click="handleSave"
|
||||
v-hasPermi="['mp:menu:save']"
|
||||
>保存并发布菜单</el-button
|
||||
>
|
||||
<el-button
|
||||
class="save_btn"
|
||||
type="danger"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['mp:menu:delete']"
|
||||
>清空菜单</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!--右边配置-->
|
||||
<div v-if="showRightFlag" class="right">
|
||||
<div class="configure_page">
|
||||
<div class="delete_btn">
|
||||
<el-button size="small" type="danger" @click="handleDeleteMenu(tempObj)">
|
||||
删除当前菜单<Icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<span>菜单名称:</span>
|
||||
<el-input
|
||||
class="input_width"
|
||||
v-model="tempObj.name"
|
||||
placeholder="请输入菜单名称"
|
||||
:maxlength="nameMaxLength"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<div v-if="showConfigureContent">
|
||||
<div class="menu_content">
|
||||
<span>菜单标识:</span>
|
||||
<el-input
|
||||
class="input_width"
|
||||
v-model="tempObj.menuKey"
|
||||
placeholder="请输入菜单 KEY"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<div class="menu_content">
|
||||
<span>菜单内容:</span>
|
||||
<el-select v-model="tempObj.type" clearable placeholder="请选择" class="menu_option">
|
||||
<el-option
|
||||
v-for="item in menuOptions"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:key="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="configur_content" v-if="tempObj.type === 'view'">
|
||||
<span>跳转链接:</span>
|
||||
<el-input
|
||||
class="input_width"
|
||||
v-model="tempObj.url"
|
||||
placeholder="请输入链接"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<div class="configur_content" v-if="tempObj.type === 'miniprogram'">
|
||||
<div class="applet">
|
||||
<span>小程序的 appid :</span>
|
||||
<el-input
|
||||
class="input_width"
|
||||
v-model="tempObj.miniProgramAppId"
|
||||
placeholder="请输入小程序的appid"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<div class="applet">
|
||||
<span>小程序的页面路径:</span>
|
||||
<el-input
|
||||
class="input_width"
|
||||
v-model="tempObj.miniProgramPagePath"
|
||||
placeholder="请输入小程序的页面路径,如:pages/index"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<div class="applet">
|
||||
<span>小程序的备用网页:</span>
|
||||
<el-input
|
||||
class="input_width"
|
||||
v-model="tempObj.url"
|
||||
placeholder="不支持小程序的老版本客户端将打开本网页"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<p class="blue">tips:需要和公众号进行关联才可以把小程序绑定带微信菜单上哟!</p>
|
||||
</div>
|
||||
<div class="configur_content" v-if="tempObj.type === 'article_view_limited'">
|
||||
<el-row>
|
||||
<div class="select-item" v-if="tempObj && tempObj.replyArticles">
|
||||
<WxNews :articles="tempObj.replyArticles" />
|
||||
<el-row class="ope-row" justify="center" align="middle">
|
||||
<el-button type="danger" circle @click="deleteMaterial">
|
||||
<icon icon="ep:delete" />
|
||||
</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-row justify="center">
|
||||
<el-col :span="24" style="text-align: center">
|
||||
<el-button type="success" @click="openMaterial">
|
||||
素材库选择<Icon icon="ep:circle-check" />
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-dialog title="选择图文" v-model="dialogNewsVisible" width="90%">
|
||||
<WxMaterialSelect
|
||||
:objData="{ type: 'news', accountId: accountId }"
|
||||
@select-material="selectMaterial"
|
||||
/>
|
||||
</el-dialog>
|
||||
</el-row>
|
||||
</div>
|
||||
<div
|
||||
class="configur_content"
|
||||
v-if="tempObj.type === 'click' || tempObj.type === 'scancode_waitmsg'"
|
||||
>
|
||||
<WxReplySelect :objData="tempObj.reply" v-if="hackResetWxReplySelect" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 一进页面就显示的默认页面,当点击左边按钮的时候,就不显示了-->
|
||||
<div v-else class="right">
|
||||
<p>请选择菜单配置</p>
|
||||
</div>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
<script setup name="MpMenu">
|
||||
import { handleTree } from '@/utils/tree'
|
||||
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
|
||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
|
||||
import { deleteMenu, getMenuList, saveMenu } from '@/api/mp/menu'
|
||||
import * as MpAccountApi from '@/api/mp/account'
|
||||
import menuOptions from './menuOptions'
|
||||
const message = useMessage() // 消息
|
||||
|
||||
// ======================== 列表查询 ========================
|
||||
const loading = ref(true) // 遮罩层
|
||||
const accountId = ref(undefined) // 公众号Id
|
||||
const name = ref('') // 公众号名
|
||||
const menuList = ref({ children: [] })
|
||||
const accountList = ref([]) // 公众号账号列表
|
||||
|
||||
// ======================== 菜单操作 ========================
|
||||
const isActive = ref(-1) // 一级菜单点中样式
|
||||
const isSubMenuActive = ref(-1) // 一级菜单点中样式
|
||||
const isSubMenuFlag = ref(-1) // 二级菜单显示标志
|
||||
|
||||
// ======================== 菜单编辑 ========================
|
||||
const showRightFlag = ref(false) // 右边配置显示默认详情还是配置详情
|
||||
const nameMaxLength = ref(0) // 菜单名称最大长度;1 级是 4 字符;2 级是 7 字符;
|
||||
const showConfigureContent = ref(true) // 是否展示配置内容;如果有子菜单,就不显示配置内容
|
||||
const hackResetWxReplySelect = ref(false) // 重置 WxReplySelect 组件
|
||||
const tempObj = ref({}) // 右边临时变量,作为中间值牵引关系
|
||||
|
||||
const tempSelfObj = ref({
|
||||
// 一些临时值放在这里进行判断,如果放在 tempObj,由于引用关系,menu 也会多了多余的参数
|
||||
})
|
||||
const dialogNewsVisible = ref(false) // 跳转图文时的素材选择弹窗
|
||||
|
||||
onMounted(async () => {
|
||||
accountList.value = await MpAccountApi.getSimpleAccountList()
|
||||
// 选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
// @ts-ignore
|
||||
setAccountId(accountList.value[0].id)
|
||||
}
|
||||
await getList()
|
||||
})
|
||||
|
||||
// ======================== 列表查询 ========================
|
||||
/** 设置账号编号 */
|
||||
const setAccountId = (id) => {
|
||||
accountId.value = id
|
||||
name.value = accountList.value.find((item) => item.id === accountId.value)?.name
|
||||
}
|
||||
|
||||
const getList = async () => {
|
||||
loading.value = false
|
||||
getMenuList(accountId.value)
|
||||
.then((response) => {
|
||||
const menuData = convertMenuList(response)
|
||||
menuList.value = handleTree(menuData, 'id')
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
resetForm()
|
||||
// 默认选中第一个
|
||||
if (accountId.value) {
|
||||
setAccountId(accountId.value)
|
||||
}
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
resetForm()
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
setAccountId(accountList.value[0].id)
|
||||
}
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
// 将后端返回的 menuList,转换成前端的 menuList
|
||||
const convertMenuList = (list) => {
|
||||
if (!list) return []
|
||||
|
||||
const menuList = []
|
||||
list.forEach((item) => {
|
||||
const menu = {
|
||||
...item
|
||||
}
|
||||
if (item.type === 'click' || item.type === 'scancode_waitmsg') {
|
||||
delete menu.replyMessageType
|
||||
delete menu.replyContent
|
||||
delete menu.replyMediaId
|
||||
delete menu.replyMediaUrl
|
||||
delete menu.replyDescription
|
||||
delete menu.replyArticles
|
||||
menu.reply = {
|
||||
type: item.replyMessageType,
|
||||
accountId: item.accountId,
|
||||
content: item.replyContent,
|
||||
mediaId: item.replyMediaId,
|
||||
url: item.replyMediaUrl,
|
||||
title: item.replyTitle,
|
||||
description: item.replyDescription,
|
||||
thumbMediaId: item.replyThumbMediaId,
|
||||
thumbMediaUrl: item.replyThumbMediaUrl,
|
||||
articles: item.replyArticles,
|
||||
musicUrl: item.replyMusicUrl,
|
||||
hqMusicUrl: item.replyHqMusicUrl
|
||||
}
|
||||
}
|
||||
menuList.push(menu)
|
||||
})
|
||||
return menuList
|
||||
}
|
||||
|
||||
// 重置表单,清空表单数据
|
||||
const resetForm = () => {
|
||||
// 菜单操作
|
||||
isActive.value = -1
|
||||
isSubMenuActive.value = -1
|
||||
isSubMenuFlag.value = -1
|
||||
|
||||
// 菜单编辑
|
||||
showRightFlag.value = false
|
||||
nameMaxLength.value = 0
|
||||
showConfigureContent.value = 0
|
||||
hackResetWxReplySelect.value = false
|
||||
tempObj.value = {}
|
||||
tempSelfObj.value = {}
|
||||
dialogNewsVisible.value = false
|
||||
}
|
||||
|
||||
// ======================== 菜单操作 ========================
|
||||
// 一级菜单点击事件
|
||||
const menuClick = (i, item) => {
|
||||
// 右侧的表单相关
|
||||
resetEditor()
|
||||
showRightFlag.value = true // 右边菜单
|
||||
tempObj.value = item // 这个如果放在顶部,flag 会没有。因为重新赋值了。
|
||||
tempSelfObj.value.grand = '1' // 表示一级菜单
|
||||
tempSelfObj.value.index = i // 表示一级菜单索引
|
||||
nameMaxLength.value = 4
|
||||
showConfigureContent.value = !(item.children && item.children.length > 0) // 有子菜单,就不显示配置内容
|
||||
|
||||
// 左侧的选中
|
||||
isActive.value = i // 一级菜单选中样式
|
||||
isSubMenuFlag.value = i // 二级菜单显示标志
|
||||
isSubMenuActive.value = -1 // 二级菜单去除选中样式
|
||||
}
|
||||
|
||||
// 二级菜单点击事件
|
||||
const subMenuClick = (subItem, index, k) => {
|
||||
// 右侧的表单相关
|
||||
resetEditor()
|
||||
showRightFlag.value = true // 右边菜单
|
||||
console.log(subItem)
|
||||
tempObj.value = subItem // 将点击的数据放到临时变量,对象有引用作用
|
||||
tempSelfObj.value.grand = '2' // 表示二级菜单
|
||||
tempSelfObj.value.index = index // 表示一级菜单索引
|
||||
tempSelfObj.value.secondIndex = k // 表示二级菜单索引
|
||||
nameMaxLength.value = 7
|
||||
showConfigureContent.value = true
|
||||
|
||||
// 左侧的选中
|
||||
isActive.value = -1 // 一级菜单去除样式
|
||||
isSubMenuActive.value = index + '' + k // 二级菜单选中样式
|
||||
}
|
||||
|
||||
// 添加横向一级菜单
|
||||
const addMenu = () => {
|
||||
const menuKeyLength = menuList.value.length
|
||||
const addButton = {
|
||||
name: '菜单名称',
|
||||
children: [],
|
||||
reply: {
|
||||
// 用于存储回复内容
|
||||
type: 'text',
|
||||
accountId: accountId.value // 保证组件里,可以使用到对应的公众号
|
||||
}
|
||||
}
|
||||
menuList.value[menuKeyLength] = addButton
|
||||
menuClick(menuKeyLength.value - 1, addButton)
|
||||
}
|
||||
// 添加横向二级菜单;item 表示要操作的父菜单
|
||||
const addSubMenu = (i, item) => {
|
||||
// 清空父菜单的属性,因为它只需要 name 属性即可
|
||||
if (!item.children || item.children.length <= 0) {
|
||||
item.children = []
|
||||
delete item['type']
|
||||
delete item['menuKey']
|
||||
delete item['miniProgramAppId']
|
||||
delete item['miniProgramPagePath']
|
||||
delete item['url']
|
||||
delete item['reply']
|
||||
delete item['articleId']
|
||||
delete item['replyArticles']
|
||||
// 关闭配置面板
|
||||
showConfigureContent.value = false
|
||||
}
|
||||
|
||||
let subMenuKeyLength = item.children.length // 获取二级菜单key长度
|
||||
let addButton = {
|
||||
name: '子菜单名称',
|
||||
reply: {
|
||||
// 用于存储回复内容
|
||||
type: 'text',
|
||||
accountId: accountId.value // 保证组件里,可以使用到对应的公众号
|
||||
}
|
||||
}
|
||||
item.children[subMenuKeyLength] = addButton
|
||||
subMenuClick(item.children[subMenuKeyLength], i, subMenuKeyLength)
|
||||
}
|
||||
|
||||
// 删除当前菜单
|
||||
const handleDeleteMenu = async () => {
|
||||
try {
|
||||
await message.confirm('确定要删除吗?')
|
||||
if (tempSelfObj.value.grand === '1') {
|
||||
// 一级菜单的删除方法
|
||||
menuList.value.splice(tempSelfObj.value.index, 1)
|
||||
} else if (tempSelfObj.value.grand === '2') {
|
||||
// 二级菜单的删除方法
|
||||
menuList.value[tempSelfObj.value.index].children.splice(tempSelfObj.value.secondIndex, 1)
|
||||
}
|
||||
// 提示
|
||||
message.notifySuccess('删除成功')
|
||||
|
||||
// 处理菜单的选中
|
||||
tempObj.value = {}
|
||||
showRightFlag.value = false
|
||||
isActive.value = -1
|
||||
isSubMenuActive.value = -1
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// ======================== 菜单编辑 ========================
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
await message.confirm('确定要删除吗?')
|
||||
loading.value = true
|
||||
await saveMenu(accountId.value, convertMenuFormList())
|
||||
getList()
|
||||
message.notifySuccess('发布成功')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 表单 Editor 重置
|
||||
const resetEditor = () => {
|
||||
hackResetWxReplySelect.value = false // 销毁组件
|
||||
nextTick(() => {
|
||||
console.log('nextTick')
|
||||
hackResetWxReplySelect.value = true // 重建组件
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
await message.confirm('确定要删除吗?')
|
||||
loading.value = true
|
||||
await deleteMenu(accountId.value)
|
||||
handleQuery()
|
||||
message.notifySuccess('清空成功')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 将前端的 menuList,转换成后端接收的 menuList
|
||||
const convertMenuFormList = () => {
|
||||
const result = []
|
||||
menuList.value.forEach((item) => {
|
||||
let menu = convertMenuForm(item)
|
||||
result.push(menu)
|
||||
|
||||
// 处理子菜单
|
||||
if (!item.children || item.children.length <= 0) {
|
||||
return
|
||||
}
|
||||
menu.children = []
|
||||
item.children.forEach((subItem) => {
|
||||
menu.children.push(convertMenuForm(subItem))
|
||||
})
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
// 将前端的 menu,转换成后端接收的 menu
|
||||
const convertMenuForm = (menu) => {
|
||||
let result = {
|
||||
...menu,
|
||||
children: undefined, // 不处理子节点
|
||||
reply: undefined // 稍后复制
|
||||
}
|
||||
if (menu.type === 'click' || menu.type === 'scancode_waitmsg') {
|
||||
result.replyMessageType = menu.reply.type
|
||||
result.replyContent = menu.reply.content
|
||||
result.replyMediaId = menu.reply.mediaId
|
||||
result.replyMediaUrl = menu.reply.url
|
||||
result.replyTitle = menu.reply.title
|
||||
result.replyDescription = menu.reply.description
|
||||
result.replyThumbMediaId = menu.reply.thumbMediaId
|
||||
result.replyThumbMediaUrl = menu.reply.thumbMediaUrl
|
||||
result.replyArticles = menu.reply.articles
|
||||
result.replyMusicUrl = menu.reply.musicUrl
|
||||
result.replyHqMusicUrl = menu.reply.hqMusicUrl
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ======================== 菜单编辑(素材选择) ========================
|
||||
const openMaterial = () => {
|
||||
dialogNewsVisible.value = true
|
||||
}
|
||||
|
||||
const selectMaterial = (item) => {
|
||||
const articleId = item.articleId
|
||||
const articles = item.content.newsItem
|
||||
// 提示,针对多图文
|
||||
if (articles.length > 1) {
|
||||
message.alertWarning('您选择的是多图文,将默认跳转第一篇')
|
||||
}
|
||||
dialogNewsVisible.value = false
|
||||
|
||||
// 设置菜单的回复
|
||||
tempObj.value.articleId = articleId
|
||||
tempObj.value.replyArticles = []
|
||||
articles.forEach((article) => {
|
||||
tempObj.value.replyArticles.push({
|
||||
title: article.title,
|
||||
description: article.digest,
|
||||
picUrl: article.picUrl,
|
||||
url: article.url
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const deleteMaterial = () => {
|
||||
delete tempObj.value['articleId']
|
||||
delete tempObj.value['replyArticles']
|
||||
}
|
||||
</script>
|
||||
<!--本组件样式-->
|
||||
<style lang="scss" scoped="scoped">
|
||||
/* 公共颜色变量 */
|
||||
.clearfix {
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
.clearfix::after {
|
||||
content: '';
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
div {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.weixin-hd {
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
bottom: 426px;
|
||||
left: 0px;
|
||||
width: 300px;
|
||||
height: 64px;
|
||||
background: transparent url('./assets/menu_head.png') no-repeat 0 0;
|
||||
background-position: 0 0;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.weixin-title {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: 33px;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.weixin-menu {
|
||||
background: transparent url('./assets/menu_foot.png') no-repeat 0 0;
|
||||
padding-left: 43px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.menu_option {
|
||||
width: 40% !important;
|
||||
}
|
||||
|
||||
.public-account-management {
|
||||
min-width: 1200px;
|
||||
width: 1200px;
|
||||
margin: 0 auto;
|
||||
|
||||
.left {
|
||||
float: left;
|
||||
display: inline-block;
|
||||
width: 350px;
|
||||
height: 715px;
|
||||
background: url('./assets/iphone_backImg.png') no-repeat;
|
||||
background-size: 100% auto;
|
||||
padding: 518px 25px 88px;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
|
||||
/*第一级菜单*/
|
||||
.menu_main {
|
||||
.menu_bottom {
|
||||
position: relative;
|
||||
float: left;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
width: 85.5px;
|
||||
text-align: center;
|
||||
border: 1px solid #ebedee;
|
||||
background-color: #fff;
|
||||
cursor: pointer;
|
||||
|
||||
&.menu_addicon {
|
||||
height: 46px;
|
||||
line-height: 46px;
|
||||
}
|
||||
|
||||
.menu_item {
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
// text-align: center;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&.active {
|
||||
border: 1px solid #2bb673;
|
||||
}
|
||||
}
|
||||
|
||||
.menu_subItem {
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.active {
|
||||
border: 1px solid #2bb673;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
color: #2bb673;
|
||||
}
|
||||
|
||||
/*第二级菜单*/
|
||||
.submenu {
|
||||
position: absolute;
|
||||
width: 85.5px;
|
||||
bottom: 45px;
|
||||
|
||||
.subtitle {
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.save_div {
|
||||
margin-top: 15px;
|
||||
text-align: center;
|
||||
|
||||
.save_btn {
|
||||
bottom: 20px;
|
||||
left: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*右边菜单内容*/
|
||||
.right {
|
||||
float: left;
|
||||
width: 63%;
|
||||
background-color: #e8e7e7;
|
||||
padding: 20px;
|
||||
margin-left: 20px;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
|
||||
.configure_page {
|
||||
.delete_btn {
|
||||
text-align: right;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.menu_content {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.configur_content {
|
||||
margin-top: 20px;
|
||||
background-color: #fff;
|
||||
padding: 20px 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.blue {
|
||||
color: #29b6f6;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.applet {
|
||||
margin-bottom: 20px;
|
||||
|
||||
span {
|
||||
width: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
.input_width {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.material {
|
||||
.input_width {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.el-textarea {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-input {
|
||||
width: 70%;
|
||||
margin-right: 2%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<!--素材样式-->
|
||||
<style lang="scss" scoped>
|
||||
.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;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
export default [
|
||||
{
|
||||
value: 'view',
|
||||
label: '跳转网页'
|
||||
},
|
||||
{
|
||||
value: 'miniprogram',
|
||||
label: '跳转小程序'
|
||||
},
|
||||
{
|
||||
value: 'click',
|
||||
label: '点击回复'
|
||||
},
|
||||
{
|
||||
value: 'article_view_limited',
|
||||
label: '跳转图文消息'
|
||||
},
|
||||
{
|
||||
value: 'scancode_push',
|
||||
label: '扫码直接返回结果'
|
||||
},
|
||||
{
|
||||
value: 'scancode_waitmsg',
|
||||
label: '扫码回复'
|
||||
},
|
||||
{
|
||||
value: 'pic_sysphoto',
|
||||
label: '系统拍照发图'
|
||||
},
|
||||
{
|
||||
value: 'pic_photo_or_album',
|
||||
label: '拍照或者相册'
|
||||
},
|
||||
{
|
||||
value: 'pic_weixin',
|
||||
label: '微信相册'
|
||||
},
|
||||
{
|
||||
value: 'location_select',
|
||||
label: '选择地理位置'
|
||||
}
|
||||
]
|
|
@ -1,3 +0,0 @@
|
|||
<template>
|
||||
<span>开发中</span>
|
||||
</template>
|
|
@ -0,0 +1,99 @@
|
|||
<template>
|
||||
<Dialog title="修改" v-model="dialogVisible">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="80px"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="昵称" prop="nickname">
|
||||
<el-input v-model="formData.nickname" placeholder="请输入昵称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
<el-form-item label="标签" prop="tagIds">
|
||||
<el-select v-model="formData.tagIds" multiple clearable placeholder="请选择标签">
|
||||
<el-option
|
||||
v-for="item in tagList"
|
||||
:key="item.tagId"
|
||||
:label="item.name"
|
||||
:value="item.tagId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as MpTagApi from '@/api/mp/tag'
|
||||
import * as MpUserApi from '@/api/mp/user'
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const formData = ref({
|
||||
id: undefined,
|
||||
nickname: undefined,
|
||||
remark: undefined,
|
||||
tagIds: []
|
||||
})
|
||||
const formRules = reactive({}) // 表单的校验
|
||||
const formRef = ref() // 表单 Ref
|
||||
const tagList = ref([]) // 公众号标签列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: number) => {
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await MpUserApi.getUser(id)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
// 加载标签
|
||||
tagList.value = await MpTagApi.getSimpleTagList()
|
||||
}
|
||||
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 {
|
||||
await MpUserApi.updateUser(formData.value)
|
||||
message.success(t('common.updateSuccess'))
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
id: undefined,
|
||||
nickname: undefined,
|
||||
remark: undefined,
|
||||
tagIds: []
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,187 @@
|
|||
<template>
|
||||
<doc-alert title="公众号粉丝" url="https://doc.iocoder.cn/mp/user/" />
|
||||
|
||||
<!-- 搜索工作栏 -->
|
||||
<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="openid">
|
||||
<el-input
|
||||
v-model="queryParams.openid"
|
||||
placeholder="请输入用户标识"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="昵称" prop="nickname">
|
||||
<el-input
|
||||
v-model="queryParams.nickname"
|
||||
placeholder="请输入昵称"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
|
||||
<el-button type="success" plain @click="handleSync" v-hasPermi="['mp:user: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="openid" width="260" />
|
||||
<el-table-column label="昵称" align="center" prop="nickname" />
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="标签" align="center" prop="tagIds" width="200">
|
||||
<template #default="scope">
|
||||
<span v-for="(tagId, index) in scope.row.tagIds" :key="index">
|
||||
<el-tag>{{ tagList.find((tag) => tag.tagId === tagId)?.name }} </el-tag>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="订阅状态" align="center" prop="subscribeStatus">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.subscribeStatus === 0" type="success">已订阅</el-tag>
|
||||
<el-tag v-else type="danger">未订阅</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="订阅时间"
|
||||
align="center"
|
||||
prop="subscribeTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="openForm(scope.row.id)"
|
||||
v-hasPermi="['mp:user:update']"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 表单弹窗:修改 -->
|
||||
<UserForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script lang="ts" setup name="MpUser">
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as MpAccountApi from '@/api/mp/account'
|
||||
import * as MpUserApi from '@/api/mp/user'
|
||||
import * as MpTagApi from '@/api/mp/tag'
|
||||
import UserForm from './UserForm.vue'
|
||||
const message = useMessage() // 消息
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
accountId: null,
|
||||
openid: null,
|
||||
nickname: null
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const accountList = ref([]) // 公众号账号列表
|
||||
const tagList = ref([]) // 公众号标签列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
// 如果没有选中公众号账号,则进行提示。
|
||||
if (!queryParams.accountId) {
|
||||
message.error('未选中公众号,无法查询用户')
|
||||
return false
|
||||
}
|
||||
try {
|
||||
loading.value = true
|
||||
const data = await MpUserApi.getUserPage(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) {
|
||||
queryParams.accountId = accountList.value[0].id
|
||||
}
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const openForm = (id: number) => {
|
||||
formRef.value.open(id)
|
||||
}
|
||||
|
||||
/** 同步标签 */
|
||||
const handleSync = async () => {
|
||||
const accountId = queryParams.accountId
|
||||
try {
|
||||
await message.confirm('是否确认同步粉丝?')
|
||||
await MpUserApi.syncUser(accountId)
|
||||
message.success('开始从微信公众号同步粉丝信息,同步需要一段时间,建议稍后再查询')
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
// 加载标签
|
||||
tagList.value = await MpTagApi.getSimpleTagList()
|
||||
|
||||
// 加载账号
|
||||
accountList.value = await MpAccountApi.getSimpleAccountList()
|
||||
if (accountList.value.length > 0) {
|
||||
queryParams.accountId = accountList.value[0].id
|
||||
}
|
||||
await getList()
|
||||
})
|
||||
</script>
|
|
@ -75,7 +75,7 @@
|
|||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="App">
|
||||
<script setup lang="ts" name="PayApp">
|
||||
import type { FormExpose } from '@/components/Form'
|
||||
import { rules, allSchemas } from './app.data'
|
||||
import * as AppApi from '@/api/pay/app'
|
||||
|
|
|
@ -137,7 +137,7 @@
|
|||
<!-- 表单弹窗:添加/修改 -->
|
||||
<MerchantForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script setup lang="ts" name="Merchant">
|
||||
<script setup lang="ts" name="PayMerchant">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { CommonStatusEnum } from '@/utils/constants'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="Order">
|
||||
<script setup lang="ts" name="PayOrder">
|
||||
import { allSchemas } from './order.data'
|
||||
import * as OrderApi from '@/api/pay/order'
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
</template>
|
||||
</XModal>
|
||||
</template>
|
||||
<script setup lang="ts" name="Refund">
|
||||
<script setup lang="ts" name="PayRefund">
|
||||
import { allSchemas } from './refund.data'
|
||||
import * as RefundApi from '@/api/pay/refund'
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<!-- 表单弹窗:添加/修改 -->
|
||||
<AreaForm ref="formRef" />
|
||||
</template>
|
||||
<script setup lang="tsx" name="Area">
|
||||
<script setup lang="tsx" name="SystemArea">
|
||||
import type { Column } from 'element-plus'
|
||||
import AreaForm from './AreaForm.vue'
|
||||
import * as AreaApi from '@/api/system/area'
|
||||
|
|
|
@ -103,7 +103,7 @@
|
|||
<!-- 表单弹窗:添加/修改 -->
|
||||
<DeptForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script setup lang="ts" name="Dept">
|
||||
<script setup lang="ts" name="SystemDept">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { handleTree } from '@/utils/tree'
|
||||
|
|
|
@ -115,7 +115,7 @@
|
|||
<!-- 表单弹窗:添加/修改 -->
|
||||
<DictDataForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
<script setup lang="ts" name="DictData">
|
||||
<script setup lang="ts" name="SystemDictData">
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
|
|
|
@ -132,7 +132,7 @@
|
|||
<DictTypeForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="DictType">
|
||||
<script setup lang="ts" name="SystemDictType">
|
||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as DictTypeApi from '@/api/system/dict/dict.type'
|
||||
|
|
|
@ -137,7 +137,7 @@
|
|||
<ErrorCodeForm ref="formRef" @success="getList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ErrorCode">
|
||||
<script setup lang="ts" name="SystemErrorCode">
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
|
|
|
@ -104,7 +104,7 @@
|
|||
<!-- 表单弹窗:详情 -->
|
||||
<LoginLogDetail ref="detailRef" />
|
||||
</template>
|
||||
<script setup lang="ts" name="LoginLog">
|
||||
<script setup lang="ts" name="SystemLoginLog">
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<template>
|
||||
<Dialog title="详情" v-model="dialogVisible">
|
||||
<Descriptions :schema="allSchemas.detailSchema" :data="detailData" />
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as MailAccountApi from '@/api/system/mail/account'
|
||||
import { allSchemas } from './account.data'
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const detailLoading = ref(false) // 表单的加载中
|
||||
const detailData = ref() // 详情数据
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (id: number) => {
|
||||
dialogVisible.value = true
|
||||
// 设置数据
|
||||
detailLoading.value = true
|
||||
try {
|
||||
detailData.value = await MailAccountApi.getMailAccount(id)
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
</script>
|
|
@ -61,12 +61,16 @@ const crudSchemas = reactive<CrudSchema[]>([
|
|||
label: '创建时间',
|
||||
field: 'createTime',
|
||||
isForm: false,
|
||||
formatter: dateFormatter
|
||||
formatter: dateFormatter,
|
||||
detail: {
|
||||
dateFormat: 'YYYY-MM-DD HH:mm:ss'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '操作',
|
||||
field: 'action',
|
||||
isForm: false
|
||||
isForm: false,
|
||||
isDetail: false
|
||||
}
|
||||
])
|
||||
export const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
|
|
|
@ -39,6 +39,14 @@
|
|||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openDetail(row.id)"
|
||||
v-hasPermi="['system:mail-account:query']"
|
||||
>
|
||||
详情
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
|
@ -53,11 +61,14 @@
|
|||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<MailAccountForm ref="formRef" @success="getList" />
|
||||
<!-- 详情弹窗 -->
|
||||
<MailAccountDetail ref="detailRef" />
|
||||
</template>
|
||||
<script setup lang="ts" name="MailAccount">
|
||||
<script setup lang="ts" name="SystemMailAccount">
|
||||
import { allSchemas } from './account.data'
|
||||
import * as MailAccountApi from '@/api/system/mail/account'
|
||||
import MailAccountForm from './MailAccountForm.vue'
|
||||
import MailAccountDetail from './MailAccountDetail.vue'
|
||||
|
||||
// tableObject:表格的属性对象,可获得分页大小、条数等属性
|
||||
// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作
|
||||
|
@ -75,6 +86,12 @@ const openForm = (type: string, id?: number) => {
|
|||
formRef.value.open(type, id)
|
||||
}
|
||||
|
||||
/** 详情操作 */
|
||||
const detailRef = ref()
|
||||
const openDetail = (id: number) => {
|
||||
detailRef.value.open(id)
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = (id: number) => {
|
||||
tableMethods.delList(id, false)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue