commit
60cc4357cb
12
.vscode/settings.json
vendored
12
.vscode/settings.json
vendored
@ -40,5 +40,15 @@
|
|||||||
"i18n-ally.displayLanguage": "zh-CN",
|
"i18n-ally.displayLanguage": "zh-CN",
|
||||||
"i18n-ally.enabledFrameworks": ["vue", "react"],
|
"i18n-ally.enabledFrameworks": ["vue", "react"],
|
||||||
"god.tsconfig": "./tsconfig.json",
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,8 @@ const include = [
|
|||||||
'element-plus/es/components/dropdown-item/style/css',
|
'element-plus/es/components/dropdown-item/style/css',
|
||||||
'element-plus/es/components/badge/style/css',
|
'element-plus/es/components/badge/style/css',
|
||||||
'element-plus/es/components/breadcrumb/style/css',
|
'element-plus/es/components/breadcrumb/style/css',
|
||||||
'element-plus/es/components/breadcrumb-item/style/css'
|
'element-plus/es/components/breadcrumb-item/style/css',
|
||||||
|
'element-plus/es/components/image/style/css'
|
||||||
]
|
]
|
||||||
|
|
||||||
const exclude = ['@iconify/json']
|
const exclude = ['@iconify/json']
|
||||||
|
@ -32,7 +32,6 @@
|
|||||||
"@form-create/element-ui": "^3.1.17",
|
"@form-create/element-ui": "^3.1.17",
|
||||||
"@iconify/iconify": "^3.1.0",
|
"@iconify/iconify": "^3.1.0",
|
||||||
"@videojs-player/vue": "^1.0.0",
|
"@videojs-player/vue": "^1.0.0",
|
||||||
"@vueup/vue-quill": "^1.1.1",
|
|
||||||
"@vueuse/core": "^9.13.0",
|
"@vueuse/core": "^9.13.0",
|
||||||
"@wangeditor/editor": "^5.1.23",
|
"@wangeditor/editor": "^5.1.23",
|
||||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||||
|
@ -20,7 +20,7 @@ const props = defineProps({
|
|||||||
editorId: propTypes.string.def('wangeEditor-1'),
|
editorId: propTypes.string.def('wangeEditor-1'),
|
||||||
height: propTypes.oneOfType([Number, String]).def('500px'),
|
height: propTypes.oneOfType([Number, String]).def('500px'),
|
||||||
editorConfig: {
|
editorConfig: {
|
||||||
type: Object as PropType<IEditorConfig>,
|
type: Object as PropType<Partial<IEditorConfig>>,
|
||||||
default: () => undefined
|
default: () => undefined
|
||||||
},
|
},
|
||||||
readonly: propTypes.bool.def(false),
|
readonly: propTypes.bool.def(false),
|
||||||
@ -147,6 +147,7 @@ const editorConfig = computed((): IEditorConfig => {
|
|||||||
props.editorConfig || {}
|
props.editorConfig || {}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const editorStyle = computed(() => {
|
const editorStyle = computed(() => {
|
||||||
return {
|
return {
|
||||||
height: isNumber(props.height) ? `${props.height}px` : props.height
|
height: isNumber(props.height) ? `${props.height}px` : props.height
|
||||||
|
@ -95,7 +95,7 @@ watch(
|
|||||||
return props.modelValue
|
return props.modelValue
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
if (props.modelValue && props.modelValue.contains(':')) {
|
if (props.modelValue && props.modelValue.indexOf(':') >= 0) {
|
||||||
currentActiveType.value = props.modelValue.substring(0, props.modelValue.indexOf(':') + 1)
|
currentActiveType.value = props.modelValue.substring(0, props.modelValue.indexOf(':') + 1)
|
||||||
icon.value = props.modelValue.substring(props.modelValue.indexOf(':') + 1)
|
icon.value = props.modelValue.substring(props.modelValue.indexOf(':') + 1)
|
||||||
}
|
}
|
||||||
|
@ -111,7 +111,8 @@
|
|||||||
>
|
>
|
||||||
<el-divider direction="vertical" />
|
<el-divider direction="vertical" />
|
||||||
<el-button
|
<el-button
|
||||||
type="text"
|
type="primary"
|
||||||
|
link
|
||||||
style="color: #ff4d4f"
|
style="color: #ff4d4f"
|
||||||
@click="removeFieldOptionItem(scope, scope.$index, 'enum')"
|
@click="removeFieldOptionItem(scope, scope.$index, 'enum')"
|
||||||
>移除</el-button
|
>移除</el-button
|
||||||
@ -143,7 +144,8 @@
|
|||||||
>
|
>
|
||||||
<el-divider direction="vertical" />
|
<el-divider direction="vertical" />
|
||||||
<el-button
|
<el-button
|
||||||
type="text"
|
type="primary"
|
||||||
|
link
|
||||||
style="color: #ff4d4f"
|
style="color: #ff4d4f"
|
||||||
@click="removeFieldOptionItem(scope, scope.$index, 'constraint')"
|
@click="removeFieldOptionItem(scope, scope.$index, 'constraint')"
|
||||||
>移除</el-button
|
>移除</el-button
|
||||||
@ -174,7 +176,8 @@
|
|||||||
>
|
>
|
||||||
<el-divider direction="vertical" />
|
<el-divider direction="vertical" />
|
||||||
<el-button
|
<el-button
|
||||||
type="text"
|
type="primary"
|
||||||
|
link
|
||||||
style="color: #ff4d4f"
|
style="color: #ff4d4f"
|
||||||
@click="removeFieldOptionItem(scope, scope.$index, 'property')"
|
@click="removeFieldOptionItem(scope, scope.$index, 'property')"
|
||||||
>移除</el-button
|
>移除</el-button
|
||||||
|
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'
|
|
||||||
}
|
|
@ -18,7 +18,8 @@
|
|||||||
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
||||||
<el-form-item prop="tenantName" v-if="loginData.tenantEnable === 'true'">
|
<el-form-item prop="tenantName" v-if="loginData.tenantEnable === 'true'">
|
||||||
<el-input
|
<el-input
|
||||||
type="text"
|
type="primary"
|
||||||
|
link
|
||||||
v-model="loginData.loginForm.tenantName"
|
v-model="loginData.loginForm.tenantName"
|
||||||
:placeholder="t('login.tenantNamePlaceholder')"
|
:placeholder="t('login.tenantNamePlaceholder')"
|
||||||
:prefix-icon="iconHouse"
|
:prefix-icon="iconHouse"
|
||||||
|
@ -19,7 +19,8 @@
|
|||||||
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
<el-col :span="24" style="padding-left: 10px; padding-right: 10px">
|
||||||
<el-form-item prop="tenantName" v-if="loginData.tenantEnable === 'true'">
|
<el-form-item prop="tenantName" v-if="loginData.tenantEnable === 'true'">
|
||||||
<el-input
|
<el-input
|
||||||
type="text"
|
type="primary"
|
||||||
|
link
|
||||||
v-model="loginData.loginForm.tenantName"
|
v-model="loginData.loginForm.tenantName"
|
||||||
:placeholder="t('login.tenantNamePlaceholder')"
|
:placeholder="t('login.tenantNamePlaceholder')"
|
||||||
:prefix-icon="iconHouse"
|
:prefix-icon="iconHouse"
|
||||||
|
@ -18,7 +18,8 @@
|
|||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="scope.row.formType === 10"
|
v-if="scope.row.formType === 10"
|
||||||
type="text"
|
type="primary"
|
||||||
|
link
|
||||||
@click="handleFormDetail(scope.row)"
|
@click="handleFormDetail(scope.row)"
|
||||||
>
|
>
|
||||||
<span>{{ scope.row.formName }}</span>
|
<span>{{ scope.row.formName }}</span>
|
||||||
|
@ -79,14 +79,16 @@
|
|||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="scope.row.formType === 10"
|
v-if="scope.row.formType === 10"
|
||||||
type="text"
|
type="primary"
|
||||||
|
link
|
||||||
@click="handleFormDetail(scope.row)"
|
@click="handleFormDetail(scope.row)"
|
||||||
>
|
>
|
||||||
<span>{{ scope.row.formName }}</span>
|
<span>{{ scope.row.formName }}</span>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-else-if="scope.row.formType === 20"
|
v-else-if="scope.row.formType === 20"
|
||||||
type="text"
|
type="primary"
|
||||||
|
link
|
||||||
@click="handleFormDetail(scope.row)"
|
@click="handleFormDetail(scope.row)"
|
||||||
>
|
>
|
||||||
<span>{{ scope.row.formCustomCreatePath }}</span>
|
<span>{{ scope.row.formCustomCreatePath }}</span>
|
||||||
|
@ -3,13 +3,16 @@
|
|||||||
|
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- TODO @芋艿:调整成 el-form 和 WxAccountSelect -->
|
<el-form class="-mb-15px" :model="queryParams" :inline="true" label-width="68px">
|
||||||
<WxAccountSelect @change="accountChanged" />
|
<el-form-item label="公众号" prop="accountId">
|
||||||
|
<WxAccountSelect @change="onAccountChanged" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- tab 切换 -->
|
<!-- tab 切换 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-tabs v-model="type" @tab-change="handleTabChange">
|
<el-tabs v-model="msgType" @tab-change="handleTabChange">
|
||||||
<!-- 操作工具栏 -->
|
<!-- 操作工具栏 -->
|
||||||
<el-row :gutter="10" class="mb8">
|
<el-row :gutter="10" class="mb8">
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
@ -18,26 +21,26 @@
|
|||||||
plain
|
plain
|
||||||
@click="handleAdd"
|
@click="handleAdd"
|
||||||
v-hasPermi="['mp:auto-reply:create']"
|
v-hasPermi="['mp:auto-reply:create']"
|
||||||
v-if="type !== '1' || list.length <= 0"
|
v-if="msgType !== MsgType.Follow || list.length <= 0"
|
||||||
>
|
>
|
||||||
<Icon icon="ep:plus" />新增
|
<Icon icon="ep:plus" />新增
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<!-- tab 项 -->
|
<!-- tab 项 -->
|
||||||
<el-tab-pane name="1">
|
<el-tab-pane :name="MsgType.Follow">
|
||||||
<template #label>
|
<template #label>
|
||||||
<span><Icon icon="ep:star-off" /> 关注时回复</span>
|
<span><Icon icon="ep:star" /> 关注时回复</span>
|
||||||
</template>
|
</template>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane name="2">
|
<el-tab-pane :name="MsgType.Message">
|
||||||
<template #label>
|
<template #label>
|
||||||
<span><Icon icon="ep:chat-line-round" /> 消息回复</span>
|
<span><Icon icon="ep:chat-line-round" /> 消息回复</span>
|
||||||
</template>
|
</template>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane name="3">
|
<el-tab-pane :name="MsgType.Keyword">
|
||||||
<template #label>
|
<template #label>
|
||||||
<span><Icon icon="ep:news" /> 关键词回复</span>
|
<span><Icon icon="fa:newspaper-o" /> 关键词回复</span>
|
||||||
</template>
|
</template>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
@ -47,10 +50,20 @@
|
|||||||
label="请求消息类型"
|
label="请求消息类型"
|
||||||
align="center"
|
align="center"
|
||||||
prop="requestMessageType"
|
prop="requestMessageType"
|
||||||
v-if="type === '2'"
|
v-if="msgType === MsgType.Message"
|
||||||
/>
|
/>
|
||||||
<el-table-column label="关键词" align="center" prop="requestKeyword" v-if="type === '3'" />
|
<el-table-column
|
||||||
<el-table-column label="匹配类型" align="center" prop="requestMatch" v-if="type === '3'">
|
label="关键词"
|
||||||
|
align="center"
|
||||||
|
prop="requestKeyword"
|
||||||
|
v-if="msgType === MsgType.Keyword"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="匹配类型"
|
||||||
|
align="center"
|
||||||
|
prop="requestMatch"
|
||||||
|
v-if="msgType === MsgType.Keyword"
|
||||||
|
>
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<dict-tag :type="DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH" :value="scope.row.requestMatch" />
|
<dict-tag :type="DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH" :value="scope.row.requestMatch" />
|
||||||
</template>
|
</template>
|
||||||
@ -64,7 +77,7 @@
|
|||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div v-if="scope.row.responseMessageType === 'text'">{{ scope.row.responseContent }}</div>
|
<div v-if="scope.row.responseMessageType === 'text'">{{ scope.row.responseContent }}</div>
|
||||||
<div v-else-if="scope.row.responseMessageType === 'voice'">
|
<div v-else-if="scope.row.responseMessageType === 'voice'">
|
||||||
<WxVoicePlayer :url="scope.row.responseMediaUrl" />
|
<WxVoicePlayer v-if="scope.row.responseMediaUrl" :url="scope.row.responseMediaUrl" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="scope.row.responseMessageType === 'image'">
|
<div v-else-if="scope.row.responseMessageType === 'image'">
|
||||||
<a target="_blank" :href="scope.row.responseMediaUrl">
|
<a target="_blank" :href="scope.row.responseMediaUrl">
|
||||||
@ -77,7 +90,11 @@
|
|||||||
scope.row.responseMessageType === 'shortvideo'
|
scope.row.responseMessageType === 'shortvideo'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<WxVideoPlayer :url="scope.row.responseMediaUrl" style="margin-top: 10px" />
|
<WxVideoPlayer
|
||||||
|
v-if="scope.row.responseMediaUrl"
|
||||||
|
:url="scope.row.responseMediaUrl"
|
||||||
|
style="margin-top: 10px"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="scope.row.responseMessageType === 'news'">
|
<div v-else-if="scope.row.responseMessageType === 'news'">
|
||||||
<WxNews :articles="scope.row.responseArticles" />
|
<WxNews :articles="scope.row.responseArticles" />
|
||||||
@ -123,21 +140,21 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<!-- 添加或修改自动回复的对话框 -->
|
<!-- 添加或修改自动回复的对话框 -->
|
||||||
<el-dialog :title="title" v-model="open" width="800px" append-to-body>
|
<el-dialog :title="title" v-model="showReplyFormDialog" width="800px" destroy-on-close>
|
||||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
|
<el-form ref="formRef" :model="replyForm" :rules="rules" label-width="80px">
|
||||||
<el-form-item label="消息类型" prop="requestMessageType" v-if="type === '2'">
|
<el-form-item label="消息类型" prop="requestMessageType" v-if="msgType === MsgType.Message">
|
||||||
<el-select v-model="form.requestMessageType" placeholder="请选择">
|
<el-select v-model="replyForm.requestMessageType" placeholder="请选择">
|
||||||
<template v-for="dict in getDictOptions(DICT_TYPE.MP_MESSAGE_TYPE)" :key="dict.value">
|
<template v-for="dict in getDictOptions(DICT_TYPE.MP_MESSAGE_TYPE)" :key="dict.value">
|
||||||
<el-option
|
<el-option
|
||||||
v-if="requestMessageTypes.includes(dict.value)"
|
v-if="RequestMessageTypes.includes(dict.value)"
|
||||||
:label="dict.label"
|
:label="dict.label"
|
||||||
:value="dict.value"
|
:value="dict.value"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="匹配类型" prop="requestMatch" v-if="type === '3'">
|
<el-form-item label="匹配类型" prop="requestMatch" v-if="msgType === MsgType.Keyword">
|
||||||
<el-select v-model="form.requestMatch" placeholder="请选择匹配类型" clearable>
|
<el-select v-model="replyForm.requestMatch" placeholder="请选择匹配类型" clearable>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in getDictOptions(DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH)"
|
v-for="dict in getDictOptions(DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
@ -146,11 +163,11 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="关键词" prop="requestKeyword" v-if="type === '3'">
|
<el-form-item label="关键词" prop="requestKeyword" v-if="msgType === MsgType.Keyword">
|
||||||
<el-input v-model="form.requestKeyword" placeholder="请输入内容" clearable />
|
<el-input v-model="replyForm.requestKeyword" placeholder="请输入内容" clearable />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="回复消息">
|
<el-form-item label="回复消息">
|
||||||
<WxReplySelect :objData="objData" v-if="hackResetWxReplySelect" />
|
<WxReplySelect :objData="objData" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@ -160,7 +177,7 @@
|
|||||||
</el-dialog>
|
</el-dialog>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
<script setup name="MpAutoReply">
|
<script setup lang="ts" name="MpAutoReply">
|
||||||
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
|
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
|
||||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
|
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
|
||||||
import WxMusic from '@/views/mp/components/wx-music/main.vue'
|
import WxMusic from '@/views/mp/components/wx-music/main.vue'
|
||||||
@ -168,30 +185,39 @@ import WxNews from '@/views/mp/components/wx-news/main.vue'
|
|||||||
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
|
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
|
||||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
||||||
import * as MpAutoReplyApi from '@/api/mp/autoReply'
|
import * as MpAutoReplyApi from '@/api/mp/autoReply'
|
||||||
|
|
||||||
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getDictOptions } from '@/utils/dict'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import { ContentWrap } from '@/components/ContentWrap'
|
import { ContentWrap } from '@/components/ContentWrap'
|
||||||
|
import type { TabPaneName } from 'element-plus'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
// const queryFormRef = ref()
|
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
|
|
||||||
// tab 类型(1、关注时回复;2、消息回复;3、关键词回复)
|
// 消息类型(Follow: 关注时回复;Message: 消息回复;Keyword: 关键词回复)
|
||||||
const type = ref('3')
|
// 作为tab.name,enum的数字不能随意修改,与api参数相关
|
||||||
|
enum MsgType {
|
||||||
|
Follow = 1,
|
||||||
|
Message = 2,
|
||||||
|
Keyword = 3
|
||||||
|
}
|
||||||
|
const msgType = ref<MsgType>(MsgType.Keyword)
|
||||||
// 允许选择的请求消息类型
|
// 允许选择的请求消息类型
|
||||||
const requestMessageTypes = ['text', 'image', 'voice', 'video', 'shortvideo', 'location', 'link']
|
const RequestMessageTypes = ['text', 'image', 'voice', 'video', 'shortvideo', 'location', 'link']
|
||||||
// 遮罩层
|
// 遮罩层
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
// 显示搜索条件
|
|
||||||
// const showSearch = ref(true)
|
|
||||||
// 总条数
|
// 总条数
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
// 自动回复列表
|
// 自动回复列表
|
||||||
const list = ref([])
|
const list = ref<any[]>([])
|
||||||
|
|
||||||
// 查询参数
|
// 查询参数
|
||||||
const queryParams = reactive({
|
interface QueryParams {
|
||||||
|
pageNo: number
|
||||||
|
pageSize: number
|
||||||
|
accountId?: number
|
||||||
|
}
|
||||||
|
const queryParams: QueryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
accountId: undefined
|
accountId: undefined
|
||||||
@ -200,12 +226,50 @@ const queryParams = reactive({
|
|||||||
// 弹出层标题
|
// 弹出层标题
|
||||||
const title = ref('')
|
const title = ref('')
|
||||||
// 是否显示弹出层
|
// 是否显示弹出层
|
||||||
const open = ref(false)
|
const showReplyFormDialog = ref(false)
|
||||||
// 表单参数
|
// 表单参数
|
||||||
const form = ref({})
|
type ReplyType = 'text' | 'image' | 'voice' | 'video' | 'shortvideo' | 'location' | 'link'
|
||||||
|
interface ReplyForm {
|
||||||
|
// relation:
|
||||||
|
id?: number
|
||||||
|
accountId?: number
|
||||||
|
type?: MsgType
|
||||||
|
// request:
|
||||||
|
requestMessageType?: ReplyType
|
||||||
|
requestMatch?: number
|
||||||
|
requestKeyword?: string
|
||||||
|
// response:
|
||||||
|
responseMessageType?: ReplyType
|
||||||
|
responseContent?: string
|
||||||
|
responseMediaId?: number
|
||||||
|
responseMediaUrl?: string
|
||||||
|
responseTitle?: string
|
||||||
|
responseDescription?: number
|
||||||
|
responseThumbMediaId?: string
|
||||||
|
responseThumbMediaUrl?: string
|
||||||
|
responseArticles?: any[]
|
||||||
|
responseMusicUrl?: string
|
||||||
|
responseHqMusicUrl?: string
|
||||||
|
}
|
||||||
|
interface ObjData {
|
||||||
|
type: ReplyType
|
||||||
|
accountId?: number
|
||||||
|
content?: string
|
||||||
|
mediaId?: number
|
||||||
|
url?: string
|
||||||
|
title?: string
|
||||||
|
description?: string
|
||||||
|
thumbMediaId?: number
|
||||||
|
thumbMediaUrl?: string
|
||||||
|
articles?: any[]
|
||||||
|
musicUrl?: string
|
||||||
|
hqMusicUrl?: string
|
||||||
|
}
|
||||||
|
const replyForm = ref<ReplyForm>({})
|
||||||
// 回复消息
|
// 回复消息
|
||||||
const objData = ref({
|
const objData = ref<ObjData>({
|
||||||
type: 'text'
|
type: 'text',
|
||||||
|
accountId: undefined
|
||||||
})
|
})
|
||||||
// 表单校验
|
// 表单校验
|
||||||
const rules = {
|
const rules = {
|
||||||
@ -213,11 +277,8 @@ const rules = {
|
|||||||
requestMatch: [{ required: true, message: '请求的关键字的匹配不能为空', trigger: 'blur' }]
|
requestMatch: [{ required: true, message: '请求的关键字的匹配不能为空', trigger: 'blur' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置 WxReplySelect 组件,解决无法清除的问题
|
const onAccountChanged = (id?: number) => {
|
||||||
const hackResetWxReplySelect = ref(false)
|
queryParams.accountId = id
|
||||||
|
|
||||||
const accountChanged = (accountId) => {
|
|
||||||
queryParams.accountId = accountId
|
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,7 +288,7 @@ const getList = async () => {
|
|||||||
try {
|
try {
|
||||||
const data = await MpAutoReplyApi.getAutoReplyPage({
|
const data = await MpAutoReplyApi.getAutoReplyPage({
|
||||||
...queryParams,
|
...queryParams,
|
||||||
type: type.value
|
type: msgType.value
|
||||||
})
|
})
|
||||||
list.value = data.list
|
list.value = data.list
|
||||||
total.value = data.total
|
total.value = data.total
|
||||||
@ -242,104 +303,95 @@ const handleQuery = () => {
|
|||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTabChange = (tabName) => {
|
const handleTabChange = (tabName: TabPaneName) => {
|
||||||
type.value = tabName
|
msgType.value = tabName as MsgType
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 新增按钮操作 */
|
/** 新增按钮操作 */
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
reset()
|
reset()
|
||||||
resetEditor()
|
|
||||||
// 打开表单,并设置初始化
|
// 打开表单,并设置初始化
|
||||||
open.value = true
|
|
||||||
title.value = '新增自动回复'
|
|
||||||
objData.value = {
|
objData.value = {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
accountId: queryParams.accountId
|
accountId: queryParams.accountId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
title.value = '新增自动回复'
|
||||||
|
showReplyFormDialog.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 修改按钮操作 */
|
/** 修改按钮操作 */
|
||||||
const handleUpdate = (row) => {
|
const handleUpdate = async (row: any) => {
|
||||||
reset()
|
reset()
|
||||||
resetEditor()
|
|
||||||
console.log(row)
|
|
||||||
|
|
||||||
MpAutoReplyApi.getAutoReply(row.id).then((data) => {
|
const data = await MpAutoReplyApi.getAutoReply(row.id)
|
||||||
// 设置属性
|
// 设置属性
|
||||||
form.value = { ...data }
|
replyForm.value = { ...data }
|
||||||
delete form.value['responseMessageType']
|
delete replyForm.value['responseMessageType']
|
||||||
delete form.value['responseContent']
|
delete replyForm.value['responseContent']
|
||||||
delete form.value['responseMediaId']
|
delete replyForm.value['responseMediaId']
|
||||||
delete form.value['responseMediaUrl']
|
delete replyForm.value['responseMediaUrl']
|
||||||
delete form.value['responseDescription']
|
delete replyForm.value['responseDescription']
|
||||||
delete form.value['responseArticles']
|
delete replyForm.value['responseArticles']
|
||||||
objData.value = {
|
objData.value = {
|
||||||
type: data.responseMessageType,
|
type: data.responseMessageType,
|
||||||
accountId: queryParams.accountId,
|
accountId: queryParams.accountId,
|
||||||
content: data.responseContent,
|
content: data.responseContent,
|
||||||
mediaId: data.responseMediaId,
|
mediaId: data.responseMediaId,
|
||||||
url: data.responseMediaUrl,
|
url: data.responseMediaUrl,
|
||||||
title: data.responseTitle,
|
title: data.responseTitle,
|
||||||
description: data.responseDescription,
|
description: data.responseDescription,
|
||||||
thumbMediaId: data.responseThumbMediaId,
|
thumbMediaId: data.responseThumbMediaId,
|
||||||
thumbMediaUrl: data.responseThumbMediaUrl,
|
thumbMediaUrl: data.responseThumbMediaUrl,
|
||||||
articles: data.responseArticles,
|
articles: data.responseArticles,
|
||||||
musicUrl: data.responseMusicUrl,
|
musicUrl: data.responseMusicUrl,
|
||||||
hqMusicUrl: data.responseHqMusicUrl
|
hqMusicUrl: data.responseHqMusicUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打开表单
|
// 打开表单
|
||||||
open.value = true
|
title.value = '修改自动回复'
|
||||||
title.value = '修改自动回复'
|
showReplyFormDialog.value = true
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = async () => {
|
||||||
formRef.value?.validate((valid) => {
|
const valid = await formRef.value?.validate()
|
||||||
if (!valid) {
|
if (!valid) return
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理回复消息
|
// 处理回复消息
|
||||||
const form = { ...form.value }
|
const submitForm: any = { ...replyForm.value }
|
||||||
form.responseMessageType = objData.value.type
|
submitForm.responseMessageType = objData.value.type
|
||||||
form.responseContent = objData.value.content
|
submitForm.responseContent = objData.value.content
|
||||||
form.responseMediaId = objData.value.mediaId
|
submitForm.responseMediaId = objData.value.mediaId
|
||||||
form.responseMediaUrl = objData.value.url
|
submitForm.responseMediaUrl = objData.value.url
|
||||||
form.responseTitle = objData.value.title
|
submitForm.responseTitle = objData.value.title
|
||||||
form.responseDescription = objData.value.description
|
submitForm.responseDescription = objData.value.description
|
||||||
form.responseThumbMediaId = objData.value.thumbMediaId
|
submitForm.responseThumbMediaId = objData.value.thumbMediaId
|
||||||
form.responseThumbMediaUrl = objData.value.thumbMediaUrl
|
submitForm.responseThumbMediaUrl = objData.value.thumbMediaUrl
|
||||||
form.responseArticles = objData.value.articles
|
submitForm.responseArticles = objData.value.articles
|
||||||
form.responseMusicUrl = objData.value.musicUrl
|
submitForm.responseMusicUrl = objData.value.musicUrl
|
||||||
form.responseHqMusicUrl = objData.value.hqMusicUrl
|
submitForm.responseHqMusicUrl = objData.value.hqMusicUrl
|
||||||
|
|
||||||
if (form.value.id !== undefined) {
|
if (replyForm.value.id !== undefined) {
|
||||||
MpAutoReplyApi.updateAutoReply(form).then(() => {
|
await MpAutoReplyApi.updateAutoReply(submitForm)
|
||||||
message.success('修改成功')
|
message.success('修改成功')
|
||||||
open.value = false
|
} else {
|
||||||
getList()
|
await MpAutoReplyApi.createAutoReply(submitForm)
|
||||||
})
|
message.success('新增成功')
|
||||||
} else {
|
}
|
||||||
MpAutoReplyApi.createAutoReply(form).then(() => {
|
|
||||||
message.success('新增成功')
|
showReplyFormDialog.value = false
|
||||||
open.value = false
|
await getList()
|
||||||
getList()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表单重置
|
// 表单重置
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
form.value = {
|
replyForm.value = {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
accountId: queryParams.accountId,
|
accountId: queryParams.accountId,
|
||||||
type: type.value,
|
type: msgType.value,
|
||||||
requestKeyword: undefined,
|
requestKeyword: undefined,
|
||||||
requestMatch: type.value === '3' ? 1 : undefined,
|
requestMatch: msgType.value === MsgType.Keyword ? 1 : undefined,
|
||||||
requestMessageType: undefined
|
requestMessageType: undefined
|
||||||
}
|
}
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
@ -347,18 +399,10 @@ const reset = () => {
|
|||||||
|
|
||||||
// 取消按钮
|
// 取消按钮
|
||||||
const cancel = () => {
|
const cancel = () => {
|
||||||
open.value = false
|
showReplyFormDialog.value = false
|
||||||
reset()
|
reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表单 Editor 重置
|
|
||||||
const resetEditor = () => {
|
|
||||||
hackResetWxReplySelect.value = false // 销毁组件
|
|
||||||
nextTick(() => {
|
|
||||||
hackResetWxReplySelect.value = true // 重建组件
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDelete = async (row) => {
|
const handleDelete = async (row) => {
|
||||||
await message.confirm('是否确认删除此数据?')
|
await message.confirm('是否确认删除此数据?')
|
||||||
await MpAutoReplyApi.deleteAutoReply(row.id)
|
await MpAutoReplyApi.deleteAutoReply(row.id)
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
<template>
|
|
||||||
<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>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup name="WxMpSelect">
|
|
||||||
import * as MpAccountApi from '@/api/mp/account'
|
|
||||||
|
|
||||||
const accountId: Ref<number | undefined> = ref()
|
|
||||||
const accountList: Ref<MpAccountApi.AccountVO[]> = ref([])
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: 'change', id: number | undefined): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
handleQuery()
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleQuery = async () => {
|
|
||||||
accountList.value = await MpAccountApi.getSimpleAccountList()
|
|
||||||
// 默认选中第一个
|
|
||||||
if (accountList.value.length > 0) {
|
|
||||||
accountId.value = accountList.value[0].id
|
|
||||||
emit('change', accountId.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountChanged = () => {
|
|
||||||
emit('change', accountId.value)
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,44 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-form class="-mb-15px" ref="queryFormRef" :inline="true" label-width="68px">
|
<el-select v-model="account.id" placeholder="请选择公众号" class="!w-240px" @change="onChanged">
|
||||||
<el-form-item label="公众号" prop="accountId">
|
<el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" />
|
||||||
<!-- TODO 芋艿:需要将 el-form 和 el-select 解耦 -->
|
</el-select>
|
||||||
<el-select
|
|
||||||
v-model="accountId"
|
|
||||||
placeholder="请选择公众号"
|
|
||||||
class="!w-240px"
|
|
||||||
@change="accountChanged()"
|
|
||||||
>
|
|
||||||
<el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<slot name="actions"></slot>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup name="WxAccountSelect">
|
<script lang="ts" setup name="WxAccountSelect">
|
||||||
import * as MpAccountApi from '@/api/mp/account'
|
import * as MpAccountApi from '@/api/mp/account'
|
||||||
const accountId = ref()
|
|
||||||
const accountList = ref([])
|
|
||||||
const queryFormRef = ref()
|
|
||||||
|
|
||||||
const emit = defineEmits(['change'])
|
const account: MpAccountApi.AccountVO = reactive({
|
||||||
|
id: undefined,
|
||||||
onMounted(() => {
|
name: ''
|
||||||
handleQuery()
|
|
||||||
})
|
})
|
||||||
|
const accountList: Ref<MpAccountApi.AccountVO[]> = ref([])
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'change', id?: number, name?: string): void
|
||||||
|
}>()
|
||||||
|
|
||||||
const handleQuery = async () => {
|
const handleQuery = async () => {
|
||||||
accountList.value = await MpAccountApi.getSimpleAccountList()
|
accountList.value = await MpAccountApi.getSimpleAccountList()
|
||||||
// 默认选中第一个
|
// 默认选中第一个
|
||||||
if (accountList.value.length > 0) {
|
if (accountList.value.length > 0) {
|
||||||
accountId.value = accountList.value[0].id
|
account.id = accountList.value[0].id
|
||||||
emit('change', accountId.value)
|
emit('change', account.id, account.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const accountChanged = () => {
|
const onChanged = () => {
|
||||||
emit('change', accountId.value)
|
emit('change', account.id, account.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
onMounted(() => {
|
||||||
|
handleQuery()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,201 +0,0 @@
|
|||||||
<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>
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -31,7 +31,6 @@
|
|||||||
/>
|
/>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-icon><Location /></el-icon>
|
|
||||||
<Icon icon="ep:location" />
|
<Icon icon="ep:location" />
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</el-row>
|
</el-row>
|
||||||
@ -39,6 +38,7 @@
|
|||||||
</el-link>
|
</el-link>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts" name="WxLocation">
|
<script setup lang="ts" name="WxLocation">
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
locationX: {
|
locationX: {
|
||||||
|
@ -39,79 +39,79 @@
|
|||||||
:style="item.sendFrom === 2 ? 'background: #6BED72;' : ''"
|
:style="item.sendFrom === 2 ? 'background: #6BED72;' : ''"
|
||||||
>
|
>
|
||||||
<!-- 【事件】区域 -->
|
<!-- 【事件】区域 -->
|
||||||
<div v-if="item.type === 'event' && item.event === 'subscribe'">
|
<div v-if="item.type === MsgType.Event && item.event === 'subscribe'">
|
||||||
<el-tag type="success">关注</el-tag>
|
<el-tag type="success">关注</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="item.type === 'event' && item.event === 'unsubscribe'">
|
<div v-else-if="item.type === MsgType.Event && item.event === 'unsubscribe'">
|
||||||
<el-tag type="danger">取消关注</el-tag>
|
<el-tag type="danger">取消关注</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="item.type === 'event' && item.event === 'CLICK'">
|
<div v-else-if="item.type === MsgType.Event && item.event === 'CLICK'">
|
||||||
<el-tag>点击菜单</el-tag>
|
<el-tag>点击菜单</el-tag>
|
||||||
【{{ item.eventKey }}】
|
【{{ item.eventKey }}】
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="item.type === 'event' && item.event === 'VIEW'">
|
<div v-else-if="item.type === MsgType.Event && item.event === 'VIEW'">
|
||||||
<el-tag>点击菜单链接</el-tag>
|
<el-tag>点击菜单链接</el-tag>
|
||||||
【{{ item.eventKey }}】
|
【{{ item.eventKey }}】
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="item.type === 'event' && item.event === 'scancode_waitmsg'">
|
<div v-else-if="item.type === MsgType.Event && item.event === 'scancode_waitmsg'">
|
||||||
<el-tag>扫码结果</el-tag>
|
<el-tag>扫码结果</el-tag>
|
||||||
【{{ item.eventKey }}】
|
【{{ item.eventKey }}】
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="item.type === 'event' && item.event === 'scancode_push'">
|
<div v-else-if="item.type === MsgType.Event && item.event === 'scancode_push'">
|
||||||
<el-tag>扫码结果</el-tag>
|
<el-tag>扫码结果</el-tag>
|
||||||
【{{ item.eventKey }}】
|
【{{ item.eventKey }}】
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="item.type === 'event' && item.event === 'pic_sysphoto'">
|
<div v-else-if="item.type === MsgType.Event && item.event === 'pic_sysphoto'">
|
||||||
<el-tag>系统拍照发图</el-tag>
|
<el-tag>系统拍照发图</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="item.type === 'event' && item.event === 'pic_photo_or_album'">
|
<div v-else-if="item.type === MsgType.Event && item.event === 'pic_photo_or_album'">
|
||||||
<el-tag>拍照或者相册</el-tag>
|
<el-tag>拍照或者相册</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="item.type === 'event' && item.event === 'pic_weixin'">
|
<div v-else-if="item.type === MsgType.Event && item.event === 'pic_weixin'">
|
||||||
<el-tag>微信相册</el-tag>
|
<el-tag>微信相册</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="item.type === 'event' && item.event === 'location_select'">
|
<div v-else-if="item.type === MsgType.Event && item.event === 'location_select'">
|
||||||
<el-tag>选择地理位置</el-tag>
|
<el-tag>选择地理位置</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="item.type === 'event'">
|
<div v-else-if="item.type === MsgType.Event">
|
||||||
<el-tag type="danger">未知事件类型</el-tag>
|
<el-tag type="danger">未知事件类型</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<!-- 【消息】区域 -->
|
<!-- 【消息】区域 -->
|
||||||
<div v-else-if="item.type === 'text'">{{ item.content }}</div>
|
<div v-else-if="item.type === MsgType.Text">{{ item.content }}</div>
|
||||||
<div v-else-if="item.type === 'voice'">
|
<div v-else-if="item.type === MsgType.Voice">
|
||||||
<wx-voice-player :url="item.mediaUrl" :content="item.recognition" />
|
<WxVoicePlayer :url="item.mediaUrl" :content="item.recognition" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="item.type === 'image'">
|
<div v-else-if="item.type === MsgType.Image">
|
||||||
<a target="_blank" :href="item.mediaUrl">
|
<a target="_blank" :href="item.mediaUrl">
|
||||||
<img :src="item.mediaUrl" style="width: 100px" />
|
<img :src="item.mediaUrl" style="width: 100px" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="item.type === 'video' || item.type === 'shortvideo'"
|
v-else-if="item.type === MsgType.Video || item.type === 'shortvideo'"
|
||||||
style="text-align: center"
|
style="text-align: center"
|
||||||
>
|
>
|
||||||
<wx-video-player :url="item.mediaUrl" />
|
<WxVideoPlayer :url="item.mediaUrl" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="item.type === 'link'" class="avue-card__detail">
|
<div v-else-if="item.type === MsgType.Link" class="avue-card__detail">
|
||||||
<el-link type="success" :underline="false" target="_blank" :href="item.url">
|
<el-link type="success" :underline="false" target="_blank" :href="item.url">
|
||||||
<div class="avue-card__title"><i class="el-icon-link"></i>{{ item.title }}</div>
|
<div class="avue-card__title"><i class="el-icon-link"></i>{{ item.title }}</div>
|
||||||
</el-link>
|
</el-link>
|
||||||
<div class="avue-card__info" style="height: unset">{{ item.description }}</div>
|
<div class="avue-card__info" style="height: unset">{{ item.description }}</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- TODO 芋艿:待完善 -->
|
<!-- TODO 芋艿:待完善 -->
|
||||||
<div v-else-if="item.type === 'location'">
|
<div v-else-if="item.type === MsgType.Location">
|
||||||
<wx-location
|
<WxLocation
|
||||||
:label="item.label"
|
:label="item.label"
|
||||||
:location-y="item.locationY"
|
:location-y="item.locationY"
|
||||||
:location-x="item.locationX"
|
:location-x="item.locationX"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="item.type === 'news'" style="width: 300px">
|
<div v-else-if="item.type === MsgType.News" style="width: 300px">
|
||||||
<!-- TODO 芋艿:待测试;详情页也存在类似的情况 -->
|
<!-- TODO 芋艿:待测试;详情页也存在类似的情况 -->
|
||||||
<wx-news :articles="item.articles" />
|
<WxNews :articles="item.articles" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="item.type === 'music'">
|
<div v-else-if="item.type === MsgType.Music">
|
||||||
<wx-music
|
<WxMusic
|
||||||
:title="item.title"
|
:title="item.title"
|
||||||
:description="item.description"
|
:description="item.description"
|
||||||
:thumb-media-url="item.thumbMediaUrl"
|
:thumb-media-url="item.thumbMediaUrl"
|
||||||
@ -125,182 +125,185 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="msg-send" v-loading="sendLoading">
|
<div class="msg-send" v-loading="sendLoading">
|
||||||
<wx-reply-select ref="replySelect" :objData="objData" />
|
<WxReplySelect ref="replySelectRef" :objData="objData" />
|
||||||
<el-button type="success" size="small" class="send-but" @click="sendMsg">发送(S)</el-button>
|
<el-button type="success" size="small" class="send-but" @click="sendMsg">发送(S)</el-button>
|
||||||
</div>
|
</div>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" name="WxMsg">
|
<script setup lang="ts" name="WxMsg">
|
||||||
import { getMessagePage, sendMessage } from '@/api/mp/message'
|
|
||||||
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
|
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
|
||||||
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
|
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
|
||||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
|
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
|
||||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||||
import WxLocation from '@/views/mp/components/wx-location/main.vue'
|
import WxLocation from '@/views/mp/components/wx-location/main.vue'
|
||||||
import WxMusic from '@/views/mp/components/wx-music/main.vue'
|
import WxMusic from '@/views/mp/components/wx-music/main.vue'
|
||||||
|
import { getMessagePage, sendMessage } from '@/api/mp/message'
|
||||||
import { getUser } from '@/api/mp/user'
|
import { getUser } from '@/api/mp/user'
|
||||||
import { defineComponent } from 'vue'
|
import { formatDate } from '@/utils/formatTime'
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
|
||||||
import profile from '@/assets/imgs/profile.jpg'
|
import profile from '@/assets/imgs/profile.jpg'
|
||||||
import wechat from '@/assets/imgs/wechat.png'
|
import wechat from '@/assets/imgs/wechat.png'
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { MsgType } from './types'
|
||||||
|
|
||||||
export default defineComponent({
|
const message = useMessage() // 消息弹窗
|
||||||
components: {
|
|
||||||
WxReplySelect,
|
|
||||||
WxVideoPlayer,
|
|
||||||
WxVoicePlayer,
|
|
||||||
WxNews,
|
|
||||||
WxLocation,
|
|
||||||
WxMusic
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
userId: {
|
|
||||||
type: Number,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const nowStr = ref(new Date().getTime()) // 当前的时间戳,用于每次消息加载后,回到原位置;具体见 :id="'msg-div' + nowStr" 处
|
|
||||||
const loading = ref(false) // 消息列表是否正在加载中
|
|
||||||
const loadMore = ref(true) // 是否可以加载更多
|
|
||||||
const list = ref<any[]>([]) // 消息列表
|
|
||||||
const queryParams = reactive({
|
|
||||||
pageNo: 1, // 当前页数
|
|
||||||
pageSize: 14, // 每页显示多少条
|
|
||||||
accountId: undefined
|
|
||||||
})
|
|
||||||
const user = reactive({
|
|
||||||
// 由于微信不再提供昵称,直接使用“用户”展示
|
|
||||||
nickname: '用户',
|
|
||||||
avatar: profile,
|
|
||||||
accountId: 0 // 公众号账号编号
|
|
||||||
})
|
|
||||||
const mp = reactive({
|
|
||||||
nickname: '公众号',
|
|
||||||
avatar: wechat
|
|
||||||
})
|
|
||||||
|
|
||||||
// ========= 消息发送 =========
|
const props = defineProps({
|
||||||
const sendLoading = ref(false) // 发送消息是否加载中
|
userId: {
|
||||||
const objData = reactive({
|
type: Number,
|
||||||
// 微信发送消息
|
required: true
|
||||||
type: 'text',
|
|
||||||
accountId: null,
|
|
||||||
articles: []
|
|
||||||
})
|
|
||||||
|
|
||||||
const replySelect = ref(null)
|
|
||||||
// 执行发送
|
|
||||||
const sendMsg = async () => {
|
|
||||||
if (!objData) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// // 公众号限制:客服消息,公众号只允许发送一条
|
|
||||||
if (objData.type === 'news' && objData.articles.length > 1) {
|
|
||||||
objData.articles = [objData.articles[0]]
|
|
||||||
message.success('图文消息条数限制在 1 条以内,已默认发送第一条')
|
|
||||||
}
|
|
||||||
let data = await sendMessage(Object.assign({ userId: props.userId }, { ...objData }))
|
|
||||||
sendLoading.value = false
|
|
||||||
list.value = [...list.value, ...[data]]
|
|
||||||
scrollToBottom()
|
|
||||||
//ts檢查的時候會判斷這個組件可能是空的,所以需要進行斷言。
|
|
||||||
//避免 tab 的数据未清理
|
|
||||||
const deleteObj = (replySelect.value as any).deleteObj
|
|
||||||
if (deleteObj) {
|
|
||||||
deleteObj()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const loadingMore = () => {
|
|
||||||
queryParams.pageNo++
|
|
||||||
getPage(queryParams, null)
|
|
||||||
}
|
|
||||||
const getPage = async (page, params) => {
|
|
||||||
loading.value = true
|
|
||||||
let dataTemp = await getMessagePage(
|
|
||||||
Object.assign(
|
|
||||||
{
|
|
||||||
pageNo: page.pageNo,
|
|
||||||
pageSize: page.pageSize,
|
|
||||||
userId: props.userId,
|
|
||||||
accountId: page.accountId
|
|
||||||
},
|
|
||||||
params
|
|
||||||
)
|
|
||||||
)
|
|
||||||
const msgDiv = document.getElementById('msg-div' + nowStr.value)
|
|
||||||
let scrollHeight = 0
|
|
||||||
if (msgDiv) {
|
|
||||||
scrollHeight = msgDiv.scrollHeight
|
|
||||||
}
|
|
||||||
// 处理数据
|
|
||||||
let data = dataTemp.list.reverse()
|
|
||||||
list.value = [...data, ...list.value]
|
|
||||||
loading.value = false
|
|
||||||
if (data.length < queryParams.pageSize || data.length === 0) {
|
|
||||||
loadMore.value = false
|
|
||||||
}
|
|
||||||
queryParams.pageNo = page.pageNo
|
|
||||||
queryParams.pageSize = page.pageSize
|
|
||||||
// 滚动到原来的位置
|
|
||||||
if (queryParams.pageNo === 1) {
|
|
||||||
// 定位到消息底部
|
|
||||||
scrollToBottom()
|
|
||||||
} else if (data.length !== 0) {
|
|
||||||
// 定位滚动条
|
|
||||||
await nextTick(() => {
|
|
||||||
if (scrollHeight !== 0) {
|
|
||||||
let div = document.getElementById('msg-div' + nowStr.value)
|
|
||||||
if (div && msgDiv) {
|
|
||||||
msgDiv.scrollTop = div.scrollHeight - scrollHeight - 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const refreshChange = () => {
|
|
||||||
getPage(queryParams, null)
|
|
||||||
}
|
|
||||||
/** 定位到消息底部 */
|
|
||||||
const scrollToBottom = () => {
|
|
||||||
nextTick(() => {
|
|
||||||
let div = document.getElementById('msg-div' + nowStr.value)
|
|
||||||
if (div) {
|
|
||||||
div.scrollTop = div.scrollHeight
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
let data = await getUser(props.userId)
|
|
||||||
user.nickname = data.nickname && data.nickname.length > 0 ? data.nickname : user.nickname
|
|
||||||
user.avatar = data.avatar && user.avatar.length > 0 ? data.avatar : user.avatar
|
|
||||||
user.accountId = data.accountId
|
|
||||||
queryParams.accountId = data.accountId
|
|
||||||
objData.accountId = data.accountId
|
|
||||||
refreshChange()
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
sendMsg,
|
|
||||||
loadingMore,
|
|
||||||
formatDate,
|
|
||||||
scrollToBottom,
|
|
||||||
objData,
|
|
||||||
mp,
|
|
||||||
user,
|
|
||||||
queryParams,
|
|
||||||
list,
|
|
||||||
loadMore,
|
|
||||||
loading,
|
|
||||||
nowStr,
|
|
||||||
sendLoading
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const nowStr = ref(new Date().getTime()) // 当前的时间戳,用于每次消息加载后,回到原位置;具体见 :id="'msg-div' + nowStr" 处
|
||||||
|
const loading = ref(false) // 消息列表是否正在加载中
|
||||||
|
const loadMore = ref(true) // 是否可以加载更多
|
||||||
|
const list = ref<any[]>([]) // 消息列表
|
||||||
|
const queryParams = reactive({
|
||||||
|
pageNo: 1, // 当前页数
|
||||||
|
pageSize: 14, // 每页显示多少条
|
||||||
|
accountId: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
nickname: string
|
||||||
|
avatar: string
|
||||||
|
accountId: number
|
||||||
|
}
|
||||||
|
// 由于微信不再提供昵称,直接使用“用户”展示
|
||||||
|
const user: User = reactive({
|
||||||
|
nickname: '用户',
|
||||||
|
avatar: profile,
|
||||||
|
accountId: 0 // 公众号账号编号
|
||||||
|
})
|
||||||
|
|
||||||
|
interface Mp {
|
||||||
|
nickname: string
|
||||||
|
avatar: string
|
||||||
|
}
|
||||||
|
const mp: Mp = reactive({
|
||||||
|
nickname: '公众号',
|
||||||
|
avatar: wechat
|
||||||
|
})
|
||||||
|
|
||||||
|
// ========= 消息发送 =========
|
||||||
|
const sendLoading = ref(false) // 发送消息是否加载中
|
||||||
|
interface ObjData {
|
||||||
|
type: MsgType
|
||||||
|
accountId: number | null
|
||||||
|
articles: any[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 微信发送消息
|
||||||
|
const objData: ObjData = reactive({
|
||||||
|
type: MsgType.Text,
|
||||||
|
accountId: null,
|
||||||
|
articles: []
|
||||||
|
})
|
||||||
|
|
||||||
|
const replySelectRef = ref<InstanceType<typeof WxReplySelect> | null>(null)
|
||||||
|
|
||||||
|
/** 完成加载 */
|
||||||
|
onMounted(async () => {
|
||||||
|
const data = await getUser(props.userId)
|
||||||
|
user.nickname = data.nickname?.length > 0 ? data.nickname : user.nickname
|
||||||
|
user.avatar = user.avatar?.length > 0 ? data.avatar : user.avatar
|
||||||
|
user.accountId = data.accountId
|
||||||
|
queryParams.accountId = data.accountId
|
||||||
|
objData.accountId = data.accountId
|
||||||
|
|
||||||
|
refreshChange()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 执行发送
|
||||||
|
const sendMsg = async () => {
|
||||||
|
if (!objData) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 公众号限制:客服消息,公众号只允许发送一条
|
||||||
|
if (objData.type === MsgType.News && objData.articles.length > 1) {
|
||||||
|
objData.articles = [objData.articles[0]]
|
||||||
|
message.success('图文消息条数限制在 1 条以内,已默认发送第一条')
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await sendMessage(Object.assign({ userId: props.userId }, { ...objData }))
|
||||||
|
sendLoading.value = false
|
||||||
|
|
||||||
|
list.value = [...list.value, ...[data]]
|
||||||
|
scrollToBottom()
|
||||||
|
|
||||||
|
//ts检查的時候会判断这个组件可能是空的,所以需要进行断言。
|
||||||
|
//避免 tab 的数据未清理
|
||||||
|
const deleteObj = replySelectRef.value?.deleteObj
|
||||||
|
if (deleteObj) {
|
||||||
|
deleteObj()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadingMore = () => {
|
||||||
|
queryParams.pageNo++
|
||||||
|
getPage(queryParams, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPage = async (page, params) => {
|
||||||
|
loading.value = true
|
||||||
|
let dataTemp = await getMessagePage(
|
||||||
|
Object.assign(
|
||||||
|
{
|
||||||
|
pageNo: page.pageNo,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
userId: props.userId,
|
||||||
|
accountId: page.accountId
|
||||||
|
},
|
||||||
|
params
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const msgDiv = document.getElementById('msg-div' + nowStr.value)
|
||||||
|
let scrollHeight = 0
|
||||||
|
if (msgDiv) {
|
||||||
|
scrollHeight = msgDiv.scrollHeight
|
||||||
|
}
|
||||||
|
// 处理数据
|
||||||
|
const data = dataTemp.list.reverse()
|
||||||
|
list.value = [...data, ...list.value]
|
||||||
|
loading.value = false
|
||||||
|
if (data.length < queryParams.pageSize || data.length === 0) {
|
||||||
|
loadMore.value = false
|
||||||
|
}
|
||||||
|
queryParams.pageNo = page.pageNo
|
||||||
|
queryParams.pageSize = page.pageSize
|
||||||
|
// 滚动到原来的位置
|
||||||
|
if (queryParams.pageNo === 1) {
|
||||||
|
// 定位到消息底部
|
||||||
|
scrollToBottom()
|
||||||
|
} else if (data.length !== 0) {
|
||||||
|
// 定位滚动条
|
||||||
|
await nextTick(() => {
|
||||||
|
if (scrollHeight !== 0) {
|
||||||
|
let div = document.getElementById('msg-div' + nowStr.value)
|
||||||
|
if (div && msgDiv) {
|
||||||
|
msgDiv.scrollTop = div.scrollHeight - scrollHeight - 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshChange = () => {
|
||||||
|
getPage(queryParams, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 定位到消息底部 */
|
||||||
|
const scrollToBottom = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
let div = document.getElementById('msg-div' + nowStr.value)
|
||||||
|
if (div) {
|
||||||
|
div.scrollTop = div.scrollHeight
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
/* 因为 joolun 实现依赖 avue 组件,该页面使用了 comment.scss、card.scc */
|
/* 因为 joolun 实现依赖 avue 组件,该页面使用了 comment.scss、card.scc */
|
||||||
@import './comment.scss';
|
@import './comment.scss';
|
||||||
|
11
src/views/mp/components/wx-msg/types.ts
Normal file
11
src/views/mp/components/wx-msg/types.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export enum MsgType {
|
||||||
|
Event = 'event',
|
||||||
|
Text = 'text',
|
||||||
|
Voice = 'voice',
|
||||||
|
Image = 'image',
|
||||||
|
Video = 'video',
|
||||||
|
Link = 'link',
|
||||||
|
Location = 'location',
|
||||||
|
Music = 'music',
|
||||||
|
News = 'news'
|
||||||
|
}
|
@ -231,7 +231,7 @@
|
|||||||
:on-success="handleUploadSuccess"
|
:on-success="handleUploadSuccess"
|
||||||
>
|
>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<el-button type="text">本地上传</el-button>
|
<el-button type="primary" link>本地上传</el-button>
|
||||||
</template>
|
</template>
|
||||||
<el-button type="primary" link @click="openMaterial" style="margin-left: 5px"
|
<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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,14 +3,22 @@
|
|||||||
|
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- TODO @芋艿:调整成 el-form 和 WxAccountSelect -->
|
<el-form
|
||||||
<WxAccountSelect @change="accountChanged">
|
class="-mb-15px"
|
||||||
<template #actions>
|
:model="queryParams"
|
||||||
|
ref="queryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
label-width="68px"
|
||||||
|
>
|
||||||
|
<el-form-item label="公众号" prop="accountId">
|
||||||
|
<WxAccountSelect @change="onAccountChanged" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
<el-button type="primary" plain @click="handleAdd" v-hasPermi="['mp:draft:create']">
|
<el-button type="primary" plain @click="handleAdd" v-hasPermi="['mp:draft:create']">
|
||||||
<Icon icon="ep:plus" />新增
|
<Icon icon="ep:plus" />新增
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</el-form-item>
|
||||||
</WxAccountSelect>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
@ -58,19 +66,17 @@
|
|||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- TODO @Dhb52:迁移成独立路由 -->
|
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<!-- 添加或修改草稿对话框 -->
|
<!-- 添加或修改草稿对话框 -->
|
||||||
<Teleport to="body">
|
<el-dialog
|
||||||
<el-dialog
|
:title="operateMaterial === 'add' ? '新建图文' : '修改图文'"
|
||||||
:title="operateMaterial === 'add' ? '新建图文' : '修改图文'"
|
width="80%"
|
||||||
width="80%"
|
v-model="dialogNewsVisible"
|
||||||
top="20px"
|
:before-close="dialogNewsClose"
|
||||||
v-model="dialogNewsVisible"
|
destroy-on-close
|
||||||
:before-close="dialogNewsClose"
|
>
|
||||||
:close-on-click-modal="false"
|
<el-container>
|
||||||
>
|
<el-aside width="40%">
|
||||||
<div class="left">
|
|
||||||
<div class="select-item">
|
<div class="select-item">
|
||||||
<div v-for="(news, index) in articlesAdd" :key="news.id">
|
<div v-for="(news, index) in articlesAdd" :key="news.id">
|
||||||
<div
|
<div
|
||||||
@ -105,7 +111,7 @@
|
|||||||
class="material-img"
|
class="material-img"
|
||||||
v-if="news.thumbUrl"
|
v-if="news.thumbUrl"
|
||||||
:src="news.thumbUrl"
|
:src="news.thumbUrl"
|
||||||
height="100%"
|
width="100%"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -133,174 +139,206 @@
|
|||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
circle
|
circle
|
||||||
@click="plusNews(item)"
|
@click="plusNews"
|
||||||
v-if="articlesAdd.length < 8 && operateMaterial === 'add'"
|
v-if="articlesAdd.length < 8 && operateMaterial === 'add'"
|
||||||
>
|
>
|
||||||
<Icon icon="ep:plus" />
|
<Icon icon="ep:plus" />
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</el-aside>
|
||||||
<div class="right" v-loading="addMaterialLoading" v-if="articlesAdd.length > 0">
|
<el-main>
|
||||||
<br />
|
<div class="" v-loading="addMaterialLoading" v-if="articlesAdd.length > 0">
|
||||||
<br />
|
<!-- 标题、作者、原文地址 -->
|
||||||
<br />
|
<el-row :gutter="20">
|
||||||
<br />
|
<el-input
|
||||||
<!-- 标题、作者、原文地址 -->
|
v-model="articlesAdd[isActiveAddNews].title"
|
||||||
<el-input v-model="articlesAdd[isActiveAddNews].title" placeholder="请输入标题(必填)" />
|
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
|
<el-input
|
||||||
v-else
|
v-model="articlesAdd[isActiveAddNews].author"
|
||||||
icon="ep:plus"
|
placeholder="请输入作者"
|
||||||
class="avatar-uploader-icon"
|
style="margin-top: 5px"
|
||||||
:class="isActiveAddNews === 0 ? 'avatar' : 'avatar1'"
|
|
||||||
/>
|
/>
|
||||||
<div class="thumb-but">
|
<el-input
|
||||||
<el-upload
|
v-model="articlesAdd[isActiveAddNews].contentSourceUrl"
|
||||||
:action="actionUrl"
|
placeholder="请输入原文地址"
|
||||||
:headers="headers"
|
style="margin-top: 5px"
|
||||||
multiple
|
/>
|
||||||
:limit="1"
|
</el-row>
|
||||||
:file-list="fileList"
|
<!-- 封面和摘要 -->
|
||||||
:data="uploadData"
|
<el-row :gutter="20">
|
||||||
:before-upload="beforeThumbImageUpload"
|
<el-col :span="12">
|
||||||
:on-success="handleUploadSuccess"
|
<p>封面:</p>
|
||||||
>
|
<div class="thumb-div">
|
||||||
<template #trigger>
|
<el-image
|
||||||
<el-button size="small" type="primary">本地上传</el-button>
|
v-if="articlesAdd[isActiveAddNews].thumbUrl"
|
||||||
</template>
|
style="width: 300px; max-height: 300px"
|
||||||
<el-button
|
:src="articlesAdd[isActiveAddNews].thumbUrl"
|
||||||
size="small"
|
fit="contain"
|
||||||
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>
|
<Icon
|
||||||
</Teleport>
|
v-else
|
||||||
</div>
|
icon="ep:plus"
|
||||||
<el-input
|
class="avatar-uploader-icon"
|
||||||
:rows="8"
|
:class="isActiveAddNews === 0 ? 'avatar' : 'avatar1'"
|
||||||
type="textarea"
|
/>
|
||||||
v-model="articlesAdd[isActiveAddNews].digest"
|
<div class="thumb-but">
|
||||||
placeholder="请输入摘要"
|
<el-upload
|
||||||
class="digest"
|
:action="uploadUrl"
|
||||||
maxlength="120"
|
:headers="headers"
|
||||||
style="float: right"
|
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>
|
</div>
|
||||||
<!--富文本编辑器组件-->
|
</el-main>
|
||||||
<el-row>
|
</el-container>
|
||||||
<WxEditor
|
<template #footer>
|
||||||
v-model="articlesAdd[isActiveAddNews].content"
|
<el-button @click="dialogNewsVisible = false">取 消</el-button>
|
||||||
:account-id="uploadData.accountId"
|
<el-button type="primary" @click="submitForm">提 交</el-button>
|
||||||
v-if="hackResetEditor"
|
</template>
|
||||||
/>
|
</el-dialog>
|
||||||
</el-row>
|
|
||||||
</div>
|
|
||||||
<template #footer>
|
|
||||||
<el-button @click="dialogNewsVisible = false">取 消</el-button>
|
|
||||||
<el-button type="primary" @click="submitForm">提 交</el-button>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</Teleport>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup name="MpDraft">
|
|
||||||
import WxEditor from '@/views/mp/components/wx-editor/WxEditor.vue'
|
<script setup lang="ts" name="MpDraft">
|
||||||
|
import { Editor } from '@/components/Editor'
|
||||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
|
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
|
||||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
||||||
import { getAccessToken } from '@/utils/auth'
|
import { getAccessToken } from '@/utils/auth'
|
||||||
import * as MpDraftApi from '@/api/mp/draft'
|
import * as MpDraftApi from '@/api/mp/draft'
|
||||||
import * as MpFreePublishApi from '@/api/mp/freePublish'
|
import * as MpFreePublishApi from '@/api/mp/freePublish'
|
||||||
// 可以用改本地数据模拟,避免API调用超限
|
import type { UploadFiles, UploadProps, UploadRawFile } from 'element-plus'
|
||||||
// import drafts from './mock'
|
import { createEditorConfig } from './editor-config'
|
||||||
|
// import drafts from './mock' // 可以用改本地数据模拟,避免API调用超限
|
||||||
|
import { IEditorConfig } from '@wangeditor/editor'
|
||||||
|
|
||||||
const message = useMessage() // 消息
|
const message = useMessage() // 消息
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
|
const list = ref<any[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
const list = ref([]) // 列表的数据
|
interface QueryParams {
|
||||||
const queryParams = reactive({
|
pageNo: number
|
||||||
|
pageSize: number
|
||||||
|
accountId?: number
|
||||||
|
}
|
||||||
|
const queryParams: QueryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
accountId: undefined
|
accountId: undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
// ========== 文件上传 ==========
|
// ========== 文件上传 ==========
|
||||||
const materialSelectRef = ref()
|
|
||||||
const BASE_URL = import.meta.env.VITE_BASE_URL
|
const BASE_URL = import.meta.env.VITE_BASE_URL
|
||||||
const actionUrl = ref(BASE_URL + '/admin-api/mp/material/upload-permanent') // 上传永久素材的地址
|
const uploadUrl = BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传永久素材的地址
|
||||||
const headers = ref({ Authorization: 'Bearer ' + getAccessToken() }) // 设置上传的请求头部
|
const headers = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部
|
||||||
const fileList = ref([])
|
|
||||||
const uploadData = reactive({
|
const materialSelectRef = ref<InstanceType<typeof WxMaterialSelect> | null>(null)
|
||||||
|
const fileList = ref<UploadFiles>([])
|
||||||
|
interface UploadData {
|
||||||
|
type: 'image' | 'video' | 'audio'
|
||||||
|
accountId?: number
|
||||||
|
}
|
||||||
|
const uploadData: UploadData = reactive({
|
||||||
type: 'image',
|
type: 'image',
|
||||||
accountId: 1
|
accountId: 1
|
||||||
})
|
})
|
||||||
|
|
||||||
// ========== 草稿新建 or 修改 ==========
|
// ========== 草稿新建 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 dialogNewsVisible = ref(false)
|
||||||
const addMaterialLoading = ref(false) // 添加草稿的 loading 标识
|
const addMaterialLoading = ref(false) // 添加草稿的 loading 标识
|
||||||
const articlesAdd = ref([])
|
const articlesAdd = ref<Article[]>([])
|
||||||
const isActiveAddNews = ref(0)
|
const isActiveAddNews = ref(0)
|
||||||
const dialogImageVisible = ref(false)
|
const dialogImageVisible = ref(false)
|
||||||
const operateMaterial = ref('add')
|
const operateMaterial = ref<'add' | 'edit'>('add')
|
||||||
const articlesMediaId = ref('')
|
const articlesMediaId = ref('')
|
||||||
const hackResetEditor = ref(false)
|
|
||||||
|
|
||||||
/** 侦听公众号变化 **/
|
/** 侦听公众号变化 **/
|
||||||
const accountChanged = (accountId) => {
|
const onAccountChanged = (id?: number) => {
|
||||||
setAccountId(accountId)
|
setAccountId(id)
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======================== 列表查询 ========================
|
// ======================== 列表查询 ========================
|
||||||
/** 设置账号编号 */
|
/** 设置账号编号 */
|
||||||
const setAccountId = (accountId) => {
|
const setAccountId = (id?: number) => {
|
||||||
queryParams.accountId = accountId
|
queryParams.accountId = id
|
||||||
uploadData.accountId = accountId
|
uploadData.accountId = id
|
||||||
|
editorConfig.value = createEditorConfig(uploadUrl, queryParams.accountId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const editorConfig = ref<Partial<IEditorConfig>>({})
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
// 如果没有选中公众号账号,则进行提示。
|
|
||||||
if (!queryParams.accountId) {
|
|
||||||
message.error('未选中公众号,无法查询草稿箱')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const drafts = await MpDraftApi.getDraftPage(queryParams)
|
const drafts = await MpDraftApi.getDraftPage(queryParams)
|
||||||
@ -321,7 +359,6 @@ const getList = async () => {
|
|||||||
// ======================== 新增/修改草稿 ========================
|
// ======================== 新增/修改草稿 ========================
|
||||||
/** 新增按钮操作 */
|
/** 新增按钮操作 */
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
resetEditor()
|
|
||||||
reset()
|
reset()
|
||||||
// 打开表单,并设置初始化
|
// 打开表单,并设置初始化
|
||||||
operateMaterial.value = 'add'
|
operateMaterial.value = 'add'
|
||||||
@ -329,8 +366,7 @@ const handleAdd = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 更新按钮操作 */
|
/** 更新按钮操作 */
|
||||||
const handleUpdate = (item) => {
|
const handleUpdate = (item: any) => {
|
||||||
resetEditor()
|
|
||||||
reset()
|
reset()
|
||||||
articlesMediaId.value = item.mediaId
|
articlesMediaId.value = item.mediaId
|
||||||
articlesAdd.value = JSON.parse(JSON.stringify(item.content.newsItem))
|
articlesAdd.value = JSON.parse(JSON.stringify(item.content.newsItem))
|
||||||
@ -340,39 +376,29 @@ const handleUpdate = (item) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 提交按钮 */
|
/** 提交按钮 */
|
||||||
const submitForm = () => {
|
const submitForm = async () => {
|
||||||
// TODO @Dhb52: 参考别的模块写法,改成 await 方式
|
|
||||||
addMaterialLoading.value = true
|
addMaterialLoading.value = true
|
||||||
if (operateMaterial.value === 'add') {
|
try {
|
||||||
MpDraftApi.createDraft(queryParams.accountId, articlesAdd.value)
|
if (operateMaterial.value === 'add') {
|
||||||
.then(() => {
|
await MpDraftApi.createDraft(queryParams.accountId, articlesAdd.value)
|
||||||
message.notifySuccess('新增成功')
|
message.notifySuccess('新增成功')
|
||||||
dialogNewsVisible.value = false
|
} else {
|
||||||
getList()
|
await MpDraftApi.updateDraft(queryParams.accountId, articlesMediaId.value, articlesAdd.value)
|
||||||
})
|
message.notifySuccess('更新成功')
|
||||||
.finally(() => {
|
}
|
||||||
addMaterialLoading.value = false
|
} finally {
|
||||||
})
|
dialogNewsVisible.value = false
|
||||||
} else {
|
addMaterialLoading.value = false
|
||||||
MpDraftApi.updateDraft(queryParams.accountId, articlesMediaId.value, articlesAdd.value)
|
await getList()
|
||||||
.then(() => {
|
|
||||||
message.notifySuccess('更新成功')
|
|
||||||
dialogNewsVisible.value = false
|
|
||||||
getList()
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
addMaterialLoading.value = false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭弹窗
|
// 关闭弹窗
|
||||||
const dialogNewsClose = async (done) => {
|
const dialogNewsClose = async (onDone: () => {}) => {
|
||||||
try {
|
try {
|
||||||
await message.confirm('修改内容可能还未保存,确定关闭吗?')
|
await message.confirm('修改内容可能还未保存,确定关闭吗?')
|
||||||
reset()
|
reset()
|
||||||
resetEditor()
|
onDone()
|
||||||
done()
|
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,16 +408,8 @@ const reset = () => {
|
|||||||
articlesAdd.value = [buildEmptyArticle()]
|
articlesAdd.value = [buildEmptyArticle()]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表单 Editor 重置
|
|
||||||
const resetEditor = () => {
|
|
||||||
hackResetEditor.value = false // 销毁组件
|
|
||||||
nextTick(() => {
|
|
||||||
hackResetEditor.value = true // 重建组件
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将图文向下移动
|
// 将图文向下移动
|
||||||
const downNews = (index) => {
|
const downNews = (index: number) => {
|
||||||
let temp = articlesAdd.value[index]
|
let temp = articlesAdd.value[index]
|
||||||
articlesAdd.value[index] = articlesAdd.value[index + 1]
|
articlesAdd.value[index] = articlesAdd.value[index + 1]
|
||||||
articlesAdd.value[index + 1] = temp
|
articlesAdd.value[index + 1] = temp
|
||||||
@ -399,21 +417,20 @@ const downNews = (index) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 将图文向上移动
|
// 将图文向上移动
|
||||||
const upNews = (index) => {
|
const upNews = (index: number) => {
|
||||||
let temp = articlesAdd.value[index]
|
const temp = articlesAdd.value[index]
|
||||||
articlesAdd.value[index] = articlesAdd.value[index - 1]
|
articlesAdd.value[index] = articlesAdd.value[index - 1]
|
||||||
articlesAdd.value[index - 1] = temp
|
articlesAdd.value[index - 1] = temp
|
||||||
isActiveAddNews.value = index - 1
|
isActiveAddNews.value = index - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选中指定 index 的图文
|
// 选中指定 index 的图文
|
||||||
const activeNews = (index) => {
|
const activeNews = (index: number) => {
|
||||||
resetEditor()
|
|
||||||
isActiveAddNews.value = index
|
isActiveAddNews.value = index
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除指定 index 的图文
|
// 删除指定 index 的图文
|
||||||
const minusNews = async (index) => {
|
const minusNews = async (index: number) => {
|
||||||
try {
|
try {
|
||||||
await message.confirm('确定删除该图文吗?')
|
await message.confirm('确定删除该图文吗?')
|
||||||
articlesAdd.value.splice(index, 1)
|
articlesAdd.value.splice(index, 1)
|
||||||
@ -430,7 +447,7 @@ const plusNews = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建空的 article
|
// 创建空的 article
|
||||||
const buildEmptyArticle = () => {
|
const buildEmptyArticle = (): Article => {
|
||||||
return {
|
return {
|
||||||
title: '',
|
title: '',
|
||||||
thumbMediaId: '',
|
thumbMediaId: '',
|
||||||
@ -446,21 +463,18 @@ const buildEmptyArticle = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ======================== 文件上传 ========================
|
// ======================== 文件上传 ========================
|
||||||
const beforeThumbImageUpload = (file) => {
|
const beforeThumbImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
|
||||||
addMaterialLoading.value = true
|
addMaterialLoading.value = true
|
||||||
const isType =
|
const isType = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/jpg'].includes(
|
||||||
file.type === 'image/jpeg' ||
|
rawFile.type
|
||||||
file.type === 'image/png' ||
|
)
|
||||||
file.type === 'image/gif' ||
|
|
||||||
file.type === 'image/bmp' ||
|
|
||||||
file.type === 'image/jpg'
|
|
||||||
if (!isType) {
|
if (!isType) {
|
||||||
message.error('上传图片格式不对!')
|
message.error('上传图片格式不对!')
|
||||||
addMaterialLoading.value = false
|
addMaterialLoading.value = false
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const isLt = file.size / 1024 / 1024 < 2
|
|
||||||
if (!isLt) {
|
if (rawFile.size / 1024 / 1024 > 2) {
|
||||||
message.error('上传图片大小不能超过 2M!')
|
message.error('上传图片大小不能超过 2M!')
|
||||||
addMaterialLoading.value = false
|
addMaterialLoading.value = false
|
||||||
return false
|
return false
|
||||||
@ -469,10 +483,10 @@ const beforeThumbImageUpload = (file) => {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUploadSuccess = (response, file, fileList) => {
|
const handleUploadSuccess: UploadProps['onSuccess'] = (res: any) => {
|
||||||
addMaterialLoading.value = false
|
addMaterialLoading.value = false
|
||||||
if (response.code !== 0) {
|
if (res.code !== 0) {
|
||||||
message.error('上传出错:' + response.msg)
|
message.error('上传出错:' + res.msg)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -480,12 +494,12 @@ const handleUploadSuccess = (response, file, fileList) => {
|
|||||||
fileList.value = []
|
fileList.value = []
|
||||||
|
|
||||||
// 设置草稿的封面字段
|
// 设置草稿的封面字段
|
||||||
articlesAdd.value[isActiveAddNews.value].thumbMediaId = response.data.mediaId
|
articlesAdd.value[isActiveAddNews.value].thumbMediaId = res.data.mediaId
|
||||||
articlesAdd.value[isActiveAddNews.value].thumbUrl = response.data.url
|
articlesAdd.value[isActiveAddNews.value].thumbUrl = res.data.url
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选择 or 上传完素材,设置回草稿
|
// 选择 or 上传完素材,设置回草稿
|
||||||
const selectMaterial = (item) => {
|
const selectMaterial = (item: any) => {
|
||||||
dialogImageVisible.value = false
|
dialogImageVisible.value = false
|
||||||
articlesAdd.value[isActiveAddNews.value].thumbMediaId = item.mediaId
|
articlesAdd.value[isActiveAddNews.value].thumbMediaId = item.mediaId
|
||||||
articlesAdd.value[isActiveAddNews.value].thumbUrl = item.url
|
articlesAdd.value[isActiveAddNews.value].thumbUrl = item.url
|
||||||
@ -494,14 +508,10 @@ const selectMaterial = (item) => {
|
|||||||
// 打开素材选择
|
// 打开素材选择
|
||||||
const openMaterial = () => {
|
const openMaterial = () => {
|
||||||
dialogImageVisible.value = true
|
dialogImageVisible.value = true
|
||||||
try {
|
|
||||||
materialSelectRef.value.queryParams.accountId = queryParams.accountId // 强制设置下 accountId,避免二次查询不对
|
|
||||||
materialSelectRef.value.handleQuery() // 刷新列表,失败也无所谓
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======================== 草稿箱发布 ========================
|
// ======================== 草稿箱发布 ========================
|
||||||
const handlePublish = async (item) => {
|
const handlePublish = async (item: any) => {
|
||||||
const accountId = queryParams.accountId
|
const accountId = queryParams.accountId
|
||||||
const mediaId = item.mediaId
|
const mediaId = item.mediaId
|
||||||
const content =
|
const content =
|
||||||
@ -515,7 +525,7 @@ const handlePublish = async (item) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (item) => {
|
const handleDelete = async (item: any) => {
|
||||||
const accountId = queryParams.accountId
|
const accountId = queryParams.accountId
|
||||||
const mediaId = item.mediaId
|
const mediaId = item.mediaId
|
||||||
try {
|
try {
|
||||||
@ -543,6 +553,13 @@ const handleDelete = async (item) => {
|
|||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-row {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.el-row:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.item-name {
|
.item-name {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -556,35 +573,33 @@ const handleDelete = async (item) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*新增图文*/
|
/*新增图文*/
|
||||||
.left {
|
// .left {
|
||||||
display: inline-block;
|
// display: inline-block;
|
||||||
width: 35%;
|
// vertical-align: top;
|
||||||
vertical-align: top;
|
// margin-top: 200px;
|
||||||
margin-top: 200px;
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
// .right {
|
||||||
display: inline-block;
|
// display: inline-block;
|
||||||
width: 60%;
|
// width: 100%;
|
||||||
margin-top: -40px;
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-uploader {
|
// .avatar-uploader {
|
||||||
width: 20%;
|
// width: 20%;
|
||||||
display: inline-block;
|
// display: inline-block;
|
||||||
}
|
// }
|
||||||
|
|
||||||
.avatar-uploader .el-upload {
|
// .avatar-uploader .el-upload {
|
||||||
border-radius: 6px;
|
// border-radius: 6px;
|
||||||
cursor: pointer;
|
// cursor: pointer;
|
||||||
position: relative;
|
// position: relative;
|
||||||
overflow: hidden;
|
// overflow: hidden;
|
||||||
text-align: unset !important;
|
// text-align: unset !important;
|
||||||
}
|
// }
|
||||||
|
|
||||||
.avatar-uploader .el-upload:hover {
|
// .avatar-uploader .el-upload:hover {
|
||||||
border-color: #165dff;
|
// border-color: #165dff;
|
||||||
}
|
// }
|
||||||
|
|
||||||
.avatar-uploader-icon {
|
.avatar-uploader-icon {
|
||||||
border: 1px solid #d9d9d9;
|
border: 1px solid #d9d9d9;
|
||||||
@ -607,7 +622,7 @@ const handleDelete = async (item) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.digest {
|
.digest {
|
||||||
width: 60%;
|
width: 100%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
@ -628,28 +643,16 @@ const handleDelete = async (item) => {
|
|||||||
border: 1px solid #eaeaea;
|
border: 1px solid #eaeaea;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
|
||||||
line-height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 992px) and (max-width: 1300px) {
|
@media (min-width: 992px) and (max-width: 1300px) {
|
||||||
.waterfall {
|
.waterfall {
|
||||||
column-count: 3;
|
column-count: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) and (max-width: 991px) {
|
@media (min-width: 768px) and (max-width: 991px) {
|
||||||
.waterfall {
|
.waterfall {
|
||||||
column-count: 2;
|
column-count: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
|
||||||
color: orange;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
@ -715,10 +718,6 @@ p {
|
|||||||
background-color: #acadae;
|
background-color: #acadae;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-tt {
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.activeAddNews {
|
.activeAddNews {
|
||||||
border: 5px solid #2bb673;
|
border: 5px solid #2bb673;
|
||||||
}
|
}
|
||||||
@ -755,7 +754,7 @@ p {
|
|||||||
|
|
||||||
.thumb-div {
|
.thumb-div {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 30%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,8 +3,17 @@
|
|||||||
|
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- TODO @芋艿:调整成 el-form 和 WxAccountSelect -->
|
<el-form
|
||||||
<WxAccountSelect @change="(accountId) => accountChanged(accountId)" />
|
class="-mb-15px"
|
||||||
|
:model="queryParams"
|
||||||
|
ref="queryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
label-width="68px"
|
||||||
|
>
|
||||||
|
<el-form-item label="公众号" prop="accountId">
|
||||||
|
<WxAccountSelect @change="onAccountChanged" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
@ -39,26 +48,31 @@
|
|||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup name="MpFreePublish">
|
<script lang="ts" setup name="MpFreePublish">
|
||||||
import * as FreePublishApi from '@/api/mp/freePublish'
|
import * as FreePublishApi from '@/api/mp/freePublish'
|
||||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
const list = ref([]) // 列表的数据
|
const list = ref<any[]>([]) // 列表的数据
|
||||||
const queryParams = reactive({
|
|
||||||
|
interface QueryParams {
|
||||||
|
pageNo: number
|
||||||
|
pageSize: number
|
||||||
|
accountId?: number
|
||||||
|
}
|
||||||
|
const queryParams: QueryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
accountId: undefined
|
accountId: undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 侦听公众号变化 **/
|
/** 侦听公众号变化 **/
|
||||||
const accountChanged = (accountId) => {
|
const onAccountChanged = (id: number | undefined) => {
|
||||||
queryParams.accountId = accountId
|
queryParams.accountId = id
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +89,7 @@ const getList = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (item) => {
|
const handleDelete = async (item: any) => {
|
||||||
try {
|
try {
|
||||||
// 删除的二次确认
|
// 删除的二次确认
|
||||||
await message.delConfirm('删除后用户将无法访问此页面,确定删除?')
|
await message.delConfirm('删除后用户将无法访问此页面,确定删除?')
|
||||||
@ -87,7 +101,6 @@ const handleDelete = async (item) => {
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.ope-row {
|
.ope-row {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
|
@ -4,13 +4,13 @@
|
|||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-form class="-mb-15px" :inline="true" label-width="68px">
|
<el-form class="-mb-15px" :inline="true" label-width="68px">
|
||||||
<el-form-item label="公众号" prop="accountId">
|
<el-form-item label="公众号" prop="accountId">
|
||||||
<WxMpSelect @change="accountChange" />
|
<WxAccountSelect @change="onAccountChanged" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-tabs v-model="type" @tab-change="handleTabChange">
|
<el-tabs v-model="type" @tab-change="onTabChange">
|
||||||
<!-- tab 1:图片 -->
|
<!-- tab 1:图片 -->
|
||||||
<el-tab-pane name="image">
|
<el-tab-pane name="image">
|
||||||
<template #label>
|
<template #label>
|
||||||
@ -93,7 +93,7 @@
|
|||||||
<el-table-column label="文件名" align="center" prop="name" />
|
<el-table-column label="文件名" align="center" prop="name" />
|
||||||
<el-table-column label="语音" align="center">
|
<el-table-column label="语音" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<WxVoicePlayer :url="scope.row.url" />
|
<WxVoicePlayer v-if="scope.row.url" :url="scope.row.url" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
@ -188,10 +188,8 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<!-- <span class="dialog-footer"> -->
|
|
||||||
<el-button @click="cancelVideo">取 消</el-button>
|
<el-button @click="cancelVideo">取 消</el-button>
|
||||||
<el-button type="primary" @click="submitVideo">提 交</el-button>
|
<el-button type="primary" @click="submitVideo">提 交</el-button>
|
||||||
<!-- </span> -->
|
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
@ -203,7 +201,7 @@
|
|||||||
<el-table-column label="介绍" align="center" prop="introduction" />
|
<el-table-column label="介绍" align="center" prop="introduction" />
|
||||||
<el-table-column label="视频" align="center">
|
<el-table-column label="视频" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<WxVideoPlayer :url="scope.row.url" />
|
<WxVideoPlayer v-if="scope.row.url" :url="scope.row.url" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
@ -250,7 +248,7 @@
|
|||||||
<script lang="ts" setup name="MpMaterial">
|
<script lang="ts" setup name="MpMaterial">
|
||||||
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
|
import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue'
|
||||||
import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
|
import 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 MpMaterialApi from '@/api/mp/material'
|
||||||
import * as authUtil from '@/utils/auth'
|
import * as authUtil from '@/utils/auth'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
@ -279,14 +277,11 @@ const uploadRules: FormRules = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 素材类型
|
// 素材类型
|
||||||
type MatertialType = 'image' | 'voice' | 'video'
|
type MaterialType = 'image' | 'voice' | 'video'
|
||||||
const type = ref<MatertialType>('image')
|
const type = ref<MaterialType>('image')
|
||||||
// 遮罩层
|
const loading = ref(false) // 遮罩层
|
||||||
const loading = ref(false)
|
const list = ref<any[]>([]) // 总条数
|
||||||
// 总条数
|
const total = ref(0) // 数据列表
|
||||||
const total = ref(0)
|
|
||||||
// 数据列表
|
|
||||||
const list = ref([])
|
|
||||||
// 查询参数
|
// 查询参数
|
||||||
interface QueryParams {
|
interface QueryParams {
|
||||||
pageNo: number
|
pageNo: number
|
||||||
@ -304,7 +299,7 @@ const queryParams: QueryParams = reactive({
|
|||||||
const fileList = ref<UploadUserFile[]>([])
|
const fileList = ref<UploadUserFile[]>([])
|
||||||
|
|
||||||
interface UploadData {
|
interface UploadData {
|
||||||
type: MatertialType
|
type: MaterialType
|
||||||
title: string
|
title: string
|
||||||
introduction: string
|
introduction: string
|
||||||
}
|
}
|
||||||
@ -319,8 +314,8 @@ const dialogVideoVisible = ref(false)
|
|||||||
const addMaterialLoading = ref(false)
|
const addMaterialLoading = ref(false)
|
||||||
|
|
||||||
/** 侦听公众号变化 **/
|
/** 侦听公众号变化 **/
|
||||||
const accountChange = (accountId: number | undefined) => {
|
const onAccountChanged = (id?: number) => {
|
||||||
queryParams.accountId = accountId
|
queryParams.accountId = id
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,9 +341,9 @@ const handleQuery = () => {
|
|||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTabChange = (tabName: TabPaneName) => {
|
const onTabChange = (tabName: TabPaneName) => {
|
||||||
// 设置 type
|
// 设置 type
|
||||||
uploadData.type = tabName as MatertialType
|
uploadData.type = tabName as MaterialType
|
||||||
|
|
||||||
// 提前情况数据,避免tab切换后显示垃圾数据
|
// 提前情况数据,避免tab切换后显示垃圾数据
|
||||||
list.value = []
|
list.value = []
|
||||||
@ -359,54 +354,49 @@ const handleTabChange = (tabName: TabPaneName) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ======================== 文件上传 ========================
|
// ======================== 文件上传 ========================
|
||||||
const beforeImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
|
const beforeUpload = (rawFile: UploadRawFile, category: 'image' | 'audio' | 'video'): boolean => {
|
||||||
const isType = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/jpg'].includes(
|
let allowTypes: string[] = []
|
||||||
rawFile.type
|
let maxSizeMB = 0
|
||||||
)
|
let name = ''
|
||||||
if (!isType) {
|
|
||||||
message.error('上传图片格式不对!')
|
switch (category) {
|
||||||
|
case 'image':
|
||||||
|
allowTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/jpg']
|
||||||
|
maxSizeMB = 2
|
||||||
|
name = '图片'
|
||||||
|
break
|
||||||
|
case 'audio':
|
||||||
|
allowTypes = ['audio/mp3', 'audio/mpeg', 'audio/wma', 'audio/wav', 'audio/amr']
|
||||||
|
maxSizeMB = 2
|
||||||
|
name = '图片'
|
||||||
|
break
|
||||||
|
case 'video':
|
||||||
|
allowTypes = ['video/mp4']
|
||||||
|
maxSizeMB = 10
|
||||||
|
name = '视频'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allowTypes.includes(rawFile.type)) {
|
||||||
|
message.error(`上传${name}格式不对!`)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const isLt = rawFile.size / 1024 / 1024 < 2
|
// 校验大小
|
||||||
if (!isLt) {
|
if (rawFile.size / 1024 / 1024 > maxSizeMB) {
|
||||||
message.error('上传图片大小不能超过 2M!')
|
message.error(`上传${name}大小不能超过${maxSizeMB}M!`)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
loading.value = true
|
loading.value = true
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const beforeVoiceUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
|
const beforeImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
|
||||||
const isType = ['audio/mp3', 'audio/wma', 'audio/wav', 'audio/amr'].includes(file.type)
|
beforeUpload(rawFile, 'image')
|
||||||
const isLt = rawFile.size / 1024 / 1024 < 2
|
|
||||||
if (!isType) {
|
|
||||||
message.error('上传语音格式不对!')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!isLt) {
|
|
||||||
message.error('上传语音大小不能超过 2M!')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
loading.value = true
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const beforeVideoUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) => {
|
const beforeVoiceUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
|
||||||
const isType = rawFile.type === 'video/mp4'
|
beforeUpload(rawFile, 'audio')
|
||||||
if (!isType) {
|
|
||||||
message.error('上传视频格式不对!')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const isLt = rawFile.size / 1024 / 1024 < 10
|
const beforeVideoUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>
|
||||||
if (!isLt) {
|
beforeUpload(rawFile, 'video')
|
||||||
message.error('上传视频大小不能超过 10M!')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
addMaterialLoading.value = true
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleUploadSuccess: UploadProps['onSuccess'] = (response: any) => {
|
const handleUploadSuccess: UploadProps['onSuccess'] = (response: any) => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@ -441,6 +431,7 @@ const submitVideo = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 弹出 video 新建的表单
|
||||||
const handleAddVideo = () => {
|
const handleAddVideo = () => {
|
||||||
resetVideo()
|
resetVideo()
|
||||||
dialogVideoVisible.value = true
|
dialogVideoVisible.value = true
|
||||||
|
@ -2,8 +2,11 @@
|
|||||||
<doc-alert title="公众号菜单" url="https://doc.iocoder.cn/mp/menu/" />
|
<doc-alert title="公众号菜单" url="https://doc.iocoder.cn/mp/menu/" />
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- TODO @芋艿:调整成 el-form 和 WxAccountSelect -->
|
<el-form class="-mb-15px" ref="queryFormRef" :inline="true" label-width="68px">
|
||||||
<WxAccountSelect @change="accountChanged" />
|
<el-form-item label="公众号" prop="accountId">
|
||||||
|
<WxAccountSelect @change="onAccountChanged" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
@ -12,7 +15,7 @@
|
|||||||
<!--左边配置菜单-->
|
<!--左边配置菜单-->
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<div class="weixin-hd">
|
<div class="weixin-hd">
|
||||||
<div class="weixin-title">{{ name }}</div>
|
<div class="weixin-title">{{ accountName }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="weixin-menu menu_main clearfix">
|
<div class="weixin-menu menu_main clearfix">
|
||||||
<div class="menu_bottom" v-for="(item, i) of menuList" :key="i">
|
<div class="menu_bottom" v-for="(item, i) of menuList" :key="i">
|
||||||
@ -68,7 +71,7 @@
|
|||||||
<div v-if="showRightFlag" class="right">
|
<div v-if="showRightFlag" class="right">
|
||||||
<div class="configure_page">
|
<div class="configure_page">
|
||||||
<div class="delete_btn">
|
<div class="delete_btn">
|
||||||
<el-button size="small" type="danger" @click="handleDeleteMenu(tempObj)">
|
<el-button size="small" type="danger" @click="handleDeleteMenu">
|
||||||
删除当前菜单<Icon icon="ep:delete" />
|
删除当前菜单<Icon icon="ep:delete" />
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@ -155,7 +158,7 @@
|
|||||||
<div v-else>
|
<div v-else>
|
||||||
<el-row justify="center">
|
<el-row justify="center">
|
||||||
<el-col :span="24" style="text-align: center">
|
<el-col :span="24" style="text-align: center">
|
||||||
<el-button type="success" @click="openMaterial">
|
<el-button type="success" @click="dialogNewsVisible = true">
|
||||||
素材库选择<Icon icon="ep:circle-check" />
|
素材库选择<Icon icon="ep:circle-check" />
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
@ -185,24 +188,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
<script setup name="MpMenu">
|
<script lang="ts" setup name="MpMenu">
|
||||||
import { handleTree } from '@/utils/tree'
|
|
||||||
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
|
import WxReplySelect from '@/views/mp/components/wx-reply/main.vue'
|
||||||
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
import WxNews from '@/views/mp/components/wx-news/main.vue'
|
||||||
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
|
import WxMaterialSelect from '@/views/mp/components/wx-material-select/main.vue'
|
||||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
||||||
import * as MpMenuApi from '@/api/mp/menu'
|
import * as MpMenuApi from '@/api/mp/menu'
|
||||||
|
import { handleTree } from '@/utils/tree'
|
||||||
import menuOptions from './menuOptions'
|
import menuOptions from './menuOptions'
|
||||||
|
|
||||||
const message = useMessage() // 消息
|
const message = useMessage() // 消息
|
||||||
|
|
||||||
// ======================== 列表查询 ========================
|
// ======================== 列表查询 ========================
|
||||||
const loading = ref(true) // 遮罩层
|
const loading = ref(false) // 遮罩层
|
||||||
const accountId = ref(undefined) // 公众号Id
|
const accountId = ref<number | undefined>()
|
||||||
const menuList = ref({ children: [] })
|
const accountName = ref<string | undefined>('')
|
||||||
|
const menuList = ref<any>({ children: [] })
|
||||||
|
|
||||||
// ======================== 菜单操作 ========================
|
// ======================== 菜单操作 ========================
|
||||||
const isActive = ref(-1) // 一级菜单点中样式
|
const isActive = ref(-1) // 一级菜单点中样式
|
||||||
const isSubMenuActive = ref(-1) // 一级菜单点中样式
|
const isSubMenuActive = ref<string | number>(-1) // 一级菜单点中样式
|
||||||
const isSubMenuFlag = ref(-1) // 二级菜单显示标志
|
const isSubMenuFlag = ref(-1) // 二级菜单显示标志
|
||||||
|
|
||||||
// ======================== 菜单编辑 ========================
|
// ======================== 菜单编辑 ========================
|
||||||
@ -210,15 +215,16 @@ const showRightFlag = ref(false) // 右边配置显示默认详情还是配置
|
|||||||
const nameMaxLength = ref(0) // 菜单名称最大长度;1 级是 4 字符;2 级是 7 字符;
|
const nameMaxLength = ref(0) // 菜单名称最大长度;1 级是 4 字符;2 级是 7 字符;
|
||||||
const showConfigureContent = ref(true) // 是否展示配置内容;如果有子菜单,就不显示配置内容
|
const showConfigureContent = ref(true) // 是否展示配置内容;如果有子菜单,就不显示配置内容
|
||||||
const hackResetWxReplySelect = ref(false) // 重置 WxReplySelect 组件
|
const hackResetWxReplySelect = ref(false) // 重置 WxReplySelect 组件
|
||||||
const tempObj = ref({}) // 右边临时变量,作为中间值牵引关系
|
const tempObj = ref<any>({}) // 右边临时变量,作为中间值牵引关系
|
||||||
|
|
||||||
// 一些临时值放在这里进行判断,如果放在 tempObj,由于引用关系,menu 也会多了多余的参数
|
// 一些临时值放在这里进行判断,如果放在 tempObj,由于引用关系,menu 也会多了多余的参数
|
||||||
const tempSelfObj = ref({})
|
const tempSelfObj = ref<any>({})
|
||||||
const dialogNewsVisible = ref(false) // 跳转图文时的素材选择弹窗
|
const dialogNewsVisible = ref(false) // 跳转图文时的素材选择弹窗
|
||||||
|
|
||||||
/** 侦听公众号变化 **/
|
/** 侦听公众号变化 **/
|
||||||
const accountChanged = (id) => {
|
const onAccountChanged = (id?: number, name?: string) => {
|
||||||
accountId.value = id
|
accountId.value = id
|
||||||
|
accountName.value = name
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,10 +247,10 @@ const handleQuery = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 将后端返回的 menuList,转换成前端的 menuList
|
// 将后端返回的 menuList,转换成前端的 menuList
|
||||||
const convertMenuList = (list) => {
|
const convertMenuList = (list: any[]) => {
|
||||||
if (!list) return []
|
if (!list) return []
|
||||||
|
|
||||||
const menuList = []
|
const result: any[] = []
|
||||||
list.forEach((item) => {
|
list.forEach((item) => {
|
||||||
const menu = {
|
const menu = {
|
||||||
...item
|
...item
|
||||||
@ -271,9 +277,9 @@ const convertMenuList = (list) => {
|
|||||||
hqMusicUrl: item.replyHqMusicUrl
|
hqMusicUrl: item.replyHqMusicUrl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
menuList.push(menu)
|
result.push(menu)
|
||||||
})
|
})
|
||||||
return menuList
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置表单,清空表单数据
|
// 重置表单,清空表单数据
|
||||||
@ -286,7 +292,7 @@ const resetForm = () => {
|
|||||||
// 菜单编辑
|
// 菜单编辑
|
||||||
showRightFlag.value = false
|
showRightFlag.value = false
|
||||||
nameMaxLength.value = 0
|
nameMaxLength.value = 0
|
||||||
showConfigureContent.value = 0
|
showConfigureContent.value = false
|
||||||
hackResetWxReplySelect.value = false
|
hackResetWxReplySelect.value = false
|
||||||
tempObj.value = {}
|
tempObj.value = {}
|
||||||
tempSelfObj.value = {}
|
tempSelfObj.value = {}
|
||||||
@ -295,7 +301,7 @@ const resetForm = () => {
|
|||||||
|
|
||||||
// ======================== 菜单操作 ========================
|
// ======================== 菜单操作 ========================
|
||||||
// 一级菜单点击事件
|
// 一级菜单点击事件
|
||||||
const menuClick = (i, item) => {
|
const menuClick = (i: number, item: any) => {
|
||||||
// 右侧的表单相关
|
// 右侧的表单相关
|
||||||
resetEditor()
|
resetEditor()
|
||||||
showRightFlag.value = true // 右边菜单
|
showRightFlag.value = true // 右边菜单
|
||||||
@ -312,11 +318,10 @@ const menuClick = (i, item) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 二级菜单点击事件
|
// 二级菜单点击事件
|
||||||
const subMenuClick = (subItem, index, k) => {
|
const subMenuClick = (subItem: any, index: number, k: number) => {
|
||||||
// 右侧的表单相关
|
// 右侧的表单相关
|
||||||
resetEditor()
|
resetEditor()
|
||||||
showRightFlag.value = true // 右边菜单
|
showRightFlag.value = true // 右边菜单
|
||||||
console.log(subItem)
|
|
||||||
tempObj.value = subItem // 将点击的数据放到临时变量,对象有引用作用
|
tempObj.value = subItem // 将点击的数据放到临时变量,对象有引用作用
|
||||||
tempSelfObj.value.grand = '2' // 表示二级菜单
|
tempSelfObj.value.grand = '2' // 表示二级菜单
|
||||||
tempSelfObj.value.index = index // 表示一级菜单索引
|
tempSelfObj.value.index = index // 表示一级菜单索引
|
||||||
@ -331,7 +336,7 @@ const subMenuClick = (subItem, index, k) => {
|
|||||||
|
|
||||||
// 添加横向一级菜单
|
// 添加横向一级菜单
|
||||||
const addMenu = () => {
|
const addMenu = () => {
|
||||||
const menuKeyLength = menuList.value.length
|
const menuKeyLength: number = menuList.value.length
|
||||||
const addButton = {
|
const addButton = {
|
||||||
name: '菜单名称',
|
name: '菜单名称',
|
||||||
children: [],
|
children: [],
|
||||||
@ -342,10 +347,10 @@ const addMenu = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
menuList.value[menuKeyLength] = addButton
|
menuList.value[menuKeyLength] = addButton
|
||||||
menuClick(menuKeyLength.value - 1, addButton)
|
menuClick(menuKeyLength - 1, addButton)
|
||||||
}
|
}
|
||||||
// 添加横向二级菜单;item 表示要操作的父菜单
|
// 添加横向二级菜单;item 表示要操作的父菜单
|
||||||
const addSubMenu = (i, item) => {
|
const addSubMenu = (i: number, item: any) => {
|
||||||
// 清空父菜单的属性,因为它只需要 name 属性即可
|
// 清空父菜单的属性,因为它只需要 name 属性即可
|
||||||
if (!item.children || item.children.length <= 0) {
|
if (!item.children || item.children.length <= 0) {
|
||||||
item.children = []
|
item.children = []
|
||||||
@ -361,8 +366,8 @@ const addSubMenu = (i, item) => {
|
|||||||
showConfigureContent.value = false
|
showConfigureContent.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
let subMenuKeyLength = item.children.length // 获取二级菜单key长度
|
const subMenuKeyLength = item.children.length // 获取二级菜单key长度
|
||||||
let addButton = {
|
const addButton = {
|
||||||
name: '子菜单名称',
|
name: '子菜单名称',
|
||||||
reply: {
|
reply: {
|
||||||
// 用于存储回复内容
|
// 用于存储回复内容
|
||||||
@ -399,7 +404,7 @@ const handleDeleteMenu = async () => {
|
|||||||
// ======================== 菜单编辑 ========================
|
// ======================== 菜单编辑 ========================
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
try {
|
try {
|
||||||
await message.confirm('确定要删除吗?')
|
await message.confirm('确定要保存吗?')
|
||||||
loading.value = true
|
loading.value = true
|
||||||
await MpMenuApi.saveMenu(accountId.value, convertMenuFormList())
|
await MpMenuApi.saveMenu(accountId.value, convertMenuFormList())
|
||||||
getList()
|
getList()
|
||||||
@ -413,7 +418,6 @@ const handleSave = async () => {
|
|||||||
const resetEditor = () => {
|
const resetEditor = () => {
|
||||||
hackResetWxReplySelect.value = false // 销毁组件
|
hackResetWxReplySelect.value = false // 销毁组件
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
console.log('nextTick')
|
|
||||||
hackResetWxReplySelect.value = true // 重建组件
|
hackResetWxReplySelect.value = true // 重建组件
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -432,9 +436,9 @@ const handleDelete = async () => {
|
|||||||
|
|
||||||
// 将前端的 menuList,转换成后端接收的 menuList
|
// 将前端的 menuList,转换成后端接收的 menuList
|
||||||
const convertMenuFormList = () => {
|
const convertMenuFormList = () => {
|
||||||
const result = []
|
const result: any[] = []
|
||||||
menuList.value.forEach((item) => {
|
menuList.value.forEach((item) => {
|
||||||
let menu = convertMenuForm(item)
|
const menu = convertMenuForm(item)
|
||||||
result.push(menu)
|
result.push(menu)
|
||||||
|
|
||||||
// 处理子菜单
|
// 处理子菜单
|
||||||
@ -450,7 +454,7 @@ const convertMenuFormList = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 将前端的 menu,转换成后端接收的 menu
|
// 将前端的 menu,转换成后端接收的 menu
|
||||||
const convertMenuForm = (menu) => {
|
const convertMenuForm = (menu: any) => {
|
||||||
let result = {
|
let result = {
|
||||||
...menu,
|
...menu,
|
||||||
children: undefined, // 不处理子节点
|
children: undefined, // 不处理子节点
|
||||||
@ -473,11 +477,7 @@ const convertMenuForm = (menu) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ======================== 菜单编辑(素材选择) ========================
|
// ======================== 菜单编辑(素材选择) ========================
|
||||||
const openMaterial = () => {
|
const selectMaterial = (item: any) => {
|
||||||
dialogNewsVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectMaterial = (item) => {
|
|
||||||
const articleId = item.articleId
|
const articleId = item.articleId
|
||||||
const articles = item.content.newsItem
|
const articles = item.content.newsItem
|
||||||
// 提示,针对多图文
|
// 提示,针对多图文
|
||||||
|
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,14 +9,7 @@
|
|||||||
label-width="68px"
|
label-width="68px"
|
||||||
>
|
>
|
||||||
<el-form-item label="公众号" prop="accountId">
|
<el-form-item label="公众号" prop="accountId">
|
||||||
<el-select v-model="queryParams.accountId" placeholder="请选择公众号" class="!w-240px">
|
<WxAccountSelect @change="onAccountChanged" />
|
||||||
<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-form-item label="消息类型" prop="type">
|
<el-form-item label="消息类型" prop="type">
|
||||||
<el-select v-model="queryParams.type" placeholder="请选择消息类型" class="!w-240px">
|
<el-select v-model="queryParams.type" placeholder="请选择消息类型" class="!w-240px">
|
||||||
@ -65,152 +58,50 @@
|
|||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list">
|
<MessageTable :list="list" :loading="loading" @send="handleSend" />
|
||||||
<el-table-column
|
<Pagination
|
||||||
label="发送时间"
|
|
||||||
align="center"
|
|
||||||
prop="createTime"
|
|
||||||
width="180"
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
/>
|
|
||||||
<el-table-column label="消息类型" align="center" prop="type" width="80" />
|
|
||||||
<el-table-column label="发送方" align="center" prop="sendFrom" width="80">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-tag v-if="scope.row.sendFrom === 1" type="success">粉丝</el-tag>
|
|
||||||
<el-tag v-else type="info">公众号</el-tag>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="用户标识" align="center" prop="openid" width="300" />
|
|
||||||
<el-table-column label="内容" prop="content">
|
|
||||||
<template #default="scope">
|
|
||||||
<!-- 【事件】区域 -->
|
|
||||||
<div v-if="scope.row.type === 'event' && scope.row.event === 'subscribe'">
|
|
||||||
<el-tag type="success">关注</el-tag>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'unsubscribe'">
|
|
||||||
<el-tag type="danger">取消关注</el-tag>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'CLICK'">
|
|
||||||
<el-tag>点击菜单</el-tag>
|
|
||||||
【{{ scope.row.eventKey }}】
|
|
||||||
</div>
|
|
||||||
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'VIEW'">
|
|
||||||
<el-tag>点击菜单链接</el-tag>
|
|
||||||
【{{ scope.row.eventKey }}】
|
|
||||||
</div>
|
|
||||||
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'scancode_waitmsg'">
|
|
||||||
<el-tag>扫码结果</el-tag>
|
|
||||||
【{{ scope.row.eventKey }}】
|
|
||||||
</div>
|
|
||||||
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'scancode_push'">
|
|
||||||
<el-tag>扫码结果</el-tag>
|
|
||||||
【{{ scope.row.eventKey }}】
|
|
||||||
</div>
|
|
||||||
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_sysphoto'">
|
|
||||||
<el-tag>系统拍照发图</el-tag>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_photo_or_album'">
|
|
||||||
<el-tag>拍照或者相册</el-tag>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'pic_weixin'">
|
|
||||||
<el-tag>微信相册</el-tag>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="scope.row.type === 'event' && scope.row.event === 'location_select'">
|
|
||||||
<el-tag>选择地理位置</el-tag>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="scope.row.type === 'event'">
|
|
||||||
<el-tag type="danger">未知事件类型</el-tag>
|
|
||||||
</div>
|
|
||||||
<!-- 【消息】区域 -->
|
|
||||||
<div v-else-if="scope.row.type === 'text'">{{ scope.row.content }}</div>
|
|
||||||
<div v-else-if="scope.row.type === 'voice'">
|
|
||||||
<wx-voice-player :url="scope.row.mediaUrl" :content="scope.row.recognition" />
|
|
||||||
</div>
|
|
||||||
<div v-else-if="scope.row.type === 'image'">
|
|
||||||
<a target="_blank" :href="scope.row.mediaUrl">
|
|
||||||
<img :src="scope.row.mediaUrl" style="width: 100px" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="scope.row.type === 'video' || scope.row.type === 'shortvideo'">
|
|
||||||
<wx-video-player :url="scope.row.mediaUrl" style="margin-top: 10px" />
|
|
||||||
</div>
|
|
||||||
<div v-else-if="scope.row.type === 'link'">
|
|
||||||
<el-tag>链接</el-tag>
|
|
||||||
:
|
|
||||||
<a :href="scope.row.url" target="_blank">{{ scope.row.title }}</a>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="scope.row.type === 'location'">
|
|
||||||
<wx-location
|
|
||||||
:label="scope.row.label"
|
|
||||||
:location-y="scope.row.locationY"
|
|
||||||
:location-x="scope.row.locationX"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="scope.row.type === 'music'">
|
|
||||||
<wx-music
|
|
||||||
:title="scope.row.title"
|
|
||||||
:description="scope.row.description"
|
|
||||||
:thumb-media-url="scope.row.thumbMediaUrl"
|
|
||||||
:music-url="scope.row.musicUrl"
|
|
||||||
:hq-music-url="scope.row.hqMusicUrl"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="scope.row.type === 'news'">
|
|
||||||
<wx-news :articles="scope.row.articles" />
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<el-tag type="danger">未知消息类型</el-tag>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-button
|
|
||||||
link
|
|
||||||
type="primary"
|
|
||||||
@click="handleSend(scope.row)"
|
|
||||||
v-hasPermi="['mp:message:send']"
|
|
||||||
>
|
|
||||||
消息
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
<!-- 分页组件 -->
|
|
||||||
<pagination
|
|
||||||
v-show="total > 0"
|
v-show="total > 0"
|
||||||
:total="total"
|
:total="total"
|
||||||
v-model:page="queryParams.pageNo"
|
v-model:page="queryParams.pageNo"
|
||||||
v-model:limit="queryParams.pageSize"
|
v-model:limit="queryParams.pageSize"
|
||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 发送消息的弹窗 -->
|
|
||||||
<el-dialog title="粉丝消息列表" v-model="open" @click="openDialog()" width="50%">
|
|
||||||
<template #footer>
|
|
||||||
<wx-msg :user-id="userId" v-if="open" />
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</ContentWrap>
|
</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>
|
</template>
|
||||||
<script setup lang="ts" name="MpMessage">
|
<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 * as MpAccountApi from '@/api/mp/account'
|
|
||||||
import * as MpMessageApi from '@/api/mp/message'
|
import * as MpMessageApi from '@/api/mp/message'
|
||||||
|
import WxMsg from '@/views/mp/components/wx-msg/main.vue'
|
||||||
const message = useMessage() // 消息弹窗
|
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
||||||
|
import MessageTable from './MessageTable.vue'
|
||||||
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
|
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 loading = ref(false)
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 数据的总页数
|
||||||
const list = ref([]) // 列表的数据
|
const list = ref<any[]>([]) // 当前页的列表数据
|
||||||
const queryParams = reactive({
|
|
||||||
|
// 搜索参数
|
||||||
|
interface QueryParams {
|
||||||
|
pageNo: number
|
||||||
|
pageSize: number
|
||||||
|
openid: string | null
|
||||||
|
accountId: number | null
|
||||||
|
type: MsgType | null
|
||||||
|
createTime: string[] | []
|
||||||
|
}
|
||||||
|
const queryParams: QueryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
openid: null,
|
openid: null,
|
||||||
@ -218,19 +109,27 @@ const queryParams = reactive({
|
|||||||
type: null,
|
type: null,
|
||||||
createTime: []
|
createTime: []
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref<FormInstance | null>(null) // 搜索的表单
|
||||||
// TODO 芋艿:下面应该移除
|
|
||||||
const open = ref(false) // 是否显示弹出层
|
// 消息对话框
|
||||||
const userId = ref(0) // 操作的用户编号
|
const messageBox = reactive({
|
||||||
const accountList = ref<MpAccountApi.AccountVO[]>([]) // 公众号账号列表
|
show: false,
|
||||||
|
userId: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 侦听accountId */
|
||||||
|
const onAccountChanged = (id?: number) => {
|
||||||
|
queryParams.accountId = id as number
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
// 如果没有选中公众号账号,则进行提示。
|
|
||||||
if (!queryParams.accountId) {
|
|
||||||
await message.error('未选中公众号,无法查询消息')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const data = await MpMessageApi.getMessagePage(queryParams)
|
const data = await MpMessageApi.getMessagePage(queryParams)
|
||||||
@ -241,42 +140,18 @@ const getList = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
|
||||||
const handleQuery = () => {
|
|
||||||
queryParams.pageNo = 1
|
|
||||||
getList()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = async () => {
|
const resetQuery = async () => {
|
||||||
queryFormRef.value.resetFields()
|
// 暂存 accountId,并在 reset 后恢复
|
||||||
// 默认选中第一个
|
const accountId = queryParams.accountId
|
||||||
if (accountList.value.length > 0) {
|
queryFormRef.value?.resetFields()
|
||||||
// @ts-ignore
|
queryParams.accountId = accountId
|
||||||
queryParams.accountId = accountList.value[0].id
|
|
||||||
}
|
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
const handleSend = async (row) => {
|
|
||||||
userId.value = row.userId
|
|
||||||
open.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const openDialog = () => {
|
/** 打开消息发送窗口 */
|
||||||
open.value = true
|
const handleSend = async (userId: number) => {
|
||||||
|
messageBox.userId = userId
|
||||||
|
messageBox.show = true
|
||||||
}
|
}
|
||||||
// const closeDiaLog = () => {
|
|
||||||
// open.value = false
|
|
||||||
// }
|
|
||||||
|
|
||||||
/** 初始化 **/
|
|
||||||
onMounted(async () => {
|
|
||||||
accountList.value = await MpAccountApi.getSimpleAccountList()
|
|
||||||
// 选中第一个
|
|
||||||
if (accountList.value.length > 0) {
|
|
||||||
// @ts-ignore
|
|
||||||
queryParams.accountId = accountList.value[0].id
|
|
||||||
}
|
|
||||||
await getList()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -19,24 +19,30 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as MpTagApi from '@/api/mp/tag'
|
import * as MpTagApi from '@/api/mp/tag'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
const dialogTitle = ref('') // 弹窗的标题
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
const formType = ref<'create' | 'update' | ''>('') // 表单的类型:create - 新增;update - 修改
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
accountId: -1,
|
accountId: -1,
|
||||||
name: ''
|
name: ''
|
||||||
})
|
})
|
||||||
const formRules = reactive({
|
const formRules: FormRules = {
|
||||||
name: [{ required: true, message: '请输入标签名称', trigger: 'blur' }]
|
name: [{ required: true, message: '请输入标签名称', trigger: 'blur' }]
|
||||||
})
|
}
|
||||||
const formRef = ref() // 表单 Ref
|
const formRef = ref<FormInstance | null>(null) // 表单 Ref
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'success'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
const open = async (type: string, accountId: number, id?: number) => {
|
const open = async (type: 'create' | 'update', accountId: number, id?: number) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
dialogTitle.value = t('action.' + type)
|
dialogTitle.value = t('action.' + type)
|
||||||
formType.value = type
|
formType.value = type
|
||||||
@ -55,11 +61,10 @@ const open = async (type: string, accountId: number, id?: number) => {
|
|||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
/** 提交表单 */
|
/** 提交表单 */
|
||||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
// 校验表单
|
// 校验表单
|
||||||
if (!formRef) return
|
if (!formRef) return
|
||||||
const valid = await formRef.value.validate()
|
const valid = await formRef.value?.validate()
|
||||||
if (!valid) return
|
if (!valid) return
|
||||||
// 提交请求
|
// 提交请求
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
|
@ -3,17 +3,25 @@
|
|||||||
|
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- TODO @芋艿:调整成 el-form 和 WxAccountSelect -->
|
<el-form
|
||||||
<WxAccountSelect @change="accountChanged">
|
class="-mb-15px"
|
||||||
<template #actions>
|
:model="queryParams"
|
||||||
|
ref="queryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
label-width="68px"
|
||||||
|
>
|
||||||
|
<el-form-item label="公众号" prop="accountId">
|
||||||
|
<WxAccountSelect @change="onAccountChanged" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['mp:tag:create']">
|
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['mp:tag:create']">
|
||||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="success" plain @click="handleSync" v-hasPermi="['mp:tag:sync']">
|
<el-button type="success" plain @click="handleSync" v-hasPermi="['mp:tag:sync']">
|
||||||
<Icon icon="ep:refresh" class="mr-5px" /> 同步
|
<Icon icon="ep:refresh" class="mr-5px" /> 同步
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</el-form-item>
|
||||||
</WxAccountSelect>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
@ -64,25 +72,32 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup lang="ts" name="MpTag">
|
<script setup lang="ts" name="MpTag">
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
|
||||||
import * as MpTagApi from '@/api/mp/tag'
|
import * as MpTagApi from '@/api/mp/tag'
|
||||||
import TagForm from './TagForm.vue'
|
import TagForm from './TagForm.vue'
|
||||||
|
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
const list = ref([]) // 列表的数据
|
const list = ref<any>([]) // 列表的数据
|
||||||
const queryParams = reactive({
|
|
||||||
|
interface QueryParams {
|
||||||
|
pageNo: number
|
||||||
|
pageSize: number
|
||||||
|
accountId?: number
|
||||||
|
}
|
||||||
|
const queryParams: QueryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
accountId: undefined
|
accountId: undefined
|
||||||
})
|
})
|
||||||
|
const formRef = ref<InstanceType<typeof TagForm> | null>(null)
|
||||||
|
|
||||||
/** 侦听公众号变化 **/
|
/** 侦听公众号变化 **/
|
||||||
const accountChanged = (accountId) => {
|
const onAccountChanged = (id?: number) => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
queryParams.accountId = accountId
|
queryParams.accountId = id
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,9 +114,8 @@ const getList = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 添加/修改操作 */
|
/** 添加/修改操作 */
|
||||||
const formRef = ref()
|
|
||||||
const openForm = (type: string, id?: number) => {
|
const openForm = (type: string, id?: number) => {
|
||||||
formRef.value.open(type, queryParams.accountId, id)
|
formRef.value?.open(type, queryParams.accountId as number, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
@ -121,8 +135,7 @@ const handleDelete = async (id: number) => {
|
|||||||
const handleSync = async () => {
|
const handleSync = async () => {
|
||||||
try {
|
try {
|
||||||
await message.confirm('是否确认同步标签?')
|
await message.confirm('是否确认同步标签?')
|
||||||
// @ts-ignore
|
await MpTagApi.syncTag(queryParams.accountId as number)
|
||||||
await MpTagApi.syncTag(queryParams.accountId)
|
|
||||||
message.success('同步标签成功')
|
message.success('同步标签成功')
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch {}
|
||||||
|
@ -3,14 +3,42 @@
|
|||||||
|
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- TODO @芋艿:调整成 el-form 和 WxAccountSelect -->
|
<el-form
|
||||||
<WxAccountSelect @change="(accountId) => accountChanged(accountId)">
|
class="-mb-15px"
|
||||||
<template #actions>
|
:model="queryParams"
|
||||||
|
ref="queryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
label-width="68px"
|
||||||
|
>
|
||||||
|
<el-form-item label="公众号" prop="accountId">
|
||||||
|
<WxAccountSelect @change="onAccountChanged" />
|
||||||
|
</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']">
|
<el-button type="success" plain @click="handleSync" v-hasPermi="['mp:user:sync']">
|
||||||
<Icon icon="ep:refresh" class="mr-5px" /> 同步
|
<Icon icon="ep:refresh" class="mr-5px" /> 同步
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</el-form-item>
|
||||||
</WxAccountSelect>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
@ -66,35 +94,40 @@
|
|||||||
<UserForm ref="formRef" @success="getList" />
|
<UserForm ref="formRef" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup name="MpUser">
|
<script lang="ts" setup name="MpUser">
|
||||||
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import * as MpUserApi from '@/api/mp/user'
|
import * as MpUserApi from '@/api/mp/user'
|
||||||
import * as MpTagApi from '@/api/mp/tag'
|
import * as MpTagApi from '@/api/mp/tag'
|
||||||
|
import WxAccountSelect from '@/views/mp/components/wx-account-select/main.vue'
|
||||||
|
import type { FormInstance } from 'element-plus'
|
||||||
import UserForm from './UserForm.vue'
|
import UserForm from './UserForm.vue'
|
||||||
|
|
||||||
const message = useMessage() // 消息
|
const message = useMessage() // 消息
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
const list = ref([]) // 列表的数据
|
const list = ref<any[]>([]) // 列表的数据
|
||||||
const queryParams = reactive({
|
|
||||||
|
interface QueryParams {
|
||||||
|
pageNo: number
|
||||||
|
pageSize: number
|
||||||
|
accountId?: number
|
||||||
|
openid: string | null
|
||||||
|
nickname: string | null
|
||||||
|
}
|
||||||
|
const queryParams: QueryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
accountId: null,
|
accountId: undefined,
|
||||||
openid: null,
|
openid: null,
|
||||||
nickname: null
|
nickname: null
|
||||||
})
|
})
|
||||||
const tagList = ref([]) // 公众号标签列表
|
const queryFormRef = ref<FormInstance | null>(null) // 搜索的表单
|
||||||
|
const tagList = ref<any[]>([]) // 公众号标签列表
|
||||||
/** 初始化 */
|
|
||||||
onMounted(async () => {
|
|
||||||
tagList.value = await MpTagApi.getSimpleTagList()
|
|
||||||
})
|
|
||||||
|
|
||||||
/** 侦听公众号变化 **/
|
/** 侦听公众号变化 **/
|
||||||
const accountChanged = (accountId) => {
|
const onAccountChanged = (id?: number) => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
queryParams.accountId = accountId
|
queryParams.accountId = id
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,20 +143,38 @@ const getList = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 搜索按钮操作 */
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置按钮操作 */
|
||||||
|
const resetQuery = () => {
|
||||||
|
const accountId = queryParams.accountId
|
||||||
|
queryFormRef.value?.resetFields()
|
||||||
|
queryParams.accountId = accountId
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
/** 添加/修改操作 */
|
/** 添加/修改操作 */
|
||||||
const formRef = ref()
|
const formRef = ref<InstanceType<typeof UserForm> | null>(null)
|
||||||
const openForm = (id: number) => {
|
const openForm = (id: number) => {
|
||||||
formRef.value.open(id)
|
formRef.value?.open(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 同步标签 */
|
/** 同步标签 */
|
||||||
const handleSync = async () => {
|
const handleSync = async () => {
|
||||||
const accountId = queryParams.accountId
|
|
||||||
try {
|
try {
|
||||||
await message.confirm('是否确认同步粉丝?')
|
await message.confirm('是否确认同步粉丝?')
|
||||||
await MpUserApi.syncUser(accountId)
|
await MpUserApi.syncUser(queryParams.accountId)
|
||||||
message.success('开始从微信公众号同步粉丝信息,同步需要一段时间,建议稍后再查询')
|
message.success('开始从微信公众号同步粉丝信息,同步需要一段时间,建议稍后再查询')
|
||||||
await getList()
|
await getList()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
onMounted(async () => {
|
||||||
|
tagList.value = await MpTagApi.getSimpleTagList()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
Reference in New Issue
Block a user