This commit is contained in:
Your Name
2026-03-01 14:17:59 +08:00
parent 65aa12f146
commit a07e844c47
6071 changed files with 777651 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
<template>
<div>
<el-card class="!border-none" shadow="never">
<el-alert type="warning" title="温馨提示:H5设置" :closable="false" show-icon />
</el-card>
<el-card class="!border-none mt-4" shadow="never">
<el-form ref="formRef" :model="formData" label-width="120px">
<el-form-item label="渠道状态" required prop="status">
<div>
<el-radio-group v-model="formData.status">
<el-radio :value="1">开启</el-radio>
<el-radio :value="0">关闭</el-radio>
</el-radio-group>
<div class="form-tips">状态为关闭时,将不对外提供服务,请谨慎操作</div>
</div>
</el-form-item>
<el-form-item label="关闭后访问页面" prop="page_status">
<el-radio-group v-model="formData.page_status">
<el-radio :value="0">空页面</el-radio>
<el-radio :value="1">自定义链接</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="" prop="page_url" v-if="formData.page_status == 1">
<div class="w-80">
<el-input v-model="formData.page_url" placeholder="请输入完整的url" />
</div>
</el-form-item>
<el-form-item label="访问链接">
<div class="flex-1 min-w-0 break-words">
{{ formData.url }}
<el-button v-copy="formData.url">复制</el-button>
</div>
</el-form-item>
</el-form>
</el-card>
<footer-btns v-perms="['channel.web_page_setting/setConfig']">
<el-button type="primary" @click="handelSave">保存</el-button>
</footer-btns>
</div>
</template>
<script lang="ts" setup name="h5Config">
import { getH5Config, setH5Config } from '@/api/channel/h5'
const formData = reactive({
status: 0,
page_status: 0,
page_url: '',
url: ''
})
const getDetail = async () => {
const data = await getH5Config()
for (const key in formData) {
//@ts-ignore
formData[key] = data[key]
}
}
const handelSave = async () => {
await setH5Config(formData)
getDetail()
}
getDetail()
</script>
+76
View File
@@ -0,0 +1,76 @@
<template>
<div>
<el-card class="!border-none" shadow="never">
<el-alert
type="warning"
title="温馨提示:填写微信开放平台开发配置,请前往微信开放平台创建应用并完成认证;网站应用配置主要用于网站微信登录和微信支付"
:closable="false"
show-icon
/>
</el-card>
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="160px">
<el-card class="!border-none mt-4" shadow="never">
<div class="font-medium mb-7">网站应用</div>
<el-form-item label="AppID" prop="app_id">
<div class="w-80">
<el-input v-model="formData.app_id" placeholder="请输入AppID" />
</div>
</el-form-item>
<el-form-item label="AppSecret" prop="app_secret">
<div>
<div class="w-80">
<el-input v-model="formData.app_secret" placeholder="请输入AppSecret" />
</div>
</div>
</el-form-item>
</el-card>
</el-form>
<footer-btns v-perms="['channel.open_setting/setConfig']">
<el-button type="primary" @click="handelSave">保存</el-button>
</footer-btns>
</div>
</template>
<script lang="ts" setup name="wxDevConfig">
import type { FormInstance } from 'element-plus'
import { getOpenSettingConfig, setOpenSettingConfig } from '@/api/channel/open_setting'
const formData = reactive({
app_id: '',
app_secret: ''
})
const formRef = shallowRef<FormInstance>()
const formRules = {
app_id: [
{
required: true,
message: '请输入AppID',
trigger: ['blur', 'change']
}
],
app_secret: [
{
required: true,
message: '请输入AppSecret',
trigger: ['blur', 'change']
}
]
}
const getDetail = async () => {
const data = await getOpenSettingConfig()
for (const key in formData) {
//@ts-ignore
formData[key] = data[key]
}
}
const handelSave = async () => {
await formRef.value?.validate()
await setOpenSettingConfig(formData)
getDetail()
}
getDetail()
</script>
+199
View File
@@ -0,0 +1,199 @@
<template>
<div>
<el-card class="!border-none" shadow="never">
<el-alert
type="warning"
title="温馨提示:填写微信小程序开发配置,请前往微信公众平台申请小程序并完成认证"
:closable="false"
show-icon
/>
</el-card>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
:label-width="appStore.isMobile ? '80px' : '160px'"
>
<el-card class="!border-none mt-4" shadow="never">
<div class="font-medium mb-7">微信小程序</div>
<el-form-item label="小程序名称" prop="name">
<div class="w-80">
<el-input v-model="formData.name" placeholder="请输入小程序名称" />
</div>
</el-form-item>
<el-form-item label="原始ID" prop="original_id">
<div class="w-80">
<el-input v-model="formData.original_id" placeholder="请输入原始ID" />
</div>
</el-form-item>
<el-form-item label="小程序码" prop="qr_code">
<div class="flex-1">
<div>
<material-picker v-model="formData.qr_code" :limit="1" />
</div>
<div class="form-tips">建议尺寸:宽400px*高400px。jpg,jpeg,png格式</div>
</div>
</el-form-item>
</el-card>
<el-card class="!border-none mt-4" shadow="never">
<div class="font-medium mb-7">开发者ID</div>
<el-form-item label="AppID" prop="app_id">
<div class="w-80">
<el-input v-model="formData.app_id" placeholder="请输入AppID" />
</div>
</el-form-item>
<el-form-item label="AppSecret" prop="app_secret">
<div class="w-80">
<el-input v-model="formData.app_secret" placeholder="请输入AppSecret" />
</div>
</el-form-item>
<el-form-item>
<div class="form-tips">
小程序账号登录微信公众平台,点击开发>开发设置->开发者ID,设置AppID和AppSecret
</div>
</el-form-item>
</el-card>
<el-card class="!border-none mt-4" shadow="never">
<div class="font-medium mb-7">服务器域名</div>
<el-form-item label="request合法域名" prop="appId">
<div class="flex-1 min-w-0">
<div class="sm:flex">
<div class="mr-4 sm:w-80 flex">
<el-input v-model="formData.request_domain" disabled />
</div>
<el-button v-copy="formData.request_domain">复制</el-button>
</div>
<div class="form-tips">
小程序账号登录微信公众平台,点击开发>开发设置->服务器域名,填写https协议域名
</div>
</div>
</el-form-item>
<el-form-item label="socket合法域名">
<div class="flex-1 min-w-0">
<div class="sm:flex">
<div class="mr-4 sm:w-80 flex">
<el-input v-model="formData.socket_domain" disabled />
</div>
<el-button v-copy="formData.socket_domain">复制</el-button>
</div>
<div class="form-tips">
小程序账号登录微信公众平台,点击开发>开发设置->服务器域名,填写wss协议域名
</div>
</div>
</el-form-item>
<el-form-item label="uploadFile合法域名">
<div class="flex-1 min-w-0">
<div class="sm:flex">
<div class="mr-4 sm:w-80 flex">
<el-input v-model="formData.upload_file_domain" disabled />
</div>
<el-button v-copy="formData.upload_file_domain">复制</el-button>
</div>
<div class="form-tips">
小程序账号登录微信公众平台,点击开发>开发设置->服务器域名,填写https协议域名
</div>
</div>
</el-form-item>
<el-form-item label="downloadFile合法域名">
<div class="flex-1 min-w-0">
<div class="sm:flex">
<div class="mr-4 sm:w-80 flex">
<el-input v-model="formData.download_file_domain" disabled />
</div>
<el-button v-copy="formData.download_file_domain">复制</el-button>
</div>
<div class="form-tips">
小程序账号登录微信公众平台,点击开发>开发设置->服务器域名,填写https协议域名
</div>
</div>
</el-form-item>
<el-form-item label="udp合法域名">
<div class="flex-1 min-w-0">
<div class="sm:flex">
<div class="mr-4 sm:w-80 flex">
<el-input v-model="formData.udp_domain" disabled />
</div>
<el-button v-copy="formData.udp_domain">复制</el-button>
</div>
<div class="form-tips">
小程序账号登录微信公众平台,点击开发>开发设置->服务器域名,填写udp协议域名
</div>
</div>
</el-form-item>
</el-card>
<el-card class="!border-none mt-4" shadow="never">
<div class="font-medium mb-7">业务域名</div>
<el-form-item label="业务域名">
<div class="flex-1 min-w-0">
<div class="sm:flex">
<div class="mr-4 sm:w-80 flex">
<el-input v-model="formData.business_domain" disabled />
</div>
<el-button v-copy="formData.business_domain">复制</el-button>
</div>
<div class="form-tips">
小程序账号登录微信公众平台,点击开发>开发设置->业务域名,填写业务域名
</div>
</div>
</el-form-item>
</el-card>
</el-form>
<footer-btns v-perms="['channel.mnp_settings/setConfig']">
<el-button type="primary" @click="handelSave">保存</el-button>
</footer-btns>
</div>
</template>
<script lang="ts" setup name="weappConfig">
import type { FormInstance } from 'element-plus'
import { getWeappConfig, setWeappConfig } from '@/api/channel/weapp'
import useAppStore from '@/stores/modules/app'
const appStore = useAppStore()
const formData = reactive({
name: '',
original_id: '',
qr_code: '',
app_id: '',
app_secret: '',
business_domain: '',
download_file_domain: '',
request_domain: '',
socket_domain: '',
tcpDomain: '',
udp_domain: '',
upload_file_domain: ''
})
const formRef = shallowRef<FormInstance>()
const formRules = {
app_id: [
{
required: true,
message: '请输入AppID',
trigger: ['blur', 'change']
}
],
app_secret: [
{
required: true,
message: '请输入AppSecret',
trigger: ['blur', 'change']
}
]
}
const getDetail = async () => {
const data = await getWeappConfig()
for (const key in formData) {
//@ts-ignore
formData[key] = data[key]
}
}
const handelSave = async () => {
await formRef.value?.validate()
await setWeappConfig(formData)
getDetail()
}
getDetail()
</script>
+217
View File
@@ -0,0 +1,217 @@
<template>
<div>
<el-card class="!border-none" shadow="never">
<el-alert
type="warning"
title="温馨提示:填写微信公众号开发配置,请前往微信公众平台申请服务号并完成认证"
:closable="false"
show-icon
/>
</el-card>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
:label-width="appStore.isMobile ? '80px' : '160px'"
>
<el-card class="!border-none mt-4" shadow="never">
<div class="font-medium mb-7">微信公众号</div>
<el-form-item label="公众号名称" prop="name">
<div class="w-80">
<el-input v-model="formData.name" placeholder="请输入公众号名称" />
</div>
</el-form-item>
<el-form-item label="原始ID" prop="original_id">
<div class="w-80">
<el-input v-model="formData.original_id" placeholder="请输入原始ID" />
</div>
</el-form-item>
<el-form-item label="公众号二维码" prop="qr_code">
<div>
<div>
<material-picker v-model="formData.qr_code" :limit="1" />
</div>
<div class="form-tips">建议尺寸:宽400px*高400px。jpg,jpeg,png格式</div>
</div>
</el-form-item>
</el-card>
<el-card class="!border-none mt-4" shadow="never">
<div class="font-medium mb-7">公众号开发者信息</div>
<el-form-item label="AppID" prop="app_id">
<div class="w-80">
<el-input v-model="formData.app_id" placeholder="请输入AppID" />
</div>
</el-form-item>
<el-form-item label="AppSecret" prop="app_secret">
<div class="w-80">
<el-input v-model="formData.app_secret" placeholder="请输入AppSecret" />
</div>
</el-form-item>
<el-form-item>
<div class="form-tips">
小程序账号登录微信公众平台,点击开发>开发设置->开发者ID,设置AppID和AppSecret
</div>
</el-form-item>
</el-card>
<el-card class="!border-none mt-4" shadow="never">
<div class="font-medium mb-7">服务器配置</div>
<el-form-item label="URL">
<div class="flex-1 min-w-0">
<div class="sm:flex">
<div class="mr-4 sm:w-80 flex">
<el-input v-model="formData.url" disabled />
</div>
<el-button v-copy="formData.url">复制</el-button>
</div>
<div class="form-tips">
登录微信公众平台,点击开发>基本配置>服务器配置,填写服务器地址(URL)
</div>
</div>
</el-form-item>
<el-form-item label="Token" prop="Token">
<div class="flex-1 min-w-0">
<div class="w-80">
<el-input v-model="formData.token" placeholder="请输入Token" />
</div>
<div class="form-tips">
登录微信公众平台,点击开发>基本配置>服务器配置,设置令牌Token。不填默认为“likeshop”
</div>
</div>
</el-form-item>
<el-form-item label="EncodingAESKey" prop="encoding_aes_key">
<div class="flex-1 min-w-0">
<div class="w-80">
<el-input
v-model="formData.encoding_aes_key"
placeholder="请输入EncodingAESKey"
/>
</div>
<div class="form-tips">
消息加密密钥由43位字符组成,字符范围为A-Z,a-z,0-9
</div>
</div>
</el-form-item>
<el-form-item label="消息加密方式" required prop="encryption_type">
<div class="flex-1 min-w-0">
<el-radio-group
class="flex-col !items-start min-w-0"
v-model="formData.encryption_type"
>
<el-radio :value="1">
明文模式 (不使用消息体加解密功能,安全系数较低)
</el-radio>
<el-radio :value="2">
兼容模式 (明文、密文将共存,方便开发者调试和维护)
</el-radio>
<el-radio :value="3">
安全模式(推荐) (消息包为纯密文,需要开发者加密和解密,安全系数高)
</el-radio>
</el-radio-group>
</div>
</el-form-item>
</el-card>
<el-card class="!border-none mt-4" shadow="never">
<div class="font-medium mb-7">功能设置</div>
<el-form-item label="业务域名">
<div class="flex-1 min-w-0">
<div class="sm:flex">
<div class="mr-4 sm:w-80 flex">
<el-input v-model="formData.business_domain" disabled />
</div>
<el-button v-copy="formData.business_domain">复制</el-button>
</div>
<div class="form-tips">
登录微信公众平台,点击设置>公众号设置>功能设置,填写业务域名
</div>
</div>
</el-form-item>
<el-form-item label="JS接口安全域名">
<div class="flex-1 min-w-0">
<div class="sm:flex">
<div class="mr-4 sm:w-80 flex">
<el-input v-model="formData.js_secure_domain" disabled />
</div>
<el-button v-copy="formData.js_secure_domain">复制</el-button>
</div>
<div class="form-tips">
登录微信公众平台,点击设置>公众号设置>功能设置,填写JS接口安全域名
</div>
</div>
</el-form-item>
<el-form-item label="网页授权域名">
<div class="flex-1 min-w-0">
<div class="sm:flex">
<div class="mr-4 sm:w-80 flex">
<el-input v-model="formData.web_auth_domain" disabled />
</div>
<el-button v-copy="formData.web_auth_domain">复制</el-button>
</div>
<div class="form-tips">
登录微信公众平台,点击设置>公众号设置>功能设置,填写网页授权域名
</div>
</div>
</el-form-item>
</el-card>
</el-form>
<footer-btns v-perms="['channel.official_account_setting/setConfig']">
<el-button type="primary" @click="handelSave">保存</el-button>
</footer-btns>
</div>
</template>
<script lang="ts" setup name="wxOaConfig">
import type { FormInstance } from 'element-plus'
import { getOaConfig, setOaConfig } from '@/api/channel/wx_oa'
import useAppStore from '@/stores/modules/app'
const appStore = useAppStore()
const formData = reactive({
name: '',
original_id: ' ',
qr_code: '',
app_id: '',
app_secret: '',
url: '',
token: '',
encoding_aes_key: '',
encryption_type: 1,
business_domain: '',
js_secure_domain: '',
web_auth_domain: ''
})
const formRef = shallowRef<FormInstance>()
const formRules = {
app_id: [
{
required: true,
message: '请输入AppID',
trigger: ['blur', 'change']
}
],
app_secret: [
{
required: true,
message: '请输入AppSecret',
trigger: ['blur', 'change']
}
]
}
const getDetail = async () => {
const data = await getOaConfig()
for (const key in formData) {
//@ts-ignore
formData[key] = data[key]
}
}
const handelSave = async () => {
await formRef.value?.validate()
await setOaConfig(formData)
getDetail()
}
getDetail()
</script>
+47
View File
@@ -0,0 +1,47 @@
<script setup lang="ts" name="wxOaMenu">
import OaAttr from './menu_com/oa-attr.vue'
import OaPhone from './menu_com/oa-phone.vue'
import { useMenuOa } from './menu_com/useMenuOa'
const { getOaMenuFunc, handleSave, handlePublish } = useMenuOa(undefined)
getOaMenuFunc()
</script>
<template>
<div class="menu-oa">
<el-card class="!border-none" shadow="never">
<el-alert
type="warning"
title="配置微信公众号菜单,点击确认,保存菜单并发布至微信公众号"
:closable="false"
show-icon
/>
</el-card>
<el-card class="!border-none mt-4" shadow="never">
<div class="lg:flex flex-1">
<!-- Phone -->
<oa-phone></oa-phone>
<!-- Attr -->
<div class="mt-4 lg:mt-0 max-w-[400px]">
<oa-attr></oa-attr>
</div>
</div>
</el-card>
<footer-btns>
<el-button type="primary" @click="handleSave" v-perms="['channel:oaMenu:save']">
保存
</el-button>
<el-button type="primary" @click="handlePublish" v-perms="['channel:oaMenu:publish']">
发布
</el-button>
</footer-btns>
</div>
</template>
<style lang="scss" scoped>
.menu-oa {
}
</style>
@@ -0,0 +1,90 @@
<script lang="ts" setup>
import oaMenuForm from './oa-menu-form.vue'
import oaMenuFormEdit from './oa-menu-form-edit.vue'
import { useMenuOa } from './useMenuOa'
const menuRef = shallowRef()
const {
menuList,
menuIndex,
handleAddSubMenu,
handleEditSubMenu,
handleDelMenu,
handleDelSubMenu
} = useMenuOa(menuRef)
</script>
<template>
<!-- Attr -->
<template v-for="(attrItem, attrIndex) in menuList" :key="attrIndex">
<div class="flex-1 oa-attr min-w-0" v-show="attrIndex === menuIndex">
<div class="text-base oa-attr-title">菜单配置</div>
<del-wrap @close="handleDelMenu(menuIndex)">
<div class="flex items-center w-full p-4 mt-4 rounded bg-fill-light">
<oa-menu-form
ref="menuRef"
modular="master"
v-model:name="attrItem.name"
v-model:menuType="attrItem.has_menu"
v-model:visitType="attrItem.type"
v-model:url="attrItem.url"
v-model:appId="attrItem.appid"
v-model:pagePath="attrItem.pagepath"
>
<div class="flex-1">
<!-- 编辑子菜单 -->
<ul>
<li
class="flex"
v-for="(subItem, subIndex) in attrItem.sub_button"
:key="subIndex"
style="padding: 8px"
>
<span class="mr-auto">{{ subItem.name }}</span>
<!-- 编辑子菜单 -->
<oa-menu-form-edit
modular="edit"
:subItem="subItem"
@edit="handleEditSubMenu($event, subIndex)"
>
<el-button link>
<el-icon><EditPen /></el-icon>
</el-button>
</oa-menu-form-edit>
<!-- 删除子菜单 -->
<popup @confirm="handleDelSubMenu(menuIndex, subIndex)">
是否删除当前子菜单?
<template #trigger>
<el-button link>
<el-icon class="ml-5"><Delete /></el-icon>
</el-button>
</template>
</popup>
</li>
</ul>
<!-- 新增子菜单 -->
<oa-menu-form-edit modular="add" @add="handleAddSubMenu">
<el-button
type="primary"
link
:disabled="attrItem.sub_button.length >= 5"
>
添加子菜单({{ attrItem.sub_button.length }}/5)
</el-button>
</oa-menu-form-edit>
</div>
</oa-menu-form>
</div>
</del-wrap>
</div>
</template>
</template>
<style lang="scss" scoped>
.oa-attr {
}
</style>
@@ -0,0 +1,73 @@
<script lang="ts" setup>
import oaMenuForm from './oa-menu-form.vue'
const emit = defineEmits<{
(event: 'add', value: any): void
(event: 'edit', value: any): void
}>()
const props = withDefaults(
defineProps<{
modular: string
subItem?: any
}>(),
{
modular: 'edit',
subItem: {}
}
)
const menuFormEditRef = shallowRef()
const menuFromPopupRef = shallowRef()
const form = {
name: '',
type: 'view',
url: '',
appid: '',
pagepath: ''
}
watchEffect(() => {
if (Object.keys(props.subItem).length != 0) {
for (const key in form) {
//@ts-ignore
form[key] = props.subItem[key]
}
}
})
const handleRules = async () => {
await menuFormEditRef.value.menuFormRef.validate()
if (props.modular === 'edit') {
emit('edit', { ...form })
} else {
emit('add', { ...form })
}
menuFromPopupRef.value.close()
menuFormEditRef.value.menuFormRef.resetFields()
}
</script>
<template>
<popup
ref="menuFromPopupRef"
async
:clickModalClose="false"
:title="`${modular === 'add' ? '新增' : '编辑'}子菜单`"
@confirm="handleRules"
>
<oa-menu-form
ref="menuFormEditRef"
modular="secondary"
v-model:name="form.name"
v-model:visitType="form.type"
v-model:url="form.url"
v-model:appId="form.appid"
v-model:pagePath="form.pagepath"
></oa-menu-form>
<template #trigger>
<slot></slot>
</template>
</popup>
</template>
@@ -0,0 +1,109 @@
<template>
<el-form ref="menuFormRef" :rules="rules" :model="menuForm" label-width="100px">
<!-- 菜单名称 -->
<el-form-item :label="modular === 'master' ? '主菜单名称' : '子菜单名称'" prop="name">
<el-input v-model="menuForm.name" />
</el-form-item>
<!-- 菜单类型 -->
<el-form-item label="主菜单类型" prop="menuType" v-if="modular === 'master'">
<el-radio-group v-model="menuForm.menuType">
<el-radio :value="false">不配置子菜单</el-radio>
<el-radio :value="true">配置子菜单</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="" v-if="menuForm.menuType && modular === 'master'">
<slot></slot>
</el-form-item>
<template v-if="!menuForm.menuType">
<!-- 跳转链接 -->
<el-form-item label="跳转链接" prop="visitType">
<el-radio-group v-model="menuForm.visitType">
<el-radio value="view">网页</el-radio>
<el-radio value="miniprogram">小程序</el-radio>
</el-radio-group>
</el-form-item>
<!-- 网址 -->
<el-form-item label="网址" prop="url">
<el-input v-model="menuForm.url" />
</el-form-item>
<template v-if="menuForm.visitType == 'miniprogram'">
<!-- AppId -->
<el-form-item label="AppId" prop="appId">
<el-input v-model="menuForm.appId" />
</el-form-item>
<!-- 路径 -->
<el-form-item label="路径" prop="pagePath">
<el-input v-model="menuForm.pagePath" />
</el-form-item>
</template>
</template>
</el-form>
</template>
<script lang="ts" setup>
import type { FormInstance } from 'element-plus'
import { rules } from './useMenuOa'
const emit = defineEmits([
'update:name',
'update:menuType',
'update:visitType',
'update:url',
'update:appId',
'update:pagePath'
])
const props = withDefaults(
defineProps<{
modular?: string
name?: string
menuType?: boolean
visitType?: string
url?: string
appId?: string
pagePath?: string
}>(),
{
modular: 'master',
name: '',
menuType: false,
visitType: 'view',
url: '',
appId: '',
pagePath: ''
}
)
const menuFormRef = shallowRef<FormInstance>()
// 表单数据
const menuForm = ref({ ...props })
watch(
() => props,
(value) => {
menuForm.value = value
},
{ immediate: true }
)
watchEffect(() => {
if (props.modular === 'master') {
emit('update:menuType', menuForm.value.menuType)
}
emit('update:name', menuForm.value.name)
emit('update:visitType', menuForm.value.visitType)
emit('update:url', menuForm.value.url)
emit('update:appId', menuForm.value.appId)
emit('update:pagePath', menuForm.value.pagePath)
})
defineExpose({
menuFormRef
})
</script>
@@ -0,0 +1,122 @@
<script lang="ts" setup>
import useSettingStore from '@/stores/modules/setting'
import { useMenuOa } from './useMenuOa'
// 菜单颜色(这里采用全局颜色)
const settingStore = useSettingStore()
const themeColor = computed(() => settingStore.theme || '#4A5DFF')
const { menuList, menuIndex, handleAddMenu } = useMenuOa(useMenuOa)
</script>
<template>
<!-- Phone -->
<div class="oa-phone mr-[35px]">
<div class="oa-phone-content"></div>
<div class="flex oa-phone-menu">
<div class="flex items-center justify-center oa-phone-menu-switch">
<el-icon>
<Grid />
</el-icon>
</div>
<template v-for="(menuItem, i) in menuList" :key="i">
<div class="relative flex-1" @click="menuIndex = i">
<!-- 一级菜单 -->
<div
class="flex items-center justify-center flex-1 text-sm oa-phone-menu-item"
:class="{ 'active-menu': menuIndex === i }"
>
{{ menuItem.name }}
</div>
<!-- 二级菜单 -->
<div
class="oa-phone-menu-subitem"
v-show="menuItem.sub_button.length && menuItem.has_menu"
>
<template v-for="(subItem, index2) in menuItem.sub_button" :key="index2">
<div class="oa-phone-menu-subitem-title">
{{ subItem.name }}
</div>
</template>
</div>
</div>
</template>
<!-- 新增菜单 -->
<template v-if="menuList.length <= 2">
<div class="flex items-center justify-center flex-1 h-full" @click="handleAddMenu">
<el-icon>
<Plus />
</el-icon>
</div>
</template>
</div>
</div>
</template>
<style lang="scss" scoped>
.oa-phone {
width: 260px;
height: 461px;
border: 1px solid #e5e5ea;
flex: none;
&-content {
height: 420px;
border-bottom: 1px solid #e5e5ea;
}
&-menu {
height: 40px;
cursor: pointer;
&-switch {
width: 40px;
height: 100%;
border-right: 1px solid #e5e5ea;
}
// 一级菜单
&-item {
height: 100%;
border-right: 1px solid #e5e5ea;
}
&-item:nth-child(4) {
border-right: none;
}
.active-menu {
position: relative;
}
.active-menu::after {
content: '';
width: 100%;
height: 41px;
top: -1px;
position: absolute;
border: 1px solid v-bind(themeColor);
}
// 二级菜单
&-subitem {
width: 98%;
left: 2px;
bottom: calc(100% + 10px);
position: absolute;
border: 1px solid #e5e5ea;
border-top: none;
&-title {
height: 40px;
line-height: 40px;
text-align: center;
border-top: 1px solid #e5e5ea;
}
}
}
}
</style>
@@ -0,0 +1,167 @@
import type { FormRules } from 'element-plus'
import { ref } from 'vue'
import type { Menu } from '@/api/channel/wx_oa'
import { getOaMenu, setOaMenuPublish, setOaMenuSave } from '@/api/channel/wx_oa'
import feedback from '@/utils/feedback'
// 菜单实例
export const menuRef = shallowRef()
// 菜单数据
const menuList = ref<Menu[]>([])
const menuIndex = ref<number>(0)
// 校验
export const rules = reactive<FormRules>({
name: [
{
required: true,
message: '必填项不能为空',
trigger: ['blur', 'change']
},
{
min: 1,
max: 12,
message: '长度限制12个字符',
trigger: ['blur', 'change']
}
],
menuType: [
{
required: true,
message: '必填项不能为空',
trigger: ['blur', 'change']
}
],
visitType: [
{
required: true,
message: '必填项不能为空',
trigger: ['blur', 'change']
}
],
url: [
{
required: true,
message: '必填项不能为空',
trigger: ['blur', 'change']
},
{
pattern:
// eslint-disable-next-line no-useless-escape
/(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/,
message: '请输入合法的网址链接',
trigger: ['blur', 'change']
}
],
appId: [
{
required: true,
message: '必填项不能为空',
trigger: ['blur', 'change']
}
],
pagePath: [
{
required: true,
message: '必填项不能为空',
trigger: ['blur', 'change']
}
]
})
export const useMenuOa = (ref: any) => {
if (ref) menuRef.value = ref
// 添加主菜单
const handleAddMenu = () => {
menuList.value.push({
name: '菜单名称',
has_menu: false,
type: 'view',
url: '',
appid: '',
pagepath: '',
sub_button: []
})
}
// 添加子菜单
const handleAddSubMenu = (event?: Menu) => {
const index = menuIndex.value
if (menuList.value[index].sub_button.length >= 5) {
feedback.msgError('已添加上限~')
return
}
menuList.value[index].sub_button.push(event)
}
// 编辑子菜单
const handleEditSubMenu = (event: Menu, subIndex: number) => {
const index = menuIndex.value
menuList.value[index].sub_button[subIndex] = event
}
// 删除主菜单
const handleDelMenu = (index: number) => {
if (index != 0) {
menuIndex.value--
}
menuList.value.splice(index, 1)
}
// 删除子菜单
const handleDelSubMenu = (index: number, subIndex: number) => {
menuList.value[index].sub_button.splice(subIndex, 1)
}
// 获取菜单
const getOaMenuFunc = async () => {
try {
menuList.value = await getOaMenu()
} catch (error) {
console.log('获取菜单=>', error)
}
}
// 保存菜单
const handleSave = async () => {
const refs = menuRef.value.value
for (let i = 0; i < refs.length; i++) {
try {
await refs[i].menuFormRef.validate()
} catch (error) {
menuIndex.value = i
return
}
}
await setOaMenuSave(menuList.value)
}
// 保存菜单
const handlePublish = async () => {
const refs = menuRef.value.value
for (let i = 0; i < refs.length; i++) {
try {
await refs[i].menuFormRef.validate()
} catch (error) {
menuIndex.value = i
return
}
}
await setOaMenuPublish(menuList.value)
}
return {
menuList,
menuIndex,
handleAddMenu,
handleAddSubMenu,
handleEditSubMenu,
handleDelMenu,
handleDelSubMenu,
getOaMenuFunc,
handleSave,
handlePublish
}
}
@@ -0,0 +1,111 @@
<template>
<div>
<el-card class="!border-none" shadow="never">
<el-alert
type="warning"
title="温馨提示:1.粉丝在公众号发送内容时,系统无法匹配情况下发送启用的默认文本回复;2.同时只能启用一个默认回复。"
:closable="false"
show-icon
/>
</el-card>
<el-card class="!border-none mt-4" shadow="never">
<div>
<el-button class="mb-4" type="primary" @click="handleAdd()">
<template #icon>
<icon name="el-icon-Plus" />
</template>
新增
</el-button>
</div>
<el-table size="large" :data="pager.lists" v-loading="pager.loading">
<el-table-column label="规则名称" prop="name" min-width="120" />
<el-table-column label="回复类型" min-width="120">
<template #default="{ row }">
{{ getContentType(row.content_type) }}
</template>
</el-table-column>
<el-table-column label="回复内容" prop="content" min-width="120" />
<el-table-column label="状态" min-width="120">
<template #default="{ row }">
<el-switch
v-model="row.status"
:active-value="1"
:inactive-value="0"
@change="changeStatus(row.id)"
/>
</template>
</el-table-column>
<el-table-column label="排序" prop="sort" min-width="120" />
<el-table-column label="操作" width="120" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)"> 编辑 </el-button>
<el-button type="danger" link @click="handleDelete(row.id)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="flex justify-end mt-4">
<pagination v-model="pager" @change="getLists" />
</div>
</el-card>
<edit-popup v-if="showEdit" ref="editRef" @success="getLists" @close="showEdit = false" />
</div>
</template>
<script lang="ts" setup>
import { changeOaReplyStatus, getOaReplyList, oaReplyDel } from '@/api/channel/wx_oa'
import { usePaging } from '@/hooks/usePaging'
import feedback from '@/utils/feedback'
import EditPopup from './edit.vue'
const editRef = shallowRef<InstanceType<typeof EditPopup>>()
const showEdit = ref(false)
const getContentType = computed(() => {
return (val: number) => {
switch (val) {
case 1:
return '文本'
}
}
})
const { pager, getLists } = usePaging({
fetchFun: getOaReplyList,
params: {
reply_type: 3
}
})
const handleAdd = async () => {
showEdit.value = true
await nextTick()
editRef.value?.open('add', 3)
}
const handleEdit = async (data: any) => {
showEdit.value = true
await nextTick()
editRef.value?.open('edit', 3)
editRef.value?.getDetail(data)
}
const handleDelete = async (id: number) => {
await feedback.confirm('确定要删除?')
await oaReplyDel({ id })
feedback.msgSuccess('删除成功')
getLists()
}
const changeStatus = async (id: number) => {
try {
await changeOaReplyStatus({ id })
getLists()
} catch (error) {
getLists()
}
}
getLists()
</script>
@@ -0,0 +1,190 @@
<template>
<div class="edit-popup">
<popup
ref="popupRef"
:title="popupTitle"
:async="true"
width="500px"
@confirm="handleSubmit"
@close="handleClose"
>
<el-form
ref="formRef"
:model="formData"
label-width="84px"
:rules="formRules"
class="pr-10"
>
<el-form-item label="规则名称" prop="name">
<div class="flex-1">
<el-input v-model="formData.name" placeholder="请输入规则名称" />
<div class="form-tips">方便通过名称管理关注回复内容</div>
</div>
</el-form-item>
<el-form-item label="关键词" prop="keyword" v-if="formData.reply_type == 2">
<div class="flex-1">
<el-input v-model="formData.keyword" placeholder="请输入关键词" />
<div class="form-tips">方便通过名称管理关注回复内容</div>
</div>
</el-form-item>
<el-form-item label="匹配方式" prop="matching_type" v-if="formData.reply_type == 2">
<div class="flex-1">
<el-radio-group v-model="formData.matching_type">
<el-radio :value="1">全匹配</el-radio>
<el-radio :value="2">模糊匹配</el-radio>
</el-radio-group>
<div class="form-tips">模糊匹配时,关键词部分匹配用户输入的内容即可</div>
</div>
</el-form-item>
<el-form-item label="回复类型" prop="content_type" :min="0">
<div class="flex-1">
<el-radio-group v-model="formData.content_type">
<el-radio :value="1">文本</el-radio>
</el-radio-group>
<div class="form-tips">暂时只支持文本类型</div>
</div>
</el-form-item>
<el-form-item label="回复内容" prop="content">
<div class="flex-1">
<el-input
v-model="formData.content"
:autosize="{ minRows: 4, maxRows: 4 }"
type="textarea"
maxlength="200"
show-word-limit
placeholder="请输入回复内容"
/>
</div>
</el-form-item>
<el-form-item label="排序">
<div class="flex-1">
<el-input-number v-model="formData.sort" :min="0" :max="9999" />
</div>
</el-form-item>
<el-form-item
label="回复数量"
prop="reply_num"
required
v-if="formData.reply_type == 2"
>
<div class="flex-1">
<el-radio-group v-model="formData.reply_num">
<el-radio :value="1">回复匹配首词条</el-radio>
</el-radio-group>
<div class="form-tips">
设置关键词匹配多条时回复的数量,暂时支持回复一条内容
</div>
</div>
</el-form-item>
<el-form-item label="启用状态">
<el-switch v-model="formData.status" :active-value="1" :inactive-value="0" />
</el-form-item>
</el-form>
</popup>
</div>
</template>
<script lang="ts" setup>
import type { FormInstance } from 'element-plus'
import type { FormRules } from 'element-plus'
import { getOaReplyDetail, oaReplyAdd, oaReplyEdit } from '@/api/channel/wx_oa'
import Popup from '@/components/popup/index.vue'
const emit = defineEmits(['success', 'close'])
const formRef = shallowRef<FormInstance>()
const popupRef = shallowRef<InstanceType<typeof Popup>>()
const mode = ref('add')
const popupTitle = computed(() => {
return mode.value == 'edit' ? '编辑' : '新增'
})
const formData = reactive({
id: '',
name: '',
reply_type: 0,
content_type: 1,
keyword: '',
content: '',
matching_type: 1,
status: 1,
sort: 0,
reply_num: 1
})
const formRules: FormRules = {
name: [
{
required: true,
message: '请输入规则名称',
trigger: ['blur']
}
],
keyword: [
{
required: true,
message: '请输入关键词',
trigger: ['blur']
}
],
matching_type: [
{
required: true,
message: '请选择匹配方式',
trigger: ['blur']
}
],
content_type: [
{
required: true,
message: '请选择回复类型',
trigger: ['blur']
}
],
content: [
{
required: true,
message: '请输入回复内容',
trigger: ['blur']
}
]
}
const handleSubmit = async () => {
await formRef.value?.validate()
mode.value == 'edit' ? await oaReplyEdit(formData) : await oaReplyAdd(formData)
popupRef.value?.close()
emit('success')
}
const open = (modes = 'add', type = 0) => {
mode.value = modes
formData.reply_type = type
popupRef.value?.open()
}
const setFormData = (data: Record<any, any>) => {
for (const key in formData) {
if (data[key] != null && data[key] != undefined) {
//@ts-ignore
formData[key] = data[key]
}
}
}
const getDetail = async (row: Record<string, any>) => {
const data = await getOaReplyDetail({
id: row.id
})
setFormData(data)
}
const handleClose = () => {
emit('close')
}
defineExpose({
open,
setFormData,
getDetail
})
</script>
@@ -0,0 +1,110 @@
<template>
<div>
<el-card class="!border-none" shadow="never">
<el-alert
type="warning"
title="温馨提示:1.粉丝关注公众号时,会自动发送启用的关注回复;2.同时只能启用一个关注回复。"
:closable="false"
show-icon
/>
</el-card>
<el-card class="!border-none mt-4" shadow="never">
<div>
<el-button class="mb-4" type="primary" @click="handleAdd()">
<template #icon>
<icon name="el-icon-Plus" />
</template>
新增
</el-button>
</div>
<el-table size="large" :data="pager.lists" v-loading="pager.loading">
<el-table-column label="规则名称" prop="name" min-width="120" />
<el-table-column label="回复类型" min-width="120">
<template #default="{ row }">
{{ getContentType(row.content_type) }}
</template>
</el-table-column>
<el-table-column label="回复内容" prop="content" min-width="120" />
<el-table-column label="状态" min-width="120">
<template #default="{ row }">
<el-switch
v-model="row.status"
:active-value="1"
:inactive-value="0"
@change="changeStatus(row.id)"
/>
</template>
</el-table-column>
<el-table-column label="排序" prop="sort" min-width="120" />
<el-table-column label="操作" width="120" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)"> 编辑 </el-button>
<el-button type="danger" link @click="handleDelete(row.id)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="flex justify-end mt-4">
<pagination v-model="pager" @change="getLists" />
</div>
</el-card>
<edit-popup v-if="showEdit" ref="editRef" @success="getLists" @close="showEdit = false" />
</div>
</template>
<script lang="ts" setup>
import { changeOaReplyStatus, getOaReplyList, oaReplyDel } from '@/api/channel/wx_oa'
import { usePaging } from '@/hooks/usePaging'
import feedback from '@/utils/feedback'
import EditPopup from './edit.vue'
const editRef = shallowRef<InstanceType<typeof EditPopup>>()
const showEdit = ref(false)
const getContentType = computed(() => {
return (val: number) => {
switch (val) {
case 1:
return '文本'
}
}
})
const { pager, getLists } = usePaging({
fetchFun: getOaReplyList,
params: {
reply_type: 1
}
})
const handleAdd = async () => {
showEdit.value = true
await nextTick()
editRef.value?.open('add', 1)
}
const handleEdit = async (data: any) => {
showEdit.value = true
await nextTick()
editRef.value?.open('edit', 1)
editRef.value?.getDetail(data)
}
const handleDelete = async (id: number) => {
await feedback.confirm('确定要删除?')
await oaReplyDel({ id })
getLists()
}
const changeStatus = async (id: number) => {
try {
await changeOaReplyStatus({ id })
getLists()
} catch (error) {
getLists()
}
}
getLists()
</script>
@@ -0,0 +1,126 @@
<template>
<div>
<el-card class="!border-none" shadow="never">
<el-alert
type="warning"
title="温馨提示:1.粉丝在公众号发送内容时,通过关键词可触发关键词回复;2.同时可启用多个关键词回复,有多条关键词匹配时优选选择排序靠前的一条"
:closable="false"
show-icon
/>
</el-card>
<el-card class="!border-none mt-4" shadow="never">
<div>
<el-button class="mb-4" type="primary" @click="handleAdd()">
<template #icon>
<icon name="el-icon-Plus" />
</template>
新增
</el-button>
</div>
<el-table size="large" :data="pager.lists" v-loading="pager.loading">
<el-table-column label="规则名称" prop="name" min-width="120" />
<el-table-column label="关键词" prop="keyword" min-width="120" />
<el-table-column label="匹配方式" min-width="120">
<template #default="{ row }">
{{ getMatchingType(row.matching_type) }}
</template>
</el-table-column>
<el-table-column label="回复类型" min-width="120">
<template #default="{ row }">
{{ getContentType(row.content_type) }}
</template>
</el-table-column>
<el-table-column label="状态" min-width="120">
<template #default="{ row }">
<el-switch
v-model="row.status"
:active-value="1"
:inactive-value="0"
@change="changeStatus(row.id)"
/>
</template>
</el-table-column>
<el-table-column label="排序" prop="sort" min-width="120" />
<el-table-column label="操作" width="120" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)"> 编辑 </el-button>
<el-button type="danger" link @click="handleDelete(row.id)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="flex justify-end mt-4">
<pagination v-model="pager" @change="getLists" />
</div>
</el-card>
<edit-popup v-if="showEdit" ref="editRef" @success="getLists" @close="showEdit = false" />
</div>
</template>
<script lang="ts" setup>
import { changeOaReplyStatus, getOaReplyList, oaReplyDel } from '@/api/channel/wx_oa'
import { usePaging } from '@/hooks/usePaging'
import feedback from '@/utils/feedback'
import EditPopup from './edit.vue'
const editRef = shallowRef<InstanceType<typeof EditPopup>>()
const showEdit = ref(false)
const getMatchingType = computed(() => {
return (val: number) => {
switch (val) {
case 1:
return '全匹配'
case 2:
return '模糊匹配'
}
}
})
const getContentType = computed(() => {
return (val: number) => {
switch (val) {
case 1:
return '文本'
}
}
})
const { pager, getLists } = usePaging({
fetchFun: getOaReplyList,
params: {
reply_type: 2
}
})
const handleAdd = async () => {
showEdit.value = true
await nextTick()
editRef.value?.open('add', 2)
}
const handleEdit = async (data: any) => {
showEdit.value = true
await nextTick()
editRef.value?.open('edit', 2)
editRef.value?.getDetail(data)
}
const handleDelete = async (id: number) => {
await feedback.confirm('确定要删除?')
await oaReplyDel({ id })
getLists()
}
const changeStatus = async (id: number) => {
try {
await changeOaReplyStatus({ id })
getLists()
} catch (error) {
getLists()
}
}
getLists()
</script>