This commit is contained in:
吴红兵
2025-12-02 10:37:49 +08:00
commit 1f645dad3e
1183 changed files with 147673 additions and 0 deletions

View File

@@ -0,0 +1,149 @@
<template>
<el-dialog v-model="visible" :close-on-click-modal="false" :title="form.id ? $t('common.editBtn') : $t('common.addBtn')" draggable>
<el-form ref="dataFormRef" v-loading="loading" :model="form" :rules="dataRules" label-width="90px">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item :label="t('fans.wxAccountName')" prop="wxAccountName">
<el-input v-model="form.wxAccountName" disabled />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('fans.wxAccountAppid')" prop="wxAccountAppid">
<el-input v-model="form.wxAccountAppid" disabled />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('fans.openid')" prop="openid">
<el-input v-model="form.openid" disabled />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('fans.nickname')" prop="nickname">
<el-input v-model="form.nickname" disabled />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('fans.remark')" prop="remark">
<el-input v-model="form.remark" :placeholder="t('fans.inputremarkTip')" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('fans.tagIds')" prop="tagIds">
<el-select v-model="form.tagIds" :placeholder="t('fans.inputTagTip')" clearable multiple>
<el-option v-for="item in tagOption" :key="item.tagId" :label="item.tag" :value="item.tagId" />
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
<el-button type="primary" @click="onSubmit">{{ $t('common.confirmButtonText') }}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" name="wx-fans" setup>
import { addObj, getObj, putObj } from '/@/api/mp/wx-account-fans';
import { list } from '/@/api/mp/wx-account-tag';
import { useMessage } from '/@/hooks/message';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const emit = defineEmits(['refresh']);
// 定义变量内容
const dataFormRef = ref();
const visible = ref(false);
const loading = ref(false);
const wxAccountAppid = ref();
// 提交表单数据
const form = reactive({
id: '',
});
const dataRules = ref([]);
// 打开弹窗
const openDialog = (row: any, accountId: string) => {
visible.value = true;
form.id = row.id;
wxAccountAppid.value = accountId;
// 重置表单数据
if (dataFormRef.value) {
dataFormRef.value.resetFields();
}
if (form.id) {
getFansData();
}
getTagList();
};
const getFansData = () => {
loading.value = true;
getObj(form.id)
.then((res) => {
Object.assign(form, res.data);
})
.finally(() => {
loading.value = false;
});
};
// 提交
const onSubmit = () => {
dataFormRef.value.validate((valid: boolean) => {
if (!valid) {
return false;
}
loading.value = true;
if (form.id) {
putObj(form)
.then(() => {
useMessage().success(t('common.editSuccessText'));
visible.value = false; // 关闭弹窗
emit('refresh');
})
.catch((err: any) => {
useMessage().error(err.msg);
})
.finally(() => {
loading.value = false;
});
} else {
addObj(form)
.then(() => {
useMessage().success(t('common.addSuccessText'));
visible.value = false; // 关闭弹窗
emit('refresh');
})
.catch((err: any) => {
useMessage().error(err.msg);
})
.finally(() => {
loading.value = false;
});
}
});
};
const tagOption = ref([]);
const getTagList = () => {
list(wxAccountAppid.value).then((res) => {
tagOption.value = res.data;
});
};
// 暴露变量
defineExpose({
openDialog,
});
</script>
<style scoped></style>

View File

@@ -0,0 +1,24 @@
export default {
fans: {
index: '#',
importwxAccountFansTip: 'import WxAccountFans',
id: 'id',
openid: 'openid',
subscribeStatus: 'subscribeStatus',
subscribeTime: 'subscribeTime',
nickname: 'nickname',
gender: 'gender',
language: 'language',
country: 'country',
province: 'province',
isBlack: 'black',
city: 'city',
tagIds: 'tagIds',
headimgUrl: 'headimgUrl',
remark: 'remark',
wxAccountId: 'wxAccountId',
wxAccountName: 'wxAccountName',
wxAccountAppid: 'wxAccountAppid',
inputNicknameTip: 'input nickname',
},
};

View File

@@ -0,0 +1,26 @@
export default {
fans: {
index: '#',
importwxAccountFansTip: '导入微信公众号粉丝表',
id: '主键',
openid: '用户标识',
subscribeStatus: '订阅状态',
subscribeTime: '订阅时间',
nickname: '昵称',
gender: '性别',
language: '语言',
country: '国家',
province: '省份',
isBlack: '黑名单',
city: '城市',
tagIds: '分组',
headimgUrl: ' headimgUrl',
remark: '备注',
wxAccountId: '微信公众号ID',
wxAccountName: '微信公众号',
wxAccountAppid: '公众号appid',
inputremarkTip: '请输入备注',
inputTagTip: '请选择分组',
inputNicknameTip: '请输入粉丝昵称',
},
};

View File

@@ -0,0 +1,222 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch">
<el-form ref="queryRef" :inline="true" :model="state.queryForm" @keyup.enter="getDataList">
<el-form-item :label="$t('fans.nickname')" prop="nickname">
<el-input v-model="state.queryForm.nickname" style="max-width: 180px" :placeholder="$t('fans.inputNicknameTip')" />
</el-form-item>
<el-form-item :label="$t('fans.wxAccountName')" prop="wxAccountAppid">
<el-select v-model="state.queryForm.wxAccountAppid" :placeholder="$t('fans.wxAccountName')" clearable>
<el-option v-for="item in accountList" :key="item.appid" :label="item.name" :value="item.appid" />
</el-select>
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="getDataList">
{{ $t('common.queryBtn') }}
</el-button>
<el-button icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
</el-form-item>
</el-form>
</el-row>
<el-row>
<div class="mb8" style="width: 100%">
<el-button type="primary" class="ml10" icon="Sort" @click="asyncFans" v-auth="'mp_wxaccountfans_sync'">同步</el-button>
<right-toolbar
:export="'mp_wxaccountfans_sync'"
@exportExcel="exportExcel"
v-model:showSearch="showSearch"
class="ml10"
style="float: right; margin-right: 20px"
@queryTable="getDataList"
></right-toolbar>
</div>
</el-row>
<el-table
v-loading="state.loading"
:data="state.dataList"
style="width: 100%"
@sort-change="sortChangeHandle"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column :label="t('fans.index')" type="index" width="60" />
<el-table-column :label="t('fans.openid')" prop="openid" show-overflow-tooltip />
<el-table-column :label="t('fans.subscribeStatus')" prop="subscribeStatus" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="subscribe" :value="scope.row.subscribeStatus"></dict-tag>
</template>
</el-table-column>
<el-table-column :label="t('fans.subscribeTime')" prop="subscribeTime" show-overflow-tooltip />
<el-table-column :label="t('fans.nickname')" prop="nickname" show-overflow-tooltip />
<el-table-column :label="t('fans.language')" prop="language" show-overflow-tooltip />
<el-table-column :label="t('fans.isBlack')" prop="isBlack" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="blackList" :value="scope.row.isBlack"></dict-tag>
</template>
</el-table-column>
<el-table-column :label="t('fans.tagIds')" prop="tagIds" show-overflow-tooltip>
<template #default="scope">
<span v-for="(tag, index) in scope.row.tagList" :key="index">
<el-tag>{{ tag.tag }} </el-tag>&nbsp;&nbsp;
</span>
</template>
</el-table-column>
<el-table-column :label="t('fans.remark')" prop="remark" show-overflow-tooltip />
<el-table-column :label="t('fans.wxAccountName')" prop="wxAccountName" show-overflow-tooltip />
<el-table-column :label="$t('common.action')" width="250" fixed="right">
<template #default="scope">
<el-button icon="edit-pen" text type="primary" @click="formDialogRef.openDialog(scope.row, state.queryForm.wxAccountAppid)"
>{{ $t('common.editBtn') }}
</el-button>
<el-button icon="delete" text type="primary" @click="handleDelete([scope.row.id])">{{ $t('common.delBtn') }} </el-button>
<el-button icon="CircleCheck" text type="primary" @click="handelUnBlack([scope.row.id])" v-if="scope.row.isBlack"> 取消拉黑 </el-button>
<el-button icon="warning" text type="primary" @click="handelBlack([scope.row.id])" v-else> 拉黑 </el-button>
</template>
</el-table-column>
</el-table>
<pagination v-bind="state.pagination" @size-change="sizeChangeHandle" @current-change="currentChangeHandle" />
<form-dialog ref="formDialogRef" @refresh="getDataList"></form-dialog>
</div>
</div>
</template>
<script lang="ts" name="systemWxAccountFans" setup>
import { BasicTableProps, useTable } from '/@/hooks/table';
import { black, delObjs, fetchList, sync, unblack } from '/@/api/mp/wx-account-fans';
import { fetchAccountList } from '/@/api/mp/wx-account';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { useI18n } from 'vue-i18n';
import { useDict } from '/@/hooks/dict';
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const { subscribe } = useDict('subscribe');
// 引入组件
const { t } = useI18n();
// 定义查询字典
const blackList = ref([
{
label: '是',
value: '1',
},
{
label: '否',
value: '0',
},
]);
// 定义变量内容
const formDialogRef = ref();
// 搜索变量
const queryRef = ref();
const showSearch = ref(true);
// 多选变量
const selectObjs = ref([]) as any;
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {},
pageList: fetchList,
createdIsNeed: false,
});
// table hook
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
const accountList = ref([]);
const getAccountList = () => {
fetchAccountList().then((res) => {
accountList.value = res.data;
if (accountList.value.length > 0) {
state.queryForm.wxAccountAppid = accountList.value[0].appid;
getDataList();
}
});
};
watch(
() => state.queryForm.wxAccountAppid,
() => {
getDataList();
}
);
const asyncFans = () => {
if (state.queryForm.wxAccountAppid) {
sync(state.queryForm.wxAccountAppid).then(() => {
useMessage().success('已开始从微信同步粉丝信息,建议等待后查询');
getDataList();
});
} else {
useMessage().error('请选择公众号');
}
};
onMounted(() => {
getAccountList();
});
// 清空搜索条件
const resetQuery = () => {
// 清空搜索条件
queryRef.value.resetFields();
// 清空多选
selectObjs.value = [];
getDataList();
};
// 导出excel
const exportExcel = () => {
downBlobFile('/mp/fans/export', state.queryForm, 'fans.xlsx');
};
// 删除操作
const handleDelete = (ids: string[]) => {
useMessageBox()
.confirm(t('common.delConfirmText'))
.then(() => {
delObjs(ids)
.then(() => {
getDataList();
useMessage().success(t('common.delSuccessText'));
})
.catch((err: any) => {
useMessage().error(err.msg);
});
});
};
const handelBlack = (ids: string[]) => {
useMessageBox()
.confirm('是否要拉黑用户')
.then(() => {
black(ids, state.queryForm.wxAccountAppid)
.then(() => {
getDataList();
useMessage().success('拉黑用户成功');
})
.catch((err: any) => {
useMessage().error(err.msg);
});
});
};
const handelUnBlack = (ids: string[]) => {
useMessageBox()
.confirm('是否要取消拉黑用户')
.then(() => {
unblack(ids, state.queryForm.wxAccountAppid)
.then(() => {
getDataList();
useMessage().success('设置成功');
})
.catch((err: any) => {
useMessage().error(err.msg);
});
});
};
</script>

View File

@@ -0,0 +1,91 @@
<template>
<el-dialog v-model="visible" :close-on-click-modal="false" :title="form.id ? $t('common.editBtn') : $t('common.addBtn')" draggable>
<el-form ref="dataFormRef" v-loading="loading" :model="form" :rules="dataRules" label-width="90px">
<el-row :gutter="24">
<el-col :span="24" class="mb20">
<el-form-item :label="t('wxAccountTag.tag')" prop="tag">
<el-input v-model="form.tag" :placeholder="t('wxAccountTag.inputTagTip')" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" name="WxAccountTagDialog" setup>
import { useMessage } from '/@/hooks/message';
import { addObj, putObj } from '/@/api/mp/wx-account-tag';
import { useI18n } from 'vue-i18n';
// 定义子组件向父组件传值/事件
const emit = defineEmits(['refresh']);
const { t } = useI18n();
// 定义变量内容
const dataFormRef = ref();
const visible = ref(false);
const loading = ref(false);
// 定义字典
// 提交表单数据
const form = reactive({
wxAccountAppid: '',
tag: '',
id: '',
});
// 定义校验规则
const dataRules = ref({});
// 打开弹窗
const openDialog = (row: any, appid: string) => {
visible.value = true;
form.wxAccountAppid = '';
form.tag = '';
form.id = '';
// 重置表单数据
if (dataFormRef.value) {
dataFormRef.value.resetFields();
}
if (row) {
Object.assign(form, row);
}
form.wxAccountAppid = appid;
};
// 提交
const onSubmit = async () => {
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) return false;
loading.value = true;
try {
if (form.id) {
await putObj(form);
useMessage().success(t('common.editSuccessText'));
} else {
await addObj(form);
useMessage().success(t('common.addSuccessText'));
}
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// 暴露变量
defineExpose({
openDialog,
});
</script>

View File

@@ -0,0 +1,18 @@
export default {
wxAccountTag: {
index: '#',
importwxAccountTagTip: 'import WxAccountTag',
id: 'id',
tag: 'tag',
wxAccountId: 'wxAccountId',
wxAccountName: 'wxAccountName',
wxAccountAppid: 'wxAccountAppid',
tagId: 'tagId',
inputIdTip: 'input id',
inputTagTip: 'input tag',
inputWxAccountIdTip: 'input wxAccountId',
inputWxAccountNameTip: 'input wxAccountName',
inputWxAccountAppidTip: 'input wxAccountAppid',
inputTagIdTip: 'input tagId',
},
};

View File

@@ -0,0 +1,18 @@
export default {
wxAccountTag: {
index: '#',
importwxAccountTagTip: '导入标签管理',
id: '主键',
tag: '标签',
wxAccountId: '微信账号ID',
wxAccountName: '微信账号名称',
wxAccountAppid: 'appID',
tagId: '标签ID',
inputIdTip: '请输入主键',
inputTagTip: '请输入标签',
inputWxAccountIdTip: '请输入微信账号ID',
inputWxAccountNameTip: '请输入微信账号名称',
inputWxAccountAppidTip: '请输入appID',
inputTagIdTip: '请输入标签ID',
},
};

View File

@@ -0,0 +1,188 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch">
<el-form ref="queryRef" :inline="true" :model="state.queryForm" @keyup.enter="getDataList">
<el-form-item :label="$t('wxAccountTag.tag')" prop="tag">
<el-input v-model="state.queryForm.tag" :placeholder="t('wxAccountTag.inputTagTip')" style="max-width: 180px" />
</el-form-item>
<el-form-item :label="$t('wxAccountTag.wxAccountAppid')" prop="wxAccountAppid">
<el-select v-model="state.queryForm.wxAccountAppid" :placeholder="t('wxAccountTag.inputWxAccountAppidTip')" clearable>
<el-option v-for="item in accountList" :key="item.appid" :label="item.name" :value="item.appid" />
</el-select>
</el-form-item>
<el-form-item>
<el-button formDialogRef icon="search" type="primary" @click="getDataList">
{{ $t('common.queryBtn') }}
</el-button>
<el-button formDialogRef icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
</el-form-item>
</el-form>
</el-row>
<el-row>
<div class="mb8" style="width: 100%">
<el-button
v-auth="'mp_wx_account_tag_add'"
class="ml10"
formDialogRef
icon="folder-add"
type="primary"
@click="formDialogRef.openDialog(null, state.queryForm.wxAccountAppid)"
>
{{ $t('common.addBtn') }}
</el-button>
<el-button v-auth="'mp_wx_account_tag_export'" class="ml10" formDialogRef icon="Download" type="primary" @click="exportExcel">
{{ $t('common.exportBtn') }}
</el-button>
<el-button
v-auth="'mp_wx_account_tag_del'"
:disabled="multiple"
class="ml10"
formDialogRef
icon="Delete"
type="primary"
@click="handleDelete(selectObjs)"
>
{{ $t('common.delBtn') }}
</el-button>
<el-button plain type="primary" icon="Sort" @click="asyncTag">同步</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10"
style="float: right; margin-right: 20px"
@queryTable="getDataList"
></right-toolbar>
</div>
</el-row>
<el-table
v-loading="state.loading"
:data="state.dataList"
style="width: 100%"
@selection-change="handleSelectionChange"
@sort-change="sortChangeHandle"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column align="center" type="selection" width="40" />
<el-table-column :label="t('wxAccountTag.index')" type="index" width="60" />
<el-table-column :label="t('wxAccountTag.tag')" prop="tag" show-overflow-tooltip />
<el-table-column :label="t('wxAccountTag.wxAccountId')" prop="wxAccountId" show-overflow-tooltip />
<el-table-column :label="t('wxAccountTag.wxAccountName')" prop="wxAccountName" show-overflow-tooltip />
<el-table-column :label="t('wxAccountTag.wxAccountAppid')" prop="wxAccountAppid" show-overflow-tooltip />
<el-table-column :label="t('wxAccountTag.tagId')" prop="tagId" show-overflow-tooltip />
<el-table-column :label="$t('common.action')" width="150">
<template #default="scope">
<el-button icon="edit-pen" text type="primary" @click="formDialogRef.openDialog(scope.row, state.queryForm.wxAccountAppid)">{{
$t('common.editBtn')
}}</el-button>
<el-button icon="delete" text type="primary" @click="handleDelete([scope.row.id])">{{ $t('common.delBtn') }}</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-bind="state.pagination" @size-change="sizeChangeHandle" @current-change="currentChangeHandle" />
</div>
<!-- 编辑新增 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
</div>
</template>
<script lang="ts" name="systemWxAccountTag" setup>
import { BasicTableProps, useTable } from '/@/hooks/table';
import { delObjs, getPage, sync } from '/@/api/mp/wx-account-tag';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { useI18n } from 'vue-i18n';
import { fetchAccountList } from '/@/api/mp/wx-account';
// 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const { t } = useI18n();
// 定义查询字典
// 定义变量内容
const formDialogRef = ref();
// 搜索变量
const queryRef = ref();
const showSearch = ref(true);
// 多选变量
const selectObjs = ref([]) as any;
const multiple = ref(true);
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {},
pageList: getPage,
createdIsNeed: false,
});
const accountList = ref([]);
const getAccountList = () => {
fetchAccountList().then((res) => {
accountList.value = res.data;
if (accountList.value.length > 0) {
state.queryForm.wxAccountAppid = accountList.value[0].appid;
getDataList();
}
});
};
onMounted(() => {
getAccountList();
});
watch(
() => state.queryForm.wxAccountAppid,
() => {
getDataList();
}
);
// table hook
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
// 清空搜索条件
const resetQuery = () => {
// 清空搜索条件
queryRef.value.resetFields();
// 清空多选
selectObjs.value = [];
getDataList();
};
// 导出excel
const exportExcel = () => {
downBlobFile('/mp/wxAccountTag/export', state.queryForm, 'wxAccountTag.xlsx');
};
// 多选事件
const handleSelectionChange = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length;
};
// 删除操作
const handleDelete = (ids: string[]) => {
useMessageBox()
.confirm(t('common.delConfirmText'))
.then(() => {
delObjs({
ids: ids,
wxAccountAppid: state.queryForm.wxAccountAppid,
})
.then(() => {
getDataList();
useMessage().success(t('common.delSuccessText'));
})
.catch((err: any) => {
useMessage().error(err.msg);
});
});
};
const asyncTag = () => {
sync(state.queryForm.wxAccountAppid).then(() => {
getDataList();
});
};
</script>

View File

@@ -0,0 +1,158 @@
<template>
<el-dialog v-model="visible" :title="form.id ? $t('common.editBtn') : $t('common.addBtn')" size="50%">
<el-form ref="dataFormRef" v-loading="loading" :model="form" :rules="dataRules" label-width="90px">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item :label="t('account.name')" prop="name">
<el-input v-model="form.name" :placeholder="t('account.inputNameTip')" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('account.account')" prop="account">
<el-input v-model="form.account" :placeholder="t('account.inputAccountTip')" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('account.appid')" prop="appid">
<el-input v-model="form.appid" :placeholder="t('account.inputAppidTip')" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('account.appsecret')" prop="appsecret">
<el-input v-model="form.appsecret" :placeholder="t('account.inputAppsecretTip')" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('account.url')" prop="url">
<el-input v-model="form.url" :placeholder="t('account.inputUrlTip')" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('account.token')" prop="token">
<el-input v-model="form.token" :placeholder="t('account.inputTokenTip')" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('account.aeskey')" prop="aeskey">
<el-input v-model="form.aeskey" :placeholder="t('account.inputAeskeyTip')" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
<el-button type="primary" @click="onSubmit" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" name="WxAccountDialog" setup>
// 定义子组件向父组件传值/事件
import { useMessage } from '/@/hooks/message';
import { addObj, getObj, putObj } from '/@/api/mp/wx-account';
import { useI18n } from 'vue-i18n';
import { rule } from '/@/utils/validate';
const emit = defineEmits(['refresh']);
const { t } = useI18n();
// 定义变量内容
const dataFormRef = ref();
const visible = ref(false);
const loading = ref(false);
// 提交表单数据
const form = reactive({
id: '',
name: '',
account: '',
appid: '',
appsecret: '' as string | undefined,
url: '',
token: '',
aeskey: '',
});
// 定义校验规则
const dataRules = ref({
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
account: [{ required: true, message: '微信号不能为空', trigger: 'blur' }],
appid: [{ required: true, message: 'appid不能为空', trigger: 'blur' }],
appsecret: [{ required: true, message: '密钥不能为空', trigger: 'blur' }],
url: [
{ required: true, message: 'url不能为空', trigger: 'blur' },
{ validator: rule.url, trigger: 'blur' },
],
token: [{ required: true, message: 'token不能为空', trigger: 'blur' }],
aeskey: [{ required: true, message: '加密密钥不能为空', trigger: 'blur' }],
});
// 打开弹窗
const openDialog = (id: string) => {
visible.value = true;
form.id = '';
// 重置表单数据
if (dataFormRef.value) {
dataFormRef.value.resetFields();
}
// 获取wxAccount信息
if (id) {
form.id = id;
getwxAccountData(id);
}
};
// 提交
const onSubmit = async () => {
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) return false;
if (form.appsecret && form.appsecret.includes('*')) {
form.appsecret = undefined;
}
loading.value = true;
try {
if (form.id) {
await putObj(form);
useMessage().success(t('common.editSuccessText'));
} else {
await addObj(form);
useMessage().success(t('common.addSuccessText'));
}
visible.value = false;
emit('refresh');
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
// 初始化表单数据
const getwxAccountData = (id: string) => {
// 获取数据
loading.value = true;
getObj(id)
.then((res: any) => {
Object.assign(form, res.data);
})
.finally(() => {
loading.value = false;
});
};
// 暴露变量
defineExpose({
openDialog,
});
</script>

View File

@@ -0,0 +1,25 @@
export default {
account: {
index: '#',
importwxAccountTip: 'import WxAccount',
id: 'id',
name: 'name',
account: 'account',
appid: 'appid',
appsecret: 'appsecret',
url: 'url',
export: 'export',
token: 'token',
aeskey: 'aeskey',
qrUrl: 'qrUrl',
inputIdTip: 'input id',
inputNameTip: 'input name',
inputAccountTip: 'input account',
inputAppidTip: 'input appid',
inputAppsecretTip: 'input appsecret',
inputUrlTip: 'input url',
inputTokenTip: 'input token',
inputAeskeyTip: 'input aeskey',
inputQrUrlTip: 'input qrUrl',
},
};

View File

@@ -0,0 +1,25 @@
export default {
account: {
index: '#',
importwxAccountTip: '导入公众号账户表',
id: '主键',
name: '名称',
account: '微信号',
appid: 'appid',
appsecret: '密钥',
export: '导出',
url: ' url',
token: 'token',
aeskey: '加密密钥',
qrUrl: '图片',
inputIdTip: '请输入主键',
inputNameTip: '请输入名称',
inputAccountTip: '请输入微信号',
inputAppidTip: '请输入appid',
inputAppsecretTip: '请输入密钥',
inputUrlTip: '请输入 url',
inputTokenTip: '请输入token',
inputAeskeyTip: '请输入加密密钥',
inputQrUrlTip: '请输入图片',
},
};

View File

@@ -0,0 +1,218 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch">
<el-form ref="queryRef" :inline="true" :model="state.queryForm">
<el-form-item :label="$t('account.name')" prop="name">
<el-input v-model="state.queryForm.name" :placeholder="t('account.inputNameTip')" style="max-width: 180px" />
</el-form-item>
<el-form-item :label="$t('account.account')" prop="account">
<el-input v-model="state.queryForm.account" :placeholder="t('account.inputAccountTip')" style="max-width: 180px" />
</el-form-item>
<el-form-item>
<el-button formDialogRef icon="search" type="primary" @click="getDataList">
{{ $t('common.queryBtn') }}
</el-button>
<el-button formDialogRef icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
</el-form-item>
</el-form>
</el-row>
<el-row>
<div class="mb8" style="width: 100%">
<el-button v-auth="'mp_wxaccount_add'" class="ml10" icon="folder-add" type="primary" @click="formDialogRef.openDialog()">
{{ $t('common.addBtn') }}
</el-button>
<el-button
plain
v-auth="'mp_wxaccount_add'"
class="ml10"
icon="Delete"
type="primary"
:disabled="multiple"
@click="handleDelete(selectObjs)"
>
{{ $t('common.delBtn') }}
</el-button>
<right-toolbar
:export="'app_social_details_del'"
@exportExcel="exportExcel"
v-model:showSearch="showSearch"
class="ml10"
style="float: right; margin-right: 20px"
@queryTable="getDataList"
></right-toolbar>
</div>
</el-row>
<el-table
v-loading="state.loading"
:data="state.dataList"
style="width: 100%"
@selection-change="handleSelectionChange"
@sort-change="sortChangeHandle"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column align="center" type="selection" width="60" />
<el-table-column :label="t('account.index')" type="index" width="60" />
<el-table-column :label="t('account.name')" prop="name" show-overflow-tooltip />
<el-table-column :label="t('account.account')" prop="account" show-overflow-tooltip />
<el-table-column :label="t('account.appid')" prop="appid" show-overflow-tooltip />
<el-table-column :label="t('account.appsecret')" prop="appsecret" show-overflow-tooltip />
<el-table-column :label="t('account.url')" prop="url" show-overflow-tooltip />
<el-table-column :label="t('account.token')" prop="token" show-overflow-tooltip />
<el-table-column :label="t('account.aeskey')" prop="aeskey" show-overflow-tooltip />
<el-table-column :label="t('account.qrUrl')" prop="qrUrl" show-overflow-tooltip>
<template #default="scope">
<ImageUpload v-model:imageUrl="scope.row.qrUrl" height="80px" width="80px" disabled />
</template>
</el-table-column>
<el-table-column :label="$t('common.action')" width="100" fixed="right">
<template #default="scope">
<el-dropdown>
<span class="el-dropdown-link">
更多功能
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>
<el-button icon="edit-pen" v-auth="'mp_wxaccount_edit'" text type="primary" @click="formDialogRef.openDialog(scope.row.id)"
>{{ $t('common.editBtn') }}
</el-button>
</el-dropdown-item>
<el-dropdown-item>
<el-button icon="delete" v-auth="'mp_wxaccount_del'" text type="primary" @click="handleDelete([scope.row.id])"
>{{ $t('common.delBtn') }}
</el-button>
</el-dropdown-item>
<el-dropdown-item>
<el-button icon="DArrowRight" v-auth="'mp_wxaccount_del'" text type="primary" @click="access(scope.row)">接入 </el-button>
</el-dropdown-item>
<el-dropdown-item>
<el-button icon="Grid" v-auth="'mp_wxaccount_del'" text type="primary" @click="generate(scope.row)">二维码 </el-button>
</el-dropdown-item>
<el-dropdown-item>
<el-button icon="refresh" v-auth="'mp_wxaccount_del'" text type="primary" @click="quota(scope.row)">quota </el-button>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<pagination v-bind="state.pagination" @size-change="sizeChangeHandle" @current-change="currentChangeHandle" />
</div>
<!-- 编辑新增 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
<el-dialog v-model="dialogFormVisible" title="接入">
<el-input v-model="wxurl">
<template #append>
<el-button @click="copyText(wxurl)">复制链接</el-button>
</template>
</el-input>
</el-dialog>
</div>
</template>
<script lang="ts" name="systemWxAccount" setup>
import { BasicTableProps, useTable } from '/@/hooks/table';
import { clearQuota, delObjs, fetchList, generateQr } from '/@/api/mp/wx-account';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { useI18n } from 'vue-i18n';
import commonFunction from '/@/utils/commonFunction';
import other from '/@/utils/other';
const { proxy } = getCurrentInstance();
const { copyText } = commonFunction();
// 引入组件
const ImageUpload = defineAsyncComponent(() => import('/@/components/Upload/Image.vue'));
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
const { t } = useI18n();
// 定义查询字典
// 定义变量内容
const formDialogRef = ref();
// 搜索变量
const queryRef = ref();
const showSearch = ref(true);
// 多选变量
const selectObjs = ref([]) as any;
const multiple = ref(true);
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {},
pageList: fetchList,
});
// table hook
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
// 清空搜索条件
const resetQuery = () => {
// 清空搜索条件
queryRef.value.resetFields();
// 清空多选
selectObjs.value = [];
getDataList();
};
// 导出excel
const exportExcel = () => {
downBlobFile('/mp/account/export', state.queryForm, 'account.xlsx');
};
// 多选事件
const handleSelectionChange = (objs: { id: string }[]) => {
selectObjs.value = objs.map(({ id }) => id);
multiple.value = !objs.length;
};
// 删除操作
const handleDelete = (ids: string[]) => {
useMessageBox()
.confirm(t('common.delConfirmText'))
.then(() => {
delObjs(ids)
.then(() => {
getDataList();
useMessage().success(t('common.delSuccessText'));
})
.catch((err: any) => {
useMessage().error(err.msg);
});
});
};
const dialogFormVisible = ref(false);
const wxurl = ref();
const access = (row: any) => {
dialogFormVisible.value = true;
let url = '/mp/wx-portal/' + row.appid;
wxurl.value = row.url + proxy.baseURL + other.adaptationUrl(url);
};
const generate = (row: any) => {
generateQr(row.appid)
.then(() => {
useMessage().success('获取成功');
getDataList();
})
.catch((err) => {
useMessage().error(err.msg);
});
};
const quota = (row) => {
clearQuota(row.appid)
.then(() => {
useMessage().success('清空api的调用quota成功');
})
.catch(() => {
useMessage().error('清空api的调用quota失败');
});
};
</script>

View File

@@ -0,0 +1,353 @@
<template>
<div class="layout-padding">
<splitpanes>
<pane size="20">
<div class="layout-padding-auto layout-padding-view">
<el-scrollbar>
<query-tree class="mt10" :query="deptData.queryList" @node-click="handleNodeClick" placeholder="请输入微信公众号名称" />
</el-scrollbar>
</div>
</pane>
<pane size="80">
<div class="layout-padding-auto layout-padding-view">
<el-tabs v-model="type" @tab-click="handleClick">
<el-tab-pane name="1" label="1">
<template #label>关注时回复</template>
<el-row>
<div class="mb8" style="width: 100%">
<el-button class="ml10" icon="folder-add" type="primary" @click="handleAdd">
{{ $t('common.addBtn') }}
</el-button>
</div>
</el-row>
<el-table v-loading="state.loading" :data="state.dataList" style="width: 100%" max-height="600px" @sort-change="sortChangeHandle">
<el-table-column label="序号" type="index" width="60" />
<el-table-column label="回复消息类型" prop="repType" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="dicDataRepType" :value="scope.row.repType"></dict-tag>
</template>
</el-table-column>
<el-table-column label="操作" prop="action" show-overflow-tooltip>
<template #default="scope">
<el-button link icon="edit" @click="handleEdit(scope.row)">编辑 </el-button>
<el-button link icon="delete" @click="handleDel(scope.row)">删除 </el-button>
</template>
</el-table-column>
</el-table>
<pagination v-bind="state.pagination" @size-change="sizeChangeHandle" @current-change="currentChangeHandle" />
</el-tab-pane>
<el-tab-pane name="2" label="2">
<template #label>消息回复</template>
<el-row>
<div class="mb8" style="width: 100%">
<el-button class="ml10" icon="folder-add" type="primary" @click="handleAdd">
{{ $t('common.addBtn') }}
</el-button>
</div>
</el-row>
<el-table v-loading="state.loading" :data="state.dataList" style="width: 100%" max-height="600px" @sort-change="sortChangeHandle">
<el-table-column label="序号" type="index" width="60" />
<el-table-column label="请求消息类型" prop="reqType" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="dicDataReqType" :value="scope.row.reqType"></dict-tag>
</template>
</el-table-column>
<el-table-column label="回复消息类型" prop="repType" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="dicDataRepType" :value="scope.row.repType"></dict-tag>
</template>
</el-table-column>
<el-table-column label="操作" prop="action" show-overflow-tooltip>
<template #default="scope">
<el-button icon="edit" link @click="handleEdit(scope.row)">编辑 </el-button>
<el-button icon="delete" link @click="handleDel(scope.row)">删除 </el-button>
</template>
</el-table-column>
</el-table>
<pagination v-bind="state.pagination" @size-change="sizeChangeHandle" @current-change="currentChangeHandle" />
</el-tab-pane>
<el-tab-pane name="3" label="3">
<template #label>关键词回复</template>
<el-row>
<div class="mb8" style="width: 100%">
<el-button class="ml10" icon="folder-add" type="primary" @click="handleAdd">
{{ $t('common.addBtn') }}
</el-button>
</div>
</el-row>
<el-table
v-loading="state.loading"
:data="state.dataList"
style="width: 100%"
max-height="600px"
@sort-change="sortChangeHandle"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column label="序号" type="index" width="60" />
<el-table-column label="关键词" prop="reqKey" show-overflow-tooltip> </el-table-column>
<el-table-column label="匹配类型" prop="repMate" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="dicRepMate" :value="scope.row.repMate"></dict-tag>
</template>
</el-table-column>
<el-table-column label="匹配类型" prop="repMate" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="dicDataRepType" :value="scope.row.repType"></dict-tag>
</template>
</el-table-column>
<el-table-column label="操作" prop="action" show-overflow-tooltip>
<template #default="scope">
<el-button icon="edit" link @click="handleEdit(scope.row)">编辑 </el-button>
<el-button icon="delete" link @click="handleDel(scope.row)">删除 </el-button>
</template>
</el-table-column>
</el-table>
<pagination v-bind="state.pagination" @size-change="sizeChangeHandle" @current-change="currentChangeHandle" />
</el-tab-pane>
</el-tabs>
</div>
</pane>
</splitpanes>
<el-dialog :title="handleType === 'add' ? '新增回复消息' : '修改回复消息'" v-model="dialog1Visible" width="50%">
<el-form label-width="100px">
<el-form-item v-if="type === '2'" label="请求消息类型">
<el-select v-model="objData.reqType" placeholder="请选择">
<template v-for="item in dicDataReqType">
<el-option v-if="item.value !== 'event'" :key="item.value" :label="item.label" :value="item.value" :disabled="item.disabled">
</el-option>
</template>
</el-select>
</el-form-item>
<el-form-item v-if="type === '3'" label="匹配类型">
<el-select v-model="objData.repMate" placeholder="请选择" style="width: 100px">
<el-option v-for="item in dicRepMate" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</el-form-item>
<el-form-item v-if="type === '3'" label="关键词">
<el-input v-model="objData.reqKey" placeholder="请输入内容" clearable></el-input>
</el-form-item>
<el-form-item label="回复消息">
<WxReply v-if="hackResetWxReplySelect" :obj-data="objData"></WxReply>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialog1Visible = false"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="wx-auto-reply">
import {fetchAccountList} from '/@/api/mp/wx-account';
import {BasicTableProps, useTable} from '/@/hooks/table';
import {useMessage, useMessageBox} from '/@/hooks/message';
import {addObj, delObj, getPage, putObj} from '/@/api/mp/wx-auto-reply';
const QueryTree = defineAsyncComponent(() => import('/@/components/QueryTree/index.vue'));
const WxReply = defineAsyncComponent(() => import('/@/components/Wechat/wx-reply/index.vue'));
// 点击树
const handleNodeClick = (node: any) => {
accountId.value = node.appid;
state.queryForm.appId = accountId.value;
getDataList();
};
const dicDataRepType = ref([
{
label: '文本',
value: 'text',
},
{
label: '图片',
value: 'image',
},
{
label: '语音',
value: 'voice',
},
{
label: '视频',
value: 'video',
},
{
label: '图文',
value: 'news',
},
]);
const dicDataReqType = ref([
{
value: 'text',
label: '文本',
},
{
value: 'image',
label: '图片',
},
{
value: 'voice',
label: '语音',
},
{
value: 'video',
label: '视频',
},
{
value: 'shortvideo',
label: '小视频',
},
{
value: 'location',
label: '地理位置',
},
{
value: 'link',
label: '链接消息',
},
{
value: 'event',
label: '事件推送',
},
]);
const dicRepMate = ref([
{
value: '1',
label: '全匹配',
},
{
value: '2',
label: '半匹配',
},
]);
const deptData = reactive({
queryList: (name?: string) => {
return fetchAccountList({
name: name,
});
},
});
const accountId = ref();
const type = ref('1');
const handleClick = (e: any) => {
type.value = e.paneName;
state.queryForm.type = type.value;
state.queryForm.appId = accountId.value;
getDataList();
};
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
type: '1',
appId: '',
},
pageList: getPage,
createdIsNeed: false,
});
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle } = useTable(state);
const dialog1Visible = ref(false);
const handleType = ref('add');
const hackResetWxReplySelect = ref(true);
const objData = ref() as any;
const handleEdit = (row: any) => {
hackResetWxReplySelect.value = false;
nextTick(() => {
hackResetWxReplySelect.value = true;
});
handleType.value = 'edit';
dialog1Visible.value = true;
if (row.content && typeof row.content === 'string') {
row.content = JSON.parse(row.content);
}
objData.value = Object.assign({}, row);
};
const handleDel = (row) => {
useMessageBox()
.confirm('是否确认删除此数据?')
.then(() => {
delObj(row.id).then(() => {
useMessage().success('删除成功');
getDataList();
});
})
.catch((err) => {
useMessage().error(err.msg);
});
};
const handleSubmit = () => {
if (objData.repType === 'news') {
objData.content = JSON.stringify(objData.content);
}
if (handleType.value === 'add') {
addObj(
Object.assign(
{
type: type.value,
appId: accountId.value,
},
objData.value
)
)
.then(() => {
useMessage().success('添加成功');
getDataList();
dialog1Visible.value = false;
})
.catch((err) => {
useMessage().error(err.msg);
});
}
if (handleType.value === 'edit') {
putObj(objData.value)
.then(() => {
useMessage().success('修改成功');
getDataList();
dialog1Visible.value = false;
})
.catch((err) => {
useMessage().error(err.msg);
});
}
};
const handleAdd = () => {
hackResetWxReplySelect.value = false; //销毁组件
nextTick(() => {
hackResetWxReplySelect.value = true; //重建组件
});
handleType.value = 'add';
dialog1Visible.value = true;
objData.value = {
repType: 'text',
appId: accountId.value,
};
};
// 默认选择第一个公众号
onMounted(async () => {
const { data } = await deptData.queryList();
if (data?.length > 0) {
handleNodeClick(data[0]);
}
});
</script>
<style scoped></style>

View File

@@ -0,0 +1,64 @@
export default {
wxFansMsg: {
index: '#',
importwxMsgTip: 'import WxMsg',
id: 'id',
appName: 'appName',
appLogo: 'appLogo',
wxUserId: 'wxUserId',
nickName: 'nickName',
headimgUrl: 'headimgUrl',
type: 'type',
repType: 'repType',
repEvent: 'repEvent',
repContent: 'repContent',
repMediaId: 'repMediaId',
repName: 'repName',
repDesc: 'repDesc',
repUrl: 'repUrl',
repHqUrl: 'repHqUrl',
content: 'content',
repThumbMediaId: 'repThumbMediaId',
repThumbUrl: 'repThumbUrl',
repLocationX: 'repLocationX',
repLocationY: 'repLocationY',
repScale: 'repScale',
readFlag: 'readFlag',
appId: 'appId',
openId: 'openId',
remark: 'remark',
delFlag: 'delFlag',
createTime: 'createTime',
updateTime: 'updateTime',
tenantId: 'tenantId',
inputIdTip: 'input id',
inputAppNameTip: 'input appName',
inputAppLogoTip: 'input appLogo',
inputWxUserIdTip: 'input wxUserId',
inputNickNameTip: 'input nickName',
inputHeadimgUrlTip: 'input headimgUrl',
inputTypeTip: 'input type',
inputRepTypeTip: 'input repType',
inputRepEventTip: 'input repEvent',
inputRepContentTip: 'input repContent',
inputRepMediaIdTip: 'input repMediaId',
inputRepNameTip: 'input repName',
inputRepDescTip: 'input repDesc',
inputRepUrlTip: 'input repUrl',
inputRepHqUrlTip: 'input repHqUrl',
inputContentTip: 'input content',
inputRepThumbMediaIdTip: 'input repThumbMediaId',
inputRepThumbUrlTip: 'input repThumbUrl',
inputRepLocationXTip: 'input repLocationX',
inputRepLocationYTip: 'input repLocationY',
inputRepScaleTip: 'input repScale',
inputReadFlagTip: 'input readFlag',
inputAppIdTip: 'input appId',
inputOpenIdTip: 'input openId',
inputRemarkTip: 'input remark',
inputDelFlagTip: 'input delFlag',
inputCreateTimeTip: 'input createTime',
inputUpdateTimeTip: 'input updateTime',
inputTenantIdTip: 'input tenantId',
},
};

View File

@@ -0,0 +1,64 @@
export default {
wxFansMsg: {
index: '#',
importwxMsgTip: '导入微信消息',
id: '主键',
appName: '公众号名称',
appLogo: '公众号logo',
wxUserId: '微信用户ID',
nickName: '微信用户昵称',
headimgUrl: '微信用户头像',
type: '消息分类',
repType: '消息类型',
repEvent: '事件类型',
repContent: '内容',
repMediaId: '回复类型',
repName: '回复的素材名、视频和音乐的标题',
repDesc: '视频和音乐的描述',
repUrl: '链接',
repHqUrl: '高质量链接',
content: '图文消息的内容',
repThumbMediaId: '缩略图的媒体id',
repThumbUrl: '缩略图url',
repLocationX: '地理位置维度',
repLocationY: '地理位置经度',
repScale: '地图缩放大小',
readFlag: '已读标记',
appId: '公众号ID',
openId: '微信唯一标识',
remark: '备注',
delFlag: '逻辑删除标记0显示1隐藏',
createTime: '创建时间',
updateTime: '更新时间',
tenantId: '租户ID',
inputIdTip: '请输入主键',
inputAppNameTip: '请输入公众号名称',
inputAppLogoTip: '请输入公众号logo',
inputWxUserIdTip: '请输入微信用户ID',
inputNickNameTip: '请输入微信用户昵称',
inputHeadimgUrlTip: '请输入微信用户头像',
inputTypeTip: '请输入消息分类',
inputRepTypeTip: '请输入消息类型',
inputRepEventTip: '请输入事件类型',
inputRepContentTip: '请输入回复类型文本保存文字、地理位置信息',
inputRepMediaIdTip: '请输入回复类型',
inputRepNameTip: '请输入回复的素材名、视频和音乐的标题',
inputRepDescTip: '请输入视频和音乐的描述',
inputRepUrlTip: '请输入链接',
inputRepHqUrlTip: '请输入高质量链接',
inputContentTip: '请输入图文消息的内容',
inputRepThumbMediaIdTip: '请输入缩略图的媒体id',
inputRepThumbUrlTip: '请输入缩略图url',
inputRepLocationXTip: '请输入地理位置维度',
inputRepLocationYTip: '请输入地理位置经度',
inputRepScaleTip: '请输入地图缩放大小',
inputReadFlagTip: '请输入已读标记10',
inputAppIdTip: '请输入公众号ID',
inputOpenIdTip: '请输入微信唯一标识',
inputRemarkTip: '请输入备注',
inputDelFlagTip: '请输入逻辑删除标记0显示1隐藏',
inputCreateTimeTip: '请输入创建时间',
inputUpdateTimeTip: '请输入更新时间',
inputTenantIdTip: '请输入租户ID',
},
};

View File

@@ -0,0 +1,194 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<el-row v-show="showSearch">
<el-form ref="queryRef" :inline="true" :model="state.queryForm" @keyup.enter="getDataList">
<el-form-item :label="$t('wxFansMsg.appName')" prop="wxAccountAppid">
<el-select v-model="state.queryForm.wxAccountAppid" :placeholder="$t('wxFansMsg.appName')" clearable >
<el-option v-for="item in accountList" :key="item.appid" :label="item.name" :value="item.appid" />
</el-select>
</el-form-item>
<el-form-item :label="$t('wxFansMsg.nickName')" prop="nickName">
<el-input v-model="state.queryForm.nickName" :placeholder="t('wxFansMsg.inputNickNameTip')" style="max-width: 180px" />
</el-form-item>
<el-form-item :label="$t('wxFansMsg.repType')" prop="repType">
<el-select v-model="state.queryForm.repType" :placeholder="$t('wxFansMsg.repType')" clearable>
<el-option v-for="item in repType" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button formDialogRef icon="search" type="primary" @click="getDataList">
{{ $t('common.queryBtn') }}
</el-button>
<el-button formDialogRef icon="Refresh" @click="resetQuery">{{ $t('common.resetBtn') }}</el-button>
</el-form-item>
</el-form>
</el-row>
<el-row>
<div class="mb8" style="width: 100%">
<el-button v-auth="'mp_wxFansMsg_export'" class="ml10" formDialogRef icon="Download" type="primary" @click="exportExcel">
{{ $t('common.exportBtn') }}
</el-button>
<el-button
v-auth="'mp_wxmsg_del'"
:disabled="multiple"
class="ml10"
formDialogRef
icon="Delete"
type="primary"
@click="handleDelete(selectObjs)"
>
{{ $t('common.delBtn') }}
</el-button>
<right-toolbar
v-model:showSearch="showSearch"
class="ml10"
style="float: right; margin-right: 20px"
@queryTable="getDataList"
></right-toolbar>
</div>
</el-row>
<el-table
v-loading="state.loading"
:data="state.dataList"
style="width: 100%"
@sort-change="sortChangeHandle"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column :label="t('wxFansMsg.index')" type="index" width="60" />
<el-table-column :label="t('wxFansMsg.appName')" prop="appName" show-overflow-tooltip />
<el-table-column :label="t('wxFansMsg.repType')" prop="repType" show-overflow-tooltip />
<el-table-column :label="t('wxFansMsg.nickName')" prop="nickName" show-overflow-tooltip />
<el-table-column :label="t('wxFansMsg.openId')" prop="openId" show-overflow-tooltip />
<el-table-column :label="t('wxFansMsg.repContent')" prop="repContent" show-overflow-tooltip>
<template #default="scope">
<div v-if="scope.row.repType === 'event' && scope.row.repEvent === 'subscribe'"><el-tag type="success">关注</el-tag></div>
<div v-if="scope.row.repType === 'event' && scope.row.repEvent === 'unsubscribe'">
<el-tag type="danger">取消关注</el-tag>
</div>
<div v-if="scope.row.repType === 'event' && scope.row.repEvent === 'CLICK'"><el-tag>点击菜单</el-tag>{{ scope.row.repName }}</div>
<div v-if="scope.row.repType === 'event' && scope.row.repEvent === 'VIEW'"><el-tag>点击菜单链接</el-tag>{{ scope.row.repUrl }}</div>
<div v-if="scope.row.repType === 'event' && scope.row.repEvent === 'scancode_waitmsg'">
<el-tag>扫码结果</el-tag>{{ scope.row.repContent }}
</div>
<div v-if="scope.row.repType === 'text'">{{ scope.row.repContent }}</div>
<div v-if="scope.row.repType === 'image'">
<a target="_blank" :href="scope.row.repUrl"><img :src="scope.row.repUrl" style="width: 100px" /></a>
</div>
<div v-if="['video', 'voice', 'link', 'shortvideo'].includes(scope.row.repType)">
<el-tag>链接</el-tag><a :href="scope.row.repUrl" target="_blank">{{ scope.row.repName }}</a>
</div>
</template>
</el-table-column>
<el-table-column :label="t('wxFansMsg.readFlag')" prop="readFlag" show-overflow-tooltip>
<template #default="scope">
<dict-tag :options="readFlag" :value="scope.row.readFlag"></dict-tag>
</template>
</el-table-column>
<el-table-column :label="t('wxFansMsg.createTime')" prop="createTime" show-overflow-tooltip />
<el-table-column :label="$t('common.action')" width="150">
<template #default="scope">
<el-button icon="ChatSquare" link type="primary" @click="wxMsgDo(scope.row, scope.index)">消息</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-bind="state.pagination" @size-change="sizeChangeHandle" @current-change="currentChangeHandle" />
</div>
<wx-msg ref="WxmsgRef"></wx-msg>
</div>
</template>
<script lang="ts" name="systemWxMsg" setup>
import { BasicTableProps, useTable } from '/@/hooks/table';
import { fetchList } from '/@/api/mp/wx-fans-msg';
import { useMessage } from '/@/hooks/message';
import { useDict } from '/@/hooks/dict';
import { useI18n } from 'vue-i18n';
import { fetchAccountList } from '/@/api/mp/wx-account';
const WxMsg = defineAsyncComponent(() => import('/@/components/Wechat/wx-msg/index.vue'));
const { t } = useI18n();
// 定义查询字典
const { repType } = useDict('repType');
const readFlag = ref([
{
value: '1',
label: '是',
},
{
value: '0',
label: '否',
},
]);
const WxmsgRef = ref();
// 搜索变量
const queryRef = ref();
const showSearch = ref(true);
// 多选变量
const selectObjs = ref([]) as any;
const multiple = ref(true);
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {},
pageList: fetchList,
descs: ['create_time'],
});
// table hook
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle } = useTable(state);
// 清空搜索条件
const resetQuery = () => {
// 清空搜索条件
queryRef.value.resetFields();
// 清空多选
selectObjs.value = [];
getDataList();
};
const accountList = ref([]);
const getAccountList = () => {
fetchAccountList()
.then((res) => {
accountList.value = res.data;
if (accountList.value.length > 0) {
state.queryForm.wxAccountAppid = accountList.value[0].appid;
getDataList();
}
})
.catch((err) => {
useMessage().error(err.msg);
});
};
watch(
() => state.queryForm.wxAccountAppid,
() => {
getDataList();
}
);
onMounted(() => {
getAccountList();
});
// 导出excel
const exportExcel = () => {
downBlobFile('/act/wxFansMsg/export', state.queryForm, 'wxFansMsg.xlsx');
};
const wxMsgDo = (row) => {
WxmsgRef.value.openDialog({
wxUserId: row.wxUserId,
appId: row.appId,
});
};
</script>

View File

@@ -0,0 +1,539 @@
<template>
<el-dialog
:title="operateMaterial === 'add' ? '新建图文' : '修改图文'"
:before-close="dialogNewsClose"
:close-on-click-modal="false"
v-model="dialogNewsVisible"
:destroy-on-close="true"
width="80%"
top="20px"
>
<div class="left">
<div class="select-item">
<div v-for="(news, index) in articlesAdd" :key="news.id">
<div v-if="index == 0" class="news-main father" :class="{ activeAddNews: isActiveAddNews === index }" @click="activeNews(index)">
<div class="news-content">
<img v-if="news.thumbUrl" class="material-img" :src="news.thumbUrl" />
<div class="news-content-title">{{ news.title }}</div>
</div>
<div v-if="articlesAdd.length > 1" class="child">
<el-button icon="top" @click="downNews(index)">下移</el-button>
<el-button v-if="operateMaterial == 'add'" icon="delete" @click="minusNews(index)">删除 </el-button>
</div>
</div>
<div v-if="index > 0" class="news-main-item father" :class="{ activeAddNews: isActiveAddNews === index }" @click="activeNews(index)">
<div class="news-content-item">
<div class="news-content-item-title">{{ news.title }}</div>
<div class="news-content-item-img">
<img v-if="news.thumbUrl" class="material-img" :src="news.thumbUrl" height="100%" />
</div>
</div>
<div class="child">
<el-button v-if="articlesAdd.length > index + 1" icon="sort-down" @click="downNews(index)">下移 </el-button>
<el-button icon="sort-up" @click="upNews(index)">上移</el-button>
<el-button v-if="operateMaterial == 'add'" icon="delete" @click="minusNews(index)">删除 </el-button>
</div>
</div>
</div>
<div v-if="articlesAdd.length < 8 && operateMaterial == 'add'" class="news-main-plus" @click="plusNews">
<el-icon><Plus /></el-icon>
</div>
</div>
</div>
<div v-loading="addMaterialLoading" class="right">
<!--富文本编辑器组件-->
<el-row>
<editor v-model:get-html="articlesAdd[isActiveAddNews].content" style="margin-top: 20px"></editor>
</el-row>
<br /><br /><br /><br />
<div class="input-tt">封面和摘要</div>
<div>
<div class="thumb-div">
<img
v-if="articlesAdd[isActiveAddNews].thumbUrl"
class="material-img"
:src="articlesAdd[isActiveAddNews].thumbUrl"
:class="isActiveAddNews === 0 ? 'avatar' : 'avatar1'"
/>
<i v-else class="el-icon-plus avatar-uploader-icon" :class="isActiveAddNews === 0 ? 'avatar' : 'avatar1'"></i>
<div class="thumb-but">
<wx-file-upload :uploadData="uploadData" @success="handleImageChange"></wx-file-upload>
<el-button type="primary" @click="openMaterial">素材库选择</el-button>
</div>
</div>
<el-input
v-model="articlesAdd[isActiveAddNews].digest"
:rows="6"
type="textarea"
placeholder="请输入摘要"
class="digest"
maxlength="120"
></el-input>
</div>
<div class="input-tt">标题</div>
<el-input v-model="articlesAdd[isActiveAddNews].title" placeholder="请输入标题"></el-input>
<div class="input-tt">作者</div>
<el-input v-model="articlesAdd[isActiveAddNews].author" placeholder="请输入作者"></el-input>
<div class="input-tt">原文地址</div>
<el-input v-model="articlesAdd[isActiveAddNews].contentSourceUrl" placeholder="请输入原文地址"></el-input>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogNewsVisible = false">{{ $t('common.cancelButtonText') }}</el-button>
<el-button type="primary" @click="onSubmit">{{ $t('common.confirmButtonText') }}</el-button>
</span>
</template>
</el-dialog>
<wx-material-select ref="WxMaterialSelectRef"></wx-material-select>
</template>
<script setup lang="ts" name="wx-news-form">
import { useMessageBox } from '/@/hooks/message';
import { addObj, materialNewsUpdate } from '/@/api/mp/wx-material';
const WxMaterialSelect = defineAsyncComponent(() => import('/@/components/Wechat/wx-material-select/main.vue'));
const WxFileUpload = defineAsyncComponent(() => import('/@/components/Wechat/fileUpload/index.vue'));
const WxMaterialSelectRef = ref();
const dialogNewsVisible = ref(false);
const operateMaterial = ref('add');
const addMaterialLoading = ref(false);
// 定义刷新表格emit
const emit = defineEmits(['ok']);
const dialogNewsClose = () => {
useMessageBox()
.confirm('修改内容可能还未保存,确定关闭吗?')
.then(() => {
dialogNewsVisible.value = false;
});
};
// 公众号id
const accountId = ref();
// 文章数据
const articlesAdd = ref([
{
title: '',
thumbMediaId: '',
author: '',
digest: '',
showCoverPic: '',
content: '',
contentSourceUrl: '',
needOpenComment: '',
onlyFansCanComment: '',
thumbUrl: '',
},
]);
// 激活文章
const isActiveAddNews = ref(0);
// 编辑媒体的id
const articlesMediaId = ref();
const openDialog = (data: any, item?: any, mediaId?: any, type: any = 'add') => {
// 设置组件内不用账号
accountId.value = data.accountId;
uploadData.appId = data.accountId;
dialogNewsVisible.value = true;
operateMaterial.value = 'add';
if (item) {
articlesAdd.value = item;
}
if (mediaId) {
articlesMediaId.value = mediaId || '';
}
if (type) {
operateMaterial.value = type;
}
};
const uploadData = reactive({
mediaType: 'image',
title: '',
introduction: '',
appId: '',
});
const openMaterial = () => {
WxMaterialSelectRef.value.openDialog({
type: 'image',
accountId: accountId.value,
});
};
const handleImageChange = (response) => {
articlesAdd.value[isActiveAddNews.value].thumbMediaId = response.data.mediaId;
articlesAdd.value[isActiveAddNews.value].thumbUrl = response.data.url;
};
const onSubmit = () => {
addMaterialLoading.value = true;
if (operateMaterial.value === 'add') {
addObj({
articles: articlesAdd.value,
appId: accountId.value,
})
.then(() => {
addMaterialLoading.value = false;
dialogNewsVisible.value = false;
isActiveAddNews.value = 0;
articlesAdd.value = [
{
title: '',
thumbMediaId: '',
author: '',
digest: '',
showCoverPic: '',
content: '',
contentSourceUrl: '',
needOpenComment: '',
onlyFansCanComment: '',
thumbUrl: '',
},
];
emit('ok');
})
.finally(() => {
addMaterialLoading.value = false;
});
}
if (operateMaterial.value === 'edit') {
materialNewsUpdate({
articles: articlesAdd.value,
mediaId: articlesMediaId.value,
appId: accountId.value,
})
.then(() => {
addMaterialLoading.value = false;
dialogNewsVisible.value = false;
isActiveAddNews.value = 0;
articlesAdd.value = [
{
title: '',
thumbMediaId: '',
author: '',
digest: '',
showCoverPic: '',
content: '',
contentSourceUrl: '',
needOpenComment: '',
onlyFansCanComment: '',
thumbUrl: '',
},
];
emit('ok');
})
.finally(() => {
addMaterialLoading.value = false;
});
}
};
const activeNews = (index) => {
isActiveAddNews.value = index;
};
const minusNews = (index) => {
useMessageBox()
.confirm('确定删除该图文吗?')
.then(() => {
articlesAdd.value.splice(index, 1);
if (isActiveAddNews.value === index) {
isActiveAddNews.value = 0;
}
});
};
const plusNews = () => {
articlesAdd.value.push({
title: '',
thumbMediaId: '',
author: '',
digest: '',
showCoverPic: '',
content: '',
contentSourceUrl: '',
needOpenComment: '',
onlyFansCanComment: '',
thumbUrl: '',
});
isActiveAddNews.value = articlesAdd.value.length - 1;
};
const downNews = (index) => {
const temp = articlesAdd.value[index];
articlesAdd.value[index] = articlesAdd.value[index + 1];
articlesAdd.value[index + 1] = temp;
isActiveAddNews.value = index + 1;
};
const upNews = (index) => {
const temp = articlesAdd[index];
articlesAdd[index] = articlesAdd[index - 1];
articlesAdd[index - 1] = temp;
isActiveAddNews.value = index - 1;
};
// 暴露变量
defineExpose({
openDialog,
});
</script>
<style lang="scss" scoped>
.tree-position {
margin: 12px 20px 0 0;
}
.pagination {
float: right;
margin-right: 25px;
}
.add_but {
padding: 10px;
}
.ope-row {
margin-top: 5px;
text-align: center;
border-top: 1px solid #eaeaea;
padding-top: 5px;
}
.item-name {
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
.el-upload__tip {
margin-left: 5px;
}
/*新增图文*/
.left {
display: inline-block;
width: 35%;
vertical-align: top;
margin-top: 200px;
}
.right {
display: inline-block;
width: 60%;
margin-top: -40px;
}
.avatar-uploader {
width: 20%;
display: inline-block;
}
.avatar-uploader .el-upload {
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
text-align: unset !important;
}
.avatar-uploader .el-upload:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
border: 1px solid #d9d9d9;
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
}
.avatar {
width: 230px;
height: 120px;
}
.avatar1 {
width: 120px;
height: 120px;
}
.digest {
width: 60%;
display: inline-block;
vertical-align: top;
}
/*新增图文*/
/*瀑布流样式*/
.waterfall {
width: 100%;
column-gap: 10px;
column-count: 5;
margin: 0 auto;
}
.waterfall-item {
padding: 10px;
margin-bottom: 10px;
break-inside: avoid;
border: 1px solid #eaeaea;
}
p {
line-height: 30px;
}
@media (min-width: 992px) and (max-width: 1300px) {
.waterfall {
column-count: 3;
}
p {
color: red;
}
}
@media (min-width: 768px) and (max-width: 991px) {
.waterfall {
column-count: 2;
}
p {
color: orange;
}
}
@media (max-width: 767px) {
.waterfall {
column-count: 1;
}
}
/*瀑布流样式*/
.news-main {
background-color: #ffffff;
width: 100%;
margin: auto;
height: 120px;
}
.news-content {
background-color: #acadae;
width: 100%;
height: 120px;
position: relative;
}
.news-content-title {
display: inline-block;
font-size: 15px;
color: #ffffff;
position: absolute;
left: 0px;
bottom: 0px;
background-color: black;
width: 98%;
padding: 1%;
opacity: 0.65;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
height: 25px;
}
.news-main-item {
background-color: #ffffff;
padding: 5px 0px;
border-top: 1px solid #eaeaea;
width: 100%;
margin: auto;
}
.news-content-item {
position: relative;
margin-left: -3px;
}
.news-content-item-title {
display: inline-block;
font-size: 12px;
width: 70%;
}
.news-content-item-img {
display: inline-block;
width: 25%;
background-color: #acadae;
}
.input-tt {
padding: 5px;
}
.activeAddNews {
border: 5px solid #2bb673;
}
.news-main-plus {
width: 280px;
text-align: center;
margin: auto;
height: 50px;
}
.icon-plus {
margin: 10px;
font-size: 25px;
}
.select-item {
width: 60%;
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.father .child {
display: none;
text-align: center;
position: relative;
bottom: 25px;
}
.father:hover .child {
display: block;
}
.thumb-div {
display: inline-block;
width: 30%;
text-align: center;
}
.thumb-but {
display: flex;
margin: 5px;
justify-content: space-between;
}
.material-img {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,553 @@
<template>
<div class="layout-padding">
<splitpanes>
<pane size="20">
<div class="layout-padding-auto layout-padding-view">
<el-scrollbar>
<query-tree class="mt10" :query="deptData.queryList" @node-click="handleNodeClick" placeholder="请输入微信公众号名称" />
</el-scrollbar>
</div>
</pane>
<pane size="80">
<div class="layout-padding-auto layout-padding-view">
<el-tabs v-model="materialType" @tab-click="handleClick">
<el-tab-pane name="image" label="image">
<template #label><i class="picture"></i> 图片</template>
<div class="add_but">
<wx-file-upload
@success="getDataList"
:uploadData="uploadData"
:type="['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/jpg']"
></wx-file-upload>
</div>
<div v-loading="state.loading" class="waterfall">
<div v-for="item in state.dataList" :key="item.id" class="waterfall-item">
<a target="_blank" :href="item.url">
<img class="material-img" :src="item.url" />
<div class="item-name">{{ item.name }}</div>
</a>
<el-row class="ope-row">
<el-button type="danger" icon="delete" circle @click="delMaterial(item)"></el-button>
</el-row>
</div>
</div>
<div v-if="state.dataList.length <= 0 && !state.loading" class="el-table__empty-block">
<span class="el-table__empty-text">暂无数据</span>
</div>
<pagination v-bind="state.pagination" @size-change="sizeChangeHandle" @current-change="currentChangeHandle" />
</el-tab-pane>
<el-tab-pane name="voice" label="voice">
<template #label><i class="microphone"></i> 语音</template>
<div class="add_but">
<wx-file-upload @success="getDataList" :uploadData="uploadData" :type="['mp3', 'wma', 'wav', 'amr']"></wx-file-upload>
</div>
<el-table v-loading="state.loading" :data="state.dataList" stripe border max-height="600px">
<el-table-column prop="mediaId" label="media_id"> </el-table-column>
<el-table-column prop="name" label="名称"> </el-table-column>
<el-table-column prop="updateTime" label="更新时间"> </el-table-column>
<el-table-column fixed="right" label="操作">
<template v-slot="scope">
<el-button type="text" icon="download" plain @click="handleDown(scope.row)">下载 </el-button>
<el-button type="text" icon="delete" plain @click="delMaterial(scope.row)">删除 </el-button>
</template>
</el-table-column>
</el-table>
<pagination v-bind="state.pagination" @size-change="sizeChangeHandle" @current-change="currentChangeHandle" />
</el-tab-pane>
<el-tab-pane name="video" label="video">
<template #label><i class="video-play"></i> 视频</template>
<div class="add_but">
<el-button type="primary" @click="handleAddVideo">新建</el-button>
</div>
<el-dialog title="新建视频" v-model="dialogVideoVisible">
<wx-file-upload
@success="getDataList"
:uploadData="uploadData"
:auto-upload="false"
ref="uploadFileVideo"
:type="['video/mp4']"
></wx-file-upload>
<el-form ref="uploadForm" :model="uploadData" v-loading="addMaterialLoading" :rules="uploadRules">
<el-form-item label="标题" prop="title">
<el-input v-model="uploadData.title" placeholder="标题将展示在相关播放页面,建议填写清晰、准确、生动的标题"></el-input>
</el-form-item>
<el-form-item label="描述" prop="introduction">
<el-input
v-model="uploadData.introduction"
:rows="3"
type="textarea"
placeholder="介绍语将展示在相关播放页面,建议填写简洁明确、有信息量的内容"
></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVideoVisible = false"> </el-button>
<el-button type="primary" @click="subVideo"> </el-button>
</template>
</el-dialog>
<el-table
v-loading="state.loading"
:data="state.dataList"
stripe
border
max-height="600px"
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column prop="mediaId" label="media_id"> </el-table-column>
<el-table-column prop="name" label="名称"> </el-table-column>
<el-table-column prop="updateTime" label="更新时间"> </el-table-column>
<el-table-column fixed="right" label="操作">
<template v-slot="scope">
<el-button type="text" icon="view" @click="handleInfo(scope.row)">查看 </el-button>
<el-button type="text" icon="delete" @click="delMaterial(scope.row)">删除 </el-button>
</template>
</el-table-column>
</el-table>
<pagination v-bind="state.pagination" @size-change="sizeChangeHandle" @current-change="currentChangeHandle" />
</el-tab-pane>
<el-tab-pane name="news" label="news">
<template #label><i class="news"></i> 图文</template>
<div class="add_but">
<el-button type="primary" @click="handleAddNews"> </el-button>
</div>
<news-form ref="dialogNewsRef" @ok="getDataList"></news-form>
<div v-loading="state.loading" class="waterfall">
<div v-for="item in state.dataList" :key="item.id" class="waterfall-item">
<wx-news :obj-data="item.content.newsItem"></wx-news>
<el-row class="ope-row">
<el-button type="primary" icon="edit" circle @click="handleEditNews(item)"></el-button>
<el-button type="danger" icon="delete" circle @click="delMaterial(item)"></el-button>
</el-row>
</div>
</div>
<div v-if="state.dataList.length <= 0 && !state.loading" class="el-table__empty-block">
<span class="el-table__empty-text">暂无数据</span>
</div>
</el-tab-pane>
</el-tabs>
</div>
</pane>
</splitpanes>
</div>
</template>
<script setup lang="ts" name="wx-material">
import { fetchAccountList } from '/@/api/mp/wx-account';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { BasicTableProps, useTable } from '/@/hooks/table';
import { delObj, getMaterialOther, getMaterialVideo, getPage } from '/@/api/mp/wx-material';
const QueryTree = defineAsyncComponent(() => import('/@/components/QueryTree/index.vue'));
const NewsForm = defineAsyncComponent(() => import('./components/news-form.vue'));
const WxFileUpload = defineAsyncComponent(() => import('/@/components/Wechat/fileUpload/index.vue'));
const WxNews = defineAsyncComponent(() => import('/@/components/Wechat/wx-news/index.vue'));
const deptData = reactive({
queryList: (name?: string) => {
return fetchAccountList({
name: name,
});
},
});
const checkAppId = ref();
const uploadData = ref({
appId: '',
mediaType: 'image',
title: '',
introduction: '',
});
const materialType = ref('image');
// 点击树
const handleNodeClick = (data: any) => {
checkAppId.value = data.appid;
uploadData.value.appId = data.appid;
state.queryForm.appId = data.appid;
state.queryForm.type = materialType.value;
getDataList();
};
const handleClick = (tab) => {
if (checkAppId.value) {
// getPage(this.page)
} else {
useMessage().error('请选择公众号');
}
materialType.value = tab.paneName;
uploadData.value.mediaType = tab.paneName;
state.queryForm.type = materialType.value;
getDataList();
};
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
appId: '',
type: '',
},
pageList: getPage,
createdIsNeed: false,
props: {
item: 'items',
totalCount: 'totalCount',
},
});
const { getDataList, currentChangeHandle, sizeChangeHandle, tableStyle } = useTable(state);
const delMaterial = (item: any) => {
useMessageBox()
.confirm('此操作将永久删除该文件, 是否继续?')
.then(() => {
delObj({
id: item.mediaId,
appId: checkAppId.value,
})
.then(() => {
getDataList();
})
.catch((err) => {
useMessage().error(err.msg);
});
});
};
// 视频
const dialogVideoVisible = ref(false);
const addMaterialLoading = ref(false);
const handleAddVideo = () => {
dialogVideoVisible.value = true;
};
const uploadRules = reactive({
title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
introduction: [{ required: true, message: '请输入描述', trigger: 'blur' }],
});
const uploadForm = ref();
const uploadFileVideo = ref();
const subVideo = () => {
uploadForm.value.validate((valid: boolean) => {
if (!valid) {
return false;
}
uploadFileVideo.value.submit().then(() => {
dialogVideoVisible.value = false;
});
});
};
const handleDown = (row: any) => {
getMaterialOther({
mediaId: row.mediaId,
fileName: row.name,
appId: checkAppId.value,
}).then((response) => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.style.display = 'none';
link.href = url;
link.setAttribute('download', row.name);
document.body.appendChild(link);
link.click();
});
};
// 图文
const dialogNewsRef = ref();
const handleAddNews = () => {
dialogNewsRef.value.openDialog({
accountId: checkAppId.value,
});
};
const handleEditNews = (item) => {
dialogNewsRef.value.openDialog(
{
accountId: checkAppId.value,
},
JSON.parse(JSON.stringify(item.content.newsItem)),
item.mediaId,
'edit'
);
};
const handleInfo = (row) => {
getMaterialVideo({
mediaId: row.mediaId,
appId: checkAppId.value,
})
.then((response) => {
const downUrl = response.data.downUrl;
window.open(downUrl, '_blank');
})
.catch((err) => {
useMessage().error(err.msg);
});
};
// 默认选择第一个公众号
onMounted(async () => {
const { data } = await deptData.queryList();
if (data?.length > 0) {
handleNodeClick(data[0]);
}
});
</script>
<style lang="scss" scoped>
.tree-position {
margin: 12px 20px 0 0;
}
.pagination {
float: right;
margin-right: 25px;
}
.add_but {
padding: 10px;
}
.ope-row {
margin-top: 5px;
text-align: center;
border-top: 1px solid #eaeaea;
padding-top: 5px;
}
.item-name {
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
.el-upload__tip {
margin-left: 5px;
}
/*新增图文*/
.left {
display: inline-block;
width: 35%;
vertical-align: top;
margin-top: 200px;
}
.right {
display: inline-block;
width: 60%;
margin-top: -40px;
}
.avatar-uploader {
width: 20%;
display: inline-block;
}
.avatar-uploader .el-upload {
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
text-align: unset !important;
}
.avatar-uploader .el-upload:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
border: 1px solid #d9d9d9;
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
}
.avatar {
width: 230px;
height: 120px;
}
.avatar1 {
width: 120px;
height: 120px;
}
.digest {
width: 60%;
display: inline-block;
vertical-align: top;
}
/*新增图文*/
/*瀑布流样式*/
.waterfall {
width: 100%;
column-gap: 10px;
column-count: 5;
margin: 0 auto;
}
.waterfall-item {
padding: 10px;
margin-bottom: 10px;
break-inside: avoid;
border: 1px solid #eaeaea;
}
p {
line-height: 30px;
}
@media (min-width: 992px) and (max-width: 1300px) {
.waterfall {
column-count: 3;
}
p {
color: red;
}
}
@media (min-width: 768px) and (max-width: 991px) {
.waterfall {
column-count: 2;
}
p {
color: orange;
}
}
@media (max-width: 767px) {
.waterfall {
column-count: 1;
}
}
/*瀑布流样式*/
.news-main {
background-color: #ffffff;
width: 100%;
margin: auto;
height: 120px;
}
.news-content {
background-color: #acadae;
width: 100%;
height: 120px;
position: relative;
}
.news-content-title {
display: inline-block;
font-size: 15px;
color: #ffffff;
position: absolute;
left: 0px;
bottom: 0px;
background-color: black;
width: 98%;
padding: 1%;
opacity: 0.65;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
height: 25px;
}
.news-main-item {
background-color: #ffffff;
padding: 5px 0px;
border-top: 1px solid #eaeaea;
width: 100%;
margin: auto;
}
.news-content-item {
position: relative;
margin-left: -3px;
}
.news-content-item-title {
display: inline-block;
font-size: 12px;
width: 70%;
}
.news-content-item-img {
display: inline-block;
width: 25%;
background-color: #acadae;
}
.input-tt {
padding: 5px;
}
.activeAddNews {
border: 5px solid #2bb673;
}
.news-main-plus {
width: 280px;
text-align: center;
margin: auto;
height: 50px;
}
.icon-plus {
margin: 10px;
font-size: 25px;
}
.select-item {
width: 60%;
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.father .child {
display: none;
text-align: center;
position: relative;
bottom: 25px;
}
.father:hover .child {
display: block;
}
.thumb-div {
display: inline-block;
width: 30%;
text-align: center;
}
.thumb-but {
margin: 5px;
}
.material-img {
width: 100%;
height: 100%;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,221 @@
.clearfix::after {
content: '';
display: table;
clear: both;
}
div {
text-align: left;
}
.weixin-hd {
color: #fff;
text-align: center;
position: relative;
bottom: 426px;
left: 0px;
width: 300px;
height: 64px;
background: transparent url('./assets/menu_head.png') no-repeat 0 0;
background-position: 0 0;
background-size: 100%;
}
.weixin-title {
color: #fff;
font-size: 14px;
width: 100%;
text-align: center;
position: absolute;
top: 33px;
left: 0px;
}
.weixin-menu {
background: transparent url('./assets/menu_foot.png') no-repeat 0 0;
padding-left: 43px;
font-size: 12px;
}
.menu_option {
width: 40% !important;
}
.public-account-management {
min-width: 1200px;
width: 1200px;
margin: 0 auto;
.left {
float: left;
display: inline-block;
width: 350px;
height: 715px;
background: url('./assets/iphone_backImg.png') no-repeat;
background-size: 100% auto;
padding: 518px 25px 88px;
position: relative;
box-sizing: border-box;
/*第一级菜单*/
.menu_main {
.menu_bottom {
position: relative;
float: left;
display: inline-block;
box-sizing: border-box;
width: 85.5px;
text-align: center;
border: 1px solid #ebedee;
background-color: #fff;
cursor: pointer;
&.menu_addicon {
height: 46px;
line-height: 46px;
}
.menu_item {
height: 44px;
line-height: 44px;
text-align: center;
box-sizing: border-box;
width: 100%;
&.active {
border: 1px solid #2bb673;
}
}
.menu_subItem {
height: 44px;
line-height: 44px;
text-align: center;
box-sizing: border-box;
&.active {
border: 1px solid #2bb673;
}
}
}
i {
color: #2bb673;
}
/*第二级菜单*/
.submenu {
position: absolute;
width: 85.5px;
bottom: 45px;
.subtitle {
background-color: #fff;
box-sizing: border-box;
}
}
}
.save_div {
margin-top: 15px;
text-align: center;
.save_btn {
bottom: 20px;
left: 100px;
}
}
}
/*右边菜单内容*/
.right {
float: left;
width: 63%;
background-color: #e8e7e7;
padding: 20px;
margin-left: 20px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
.configure_page {
.delete_btn {
text-align: right;
margin-bottom: 15px;
}
.menu_content {
margin-top: 20px;
}
.configur_content {
margin-top: 20px;
background-color: #fff;
padding: 20px 10px;
border-radius: 5px;
}
.blue {
color: #29b6f6;
margin-top: 10px;
}
.applet {
margin-bottom: 20px;
span {
width: 20%;
}
}
.input_width {
width: 40%;
}
.material {
.input_width {
width: 30%;
}
.el-textarea {
width: 80%;
}
}
}
}
.el-input {
width: 70%;
margin-right: 2%;
}
}
.pagination {
text-align: right;
margin-right: 25px;
}
.select-item {
width: 280px;
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.select-item2 {
padding: 10px;
margin: 0 auto 10px auto;
border: 1px solid #eaeaea;
}
.ope-row {
padding-top: 10px;
text-align: center;
}
.item-name {
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}

View File

@@ -0,0 +1,416 @@
<template>
<div class="layout-padding">
<splitpanes>
<pane size="20">
<div class="layout-padding-auto layout-padding-view">
<el-scrollbar>
<query-tree class="mt10" :query="deptData.queryList" @node-click="handleNodeClick" placeholder="请输入微信公众号名称" />
</el-scrollbar>
</div>
</pane>
<pane>
<div class="layout-padding-auto layout-padding-view">
<el-scrollbar>
<div v-loading="loading" class="clearfix public-account-management">
<div class="left">
<div class="weixin-hd">
<div class="weixin-title">{{ name }}</div>
</div>
<div class="clearfix weixin-menu menu_main">
<div v-for="(item, i) of menuList" :key="i" class="menu_bottom">
<div :class="{ active: isActive === i }" class="menu_item el-icon-s-fold" @click="menuClick(i, item)">
{{ item.name }}
</div>
<!-- 以下为二级菜单-->
<div v-if="isSubMenuFlag === i" class="submenu">
<template v-for="(subItem, k) in item.sub_button">
<div v-if="item.sub_button" :key="k" class="subtitle menu_bottom">
<div :class="{ active: isSubMenuActive === i + '' + k }" class="menu_subItem" @click="subMenuClick(subItem, i, k)">
{{ subItem.name }}
</div>
</div>
</template>
<!-- 二级菜单加号 当长度 小于 5 才显示二级菜单的加号 -->
<div v-if="!item.sub_button || item.sub_button.length < 5" class="menu_bottom menu_addicon" @click="addSubMenu(i, item)">
<el-icon>
<el-icon><Plus /></el-icon>
</el-icon>
</div>
</div>
</div>
<!-- 一级菜单加号 -->
<div v-if="menuList.length < 3" class="menu_bottom menu_addicon" @click="addMenu">
<el-icon>
<el-icon><Plus /></el-icon>
</el-icon>
</div>
</div>
<div class="flex items-center justify-center gap-4 mt-4 mb-6 save_div">
<el-button
class="save_btn !px-6 !h-9 hover:scale-105 transition-transform"
type="primary"
size="small"
@click="handleSave"
>
<el-icon class="mr-1"><Check /></el-icon>
保存发布
</el-button>
<el-button
class="save_btn !px-6 !h-9 hover:scale-105 transition-transform"
type="warning"
size="small"
@click="handleDelete"
>
<el-icon class="mr-1"><Delete /></el-icon>
清空菜单
</el-button>
</div>
</div>
<div v-if="showRightFlag" class="right">
<div class="configure_page">
<div class="delete_btn">
<el-button icon="Delete" size="mini" type="danger" @click="deleteMenu(tempObj)">删除当前菜单 </el-button>
</div>
<div>
<span>菜单名称</span>
<el-input v-model="tempObj.name" class="input_width" clearable placeholder="请输入菜单名称" />
</div>
<div v-if="showConfigureContent">
<div class="menu_content">
<span>菜单标识</span>
<el-input v-model="tempObj.key" class="input_width" clearable placeholder="请输入菜单 KEY" />
</div>
<div class="menu_content">
<span>菜单内容</span>
<el-select v-model="tempObj.type" class="menu_option" clearable placeholder="请选择">
<el-option v-for="item in menuOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="configur_content" v-if="tempObj.type === 'view'">
<span>跳转链接</span>
<el-input class="input_width" v-model="tempObj.url" placeholder="请输入链接" clearable />
</div>
<div class="configur_content" v-if="tempObj.type === 'miniprogram'">
<div class="applet">
<span>小程序的 appid </span>
<el-input class="input_width" v-model="tempObj.miniProgramAppId" placeholder="请输入小程序的appid" clearable />
</div>
<div class="applet">
<span>小程序的页面路径</span>
<el-input
class="input_width"
v-model="tempObj.miniProgramPagePath"
placeholder="请输入小程序的页面路径pages/index"
clearable
/>
</div>
<div class="applet">
<span>小程序的备用网页</span>
<el-input class="input_width" v-model="tempObj.url" placeholder="不支持小程序的老版本客户端将打开本网页" clearable />
</div>
<p class="blue">tips:需要和公众号进行关联才可以把小程序绑定带微信菜单上哟</p>
</div>
<div class="configur_content" v-if="tempObj.type === 'article_view_limited'">
<el-row>
<div class="select-item" v-if="tempObj && tempObj.replyArticles">
<wx-news :objData="tempObj.replyArticles" />
<el-row class="ope-row">
<el-button type="danger" icon="delete" circle @click="deleteMaterial" />
</el-row>
</div>
<div v-else>
<el-row>
<el-col :span="24" style="text-align: center">
<el-button type="success" @click="openMaterial"> 素材库选择<i class="fansel-icon--right"></i> </el-button>
</el-col>
</el-row>
</div>
<wx-material-select ref="dialogNewsRef" @selectMaterial="selectMaterial" />
</el-row>
</div>
<div class="configur_content" v-if="tempObj.type === 'click' || tempObj.type === 'scancode_waitmsg'">
<wx-reply :objData="tempObj" v-if="hackResetWxReplySelect" />
</div>
</div>
</div>
</div>
</div>
</el-scrollbar>
</div>
</pane>
</splitpanes>
</div>
</template>
<script lang="ts" name="wx-menu" setup>
import {getObj, publishObj, saveObj} from '/@/api/mp/wx-menu';
// 部门树使用的数据
import {fetchAccountList} from '/@/api/mp/wx-account';
import {useMessage, useMessageBox} from '/@/hooks/message';
const WxMaterialSelect = defineAsyncComponent(() => import('/@/components/Wechat/wx-material-select/main.vue'));
const WxReply = defineAsyncComponent(() => import('/@/components/Wechat/wx-reply/index.vue'));
const QueryTree = defineAsyncComponent(() => import('/@/components/QueryTree/index.vue'));
const WxNews = defineAsyncComponent(() => import('/@/components/Wechat/wx-news/index.vue'));
// 点击树
const handleNodeClick = (node: any) => {
accountId.value = node.appid;
name.value = node.name;
getMenuFun();
};
const deptData = reactive({
queryList: (name?: string) => {
return fetchAccountList({
name: name,
});
},
});
const loading = ref(false);
const name = ref('测试公众号');
const accountId = ref(''); // 公众号id
// 一级菜单点中样式
const isActive = ref(-1);
// 一级菜单点中样式
const isSubMenuActive = ref('-1');
// 二级菜单显示标志
const isSubMenuFlag = ref(-1);
const menuList = reactive([
{
name: '菜单名称',
sub_button: [],
},
] as any);
const hackResetWxReplySelect = ref(false);
const menuOptions = ref([
{
value: 'view',
label: '跳转网页',
},
{
value: 'miniprogram',
label: '跳转小程序',
},
{
value: 'click',
label: '点击回复',
},
{
value: 'article_view_limited',
label: '跳转图文消息',
},
{
value: 'scancode_push',
label: '扫码直接返回结果',
},
{
value: 'scancode_waitmsg',
label: '扫码回复',
},
{
value: 'pic_sysphoto',
label: '系统拍照发图',
},
{
value: 'pic_photo_or_album',
label: '拍照或者相册',
},
{
value: 'pic_weixin',
label: '微信相册',
},
{
value: 'location_select',
label: '选择地理位置',
},
]);
const showRightFlag = ref(false);
let tempObj = ref({
replyArticles: [] as any,
articleId: '',
appId: '',
});
const tempSelfObj = reactive({
grand: '', // 表示二级菜单
index: '', // 表示一级菜单索引
secondIndex: '', // 表示二级菜单索引
});
const getMenuFun = () => {
getObj(accountId.value).then((res) => {
if (res.data) {
const data = JSON.parse(res.data);
if (data && data.button) {
Object.assign(menuList, data.button);
}
} else {
menuList.length = 0;
Object.assign(menuList, {
name: '菜单名称',
sub_button: [],
});
}
});
};
const showConfigureContent = ref(true);
// 一级菜单点击事件
const menuClick = (i, item) => {
hackResetWxReplySelect.value = false;
nextTick(() => {
hackResetWxReplySelect.value = true;
});
showRightFlag.value = true; // 右边菜单
tempObj.value = item;
tempObj.value.appId = accountId.value;
showConfigureContent.value = !(item.sub_button && item.sub_button.length > 0); // 有子菜单,就不显示配置内容
isActive.value = i;
isSubMenuFlag.value = i;
isSubMenuActive.value = '-1';
tempSelfObj.grand = '1'; //表示一级菜单
tempSelfObj.index = i; //表示一级菜单索引
};
// 点击二级菜单
const subMenuClick = (subItem, index, k) => {
hackResetWxReplySelect.value = false;
nextTick(() => {
hackResetWxReplySelect.value = true;
});
showRightFlag.value = true; // 右边菜单
// Object.assign(tempObj, subItem) // 这个如果放在顶部flag 会没有。因为重新赋值了。
tempObj.value = subItem;
tempObj.value.appId = accountId.value;
showConfigureContent.value = true;
isActive.value = -1; // 一级菜单去除样式
isSubMenuActive.value = index + '' + k; // 二级菜单选中样式
tempSelfObj.grand = '2'; //表示二级菜单
tempSelfObj.index = index; //表示一级菜单索引
tempSelfObj.secondIndex = k; //表示二级菜单索引
};
// 添加横向二级菜单item 表示要操作的父菜单
const addSubMenu = (i, item) => {
if (!item.sub_button || item.sub_button.length <= 0) {
item['sub_button'] = [];
showConfigureContent.value = false;
}
let addButton = {
name: '子菜单名称',
reply: {
// 用于存储回复内容
type: 'text',
accountId: accountId.value, // 保证组件里,可以使用到对应的公众号
},
};
item.sub_button.push(addButton);
};
// 添加横向一级菜单
const addMenu = () => {
const addButton = {
name: '菜单名称',
sub_button: [],
reply: {
// 用于存储回复内容
type: 'text',
accountId: accountId.value, // 保证组件里,可以使用到对应的公众号
},
};
menuList.push(addButton);
};
const deleteMenu = () => {
useMessageBox()
.confirm('确定要删除吗?')
.then(() => {
if (tempSelfObj.grand === '1') {
menuList.splice(tempSelfObj.index, 1);
} else if (tempSelfObj.grand === '2') {
menuList[tempSelfObj.index].sub_button.splice(tempSelfObj.secondIndex, 1);
}
useMessage().success('删除成功');
Object.assign(tempObj, {});
showRightFlag.value = false;
isActive.value = -1;
isSubMenuActive.value = '-1';
})
.catch((err) => {
useMessage().error(err.msg);
});
};
const handleSave = async () => {
try {
await useMessageBox().confirm('确定要保存该菜单吗?');
await saveObj(accountId.value, { button: menuList });
await publishObj(accountId.value);
useMessage().success('发布成功');
} catch (err: any) {
useMessage().error(err.msg);
}
};
const deleteMaterial = () => {
tempObj.value.replyArticles = [];
tempObj.value.articleId = '';
};
const dialogNewsRef = ref();
const openMaterial = () => {
dialogNewsRef.value.openDialog({ type: 'news', accountId: accountId.value });
};
const selectMaterial = (item) => {
const articleId = item.articleId;
const articles = item.content.newsItem;
// 提示,针对多图文
if (articles.length > 1) {
// this.$alert('您选择的是多图文,将默认跳转第一篇', '提示', {
// confirmButtonText: '确定'
// })
}
// 设置菜单的回复
tempObj.value.articleId = articleId;
tempObj.value.replyArticles = [];
articles.forEach((article) => {
tempObj.value.replyArticles.push({
title: article.title,
description: article.digest,
picUrl: article.picUrl,
url: article.url,
});
});
};
const handleDelete = () => {};
// 默认选择第一个公众号
onMounted(async () => {
const { data } = await deptData.queryList();
if (data?.length > 0) {
handleNodeClick(data[0]);
}
});
</script>
<style lang="scss" scoped>
@import './assets/wx-menu.scss';
</style>

View File

@@ -0,0 +1,238 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<splitpanes>
<pane size="20">
<el-date-picker v-model="beginTime" placeholder="选择开始时间" @change="check" style="width: 50%"></el-date-picker>
<el-date-picker v-model="endTime" style="width: 50%" placeholder="选择结束时间" @change="check"></el-date-picker>
<div class="layout-padding-auto layout-padding-view">
<el-scrollbar>
<query-tree class="mt10" :query="deptData.queryList" @node-click="handleNodeClick" placeholder="请输入微信公众号名称" />
</el-scrollbar>
</div>
</pane>
<pane size="80" class="ml10">
<splitpanes horizontal>
<pane>
<splitpanes>
<pane>
<div class="home-card-item">
<div style="height: 100%" ref="userCumulateRef"></div>
</div>
</pane>
<pane>
<div class="home-card-item">
<div style="height: 100%" ref="userShardRef"></div>
</div>
</pane>
</splitpanes>
</pane>
<pane>
<splitpanes>
<pane>
<div class="home-card-item">
<div style="height: 100%" ref="upstreamMsgDistMonthRef"></div>
</div>
</pane>
<pane>
<div class="home-card-item">
<div style="height: 100%" ref="interfaceSummaryRef"></div>
</div>
</pane>
</splitpanes>
</pane>
</splitpanes>
</pane>
</splitpanes>
</div>
</div>
</template>
<script setup lang="ts" name="wx-statistics">
import {useMessage} from '/@/hooks/message';
import {fetchAccountList, fetchStatistics} from '/@/api/mp/wx-account';
import {markRaw} from 'vue';
import * as echarts from 'echarts';
const QueryTree = defineAsyncComponent(() => import('/@/components/QueryTree/index.vue'));
const beginTime = ref(new Date().getTime() - 3600 * 1000 * 24 * 7);
const endTime = ref(new Date().getTime() - 3600 * 1000 * 24);
const check = () => {
const start = new Date(beginTime.value);
const end = new Date(endTime.value);
if (end.getTime() >= new Date().getTime()) {
useMessage().error('统计结束日小于当前日期,请重新选择');
return false;
}
if (end.getTime() - start.getTime() >= 3600 * 1000 * 24 * 7) {
useMessage().error('时间间隔7天以内请重新选择');
return false;
}
};
const accountId = ref();
// 点击树
const handleNodeClick = (node: any) => {
accountId.value = node.appid;
initdata();
};
const deptData = reactive({
queryList: (name?: string) => {
return fetchAccountList({
name: name,
});
},
});
const userCumulateRef = ref();
// 初始化折线图
const userCumulate = () => {
const userCumulate = markRaw(echarts.init(userCumulateRef.value));
const option = {
title: {
text: '用户分析数据',
},
xAxis: {
type: 'category',
data: LintData.value[0],
},
yAxis: {
type: 'value',
},
series: [
{
type: 'line',
data: LintData.value[1],
},
],
};
userCumulate.setOption(option);
};
const userShardRef = ref();
// 初始化折线图
const userShard = () => {
const userShard = markRaw(echarts.init(userShardRef.value));
const option = {
title: {
text: '接口分析数据',
},
xAxis: {
type: 'category',
data: LintData.value[2],
},
yAxis: {
type: 'value',
},
series: [
{
type: 'line',
data: LintData.value[3],
},
],
};
userShard.setOption(option);
};
const upstreamMsgDistMonthRef = ref();
// 初始化折线图
const upstreamMsgDistMonth = () => {
const upstreamMsgDistMonth = markRaw(echarts.init(upstreamMsgDistMonthRef.value));
const option = {
title: {
text: '消息分析数据',
},
xAxis: {
type: 'category',
data: LintData.value[4],
},
yAxis: {
type: 'value',
},
series: [
{
type: 'line',
data: LintData.value[5],
},
],
};
upstreamMsgDistMonth.setOption(option);
};
const interfaceSummaryRef = ref();
// 初始化折线图
const interfaceSummary = () => {
const interfaceSummary = markRaw(echarts.init(interfaceSummaryRef.value));
const option = {
title: {
text: '图文分享数据',
},
xAxis: {
type: 'category',
data: LintData.value[0],
},
yAxis: {
type: 'value',
},
series: [
{
type: 'line',
data: LintData.value[1],
},
],
};
interfaceSummary.setOption(option);
};
const LintData = ref([[], [], [], [], [], [], [], []]);
const initdata = () => {
fetchStatistics({
appId: accountId.value,
interval: new Date(beginTime.value).getTime() + '-' + new Date(endTime.value).getTime(),
})
.then((res) => {
LintData.value = res.data;
})
.catch((err) => {
useMessage().error(err.msg);
})
.finally(() => {
userCumulate();
userShard();
upstreamMsgDistMonth();
interfaceSummary();
});
};
// 默认选择第一个公众号
onMounted(async () => {
const { data } = await deptData.queryList();
if (data?.length > 0) {
handleNodeClick(data[0]);
}
});
</script>
<style scoped>
.home-card-item {
width: 100%;
height: 400px;
border-radius: 4px;
transition: all ease 0.3s;
padding: 20px;
overflow: hidden;
background: var(--el-color-white);
color: var(--el-text-color-primary);
border: 1px solid var(--next-border-color-light);
}
</style>