Merge remote-tracking branch 'yudao/dev' into dev
# Conflicts: # src/views/Login/components/LoginForm.vue # src/views/Login/components/MobileForm.vue # src/views/mp/components/wx-editor/WxEditor.vue
This commit is contained in:
commit
ec58321dd8
12
.vscode/settings.json
vendored
12
.vscode/settings.json
vendored
@ -40,5 +40,15 @@
|
||||
"i18n-ally.displayLanguage": "zh-CN",
|
||||
"i18n-ally.enabledFrameworks": ["vue", "react"],
|
||||
"god.tsconfig": "./tsconfig.json",
|
||||
"vue-i18n.i18nPaths": "src/locales"
|
||||
"vue-i18n.i18nPaths": "src/locales",
|
||||
"explorer.fileNesting.enabled": true,
|
||||
"explorer.fileNesting.expand": false,
|
||||
"explorer.fileNesting.patterns": {
|
||||
"*.ts": "$(capture).test.ts, $(capture).test.tsx",
|
||||
"*.tsx": "$(capture).test.ts, $(capture).test.tsx",
|
||||
"*.env": "$(capture).env.*",
|
||||
"CHANGELOG.md": "CHANGELOG*",
|
||||
"package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,README*,.npmrc,.browserslistrc,vite.config.*,windi.*,tailwind.*,tsconfig.*,postcss*",
|
||||
".eslintrc.js": ".eslintignore,.eslintrc-*,.prettierignore,.stylelintignore,.commitlintrc.js,.prettierrc.js,.stylelint*,stylelint*,prettier.*,.editorconfig"
|
||||
}
|
||||
}
|
||||
|
@ -32,13 +32,12 @@
|
||||
"@form-create/element-ui": "^3.1.17",
|
||||
"@iconify/iconify": "^3.1.0",
|
||||
"@videojs-player/vue": "^1.0.0",
|
||||
"@vueup/vue-quill": "^1.1.1",
|
||||
"@vueuse/core": "^9.13.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||
"@zxcvbn-ts/core": "^2.2.1",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.3.4",
|
||||
"axios": "^1.3.5",
|
||||
"benz-amr-recorder": "^1.1.5",
|
||||
"bpmn-js-token-simulation": "^0.10.0",
|
||||
"camunda-bpmn-moddle": "^7.0.1",
|
||||
@ -48,16 +47,16 @@
|
||||
"diagram-js": "^11.6.0",
|
||||
"echarts": "^5.4.1",
|
||||
"echarts-wordcloud": "^2.1.0",
|
||||
"element-plus": "2.3.1",
|
||||
"element-plus": "2.3.3",
|
||||
"fast-xml-parser": "^4.1.3",
|
||||
"highlight.js": "^11.7.0",
|
||||
"intro.js": "^6.0.0",
|
||||
"intro.js": "^7.0.1",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"min-dash": "^4.0.0",
|
||||
"mitt": "^3.0.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.0.33",
|
||||
"pinia": "^2.0.34",
|
||||
"qrcode": "^1.5.1",
|
||||
"qs": "^6.11.1",
|
||||
"steady-xml": "^0.1.0",
|
||||
|
@ -20,7 +20,7 @@ const props = defineProps({
|
||||
editorId: propTypes.string.def('wangeEditor-1'),
|
||||
height: propTypes.oneOfType([Number, String]).def('500px'),
|
||||
editorConfig: {
|
||||
type: Object as PropType<IEditorConfig>,
|
||||
type: Object as PropType<Partial<IEditorConfig>>,
|
||||
default: () => undefined
|
||||
},
|
||||
readonly: propTypes.bool.def(false),
|
||||
@ -147,6 +147,7 @@ const editorConfig = computed((): IEditorConfig => {
|
||||
props.editorConfig || {}
|
||||
)
|
||||
})
|
||||
|
||||
const editorStyle = computed(() => {
|
||||
return {
|
||||
height: isNumber(props.height) ? `${props.height}px` : props.height
|
||||
|
@ -111,7 +111,8 @@
|
||||
>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button
|
||||
type="text"
|
||||
type="primary"
|
||||
link
|
||||
style="color: #ff4d4f"
|
||||
@click="removeFieldOptionItem(scope, scope.$index, 'enum')"
|
||||
>移除</el-button
|
||||
@ -143,7 +144,8 @@
|
||||
>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button
|
||||
type="text"
|
||||
type="primary"
|
||||
link
|
||||
style="color: #ff4d4f"
|
||||
@click="removeFieldOptionItem(scope, scope.$index, 'constraint')"
|
||||
>移除</el-button
|
||||
@ -174,7 +176,8 @@
|
||||
>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button
|
||||
type="text"
|
||||
type="primary"
|
||||
link
|
||||
style="color: #ff4d4f"
|
||||
@click="removeFieldOptionItem(scope, scope.$index, 'property')"
|
||||
>移除</el-button
|
||||
|
@ -29,6 +29,8 @@ type CrudSearchParams = {
|
||||
show?: boolean
|
||||
// 接口
|
||||
api?: () => Promise<any>
|
||||
// 搜索字段
|
||||
field?: string
|
||||
} & Omit<FormSchema, 'field'>
|
||||
|
||||
type CrudTableParams = {
|
||||
|
@ -21,16 +21,17 @@ const getCaches = computed((): string[] => {
|
||||
<template>
|
||||
<section
|
||||
:class="[
|
||||
'p-[var(--app-content-padding)] w-[100%] bg-[var(--app-contnet-bg-color)] dark:bg-[var(--el-bg-color)]',
|
||||
'p-[var(--app-content-padding)] w-[100%] bg-[var(--app-content-bg-color)] dark:bg-[var(--el-bg-color)]',
|
||||
{
|
||||
'!min-h-[calc(100%-var(--app-footer-height))]':
|
||||
fixedHeader && (layout === 'classic' || layout === 'topLeft') && footer,
|
||||
((fixedHeader && (layout === 'classic' || layout === 'topLeft')) || layout === 'top') &&
|
||||
footer,
|
||||
|
||||
'!min-h-[calc(100%-var(--tags-view-height)-var(--top-tool-height)-var(--app-footer-height))]':
|
||||
((!fixedHeader && layout === 'classic') || layout === 'top') && footer,
|
||||
!fixedHeader && layout === 'classic' && footer,
|
||||
|
||||
'!min-h-[calc(100%-var(--tags-view-height)-var(--app-footer-height))]':
|
||||
!fixedHeader && layout === 'topLeft' && footer,
|
||||
!fixedHeader && (layout === 'topLeft' || layout === 'top') && footer,
|
||||
|
||||
'!min-h-[calc(100%-var(--top-tool-height))]': fixedHeader && layout === 'cutMenu' && footer,
|
||||
|
||||
|
@ -177,7 +177,9 @@ export const useRenderLayout = () => {
|
||||
class={[
|
||||
`${prefixCls}-content-scrollbar`,
|
||||
{
|
||||
'mt-[var(--tags-view-height)]': fixedHeader.value
|
||||
'mt-[var(--tags-view-height)] !pb-[calc(var(--tags-view-height)+var(--app-footer-height))]':
|
||||
fixedHeader.value,
|
||||
'pb-[var(--app-footer-height)]': !fixedHeader.value
|
||||
}
|
||||
]}
|
||||
>
|
||||
|
@ -37,8 +37,6 @@ import App from './App.vue'
|
||||
|
||||
import './permission'
|
||||
|
||||
import { isDevMode } from '@/utils/env'
|
||||
|
||||
import { MyPD } from '@/components/bpmnProcessDesigner/package/index.js'
|
||||
import '@/components/bpmnProcessDesigner/package/theme/index.scss'
|
||||
import 'bpmn-js/dist/assets/diagram-js.css'
|
||||
@ -53,11 +51,6 @@ import '@/plugins/tongji' // 百度统计
|
||||
|
||||
import Logger from '@/utils/Logger'
|
||||
|
||||
// 本地开发模式 全局引入 element-plus 样式,加快第一次进入速度
|
||||
if (isDevMode()) {
|
||||
import('element-plus/dist/index.css')
|
||||
}
|
||||
|
||||
// 创建实例
|
||||
const setupAll = async () => {
|
||||
const app = createApp(App)
|
||||
|
@ -1,5 +1,4 @@
|
||||
@import './var.css';
|
||||
@import './vxe.css';
|
||||
@import 'element-plus/theme-chalk/dark/css-vars.css';
|
||||
|
||||
.reset-margin [class*='el-icon'] + span {
|
||||
|
@ -2,5 +2,3 @@
|
||||
$namespace: v;
|
||||
// el命名空间
|
||||
$elNamespace: el;
|
||||
// vxe命名空间
|
||||
$vxeNamespace: vxe;
|
||||
|
@ -1,30 +0,0 @@
|
||||
/*滚动条整体部分*/
|
||||
.xtable-scrollbar ::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
/*滚动条的轨道*/
|
||||
.xtable-scrollbar ::-webkit-scrollbar-track {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
/*滚动条里面的小方块,能向上向下移动*/
|
||||
.xtable-scrollbar ::-webkit-scrollbar-thumb {
|
||||
background-color: #bfbfbf;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #f1f1f1;
|
||||
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.xtable-scrollbar ::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #a8a8a8;
|
||||
}
|
||||
.xtable-scrollbar ::-webkit-scrollbar-thumb:active {
|
||||
background-color: #787878;
|
||||
}
|
||||
/*边角,即两个滚动条的交汇处*/
|
||||
.xtable-scrollbar ::-webkit-scrollbar-corner {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
/*移除 vxe-grid 右侧外框*/
|
||||
.vxe-button.size--medium.type--button.is--circle {
|
||||
border: 0;
|
||||
}
|
1
src/types/.gitignore
vendored
Normal file
1
src/types/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
auto-*.d.ts
|
128
src/types/auto-components.d.ts
vendored
128
src/types/auto-components.d.ts
vendored
@ -1,128 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
import '@vue/runtime-core'
|
||||
|
||||
export {}
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
Backtop: typeof import('./../components/Backtop/src/Backtop.vue')['default']
|
||||
ConfigGlobal: typeof import('./../components/ConfigGlobal/src/ConfigGlobal.vue')['default']
|
||||
ContentDetailWrap: typeof import('./../components/ContentDetailWrap/src/ContentDetailWrap.vue')['default']
|
||||
ContentWrap: typeof import('./../components/ContentWrap/src/ContentWrap.vue')['default']
|
||||
CopperModal: typeof import('./../components/Cropper/src/CopperModal.vue')['default']
|
||||
CountTo: typeof import('./../components/CountTo/src/CountTo.vue')['default']
|
||||
Crontab: typeof import('./../components/Crontab/src/Crontab.vue')['default']
|
||||
Cropper: typeof import('./../components/Cropper/src/Cropper.vue')['default']
|
||||
CropperAvatar: typeof import('./../components/Cropper/src/CropperAvatar.vue')['default']
|
||||
Descriptions: typeof import('./../components/Descriptions/src/Descriptions.vue')['default']
|
||||
Dialog: typeof import('./../components/Dialog/src/Dialog.vue')['default']
|
||||
DictTag: typeof import('./../components/DictTag/src/DictTag.vue')['default']
|
||||
DocAlert: typeof import('./../components/DocAlert/index.vue')['default']
|
||||
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']
|
||||
ElCollapseTransition: typeof import('element-plus/es')['ElCollapseTransition']
|
||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
|
||||
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
ElementBaseInfo: typeof import('./../components/bpmnProcessDesigner/package/penal/base/ElementBaseInfo.vue')['default']
|
||||
ElementForm: typeof import('./../components/bpmnProcessDesigner/package/penal/form/ElementForm.vue')['default']
|
||||
ElementListeners: typeof import('./../components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue')['default']
|
||||
ElementMultiInstance: typeof import('./../components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue')['default']
|
||||
ElementOtherConfig: typeof import('./../components/bpmnProcessDesigner/package/penal/other/ElementOtherConfig.vue')['default']
|
||||
ElementProperties: typeof import('./../components/bpmnProcessDesigner/package/penal/properties/ElementProperties.vue')['default']
|
||||
ElementTask: typeof import('./../components/bpmnProcessDesigner/package/penal/task/ElementTask.vue')['default']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElImage: typeof import('element-plus/es')['ElImage']
|
||||
ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||
ElLink: typeof import('element-plus/es')['ElLink']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
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']
|
||||
ElTimeline: typeof import('element-plus/es')['ElTimeline']
|
||||
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElTree: typeof import('element-plus/es')['ElTree']
|
||||
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
|
||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||
Error: typeof import('./../components/Error/src/Error.vue')['default']
|
||||
FlowCondition: typeof import('./../components/bpmnProcessDesigner/package/penal/flow-condition/FlowCondition.vue')['default']
|
||||
Form: typeof import('./../components/Form/src/Form.vue')['default']
|
||||
Highlight: typeof import('./../components/Highlight/src/Highlight.vue')['default']
|
||||
Icon: typeof import('./../components/Icon/src/Icon.vue')['default']
|
||||
IconSelect: typeof import('./../components/Icon/src/IconSelect.vue')['default']
|
||||
IFrame: typeof import('./../components/IFrame/src/IFrame.vue')['default']
|
||||
ImageViewer: typeof import('./../components/ImageViewer/src/ImageViewer.vue')['default']
|
||||
Infotip: typeof import('./../components/Infotip/src/Infotip.vue')['default']
|
||||
InputPassword: typeof import('./../components/InputPassword/src/InputPassword.vue')['default']
|
||||
Pagination: typeof import('./../components/Pagination/index.vue')['default']
|
||||
ProcessDesigner: typeof import('./../components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue')['default']
|
||||
ProcessPalette: typeof import('./../components/bpmnProcessDesigner/package/palette/ProcessPalette.vue')['default']
|
||||
ProcessViewer: typeof import('./../components/bpmnProcessDesigner/package/designer/ProcessViewer.vue')['default']
|
||||
PropertiesPanel: typeof import('./../components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue')['default']
|
||||
Qrcode: typeof import('./../components/Qrcode/src/Qrcode.vue')['default']
|
||||
ReceiveTask: typeof import('./../components/bpmnProcessDesigner/package/penal/task/task-components/ReceiveTask.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
ScriptTask: typeof import('./../components/bpmnProcessDesigner/package/penal/task/task-components/ScriptTask.vue')['default']
|
||||
Search: typeof import('./../components/Search/src/Search.vue')['default']
|
||||
SignalAndMessage: typeof import('./../components/bpmnProcessDesigner/package/penal/signal-message/SignalAndMessage.vue')['default']
|
||||
Sticky: typeof import('./../components/Sticky/src/Sticky.vue')['default']
|
||||
Table: typeof import('./../components/Table/src/Table.vue')['default']
|
||||
Tooltip: typeof import('./../components/Tooltip/src/Tooltip.vue')['default']
|
||||
UploadFile: typeof import('./../components/UploadFile/src/UploadFile.vue')['default']
|
||||
UploadImg: typeof import('./../components/UploadFile/src/UploadImg.vue')['default']
|
||||
UploadImgs: typeof import('./../components/UploadFile/src/UploadImgs.vue')['default']
|
||||
UserTask: typeof import('./../components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue')['default']
|
||||
UserTaskListeners: typeof import('./../components/bpmnProcessDesigner/package/penal/listeners/UserTaskListeners.vue')['default']
|
||||
Verify: typeof import('./../components/Verifition/src/Verify.vue')['default']
|
||||
VerifyPoints: typeof import('./../components/Verifition/src/Verify/VerifyPoints.vue')['default']
|
||||
VerifySlide: typeof import('./../components/Verifition/src/Verify/VerifySlide.vue')['default']
|
||||
XButton: typeof import('./../components/XButton/src/XButton.vue')['default']
|
||||
XTextButton: typeof import('./../components/XButton/src/XTextButton.vue')['default']
|
||||
}
|
||||
export interface ComponentCustomProperties {
|
||||
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||
}
|
||||
}
|
76
src/types/auto-imports.d.ts
vendored
76
src/types/auto-imports.d.ts
vendored
@ -1,76 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-auto-import
|
||||
export {}
|
||||
declare global {
|
||||
const DICT_TYPE: typeof import('@/utils/dict')['DICT_TYPE']
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const effectScope: typeof import('vue')['effectScope']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const h: typeof import('vue')['h']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const isProxy: typeof import('vue')['isProxy']
|
||||
const isReactive: typeof import('vue')['isReactive']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
const nextTick: typeof import('vue')['nextTick']
|
||||
const onActivated: typeof import('vue')['onActivated']
|
||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
||||
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
|
||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onMounted: typeof import('vue')['onMounted']
|
||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
const readonly: typeof import('vue')['readonly']
|
||||
const ref: typeof import('vue')['ref']
|
||||
const required: typeof import('@/utils/formRules')['required']
|
||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useCrudSchemas: typeof import('@/hooks/web/useCrudSchemas')['useCrudSchemas']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useI18n: typeof import('@/hooks/web/useI18n')['useI18n']
|
||||
const useLink: typeof import('vue-router')['useLink']
|
||||
const useMessage: typeof import('@/hooks/web/useMessage')['useMessage']
|
||||
const useRoute: typeof import('vue-router')['useRoute']
|
||||
const useRouter: typeof import('vue-router')['useRouter']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
const useTable: typeof import('@/hooks/web/useTable')['useTable']
|
||||
const useVxeCrudSchemas: typeof import('@/hooks/web/useVxeCrudSchemas')['useVxeCrudSchemas']
|
||||
const useXTable: typeof import('@/hooks/web/useXTable')['useXTable']
|
||||
const watch: typeof import('vue')['watch']
|
||||
const watchEffect: typeof import('vue')['watchEffect']
|
||||
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||
}
|
||||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from 'vue'
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
export const isDevMode = () => {
|
||||
const dev = import.meta.env.VITE_DEV
|
||||
if (dev && dev === 'true') {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
@ -21,7 +21,8 @@
|
||||
v-model="loginData.loginForm.tenantName"
|
||||
:placeholder="t('login.tenantNamePlaceholder')"
|
||||
:prefix-icon="iconHouse"
|
||||
type="text"
|
||||
type="primary"
|
||||
link
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
@ -22,7 +22,8 @@
|
||||
v-model="loginData.loginForm.tenantName"
|
||||
:placeholder="t('login.tenantNamePlaceholder')"
|
||||
:prefix-icon="iconHouse"
|
||||
type="text"
|
||||
type="primary"
|
||||
link
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
@ -18,7 +18,8 @@
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.formType === 10"
|
||||
type="text"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleFormDetail(scope.row)"
|
||||
>
|
||||
<span>{{ scope.row.formName }}</span>
|
||||
|
@ -79,14 +79,16 @@
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.formType === 10"
|
||||
type="text"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleFormDetail(scope.row)"
|
||||
>
|
||||
<span>{{ scope.row.formName }}</span>
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else-if="scope.row.formType === 20"
|
||||
type="text"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleFormDetail(scope.row)"
|
||||
>
|
||||
<span>{{ scope.row.formCustomCreatePath }}</span>
|
||||
|
@ -5,7 +5,7 @@
|
||||
<ContentWrap>
|
||||
<el-form class="-mb-15px" :model="queryParams" :inline="true" label-width="68px">
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<WxMpSelect @change="onAccountChanged" />
|
||||
<WxAccountSelect @change="onAccountChanged" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
@ -140,7 +140,7 @@
|
||||
</el-table>
|
||||
|
||||
<!-- 添加或修改自动回复的对话框 -->
|
||||
<el-dialog :title="title" v-model="showReplyFormDialog" width="800px" append-to-body>
|
||||
<el-dialog :title="title" v-model="showReplyFormDialog" width="800px" destroy-on-close>
|
||||
<el-form ref="formRef" :model="replyForm" :rules="rules" label-width="80px">
|
||||
<el-form-item label="消息类型" prop="requestMessageType" v-if="msgType === MsgType.Message">
|
||||
<el-select v-model="replyForm.requestMessageType" placeholder="请选择">
|
||||
@ -167,7 +167,7 @@
|
||||
<el-input v-model="replyForm.requestKeyword" placeholder="请输入内容" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="回复消息">
|
||||
<WxReplySelect :objData="objData" v-if="hackResetWxReplySelect" />
|
||||
<WxReplySelect :objData="objData" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@ -183,7 +183,7 @@ 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 WxMpSelect from '@/views/mp/components/WxMpSelect.vue'
|
||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
||||
import * as MpAutoReplyApi from '@/api/mp/autoReply'
|
||||
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
@ -195,7 +195,7 @@ const message = useMessage()
|
||||
const formRef = ref()
|
||||
|
||||
// 消息类型(Follow: 关注时回复;Message: 消息回复;Keyword: 关键词回复)
|
||||
// 作为tab.name
|
||||
// 作为tab.name,enum的数字不能随意修改,与api参数相关
|
||||
enum MsgType {
|
||||
Follow = 1,
|
||||
Message = 2,
|
||||
@ -277,9 +277,6 @@ const rules = {
|
||||
requestMatch: [{ required: true, message: '请求的关键字的匹配不能为空', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
// 重置 WxReplySelect 组件,解决无法清除的问题
|
||||
const hackResetWxReplySelect = ref(false)
|
||||
|
||||
const onAccountChanged = (id?: number) => {
|
||||
queryParams.accountId = id
|
||||
getList()
|
||||
@ -314,7 +311,6 @@ const handleTabChange = (tabName: TabPaneName) => {
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset()
|
||||
resetEditor()
|
||||
// 打开表单,并设置初始化
|
||||
objData.value = {
|
||||
type: 'text',
|
||||
@ -328,7 +324,6 @@ const handleAdd = () => {
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row: any) => {
|
||||
reset()
|
||||
resetEditor()
|
||||
|
||||
const data = await MpAutoReplyApi.getAutoReply(row.id)
|
||||
// 设置属性
|
||||
@ -386,7 +381,7 @@ const handleSubmit = async () => {
|
||||
}
|
||||
|
||||
showReplyFormDialog.value = false
|
||||
getList()
|
||||
await getList()
|
||||
}
|
||||
|
||||
// 表单重置
|
||||
@ -408,14 +403,6 @@ const cancel = () => {
|
||||
reset()
|
||||
}
|
||||
|
||||
// 表单 Editor 重置
|
||||
const resetEditor = () => {
|
||||
hackResetWxReplySelect.value = false // 销毁组件
|
||||
nextTick(() => {
|
||||
hackResetWxReplySelect.value = true // 重建组件
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = async (row) => {
|
||||
await message.confirm('是否确认删除此数据?')
|
||||
await MpAutoReplyApi.deleteAutoReply(row.id)
|
||||
|
@ -1,36 +0,0 @@
|
||||
<template>
|
||||
<el-select v-model="account.id" placeholder="请选择公众号" class="!w-240px" @change="onChanged">
|
||||
<el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</template>
|
||||
<!-- TODO @芋艿:WxMpSelect 改成 WxAccountSelect,然后挪到现有的 wx-account-select 包下 -->
|
||||
<script lang="ts" setup name="WxMpSelect">
|
||||
import * as MpAccountApi from '@/api/mp/account'
|
||||
|
||||
const account: MpAccountApi.AccountVO = reactive({
|
||||
id: undefined,
|
||||
name: ''
|
||||
})
|
||||
const accountList: Ref<MpAccountApi.AccountVO[]> = ref([])
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'change', id?: number, name?: string): void
|
||||
}>()
|
||||
|
||||
onMounted(() => {
|
||||
handleQuery()
|
||||
})
|
||||
|
||||
const handleQuery = async () => {
|
||||
accountList.value = await MpAccountApi.getSimpleAccountList()
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
account.id = accountList.value[0].id
|
||||
emit('change', account.id, account.name)
|
||||
}
|
||||
}
|
||||
|
||||
const onChanged = () => {
|
||||
emit('change', account.id, account.name)
|
||||
}
|
||||
</script>
|
@ -1,44 +1,37 @@
|
||||
<template>
|
||||
<el-form class="-mb-15px" ref="queryFormRef" :inline="true" label-width="68px">
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<!-- TODO 芋艿:需要将 el-form 和 el-select 解耦 -->
|
||||
<el-select
|
||||
v-model="accountId"
|
||||
placeholder="请选择公众号"
|
||||
class="!w-240px"
|
||||
@change="accountChanged()"
|
||||
>
|
||||
<el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<slot name="actions"></slot>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-select v-model="account.id" placeholder="请选择公众号" class="!w-240px" @change="onChanged">
|
||||
<el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script setup name="WxAccountSelect">
|
||||
<script lang="ts" setup name="WxAccountSelect">
|
||||
import * as MpAccountApi from '@/api/mp/account'
|
||||
const accountId = ref()
|
||||
const accountList = ref([])
|
||||
const queryFormRef = ref()
|
||||
|
||||
const emit = defineEmits(['change'])
|
||||
|
||||
onMounted(() => {
|
||||
handleQuery()
|
||||
const account: MpAccountApi.AccountVO = reactive({
|
||||
id: undefined,
|
||||
name: ''
|
||||
})
|
||||
const accountList: Ref<MpAccountApi.AccountVO[]> = ref([])
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'change', id?: number, name?: string): void
|
||||
}>()
|
||||
|
||||
const handleQuery = async () => {
|
||||
accountList.value = await MpAccountApi.getSimpleAccountList()
|
||||
// 默认选中第一个
|
||||
if (accountList.value.length > 0) {
|
||||
accountId.value = accountList.value[0].id
|
||||
emit('change', accountId.value)
|
||||
account.id = accountList.value[0].id
|
||||
emit('change', account.id, account.name)
|
||||
}
|
||||
}
|
||||
|
||||
const accountChanged = () => {
|
||||
emit('change', accountId.value)
|
||||
const onChanged = () => {
|
||||
emit('change', account.id, account.name)
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
handleQuery()
|
||||
})
|
||||
</script>
|
||||
|
@ -1,104 +0,0 @@
|
||||
<script name="WxEditor" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { getAccessToken } from '@/utils/auth'
|
||||
import { Editor } from '@/components/Editor'
|
||||
|
||||
const BASE_URL = import.meta.env.VITE_BASE_URL
|
||||
const actionUrl = BASE_URL + '/admin-api/mp/material/upload-news-image' // 这里写你要上传的图片服务器地址
|
||||
const headers = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
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 uploadData = reactive({
|
||||
type: 'image', // TODO 芋艿:试试要不要换成 thumb
|
||||
accountId: props.accountId
|
||||
})
|
||||
|
||||
const onEditorChange = (text) => {
|
||||
//内容改变事件
|
||||
emit('input', text)
|
||||
}
|
||||
|
||||
// 富文本图片上传前
|
||||
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
|
||||
:action="actionUrl"
|
||||
:before-upload="beforeUpload"
|
||||
:data="uploadData"
|
||||
:headers="headers"
|
||||
:on-error="uploadError"
|
||||
:on-success="uploadSuccess"
|
||||
:show-file-list="false"
|
||||
class="avatar-uploader"
|
||||
name="file"
|
||||
/>
|
||||
<Editor
|
||||
ref="quillEditorRef"
|
||||
:modelValue="content"
|
||||
editor-id="wxEditor"
|
||||
@change="(editor_) => onEditorChange(editor_.getText())"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@ -1,45 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -231,7 +231,7 @@
|
||||
:on-success="handleUploadSuccess"
|
||||
>
|
||||
<template #trigger>
|
||||
<el-button type="text">本地上传</el-button>
|
||||
<el-button type="primary" link>本地上传</el-button>
|
||||
</template>
|
||||
<el-button type="primary" link @click="openMaterial" style="margin-left: 5px"
|
||||
>素材库选择
|
||||
|
75
src/views/mp/draft/editor-config.ts
Normal file
75
src/views/mp/draft/editor-config.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { IEditorConfig } from '@wangeditor/editor'
|
||||
import { getAccessToken, getTenantId } from '@/utils/auth'
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
type InsertFnType = (url: string, alt: string, href: string) => void
|
||||
|
||||
export const createEditorConfig = (
|
||||
server: string,
|
||||
accountId: number | undefined
|
||||
): Partial<IEditorConfig> => {
|
||||
return {
|
||||
MENU_CONF: {
|
||||
['uploadImage']: {
|
||||
server,
|
||||
// 单个文件的最大体积限制,默认为 2M
|
||||
maxFileSize: 5 * 1024 * 1024,
|
||||
// 最多可上传几个文件,默认为 100
|
||||
maxNumberOfFiles: 10,
|
||||
// 选择文件时的类型限制,默认为 ['image/*'] 。如不想限制,则设置为 []
|
||||
allowedFileTypes: ['image/*'],
|
||||
|
||||
// 自定义上传参数,例如传递验证的 token 等。参数会被添加到 formData 中,一起上传到服务端。
|
||||
meta: {
|
||||
accountId: accountId,
|
||||
type: 'image'
|
||||
},
|
||||
// 将 meta 拼接到 url 参数中,默认 false
|
||||
metaWithUrl: true,
|
||||
|
||||
// 自定义增加 http header
|
||||
headers: {
|
||||
Accept: '*',
|
||||
Authorization: 'Bearer ' + getAccessToken(),
|
||||
'tenant-id': getTenantId()
|
||||
},
|
||||
|
||||
// 跨域是否传递 cookie ,默认为 false
|
||||
withCredentials: true,
|
||||
|
||||
// 超时时间,默认为 10 秒
|
||||
timeout: 5 * 1000, // 5 秒
|
||||
|
||||
// form-data fieldName,后端接口参数名称,默认值wangeditor-uploaded-image
|
||||
fieldName: 'file',
|
||||
|
||||
// 上传之前触发
|
||||
onBeforeUpload(file: File) {
|
||||
console.log(file)
|
||||
return file
|
||||
},
|
||||
// 上传进度的回调函数
|
||||
onProgress(progress: number) {
|
||||
// progress 是 0-100 的数字
|
||||
console.log('progress', progress)
|
||||
},
|
||||
onSuccess(file: File, res: any) {
|
||||
console.log('onSuccess', file, res)
|
||||
},
|
||||
onFailed(file: File, res: any) {
|
||||
message.alertError(res.message)
|
||||
console.log('onFailed', file, res)
|
||||
},
|
||||
onError(file: File, err: any, res: any) {
|
||||
message.alertError(err.message)
|
||||
console.error('onError', file, err, res)
|
||||
},
|
||||
// 自定义插入图片
|
||||
customInsert(res: any, insertFn: InsertFnType) {
|
||||
insertFn(res.data.url, 'image', res.data.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<WxMpSelect @change="onAccountChanged" />
|
||||
<WxAccountSelect @change="onAccountChanged" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain @click="handleAdd" v-hasPermi="['mp:draft:create']">
|
||||
@ -71,168 +71,182 @@
|
||||
<el-dialog
|
||||
:title="operateMaterial === 'add' ? '新建图文' : '修改图文'"
|
||||
width="80%"
|
||||
top="20px"
|
||||
v-model="dialogNewsVisible"
|
||||
:before-close="dialogNewsClose"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
>
|
||||
<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%"
|
||||
/>
|
||||
<el-container>
|
||||
<el-aside width="40%">
|
||||
<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="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
|
||||
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"
|
||||
width="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>
|
||||
</div>
|
||||
<el-row justify="center" class="ope-row">
|
||||
<el-button
|
||||
type="primary"
|
||||
circle
|
||||
@click="plusNews"
|
||||
v-if="articlesAdd.length < 8 && operateMaterial === 'add'"
|
||||
>
|
||||
<Icon icon="ep:plus" />
|
||||
</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right" v-loading="addMaterialLoading" v-if="articlesAdd.length > 0">
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<!-- 标题、作者、原文地址 -->
|
||||
<el-input v-model="articlesAdd[isActiveAddNews].title" placeholder="请输入标题(必填)" />
|
||||
<el-input
|
||||
v-model="articlesAdd[isActiveAddNews].author"
|
||||
placeholder="请输入作者"
|
||||
style="margin-top: 5px"
|
||||
/>
|
||||
<el-input
|
||||
v-model="articlesAdd[isActiveAddNews].contentSourceUrl"
|
||||
placeholder="请输入原文地址"
|
||||
style="margin-top: 5px"
|
||||
/>
|
||||
<!-- 封面和摘要 -->
|
||||
<div class="input-tt">封面和摘要:</div>
|
||||
<div>
|
||||
<div class="thumb-div">
|
||||
<img
|
||||
class="material-img"
|
||||
v-if="articlesAdd[isActiveAddNews].thumbUrl"
|
||||
:src="articlesAdd[isActiveAddNews].thumbUrl"
|
||||
:class="isActiveAddNews === 0 ? 'avatar' : 'avatar1'"
|
||||
/>
|
||||
<Icon
|
||||
v-else
|
||||
icon="ep:plus"
|
||||
class="avatar-uploader-icon"
|
||||
:class="isActiveAddNews === 0 ? 'avatar' : 'avatar1'"
|
||||
/>
|
||||
<div class="thumb-but">
|
||||
<el-upload
|
||||
:action="uploadUrl"
|
||||
:headers="headers"
|
||||
multiple
|
||||
:limit="1"
|
||||
:file-list="fileList"
|
||||
:data="uploadData"
|
||||
:before-upload="beforeThumbImageUpload"
|
||||
:on-success="handleUploadSuccess"
|
||||
<el-row justify="center" class="ope-row">
|
||||
<el-button
|
||||
type="primary"
|
||||
circle
|
||||
@click="plusNews"
|
||||
v-if="articlesAdd.length < 8 && operateMaterial === 'add'"
|
||||
>
|
||||
<template #trigger>
|
||||
<el-button size="small" type="primary">本地上传</el-button>
|
||||
</template>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="openMaterial"
|
||||
style="margin-left: 5px"
|
||||
>素材库选择</el-button
|
||||
>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
<el-dialog title="选择图片" v-model="dialogImageVisible" width="80%" append-to-body>
|
||||
<WxMaterialSelect
|
||||
ref="materialSelectRef"
|
||||
:objData="{ type: 'image', accountId: queryParams.accountId }"
|
||||
@select-material="selectMaterial"
|
||||
/>
|
||||
</el-dialog>
|
||||
<Icon icon="ep:plus" />
|
||||
</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-input
|
||||
:rows="8"
|
||||
type="textarea"
|
||||
v-model="articlesAdd[isActiveAddNews].digest"
|
||||
placeholder="请输入摘要"
|
||||
class="digest"
|
||||
maxlength="120"
|
||||
style="float: right"
|
||||
/>
|
||||
</div>
|
||||
<!--富文本编辑器组件-->
|
||||
<el-row>
|
||||
<WxEditor
|
||||
v-model="articlesAdd[isActiveAddNews].content"
|
||||
:account-id="uploadData.accountId"
|
||||
v-if="hackResetEditor"
|
||||
/>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-aside>
|
||||
<el-main>
|
||||
<div class="" v-loading="addMaterialLoading" v-if="articlesAdd.length > 0">
|
||||
<!-- 标题、作者、原文地址 -->
|
||||
<el-row :gutter="20">
|
||||
<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"
|
||||
/>
|
||||
</el-row>
|
||||
<!-- 封面和摘要 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<p>封面:</p>
|
||||
<div class="thumb-div">
|
||||
<el-image
|
||||
v-if="articlesAdd[isActiveAddNews].thumbUrl"
|
||||
style="width: 300px; max-height: 300px"
|
||||
:src="articlesAdd[isActiveAddNews].thumbUrl"
|
||||
fit="contain"
|
||||
/>
|
||||
<Icon
|
||||
v-else
|
||||
icon="ep:plus"
|
||||
class="avatar-uploader-icon"
|
||||
:class="isActiveAddNews === 0 ? 'avatar' : 'avatar1'"
|
||||
/>
|
||||
<div class="thumb-but">
|
||||
<el-upload
|
||||
:action="uploadUrl"
|
||||
:headers="headers"
|
||||
multiple
|
||||
:limit="1"
|
||||
:file-list="fileList"
|
||||
:data="uploadData"
|
||||
:before-upload="beforeThumbImageUpload"
|
||||
:on-success="handleUploadSuccess"
|
||||
>
|
||||
<template #trigger>
|
||||
<el-button size="small" type="primary">本地上传</el-button>
|
||||
</template>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="openMaterial"
|
||||
style="margin-left: 5px"
|
||||
>素材库选择</el-button
|
||||
>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip"
|
||||
>支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div
|
||||
>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
<el-dialog
|
||||
title="选择图片"
|
||||
v-model="dialogImageVisible"
|
||||
width="80%"
|
||||
append-to-body
|
||||
>
|
||||
<WxMaterialSelect
|
||||
ref="materialSelectRef"
|
||||
:objData="{ type: 'image', accountId: queryParams.accountId }"
|
||||
@select-material="selectMaterial"
|
||||
/>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<p>摘要:</p>
|
||||
<el-input
|
||||
:rows="8"
|
||||
type="textarea"
|
||||
v-model="articlesAdd[isActiveAddNews].digest"
|
||||
placeholder="请输入摘要"
|
||||
class="digest"
|
||||
maxlength="120"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!--富文本编辑器组件-->
|
||||
<el-row>
|
||||
<Editor
|
||||
v-model="articlesAdd[isActiveAddNews].content"
|
||||
:editor-config="editorConfig"
|
||||
/>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
<template #footer>
|
||||
<el-button @click="dialogNewsVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitForm">提 交</el-button>
|
||||
@ -242,16 +256,18 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="MpDraft">
|
||||
import WxEditor from '@/views/mp/components/wx-editor/WxEditor.vue'
|
||||
import { Editor } from '@/components/Editor'
|
||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
|
||||
import WxMpSelect from '@/views/mp/components/WxMpSelect.vue'
|
||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
||||
import { getAccessToken } from '@/utils/auth'
|
||||
import * as MpDraftApi from '@/api/mp/draft'
|
||||
import * as MpFreePublishApi from '@/api/mp/freePublish'
|
||||
import type { UploadFiles, UploadProps, UploadRawFile } from 'element-plus'
|
||||
// 可以用改本地数据模拟,避免API调用超限
|
||||
// import drafts from './mock'
|
||||
import { createEditorConfig } from './editor-config'
|
||||
// import drafts from './mock' // 可以用改本地数据模拟,避免API调用超限
|
||||
import { IEditorConfig } from '@wangeditor/editor'
|
||||
|
||||
const message = useMessage() // 消息
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
@ -285,14 +301,25 @@ const uploadData: UploadData = reactive({
|
||||
})
|
||||
|
||||
// ========== 草稿新建 or 修改 ==========
|
||||
interface Article {
|
||||
title: string
|
||||
thumbMediaId: string
|
||||
author: string
|
||||
digest: string
|
||||
showCoverPic: string
|
||||
content: string
|
||||
contentSourceUrl: string
|
||||
needOpenComment: string
|
||||
onlyFansCanComment: string
|
||||
thumbUrl: string
|
||||
}
|
||||
const dialogNewsVisible = ref(false)
|
||||
const addMaterialLoading = ref(false) // 添加草稿的 loading 标识
|
||||
const articlesAdd = ref<any[]>([])
|
||||
const articlesAdd = ref<Article[]>([])
|
||||
const isActiveAddNews = ref(0)
|
||||
const dialogImageVisible = ref(false)
|
||||
const operateMaterial = ref<'add' | 'edit'>('add')
|
||||
const articlesMediaId = ref('')
|
||||
const hackResetEditor = ref(false)
|
||||
|
||||
/** 侦听公众号变化 **/
|
||||
const onAccountChanged = (id?: number) => {
|
||||
@ -305,8 +332,11 @@ const onAccountChanged = (id?: number) => {
|
||||
const setAccountId = (id?: number) => {
|
||||
queryParams.accountId = id
|
||||
uploadData.accountId = id
|
||||
editorConfig.value = createEditorConfig(uploadUrl, queryParams.accountId)
|
||||
}
|
||||
|
||||
const editorConfig = ref<Partial<IEditorConfig>>({})
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
@ -329,7 +359,6 @@ const getList = async () => {
|
||||
// ======================== 新增/修改草稿 ========================
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
resetEditor()
|
||||
reset()
|
||||
// 打开表单,并设置初始化
|
||||
operateMaterial.value = 'add'
|
||||
@ -338,7 +367,6 @@ const handleAdd = () => {
|
||||
|
||||
/** 更新按钮操作 */
|
||||
const handleUpdate = (item: any) => {
|
||||
resetEditor()
|
||||
reset()
|
||||
articlesMediaId.value = item.mediaId
|
||||
articlesAdd.value = JSON.parse(JSON.stringify(item.content.newsItem))
|
||||
@ -370,7 +398,6 @@ const dialogNewsClose = async (onDone: () => {}) => {
|
||||
try {
|
||||
await message.confirm('修改内容可能还未保存,确定关闭吗?')
|
||||
reset()
|
||||
resetEditor()
|
||||
onDone()
|
||||
} catch {}
|
||||
}
|
||||
@ -381,14 +408,6 @@ const reset = () => {
|
||||
articlesAdd.value = [buildEmptyArticle()]
|
||||
}
|
||||
|
||||
// 表单 Editor 重置
|
||||
const resetEditor = () => {
|
||||
hackResetEditor.value = false // 销毁组件
|
||||
nextTick(() => {
|
||||
hackResetEditor.value = true // 重建组件
|
||||
})
|
||||
}
|
||||
|
||||
// 将图文向下移动
|
||||
const downNews = (index: number) => {
|
||||
let temp = articlesAdd.value[index]
|
||||
@ -398,8 +417,8 @@ const downNews = (index: number) => {
|
||||
}
|
||||
|
||||
// 将图文向上移动
|
||||
const upNews = (index) => {
|
||||
let temp = articlesAdd.value[index]
|
||||
const upNews = (index: number) => {
|
||||
const temp = articlesAdd.value[index]
|
||||
articlesAdd.value[index] = articlesAdd.value[index - 1]
|
||||
articlesAdd.value[index - 1] = temp
|
||||
isActiveAddNews.value = index - 1
|
||||
@ -407,7 +426,6 @@ const upNews = (index) => {
|
||||
|
||||
// 选中指定 index 的图文
|
||||
const activeNews = (index: number) => {
|
||||
resetEditor()
|
||||
isActiveAddNews.value = index
|
||||
}
|
||||
|
||||
@ -429,7 +447,7 @@ const plusNews = () => {
|
||||
}
|
||||
|
||||
// 创建空的 article
|
||||
const buildEmptyArticle = () => {
|
||||
const buildEmptyArticle = (): Article => {
|
||||
return {
|
||||
title: '',
|
||||
thumbMediaId: '',
|
||||
@ -455,8 +473,8 @@ const beforeThumbImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawF
|
||||
addMaterialLoading.value = false
|
||||
return false
|
||||
}
|
||||
const isLt = rawFile.size / 1024 / 1024 < 2
|
||||
if (!isLt) {
|
||||
|
||||
if (rawFile.size / 1024 / 1024 > 2) {
|
||||
message.error('上传图片大小不能超过 2M!')
|
||||
addMaterialLoading.value = false
|
||||
return false
|
||||
@ -465,10 +483,10 @@ const beforeThumbImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawF
|
||||
return true
|
||||
}
|
||||
|
||||
const handleUploadSuccess: UploadProps['onSuccess'] = (response: any) => {
|
||||
const handleUploadSuccess: UploadProps['onSuccess'] = (res: any) => {
|
||||
addMaterialLoading.value = false
|
||||
if (response.code !== 0) {
|
||||
message.error('上传出错:' + response.msg)
|
||||
if (res.code !== 0) {
|
||||
message.error('上传出错:' + res.msg)
|
||||
return false
|
||||
}
|
||||
|
||||
@ -476,8 +494,8 @@ const handleUploadSuccess: UploadProps['onSuccess'] = (response: any) => {
|
||||
fileList.value = []
|
||||
|
||||
// 设置草稿的封面字段
|
||||
articlesAdd.value[isActiveAddNews.value].thumbMediaId = response.data.mediaId
|
||||
articlesAdd.value[isActiveAddNews.value].thumbUrl = response.data.url
|
||||
articlesAdd.value[isActiveAddNews.value].thumbMediaId = res.data.mediaId
|
||||
articlesAdd.value[isActiveAddNews.value].thumbUrl = res.data.url
|
||||
}
|
||||
|
||||
// 选择 or 上传完素材,设置回草稿
|
||||
@ -502,7 +520,7 @@ const handlePublish = async (item: any) => {
|
||||
await message.confirm(content)
|
||||
await MpFreePublishApi.submitFreePublish(accountId, mediaId)
|
||||
message.notifySuccess('发布成功')
|
||||
getList()
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@ -514,7 +532,7 @@ const handleDelete = async (item: any) => {
|
||||
await message.confirm('此操作将永久删除该草稿, 是否继续?')
|
||||
await MpDraftApi.deleteDraft(accountId, mediaId)
|
||||
message.notifySuccess('删除成功')
|
||||
getList()
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
</script>
|
||||
@ -535,6 +553,13 @@ const handleDelete = async (item: any) => {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.el-row {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.el-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
@ -548,35 +573,33 @@ const handleDelete = async (item: any) => {
|
||||
}
|
||||
|
||||
/*新增图文*/
|
||||
.left {
|
||||
display: inline-block;
|
||||
width: 35%;
|
||||
vertical-align: top;
|
||||
margin-top: 200px;
|
||||
}
|
||||
// .left {
|
||||
// display: inline-block;
|
||||
// vertical-align: top;
|
||||
// margin-top: 200px;
|
||||
// }
|
||||
|
||||
.right {
|
||||
display: inline-block;
|
||||
width: 60%;
|
||||
margin-top: -40px;
|
||||
}
|
||||
// .right {
|
||||
// display: inline-block;
|
||||
// width: 100%;
|
||||
// }
|
||||
|
||||
.avatar-uploader {
|
||||
width: 20%;
|
||||
display: inline-block;
|
||||
}
|
||||
// .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 {
|
||||
// border-radius: 6px;
|
||||
// cursor: pointer;
|
||||
// position: relative;
|
||||
// overflow: hidden;
|
||||
// text-align: unset !important;
|
||||
// }
|
||||
|
||||
.avatar-uploader .el-upload:hover {
|
||||
border-color: #165dff;
|
||||
}
|
||||
// .avatar-uploader .el-upload:hover {
|
||||
// border-color: #165dff;
|
||||
// }
|
||||
|
||||
.avatar-uploader-icon {
|
||||
border: 1px solid #d9d9d9;
|
||||
@ -599,7 +622,7 @@ const handleDelete = async (item: any) => {
|
||||
}
|
||||
|
||||
.digest {
|
||||
width: 60%;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
@ -620,28 +643,16 @@ const handleDelete = async (item: any) => {
|
||||
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) {
|
||||
@ -707,10 +718,6 @@ p {
|
||||
background-color: #acadae;
|
||||
}
|
||||
|
||||
.input-tt {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.activeAddNews {
|
||||
border: 5px solid #2bb673;
|
||||
}
|
||||
@ -747,7 +754,7 @@ p {
|
||||
|
||||
.thumb-div {
|
||||
display: inline-block;
|
||||
width: 30%;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<WxMpSelect @change="onAccountChanged" />
|
||||
<WxAccountSelect @change="onAccountChanged" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
@ -51,8 +51,7 @@
|
||||
<script lang="ts" setup name="MpFreePublish">
|
||||
import * as FreePublishApi from '@/api/mp/freePublish'
|
||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||
import WxMpSelect from '@/views/mp/components/WxMpSelect.vue'
|
||||
|
||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
@ -102,7 +101,6 @@ const handleDelete = async (item: any) => {
|
||||
} catch {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ope-row {
|
||||
margin-top: 5px;
|
||||
|
@ -4,7 +4,7 @@
|
||||
<ContentWrap>
|
||||
<el-form class="-mb-15px" :inline="true" label-width="68px">
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<WxMpSelect @change="onAccountChanged" />
|
||||
<WxAccountSelect @change="onAccountChanged" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
@ -248,7 +248,7 @@
|
||||
<script lang="ts" setup name="MpMaterial">
|
||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
|
||||
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
|
||||
import WxMpSelect from '@/views/mp/components/WxMpSelect.vue'
|
||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
||||
import * as MpMaterialApi from '@/api/mp/material'
|
||||
import * as authUtil from '@/utils/auth'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
|
@ -4,7 +4,7 @@
|
||||
<ContentWrap>
|
||||
<el-form class="-mb-15px" ref="queryFormRef" :inline="true" label-width="68px">
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<WxMpSelect @change="onAccountChanged" />
|
||||
<WxAccountSelect @change="onAccountChanged" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
@ -192,7 +192,7 @@
|
||||
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 WxMpSelect from '@/views/mp/components/WxMpSelect.vue'
|
||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
||||
import * as MpMenuApi from '@/api/mp/menu'
|
||||
import { handleTree } from '@/utils/tree'
|
||||
import menuOptions from './menuOptions'
|
||||
|
145
src/views/mp/message/MessageTable.vue
Normal file
145
src/views/mp/message/MessageTable.vue
Normal file
@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-table v-loading="props.loading" :data="props.list">
|
||||
<el-table-column
|
||||
label="发送时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="消息类型" align="center" prop="type" width="80" />
|
||||
<el-table-column label="发送方" align="center" prop="sendFrom" width="80">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.sendFrom === 1" type="success">粉丝</el-tag>
|
||||
<el-tag v-else type="info">公众号</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用户标识" align="center" prop="openid" width="300" />
|
||||
<el-table-column label="内容" prop="content">
|
||||
<template #default="scope">
|
||||
<!-- 【事件】区域 -->
|
||||
<div v-if="scope.row.type === MsgType.Event && scope.row.event === 'subscribe'">
|
||||
<el-tag type="success">关注</el-tag>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'unsubscribe'">
|
||||
<el-tag type="danger">取消关注</el-tag>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'CLICK'">
|
||||
<el-tag>点击菜单</el-tag>
|
||||
【{{ scope.row.eventKey }}】
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'VIEW'">
|
||||
<el-tag>点击菜单链接</el-tag>
|
||||
【{{ scope.row.eventKey }}】
|
||||
</div>
|
||||
<div
|
||||
v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'scancode_waitmsg'"
|
||||
>
|
||||
<el-tag>扫码结果</el-tag>
|
||||
【{{ scope.row.eventKey }}】
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'scancode_push'">
|
||||
<el-tag>扫码结果</el-tag>
|
||||
【{{ scope.row.eventKey }}】
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'pic_sysphoto'">
|
||||
<el-tag>系统拍照发图</el-tag>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'pic_photo_or_album'"
|
||||
>
|
||||
<el-tag>拍照或者相册</el-tag>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'pic_weixin'">
|
||||
<el-tag>微信相册</el-tag>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'location_select'"
|
||||
>
|
||||
<el-tag>选择地理位置</el-tag>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Event">
|
||||
<el-tag type="danger">未知事件类型</el-tag>
|
||||
</div>
|
||||
<!-- 【消息】区域 -->
|
||||
<div v-else-if="scope.row.type === MsgType.Text">{{ scope.row.content }}</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Voice">
|
||||
<wx-voice-player :url="scope.row.mediaUrl" :content="scope.row.recognition" />
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Image">
|
||||
<a target="_blank" :href="scope.row.mediaUrl">
|
||||
<img :src="scope.row.mediaUrl" style="width: 100px" />
|
||||
</a>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Video || scope.row.type === 'shortvideo'">
|
||||
<wx-video-player :url="scope.row.mediaUrl" style="margin-top: 10px" />
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Link">
|
||||
<el-tag>链接</el-tag>
|
||||
:
|
||||
<a :href="scope.row.url" target="_blank">{{ scope.row.title }}</a>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Location">
|
||||
<WxLocation
|
||||
:label="scope.row.label"
|
||||
:location-y="scope.row.locationY"
|
||||
:location-x="scope.row.locationX"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Music">
|
||||
<WxMusic
|
||||
:title="scope.row.title"
|
||||
:description="scope.row.description"
|
||||
:thumb-media-url="scope.row.thumbMediaUrl"
|
||||
:music-url="scope.row.musicUrl"
|
||||
:hq-music-url="scope.row.hqMusicUrl"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.News">
|
||||
<WxNews :articles="scope.row.articles" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-tag type="danger">未知消息类型</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="emit('send', scope.row.userId)"
|
||||
v-hasPermi="['mp:message:send']"
|
||||
>
|
||||
消息
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页组件 -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
|
||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
|
||||
import WxLocation from '@/views/mp/components/wx-location/main.vue'
|
||||
import WxMusic from '@/views/mp/components/wx-music/main.vue'
|
||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { MsgType } from '@/views/mp/components/wx-msg/types'
|
||||
|
||||
const props = defineProps({
|
||||
list: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits<{ (e: 'send', v: number) }>()
|
||||
</script>
|
@ -9,7 +9,7 @@
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<WxMpSelect @change="onAccountChanged" />
|
||||
<WxAccountSelect @change="onAccountChanged" />
|
||||
</el-form-item>
|
||||
<el-form-item label="消息类型" prop="type">
|
||||
<el-select v-model="queryParams.type" placeholder="请选择消息类型" class="!w-240px">
|
||||
@ -58,124 +58,7 @@
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column
|
||||
label="发送时间"
|
||||
align="center"
|
||||
prop="createTime"
|
||||
width="180"
|
||||
:formatter="dateFormatter"
|
||||
/>
|
||||
<el-table-column label="消息类型" align="center" prop="type" width="80" />
|
||||
<el-table-column label="发送方" align="center" prop="sendFrom" width="80">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.sendFrom === 1" type="success">粉丝</el-tag>
|
||||
<el-tag v-else type="info">公众号</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用户标识" align="center" prop="openid" width="300" />
|
||||
<el-table-column label="内容" prop="content">
|
||||
<template #default="scope">
|
||||
<!-- 【事件】区域 -->
|
||||
<div v-if="scope.row.type === MsgType.Event && scope.row.event === 'subscribe'">
|
||||
<el-tag type="success">关注</el-tag>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'unsubscribe'">
|
||||
<el-tag type="danger">取消关注</el-tag>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'CLICK'">
|
||||
<el-tag>点击菜单</el-tag>
|
||||
【{{ scope.row.eventKey }}】
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'VIEW'">
|
||||
<el-tag>点击菜单链接</el-tag>
|
||||
【{{ scope.row.eventKey }}】
|
||||
</div>
|
||||
<div
|
||||
v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'scancode_waitmsg'"
|
||||
>
|
||||
<el-tag>扫码结果</el-tag>
|
||||
【{{ scope.row.eventKey }}】
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'scancode_push'">
|
||||
<el-tag>扫码结果</el-tag>
|
||||
【{{ scope.row.eventKey }}】
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'pic_sysphoto'">
|
||||
<el-tag>系统拍照发图</el-tag>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'pic_photo_or_album'"
|
||||
>
|
||||
<el-tag>拍照或者相册</el-tag>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'pic_weixin'">
|
||||
<el-tag>微信相册</el-tag>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="scope.row.type === MsgType.Event && scope.row.event === 'location_select'"
|
||||
>
|
||||
<el-tag>选择地理位置</el-tag>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Event">
|
||||
<el-tag type="danger">未知事件类型</el-tag>
|
||||
</div>
|
||||
<!-- 【消息】区域 -->
|
||||
<div v-else-if="scope.row.type === MsgType.Text">{{ scope.row.content }}</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Voice">
|
||||
<wx-voice-player :url="scope.row.mediaUrl" :content="scope.row.recognition" />
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Image">
|
||||
<a target="_blank" :href="scope.row.mediaUrl">
|
||||
<img :src="scope.row.mediaUrl" style="width: 100px" />
|
||||
</a>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Video || scope.row.type === 'shortvideo'">
|
||||
<wx-video-player :url="scope.row.mediaUrl" style="margin-top: 10px" />
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Link">
|
||||
<el-tag>链接</el-tag>
|
||||
:
|
||||
<a :href="scope.row.url" target="_blank">{{ scope.row.title }}</a>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Location">
|
||||
<WxLocation
|
||||
:label="scope.row.label"
|
||||
:location-y="scope.row.locationY"
|
||||
:location-x="scope.row.locationX"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.Music">
|
||||
<WxMusic
|
||||
:title="scope.row.title"
|
||||
:description="scope.row.description"
|
||||
:thumb-media-url="scope.row.thumbMediaUrl"
|
||||
:music-url="scope.row.musicUrl"
|
||||
:hq-music-url="scope.row.hqMusicUrl"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="scope.row.type === MsgType.News">
|
||||
<WxNews :articles="scope.row.articles" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-tag type="danger">未知消息类型</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="handleSend(scope.row)"
|
||||
v-hasPermi="['mp:message:send']"
|
||||
>
|
||||
消息
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页组件 -->
|
||||
<MessageTable :list="list" :loading="loading" @send="handleSend" />
|
||||
<Pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
@ -183,37 +66,31 @@
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 发送消息的弹窗 -->
|
||||
<el-dialog
|
||||
title="粉丝消息列表"
|
||||
v-model="showMessageBox"
|
||||
@click="showMessageBox = true"
|
||||
width="50%"
|
||||
>
|
||||
<template #footer>
|
||||
<WxMsg :user-id="userId" v-if="showMessageBox" />
|
||||
</template>
|
||||
</el-dialog>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 发送消息的弹窗 -->
|
||||
<el-dialog
|
||||
title="粉丝消息列表"
|
||||
v-model="messageBox.show"
|
||||
@click="messageBox.show = true"
|
||||
width="50%"
|
||||
destroy-on-close
|
||||
>
|
||||
<WxMsg :user-id="messageBox.userId" />
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts" name="MpMessage">
|
||||
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
|
||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
|
||||
import WxMsg from '@/views/mp/components/wx-msg/main.vue'
|
||||
import WxLocation from '@/views/mp/components/wx-location/main.vue'
|
||||
import WxMusic from '@/views/mp/components/wx-music/main.vue'
|
||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||
import WxMpSelect from '@/views/mp/components/WxMpSelect.vue'
|
||||
import * as MpMessageApi from '@/api/mp/message'
|
||||
import WxMsg from '@/views/mp/components/wx-msg/main.vue'
|
||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
||||
import MessageTable from './MessageTable.vue'
|
||||
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { MsgType } from '@/views/mp/components/wx-msg/types'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref<any[]>([]) // 列表的数据
|
||||
const loading = ref(false)
|
||||
const total = ref(0) // 数据的总页数
|
||||
const list = ref<any[]>([]) // 当前页的列表数据
|
||||
|
||||
// 搜索参数
|
||||
interface QueryParams {
|
||||
@ -233,8 +110,12 @@ const queryParams: QueryParams = reactive({
|
||||
createTime: []
|
||||
})
|
||||
const queryFormRef = ref<FormInstance | null>(null) // 搜索的表单
|
||||
const showMessageBox = ref(false) // 是否显示弹出层
|
||||
const userId = ref(0) // 操作的用户编号
|
||||
|
||||
// 消息对话框
|
||||
const messageBox = reactive({
|
||||
show: false,
|
||||
userId: 0
|
||||
})
|
||||
|
||||
/** 侦听accountId */
|
||||
const onAccountChanged = (id?: number) => {
|
||||
@ -243,6 +124,11 @@ const onAccountChanged = (id?: number) => {
|
||||
}
|
||||
|
||||
/** 查询列表 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
const getList = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
@ -254,14 +140,9 @@ const getList = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = async () => {
|
||||
// 暂存 accountId,并在 reset 后恢复
|
||||
const accountId = queryParams.accountId
|
||||
queryFormRef.value?.resetFields()
|
||||
queryParams.accountId = accountId
|
||||
@ -269,8 +150,8 @@ const resetQuery = async () => {
|
||||
}
|
||||
|
||||
/** 打开消息发送窗口 */
|
||||
const handleSend = async (row: any) => {
|
||||
userId.value = row.userId
|
||||
showMessageBox.value = true
|
||||
const handleSend = async (userId: number) => {
|
||||
messageBox.userId = userId
|
||||
messageBox.show = true
|
||||
}
|
||||
</script>
|
||||
|
@ -11,7 +11,7 @@
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<WxMpSelect @change="onAccountChanged" />
|
||||
<WxAccountSelect @change="onAccountChanged" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['mp:tag:create']">
|
||||
@ -74,7 +74,7 @@
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as MpTagApi from '@/api/mp/tag'
|
||||
import TagForm from './TagForm.vue'
|
||||
import WxMpSelect from '@/views/mp/components/WxMpSelect.vue'
|
||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="公众号" prop="accountId">
|
||||
<WxMpSelect @change="onAccountChanged" />
|
||||
<WxAccountSelect @change="onAccountChanged" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户标识" prop="openid">
|
||||
<el-input
|
||||
@ -97,7 +97,7 @@
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as MpUserApi from '@/api/mp/user'
|
||||
import * as MpTagApi from '@/api/mp/tag'
|
||||
import WxMpSelect from '@/views/mp/components/WxMpSelect.vue'
|
||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import UserForm from './UserForm.vue'
|
||||
|
||||
|
@ -59,7 +59,8 @@ const formData = ref({
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '字典名称不能为空', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '字典类型不能为空', trigger: 'blur' }]
|
||||
type: [{ required: true, message: '字典类型不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
|
@ -83,7 +83,8 @@ const formData = ref({
|
||||
const formRules = reactive({
|
||||
label: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],
|
||||
value: [{ required: true, message: '数据键值不能为空', trigger: 'blur' }],
|
||||
sort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }]
|
||||
sort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
|
||||
|
@ -105,7 +105,9 @@ const formRules = reactive({
|
||||
status: [{ required: true, message: '租户状态不能为空', trigger: 'blur' }],
|
||||
accountCount: [{ required: true, message: '账号额度不能为空', trigger: 'blur' }],
|
||||
expireTime: [{ required: true, message: '过期时间不能为空', trigger: 'blur' }],
|
||||
domain: [{ required: true, message: '绑定域名不能为空', trigger: 'blur' }]
|
||||
domain: [{ required: true, message: '绑定域名不能为空', trigger: 'blur' }],
|
||||
username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const packageList = ref([]) // 租户套餐
|
||||
|
Loading…
Reference in New Issue
Block a user