Commit 841040b4 by puhui999 Committed by Gitee

Merge branch 'dev' of gitee.com:yudaocode/yudao-ui-admin-vue3 into dev

Signed-off-by: puhui999 <puhui999@163.com>
parents b2a91aa1 5f10efbf
......@@ -27,10 +27,12 @@
"lint:pretty": "pretty-quick --staged"
},
"dependencies": {
"@form-create/designer": "^3.1.3",
"@form-create/element-ui": "^3.1.18",
"@element-plus/icons-vue": "^2.1.0",
"@form-create/designer": "^3.1.0",
"@form-create/element-ui": "^3.1.17",
"@iconify/iconify": "^3.1.0",
"@videojs-player/vue": "^1.0.0",
"@vueup/vue-quill": "^1.1.1",
"@vueuse/core": "^9.13.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.10",
......
import request from '@/config/axios'
/**
* 商品品牌
*/
export interface BrandVO {
/**
* 品牌编号
*/
id?: number
/**
* 品牌名称
*/
name: string
/**
* 品牌图片
*/
picUrl: string
/**
* 品牌排序
*/
sort?: number
/**
* 品牌描述
*/
description?: string
/**
* 开启状态
*/
status: number
}
// 创建商品品牌
export const createBrand = (data: BrandVO) => {
return request.post({ url: '/product/brand/create', data })
}
// 更新商品品牌
export const updateBrand = (data: BrandVO) => {
return request.put({ url: '/product/brand/update', data })
}
// 删除商品品牌
export const deleteBrand = (id: number) => {
return request.delete({ url: `/product/brand/delete?id=${id}` })
}
// 获得商品品牌
export const getBrand = (id: number) => {
return request.get({ url: `/product/brand/get?id=${id}` })
}
// 获得商品品牌列表
export const getBrandList = (params: any) => {
return request.get({ url: '/product/brand/page', params })
}
......@@ -53,6 +53,7 @@ declare module '@vue/runtime-core' {
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
......
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="80px"
v-loading="formLoading"
>
<el-form-item label="品牌名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入品牌名称" />
</el-form-item>
<el-form-item label="品牌图片" prop="picUrl">
<UploadImg v-model="formData.picUrl" :limit="1" :is-show-tip="false" />
<div style="font-size: 10px">推荐 100x100 图片分辨率</div>
</el-form-item>
<el-form-item label="品牌排序" prop="sort">
<el-input-number v-model="formData.sort" controls-position="right" :min="0" />
</el-form-item>
<el-form-item label="品牌描述">
<el-input v-model="formData.description" type="textarea" placeholder="请输入品牌描述" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts" name="ProductBrand">
import { CommonStatusEnum } from '@/utils/constants'
import * as ProductBrandApi from '@/api/mall/product/brand'
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改
const formData = ref({
id: undefined,
name: '',
picUrl: '',
status: CommonStatusEnum.ENABLE,
description: ''
})
const formRules = reactive({
name: [{ required: true, message: '品牌名称不能为空', trigger: 'blur' }],
picUrl: [{ required: true, message: '品牌图片不能为空', trigger: 'blur' }],
sort: [{ required: true, message: '品牌排序不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
formData.value = await ProductBrandApi.getBrand(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
const data = formData.value as ProductBrandApi.BrandVO
if (formType.value === 'create') {
await ProductBrandApi.createBrand(data)
message.success(t('common.createSuccess'))
} else {
await ProductBrandApi.updateBrand(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: '',
picUrl: '',
status: CommonStatusEnum.ENABLE,
description: ''
}
formRef.value?.resetFields()
}
</script>
<template>
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="品牌名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入品牌名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['product:brand:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" row-key="id" default-expand-all>
<el-table-column label="品牌名称" prop="name" sortable />
<el-table-column label="品牌图片" align="center" prop="picUrl">
<template #default="scope">
<img v-if="scope.row.picUrl" :src="scope.row.picUrl" alt="品牌图片" class="h-100px" />
</template>
</el-table-column>
<el-table-column label="品牌排序" align="center" prop="sort" />
<el-table-column label="开启状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['product:brand:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['product:brand:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<BrandForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts" name="ProductBrand">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as ProductBrandApi from '@/api/mall/product/brand'
import BrandForm from './BrandForm.vue'
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref<any[]>([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
status: undefined,
createTime: []
})
const queryFormRef = ref() // 搜索的表单
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ProductBrandApi.getBrandList(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await ProductBrandApi.deleteBrand(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>
<script setup>
import { ref, reactive } from 'vue'
import { QuillEditor } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'
import { getAccessToken } from '@/utils/auth'
import editorOptions from './quill-options'
const BASE_URL = import.meta.env.VITE_BASE_URL
const message = useMessage()
const props = defineProps({
/* 公众号账号编号 */
accountId: {
type: Number,
required: true
},
/* 编辑器的内容 */
value: {
type: String,
default: ''
},
/* 图片大小 */
maxSize: {
type: Number,
default: 4000 // kb
}
})
const emit = defineEmits(['input'])
const myQuillEditorRef = ref()
const content = ref(props.value.replace(/data-src/g, 'src'))
const loading = ref(false) // 根据图片上传状态来确定是否显示loading动画,刚开始是false,不显示
const actionUrl = ref(BASE_URL + '/admin-api/mp/material/upload-news-image') // 这里写你要上传的图片服务器地址
const headers = ref({ Authorization: 'Bearer ' + getAccessToken() }) // 设置上传的请求头部
const uploadData = reactive({
type: 'image', // TODO 芋艿:试试要不要换成 thumb
accountId: props.accountId
})
const onEditorChange = () => {
//内容改变事件
emit('input', content.value)
}
// 富文本图片上传前
const beforeUpload = () => {
// 显示 loading 动画
loading.value = true
}
// 图片上传成功
// 注意!由于微信公众号的图片有访问限制,所以会显示“此图片来自微信公众号,未经允许不可引用”
const uploadSuccess = (res) => {
// res为图片服务器返回的数据
// 获取富文本组件实例
const quill = myQuillEditorRef.value.quill
// 如果上传成功
const link = res.data
if (link) {
// 获取光标所在位置
let length = quill.getSelection().index
// 插入图片 res.info为服务器返回的图片地址
quill.insertEmbed(length, 'image', link)
// 调整光标到最后
quill.setSelection(length + 1)
} else {
message.error('图片插入失败')
}
// loading 动画消失
loading.value = false
}
// 富文本图片上传失败
const uploadError = () => {
// loading 动画消失
loading.value = false
message.error('图片插入失败')
}
</script>
<template>
<div id="wxEditor">
<div v-loading="loading" element-loading-text="请稍等,图片上传中">
<!-- 图片上传组件辅助-->
<el-upload
class="avatar-uploader"
name="file"
:action="actionUrl"
:headers="headers"
:show-file-list="false"
:data="uploadData"
:on-success="uploadSuccess"
:on-error="uploadError"
:before-upload="beforeUpload"
/>
<QuillEditor
class="editor"
v-model="content"
ref="quillEditorRef"
:options="editorOptions"
@change="onEditorChange($event)"
/>
</div>
</div>
</template>
<style>
.editor {
line-height: normal !important;
height: 500px;
}
.ql-snow .ql-tooltip[data-mode='link']::before {
content: '请输入链接地址:';
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0;
content: '保存';
padding-right: 0;
}
.ql-snow .ql-tooltip[data-mode='video']::before {
content: '请输入视频地址:';
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
content: '14px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='small']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before {
content: '10px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='large']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before {
content: '18px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='huge']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before {
content: '32px';
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: '文本';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before {
content: '标题1';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before {
content: '标题2';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before {
content: '标题3';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before {
content: '标题4';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before {
content: '标题5';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before {
content: '标题6';
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
content: '标准字体';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='serif']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before {
content: '衬线字体';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='monospace']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before {
content: '等宽字体';
}
</style>
const toolbarOptions = [
['bold', 'italic', 'underline', 'strike'], // 加粗 斜体 下划线 删除线
['blockquote', 'code-block'], // 引用 代码块
[{ header: 1 }, { header: 2 }], // 1、2 级标题
[{ list: 'ordered' }, { list: 'bullet' }], // 有序、无序列表
[{ script: 'sub' }, { script: 'super' }], // 上标/下标
[{ indent: '-1' }, { indent: '+1' }], // 缩进
// [{'direction': 'rtl'}], // 文本方向
[{ size: ['small', false, 'large', 'huge'] }], // 字体大小
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
[{ font: [] }], // 字体种类
[{ align: [] }], // 对齐方式
['clean'], // 清除文本格式
['link', 'image', 'video'] // 链接、图片、视频
]
export default {
theme: 'snow',
placeholder: '请输入文章内容',
modules: {
toolbar: {
container: toolbarOptions,
// container: "#toolbar",
handlers: {
image: function (value) {
if (value) {
// 触发input框选择图片文件
document.querySelector('.avatar-uploader input').click()
} else {
this.quill.format('image', false)
}
},
link: function (value) {
if (value) {
const href = prompt('注意!只支持公众号图文链接')
this.quill.format('link', href)
} else {
this.quill.format('link', false)
}
}
}
}
}
}
......@@ -5,30 +5,29 @@
① 移除 avue 组件,使用 ElementUI 原生组件
-->
<template>
<!-- 类型:图片 -->
<div class="pb-30px">
<!-- 类型:image -->
<div v-if="objData.type === 'image'">
<div class="waterfall" v-loading="loading">
<div class="waterfall-item" v-for="item in list" :key="item.mediaId">
<img class="material-img" :src="item.url" />
<p class="item-name">{{ item.name }}</p>
<el-row class="ope-row">
<el-button type="success" @click="selectMaterialFun(item)"
>选择
<i class="el-icon-circle-check el-icon--right"></i>
<el-button type="success" @click="selectMaterialFun(item)">
选择 <Icon icon="ep:circle-check" />
</el-button>
</el-row>
</div>
</div>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getMaterialPageFun"
/>
</div>
<!-- 类型:语音 -->
<!-- 类型:voice -->
<div v-else-if="objData.type === 'voice'">
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
......@@ -36,36 +35,33 @@
<el-table-column label="文件名" align="center" prop="name" />
<el-table-column label="语音" align="center">
<template #default="scope">
<wx-voice-player :url="scope.row.url" />
</template>
</el-table-column>
<el-table-column label="上传时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ formatDate(scope.row.createTime) }}</span>
<WxVoicePlayer :url="scope.row.url" />
</template>
</el-table-column>
<el-table-column
label="操作"
label="上传时间"
align="center"
fixed="right"
class-name="small-padding fixed-width"
>
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center" fixed="right">
<template #default="scope">
<el-button type="text" icon="el-icon-circle-plus" @click="selectMaterialFun(scope.row)"
>选择
<el-button type="text" @click="selectMaterialFun(scope.row)">
选择<Icon icon="ep:plus" />
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getPage"
/>
</div>
<!-- 类型:video -->
<div v-else-if="objData.type === 'video'">
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
......@@ -75,58 +71,60 @@
<el-table-column label="介绍" align="center" prop="introduction" />
<el-table-column label="视频" align="center">
<template #default="scope">
<wx-video-player :url="scope.row.url" />
</template>
</el-table-column>
<el-table-column label="上传时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ formatDate(scope.row.createTime) }}</span>
<WxVideoPlayer :url="scope.row.url" />
</template>
</el-table-column>
<el-table-column
label="上传时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column
label="操作"
align="center"
fixed="right"
class-name="small-padding fixed-width"
>
<template #default="scope">
<el-button type="text" icon="el-icon-circle-plus" @click="selectMaterialFun(scope.row)"
>选择
<el-button type="text" @click="selectMaterialFun(scope.row)">
选择<Icon icon="akar-icons:circle-plus" />
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getMaterialPageFun"
/>
</div>
<!-- 类型:news -->
<div v-else-if="objData.type === 'news'">
<div class="waterfall" v-loading="loading">
<div class="waterfall-item" v-for="item in list" :key="item.mediaId">
<div v-if="item.content && item.content.newsItem">
<wx-news :articles="item.content.newsItem" />
<WxNews :articles="item.content.newsItem" />
<el-row class="ope-row">
<el-button type="success" @click="selectMaterialFun(item)">
选择<i class="el-icon-circle-check el-icon--right"></i>
选择<Icon icon="ep:circle-check" />
</el-button>
</el-row>
</div>
</div>
</div>
<!-- 分页组件 -->
<pagination
v-show="total > 0"
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getMaterialPageFun"
/>
</div>
</div>
</template>
<script lang="ts" name="WxMaterialSelect">
......@@ -136,7 +134,7 @@ import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue'
import { getMaterialPage } from '@/api/mp/material'
import { getFreePublishPage } from '@/api/mp/freePublish'
import { getDraftPage } from '@/api/mp/draft'
import { dateFormatter, formatDate } from '@/utils/formatTime'
import { dateFormatter } from '@/utils/formatTime'
import { defineComponent, PropType } from 'vue'
export default defineComponent({
......@@ -173,7 +171,7 @@ export default defineComponent({
const newsTypeRef = ref(props.newsType)
const selectMaterialFun = (item) => {
ctx.emit('selectMaterial', item)
ctx.emit('select-material', item)
}
/** 搜索按钮操作 */
const handleQuery = () => {
......@@ -203,9 +201,10 @@ export default defineComponent({
total.value = data.total
loading.value = false
}
const getFreePublishPageFun = async () => {
let data = await getFreePublishPage(queryParams)
data.list.foreach((item) => {
data.list.forEach((item) => {
const newsItem = item.content.newsItem
newsItem.forEach((article) => {
article.picUrl = article.thumbUrl
......@@ -232,6 +231,7 @@ export default defineComponent({
onMounted(async () => {
getPage()
})
return {
handleQuery,
dateFormatter,
......@@ -239,7 +239,6 @@ export default defineComponent({
getMaterialPageFun,
getPage,
formatDate,
newsTypeRef,
queryParams,
objDataRef,
list,
......@@ -249,7 +248,6 @@ export default defineComponent({
}
})
</script>
<style lang="scss" scoped>
/*瀑布流样式*/
.waterfall {
......
export default {
list: [
{
mediaId: 'r6ryvl6LrxBU0miaST4Y-q-G9pdsmZw0OYG4FzHQkKfpLfEwIH51wy2bxisx8PvW',
content: {
newsItem: [
{
title: '我是标题(OOO)',
author: '我是作者',
digest: '我是摘要',
content: '我是内容',
contentSourceUrl: 'https://www.iocoder.cn',
thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn',
showCoverPic: 0,
needOpenComment: 0,
onlyFansCanComment: 0,
url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9XaFphcmtJVFh3VEc4Q1MxQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuN2QxTE56SFBCYXc2RE9NcUxIeS1CQjJuUHhTWjBlN2VOeGRpRi1fZUhwN1FNQjdrQV9yRU9EU0hibHREZmZoVW5acnZrN3ZjaWsxejR3RGpKczBzTHFIM0dFNFZWVkpBc0dWWlAzUEhlVmpnfn4%3D&chksm=1f6354802814dd969ef83c0f3babe555c614270b30bc383beaf7ffd13b0257f0fe5ced9af694#rd',
thumbUrl:
'http://test.yudao.iocoder.cn/r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn.png'
},
{
title: '我是标题(XXX)',
author: '我是作者',
digest: '我是摘要',
content: '我是内容',
contentSourceUrl: 'https://www.iocoder.cn',
thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn',
showCoverPic: 0,
needOpenComment: 0,
onlyFansCanComment: 0,
url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9yTlYwOEs1clpwcE5OUEhCQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuN0NSMjFqN3N1aUZMbFNVLTZHN2ZDME9qOGp2THk2RFNlSTlKZ3Y1czFVZDdQQm5IeUg3dEppSUtpQUh5SExOOTRkT3dHNUdBdHdWSWlOendlREV3dS1jUEVQbFpiVTZmVW5iRWhZcGdkNTFRfn4%3D&chksm=1f6354802814dd96a403151cd44c7da4eecf0e475d25423e46ecd795b513bafd829a75daef9b#rd',
thumbUrl:
'http://test.yudao.iocoder.cn/r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn.png'
}
]
},
updateTime: 1673655730
},
{
mediaId: 'r6ryvl6LrxBU0miaST4Y-jGpXnO73ihN0lsNXknCRQHapp2xgHMRxHKG50LituFe',
content: {
newsItem: [
{
title: '我是标题(修改)',
author: '我是作者',
digest: '我是摘要',
content: '我是内容',
contentSourceUrl: 'https://www.iocoder.cn',
thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn',
showCoverPic: 0,
needOpenComment: 0,
onlyFansCanComment: 0,
url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl95WVFXYndIZnZJd0t5cjgvQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuN1dlNURPbWswbEF4RDd5dVJTdjQ4cm9Cc0Q1TWhpMUh6SE1hVEE3ZHljaHhlZjZYSGF5N2JNSHpDTlh6ajNZbkpGTGpTcUQ4M3NMdW41ZUpXNFZZQ1VKbVlaMVp5ekxEV1czREdsY1dOYTZnfn4%3D&chksm=1f6354be2814dda8e6238037c2ebd52b1c8e80e93249a861ad80e4d40e5ca7207233475ca689#rd',
thumbUrl:
'http://test.yudao.iocoder.cn/r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn.png'
}
]
},
updateTime: 1673655584
},
{
mediaId: 'r6ryvl6LrxBU0miaST4Y-v5SrbNCPpD6M_p3TmSrYwTjKogs-0DMJgmjMyNZPeMO',
content: {
newsItem: [
{
title: '1321',
author: '3232',
digest: '1333',
content: '<p>444</p>',
contentSourceUrl: 'http://www.iocoder.cn',
thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-tlQmcl3RdC-Jcgns6IQtf7zenGy3b86WLT7GzUcrb1T',
showCoverPic: 0,
needOpenComment: 0,
onlyFansCanComment: 0,
url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9jelJiaDAzbmdpSkJOZ2M2QWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuNDNXVVc2ZDRYeTY0Zm1weXR6dE9vQWh1TzEwbEpUVnRfVzJyaGFDNXBkZ0ZXM2JFOTNaRHNhOHRUeFdEanhMeS01X01kMUNWQ1BpRER3cjYwTl9pMnpFLUJhZXFucVVfM1pDUXlTUEl1S25nfn4%3D&chksm=1f6354bc2814ddaa56a90ad5bc3d078601c8d1589ba01827a8170587bc830ff9747b5f59c3a0#rd',
thumbUrl:
'http://mmbiz.qpic.cn/mmbiz_png/btUmCVHwbJUoicwBiacjVeQbu6QxgBVrukfSJXz509boa21SpH8OVHAqXCJiaiaAaHQJNxwwsa0gHRXVr0G5EZYamw/0?wx_fmt=png'
}
]
},
updateTime: 1673628969
},
{
mediaId: 'r6ryvl6LrxBU0miaST4Y-vdWrisK5EZbk4Y3tzh8P0PG0eEUbnQrh0BcsEb3WNP0',
content: {
newsItem: [
{
title: 'tudou',
author: 'haha',
digest: '312',
content: '<p>132312</p>',
contentSourceUrl: 'http://www.iocoder.cn',
thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-pgFtUNLu1foMSAMkoOsrQrTZ8EtTMssBLfTtzP0dfjG',
showCoverPic: 0,
needOpenComment: 0,
onlyFansCanComment: 0,
url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9qdkJ1ZjBoUmg2Uk9TS3RlQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuNVg2aTJsaC1fMkU2eXNacUplN3VDTTZFZkhtMjhuTUZvWkxsNDBRSXExY2tiVXRHb09TaHgtREhzY3doZ0JYeC1TSTZ5eWZldXJsOWtfbV8yMi1aYkcyZ2pOY0haM0Ntb3VSWEtxUGVFRlNBfn4%3D&chksm=1f6354ba2814ddacf0184b24d310483641ef190b1faac098c285eb416c70017e2f54decfa1af#rd',
thumbUrl:
'http://test.yudao.iocoder.cn/r6ryvl6LrxBU0miaST4Y-pgFtUNLu1foMSAMkoOsrQrTZ8EtTMssBLfTtzP0dfjG.png'
}
]
},
updateTime: 1673628760
},
{
mediaId: 'r6ryvl6LrxBU0miaST4Y-u9kTIm1DhWZDdXyxsxUVv2Z5DAB99IPxkIRTUUD206k',
content: {
newsItem: [
{
title: '12',
author: '333',
digest: '123',
content: '123',
contentSourceUrl: 'https://www.iocoder.cn',
thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-jVixJGgnBnkBPRbuVptOW0CHYuQFyiOVNtamctS8xU8',
showCoverPic: 0,
needOpenComment: 0,
onlyFansCanComment: 0,
url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9qVVhpSDZUaFJWTzBBWWRVQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuNWRnTDJWYmF2NER0clV1bThmQ0xUR3hqQnJkZ3BJSUNmNDJmc0lCZ1dadkVnZ3Z5bkN4YWtVUjhoaWZWYzZURUR4NnpMd0Y4Z3U5aUdib0lkMzI4Rjg3SG9JX2FycTMxbUctOHplaTlQVVhnfn4%3D&chksm=1f6354b62814dda076c778af33f06580165d8aa81f7798d55cfabb1886b5c74d9b2124a3535c#rd',
thumbUrl:
'http://test.yudao.iocoder.cn/r6ryvl6LrxBU0miaST4Y-jVixJGgnBnkBPRbuVptOW0CHYuQFyiOVNtamctS8xU8.jpg'
}
]
},
updateTime: 1673626494
},
{
mediaId: 'r6ryvl6LrxBU0miaST4Y-sO24upobaENDmeByfBTfaozB3aOqSMAV0lGy-UkHXE7',
content: {
newsItem: [
{
title: '我是标题',
author: '我是作者',
digest: '我是摘要',
content: '我是内容',
contentSourceUrl: 'https://www.iocoder.cn',
thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn',
showCoverPic: 0,
needOpenComment: 0,
onlyFansCanComment: 0,
url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9LT2dqRnpMNUpsR0hjYWtBQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuNGNmazZTdlE5WkxvU0tfX2V5cjV2WjJiR0xjQUhyREFSZWo2eWNrUW9EYVh6ZkpWRXBLR3FmTEV6YldBMno3Q2ZvVXBSdzlaVDc3aFhndEpQWUwzWmFMUWt0YVVURE1VZ1FsQTdPMlRtc3JBfn4%3D&chksm=1f6354aa2814ddbcc2637382f963a8742993ac38ebcebe6e3411df5ac82ac7bbdb391be6494a#rd',
thumbUrl:
'http://test.yudao.iocoder.cn/r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn.png'
}
]
},
updateTime: 1673534279
}
],
total: 6
}
export default [
{
value: 'view',
label: '跳转网页'
},
{
value: 'miniprogram',
label: '跳转小程序'
},
{
value: 'click',
label: '点击回复'
},
{
value: 'article_view_limited',
label: '跳转图文消息'
},
{
value: 'scancode_push',
label: '扫码直接返回结果'
},
{
value: 'scancode_waitmsg',
label: '扫码回复'
},
{
value: 'pic_sysphoto',
label: '系统拍照发图'
},
{
value: 'pic_photo_or_album',
label: '拍照或者相册'
},
{
value: 'pic_weixin',
label: '微信相册'
},
{
value: 'location_select',
label: '选择地理位置'
}
]
<template>
<span>开发中</span>
</template>
<template>
<Dialog title="修改" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="80px"
v-loading="formLoading"
>
<el-form-item label="昵称" prop="nickname">
<el-input v-model="formData.nickname" placeholder="请输入昵称" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="标签" prop="tagIds">
<el-select v-model="formData.tagIds" multiple clearable placeholder="请选择标签">
<el-option
v-for="item in tagList"
:key="item.tagId"
:label="item.name"
:value="item.tagId"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import * as MpTagApi from '@/api/mp/tag'
import * as MpUserApi from '@/api/mp/user'
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const formLoading = ref(false) // 表单的加载中
const formData = ref({
id: undefined,
nickname: undefined,
remark: undefined,
tagIds: []
})
const formRules = reactive({}) // 表单的校验
const formRef = ref() // 表单 Ref
const tagList = ref([]) // 公众号标签列表
/** 打开弹窗 */
const open = async (id: number) => {
dialogVisible.value = true
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
formData.value = await MpUserApi.getUser(id)
} finally {
formLoading.value = false
}
}
// 加载标签
tagList.value = await MpTagApi.getSimpleTagList()
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
await MpUserApi.updateUser(formData.value)
message.success(t('common.updateSuccess'))
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
nickname: undefined,
remark: undefined,
tagIds: []
}
formRef.value?.resetFields()
}
</script>
<template>
<doc-alert title="公众号粉丝" url="https://doc.iocoder.cn/mp/user/" />
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="公众号" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请选择公众号" class="!w-240px">
<el-option
v-for="item in accountList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="用户标识" prop="openid">
<el-input
v-model="queryParams.openid"
placeholder="请输入用户标识"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="昵称" prop="nickname">
<el-input
v-model="queryParams.nickname"
placeholder="请输入昵称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
<el-button type="success" plain @click="handleSync" v-hasPermi="['mp:user:sync']">
<Icon icon="ep:refresh" class="mr-5px" /> 同步
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="用户标识" align="center" prop="openid" width="260" />
<el-table-column label="昵称" align="center" prop="nickname" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="标签" align="center" prop="tagIds" width="200">
<template #default="scope">
<span v-for="(tagId, index) in scope.row.tagIds" :key="index">
<el-tag>{{ tagList.find((tag) => tag.tagId === tagId)?.name }} </el-tag>&nbsp;
</span>
</template>
</el-table-column>
<el-table-column label="订阅状态" align="center" prop="subscribeStatus">
<template #default="scope">
<el-tag v-if="scope.row.subscribeStatus === 0" type="success">已订阅</el-tag>
<el-tag v-else type="danger">未订阅</el-tag>
</template>
</el-table-column>
<el-table-column
label="订阅时间"
align="center"
prop="subscribeTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
type="primary"
link
@click="openForm(scope.row.id)"
v-hasPermi="['mp:user:update']"
>
修改
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗:修改 -->
<UserForm ref="formRef" @success="getList" />
</template>
<script lang="ts" setup name="MpUser">
import { dateFormatter } from '@/utils/formatTime'
import * as MpAccountApi from '@/api/mp/account'
import * as MpUserApi from '@/api/mp/user'
import * as MpTagApi from '@/api/mp/tag'
import UserForm from './UserForm.vue'
const message = useMessage() // 消息
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
accountId: null,
openid: null,
nickname: null
})
const queryFormRef = ref() // 搜索的表单
const accountList = ref([]) // 公众号账号列表
const tagList = ref([]) // 公众号标签列表
/** 查询列表 */
const getList = async () => {
// 如果没有选中公众号账号,则进行提示。
if (!queryParams.accountId) {
message.error('未选中公众号,无法查询用户')
return false
}
try {
loading.value = true
const data = await MpUserApi.getUserPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
// 默认选中第一个
if (accountList.value.length > 0) {
queryParams.accountId = accountList.value[0].id
}
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (id: number) => {
formRef.value.open(id)
}
/** 同步标签 */
const handleSync = async () => {
const accountId = queryParams.accountId
try {
await message.confirm('是否确认同步粉丝?')
await MpUserApi.syncUser(accountId)
message.success('开始从微信公众号同步粉丝信息,同步需要一段时间,建议稍后再查询')
await getList()
} catch {}
}
/** 初始化 */
onMounted(async () => {
// 加载标签
tagList.value = await MpTagApi.getSimpleTagList()
// 加载账号
accountList.value = await MpAccountApi.getSimpleAccountList()
if (accountList.value.length > 0) {
queryParams.accountId = accountList.value[0].id
}
await getList()
})
</script>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment