fix: resolve todo
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { MpMaterialApi } from '#/api/mp/material';
|
import type { MpMaterialApi } from '#/api/mp/material';
|
||||||
|
|
||||||
import { reactive, ref, watch } from 'vue';
|
import { computed, watch } from 'vue';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
import { NewsType } from '@vben/constants';
|
import { NewsType } from '@vben/constants';
|
||||||
@@ -34,16 +34,6 @@ const emit = defineEmits<{
|
|||||||
(e: 'selectMaterial', item: any): void;
|
(e: 'selectMaterial', item: any): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// TODO @dlyan @AI:这里是不是应该都用 grid;
|
|
||||||
const loading = ref(false); // 遮罩层
|
|
||||||
const total = ref(0); // 总条数
|
|
||||||
const list = ref<any[]>([]); // 数据列表
|
|
||||||
const queryParams = reactive({
|
|
||||||
accountId: props.accountId,
|
|
||||||
pageNo: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
}); // 查询参数
|
|
||||||
|
|
||||||
const voiceGridColumns: VxeTableGridOptions<MpMaterialApi.Material>['columns'] =
|
const voiceGridColumns: VxeTableGridOptions<MpMaterialApi.Material>['columns'] =
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -123,6 +113,99 @@ const videoGridColumns: VxeTableGridOptions<MpMaterialApi.Material>['columns'] =
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Image Grid
|
||||||
|
const [ImageGrid, imageGridApi] = useVbenVxeGrid({
|
||||||
|
gridOptions: {
|
||||||
|
columns: [],
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, { accountId }) => {
|
||||||
|
const finalAccountId = accountId ?? props.accountId;
|
||||||
|
if (!finalAccountId) {
|
||||||
|
return { list: [], total: 0 };
|
||||||
|
}
|
||||||
|
return await getMaterialPage({
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
accountId: finalAccountId,
|
||||||
|
type: 'image',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
autoLoad: false,
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'mediaId',
|
||||||
|
isHover: true,
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<MpMaterialApi.Material>,
|
||||||
|
});
|
||||||
|
|
||||||
|
// News Grid
|
||||||
|
const [NewsGrid, newsGridApi] = useVbenVxeGrid({
|
||||||
|
gridOptions: {
|
||||||
|
columns: [],
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, { accountId }) => {
|
||||||
|
const finalAccountId = accountId ?? props.accountId;
|
||||||
|
if (!finalAccountId) {
|
||||||
|
return { list: [], total: 0 };
|
||||||
|
}
|
||||||
|
if (props.newsType === NewsType.Published) {
|
||||||
|
const data = await getFreePublishPage({
|
||||||
|
accountId: finalAccountId,
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
});
|
||||||
|
data.list.forEach((item: any) => {
|
||||||
|
const articles = item.content.newsItem;
|
||||||
|
articles.forEach((article: any) => {
|
||||||
|
article.picUrl = article.thumbUrl;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
const data = await getDraftPage({
|
||||||
|
accountId: finalAccountId,
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
});
|
||||||
|
data.list.forEach((draft: any) => {
|
||||||
|
const articles = draft.content.newsItem;
|
||||||
|
articles.forEach((article: any) => {
|
||||||
|
article.picUrl = article.thumbUrl;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
autoLoad: false,
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'mediaId',
|
||||||
|
isHover: true,
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<any>,
|
||||||
|
});
|
||||||
|
|
||||||
const [VoiceGrid, voiceGridApi] = useVbenVxeGrid({
|
const [VoiceGrid, voiceGridApi] = useVbenVxeGrid({
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
border: true,
|
border: true,
|
||||||
@@ -136,7 +219,7 @@ const [VoiceGrid, voiceGridApi] = useVbenVxeGrid({
|
|||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
ajax: {
|
ajax: {
|
||||||
query: async ({ page }, { accountId }) => {
|
query: async ({ page }, { accountId }) => {
|
||||||
const finalAccountId = accountId ?? queryParams.accountId;
|
const finalAccountId = accountId ?? props.accountId;
|
||||||
if (!finalAccountId) {
|
if (!finalAccountId) {
|
||||||
return { list: [], total: 0 };
|
return { list: [], total: 0 };
|
||||||
}
|
}
|
||||||
@@ -172,7 +255,7 @@ const [VideoGrid, videoGridApi] = useVbenVxeGrid({
|
|||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
ajax: {
|
ajax: {
|
||||||
query: async ({ page }, { accountId }) => {
|
query: async ({ page }, { accountId }) => {
|
||||||
const finalAccountId = accountId ?? queryParams.accountId;
|
const finalAccountId = accountId ?? props.accountId;
|
||||||
if (finalAccountId === undefined || finalAccountId === null) {
|
if (finalAccountId === undefined || finalAccountId === null) {
|
||||||
return { list: [], total: 0 };
|
return { list: [], total: 0 };
|
||||||
}
|
}
|
||||||
@@ -195,91 +278,164 @@ const [VideoGrid, videoGridApi] = useVbenVxeGrid({
|
|||||||
} as VxeTableGridOptions<MpMaterialApi.Material>,
|
} as VxeTableGridOptions<MpMaterialApi.Material>,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 从 Grid 获取数据
|
||||||
|
const imageList = computed(() => {
|
||||||
|
try {
|
||||||
|
const tableData = imageGridApi.grid?.getTableData();
|
||||||
|
return (tableData?.tableData as MpMaterialApi.Material[]) || [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const imageLoading = computed(() => {
|
||||||
|
return imageGridApi.grid?.loading || false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const imageTotal = computed(() => {
|
||||||
|
try {
|
||||||
|
const proxyInfo = imageGridApi.grid?.getProxyInfo();
|
||||||
|
return proxyInfo?.pager?.total || 0;
|
||||||
|
} catch {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const imageCurrentPage = computed({
|
||||||
|
get: () => {
|
||||||
|
try {
|
||||||
|
return imageGridApi.grid?.pagerConfig?.currentPage || 1;
|
||||||
|
} catch {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: (value: number) => {
|
||||||
|
imageGridApi.grid?.commitProxy('page', { currentPage: value });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const imagePageSize = computed({
|
||||||
|
get: () => {
|
||||||
|
try {
|
||||||
|
return imageGridApi.grid?.pagerConfig?.pageSize || 10;
|
||||||
|
} catch {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: (value: number) => {
|
||||||
|
imageGridApi.grid?.commitProxy('page', { pageSize: value, currentPage: 1 });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const newsList = computed(() => {
|
||||||
|
try {
|
||||||
|
const tableData = newsGridApi.grid?.getTableData();
|
||||||
|
return (tableData?.tableData as any[]) || [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const newsLoading = computed(() => {
|
||||||
|
return newsGridApi.grid?.loading || false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const newsTotal = computed(() => {
|
||||||
|
try {
|
||||||
|
const proxyInfo = newsGridApi.grid?.getProxyInfo();
|
||||||
|
return proxyInfo?.pager?.total || 0;
|
||||||
|
} catch {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const newsCurrentPage = computed({
|
||||||
|
get: () => {
|
||||||
|
try {
|
||||||
|
return newsGridApi.grid?.pagerConfig?.currentPage || 1;
|
||||||
|
} catch {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: (value: number) => {
|
||||||
|
newsGridApi.grid?.commitProxy('page', { currentPage: value });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const newsPageSize = computed({
|
||||||
|
get: () => {
|
||||||
|
try {
|
||||||
|
return newsGridApi.grid?.pagerConfig?.pageSize || 10;
|
||||||
|
} catch {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: (value: number) => {
|
||||||
|
newsGridApi.grid?.commitProxy('page', { pageSize: value, currentPage: 1 });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
function selectMaterialFun(item: any) {
|
function selectMaterialFun(item: any) {
|
||||||
emit('selectMaterial', item);
|
emit('selectMaterial', item);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMaterialPageFun() {
|
// 监听 accountId 变化
|
||||||
const data = await getMaterialPage({
|
|
||||||
...queryParams,
|
|
||||||
type: props.type,
|
|
||||||
});
|
|
||||||
list.value = data.list;
|
|
||||||
total.value = data.total;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getFreePublishPageFun() {
|
|
||||||
const data = await getFreePublishPage(queryParams);
|
|
||||||
data.list.forEach((item: any) => {
|
|
||||||
const articles = item.content.newsItem;
|
|
||||||
articles.forEach((article: any) => {
|
|
||||||
article.picUrl = article.thumbUrl;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
list.value = data.list;
|
|
||||||
total.value = data.total;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getDraftPageFun() {
|
|
||||||
const data = await getDraftPage(queryParams);
|
|
||||||
data.list.forEach((draft: any) => {
|
|
||||||
const articles = draft.content.newsItem;
|
|
||||||
articles.forEach((article: any) => {
|
|
||||||
article.picUrl = article.thumbUrl;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
list.value = data.list;
|
|
||||||
total.value = data.total;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getPage() {
|
|
||||||
if (props.type === 'voice') {
|
|
||||||
await voiceGridApi.reload({ accountId: queryParams.accountId });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (props.type === 'video') {
|
|
||||||
await videoGridApi.reload({ accountId: queryParams.accountId });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
if (props.type === 'news' && props.newsType === NewsType.Published) {
|
|
||||||
await getFreePublishPageFun();
|
|
||||||
} else if (props.type === 'news' && props.newsType === NewsType.Draft) {
|
|
||||||
await getDraftPageFun();
|
|
||||||
} else {
|
|
||||||
await getMaterialPageFun();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.accountId,
|
() => props.accountId,
|
||||||
(accountId) => {
|
(accountId) => {
|
||||||
queryParams.accountId = accountId;
|
switch (props.type) {
|
||||||
queryParams.pageNo = 1;
|
case 'image': {
|
||||||
getPage();
|
imageGridApi.reload({ accountId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'news': {
|
||||||
|
newsGridApi.reload({ accountId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'video': {
|
||||||
|
videoGridApi.reload({ accountId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'voice': {
|
||||||
|
voiceGridApi.reload({ accountId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 监听 type 变化
|
||||||
watch(
|
watch(
|
||||||
() => props.type,
|
() => props.type,
|
||||||
() => {
|
() => {
|
||||||
queryParams.pageNo = 1;
|
switch (props.type) {
|
||||||
getPage();
|
case 'image': {
|
||||||
|
imageGridApi.reload({ accountId: props.accountId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'news': {
|
||||||
|
newsGridApi.reload({ accountId: props.accountId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'video': {
|
||||||
|
videoGridApi.reload({ accountId: props.accountId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'voice': {
|
||||||
|
voiceGridApi.reload({ accountId: props.accountId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 监听 newsType 变化
|
||||||
watch(
|
watch(
|
||||||
() => props.newsType,
|
() => props.newsType,
|
||||||
() => {
|
() => {
|
||||||
if (props.type === 'news') {
|
if (props.type === 'news') {
|
||||||
queryParams.pageNo = 1;
|
newsGridApi.reload({ accountId: props.accountId });
|
||||||
getPage();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -289,42 +445,47 @@ watch(
|
|||||||
<Page :bordered="false" class="pb-8">
|
<Page :bordered="false" class="pb-8">
|
||||||
<!-- 类型:image -->
|
<!-- 类型:image -->
|
||||||
<template v-if="props.type === 'image'">
|
<template v-if="props.type === 'image'">
|
||||||
<Spin :spinning="loading">
|
<div class="image-grid-wrapper">
|
||||||
<div
|
<ImageGrid>
|
||||||
class="mx-auto w-full columns-1 [column-gap:10px] md:columns-2 lg:columns-3 xl:columns-4 2xl:columns-5"
|
<template #default>
|
||||||
>
|
<Spin :spinning="imageLoading">
|
||||||
<div
|
<div
|
||||||
v-for="item in list"
|
class="mx-auto w-full columns-1 [column-gap:10px] md:columns-2 lg:columns-3 xl:columns-4 2xl:columns-5"
|
||||||
:key="item.mediaId"
|
>
|
||||||
class="mb-2.5 h-72 break-inside-avoid border border-[#eaeaea] p-2.5"
|
<div
|
||||||
>
|
v-for="item in imageList"
|
||||||
<img
|
:key="item.mediaId"
|
||||||
class="h-48 w-full object-contain"
|
class="mb-2.5 h-72 break-inside-avoid border border-[#eaeaea] p-2.5"
|
||||||
:src="item.url"
|
>
|
||||||
alt="素材图片"
|
<img
|
||||||
|
class="h-48 w-full object-contain"
|
||||||
|
:src="item.url"
|
||||||
|
alt="素材图片"
|
||||||
|
/>
|
||||||
|
<p class="truncate text-center text-xs leading-[30px]">
|
||||||
|
{{ item.name }}
|
||||||
|
</p>
|
||||||
|
<Row class="flex justify-center pt-2.5">
|
||||||
|
<Button type="primary" @click="selectMaterialFun(item)">
|
||||||
|
选择
|
||||||
|
<template #icon>
|
||||||
|
<IconifyIcon icon="lucide:circle-check" />
|
||||||
|
</template>
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Spin>
|
||||||
|
<Pagination
|
||||||
|
v-model:current="imageCurrentPage"
|
||||||
|
v-model:page-size="imagePageSize"
|
||||||
|
:total="imageTotal"
|
||||||
|
class="mt-4"
|
||||||
|
show-size-changer
|
||||||
/>
|
/>
|
||||||
<p class="truncate text-center text-xs leading-[30px]">
|
</template>
|
||||||
{{ item.name }}
|
</ImageGrid>
|
||||||
</p>
|
</div>
|
||||||
<Row class="flex justify-center pt-2.5">
|
|
||||||
<Button type="primary" @click="selectMaterialFun(item)">
|
|
||||||
选择
|
|
||||||
<template #icon>
|
|
||||||
<IconifyIcon icon="lucide:circle-check" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Spin>
|
|
||||||
<Pagination
|
|
||||||
v-model:current="queryParams.pageNo"
|
|
||||||
v-model:page-size="queryParams.pageSize"
|
|
||||||
:total="total"
|
|
||||||
class="mt-4"
|
|
||||||
@change="getPage"
|
|
||||||
@show-size-change="getPage"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 类型:voice -->
|
<!-- 类型:voice -->
|
||||||
@@ -363,37 +524,52 @@ watch(
|
|||||||
|
|
||||||
<!-- 类型:news -->
|
<!-- 类型:news -->
|
||||||
<template v-else-if="props.type === 'news'">
|
<template v-else-if="props.type === 'news'">
|
||||||
<Spin :spinning="loading">
|
<div class="news-grid-wrapper">
|
||||||
<div
|
<NewsGrid>
|
||||||
class="mx-auto w-full columns-1 [column-gap:10px] md:columns-2 lg:columns-3 xl:columns-4 2xl:columns-5"
|
<template #default>
|
||||||
>
|
<Spin :spinning="newsLoading">
|
||||||
<div
|
<div
|
||||||
v-for="item in list"
|
class="mx-auto w-full columns-1 [column-gap:10px] md:columns-2 lg:columns-3 xl:columns-4 2xl:columns-5"
|
||||||
:key="item.mediaId"
|
>
|
||||||
class="mb-2.5 break-inside-avoid border border-[#eaeaea] p-2.5"
|
<div
|
||||||
>
|
v-for="item in newsList"
|
||||||
<div v-if="item.content && item.content.newsItem">
|
:key="item.mediaId"
|
||||||
<WxNews :articles="item.content.newsItem" />
|
class="mb-2.5 break-inside-avoid border border-[#eaeaea] p-2.5"
|
||||||
<Row class="flex justify-center pt-2.5">
|
>
|
||||||
<Button type="primary" @click="selectMaterialFun(item)">
|
<div v-if="item.content && item.content.newsItem">
|
||||||
选择
|
<WxNews :articles="item.content.newsItem" />
|
||||||
<template #icon>
|
<Row class="flex justify-center pt-2.5">
|
||||||
<IconifyIcon icon="lucide:circle-check" />
|
<Button type="primary" @click="selectMaterialFun(item)">
|
||||||
</template>
|
选择
|
||||||
</Button>
|
<template #icon>
|
||||||
</Row>
|
<IconifyIcon icon="lucide:circle-check" />
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</Button>
|
||||||
</div>
|
</Row>
|
||||||
</Spin>
|
</div>
|
||||||
<Pagination
|
</div>
|
||||||
v-model:current="queryParams.pageNo"
|
</div>
|
||||||
v-model:page-size="queryParams.pageSize"
|
</Spin>
|
||||||
:total="total"
|
<Pagination
|
||||||
class="mt-4"
|
v-model:current="newsCurrentPage"
|
||||||
@change="getPage"
|
v-model:page-size="newsPageSize"
|
||||||
@show-size-change="getPage"
|
:total="newsTotal"
|
||||||
/>
|
class="mt-4"
|
||||||
|
show-size-changer
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</NewsGrid>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.image-grid-wrapper :deep(.vxe-grid--body-wrapper) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news-grid-wrapper :deep(.vxe-grid--body-wrapper) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,79 +1,120 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { provide, reactive, ref } from 'vue';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { MpMaterialApi } from '#/api/mp/material';
|
||||||
|
|
||||||
|
import { computed, provide, ref } from 'vue';
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
import { useAccess } from '@vben/access';
|
||||||
import { confirm, DocAlert, Page } from '@vben/common-ui';
|
import { confirm, DocAlert, Page } from '@vben/common-ui';
|
||||||
import { IconifyIcon } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
||||||
import { Button, Card, Form, message, Pagination, Tabs } from 'ant-design-vue';
|
import { Button, message, Tabs } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { deletePermanentMaterial, getMaterialPage } from '#/api/mp/material';
|
import { deletePermanentMaterial, getMaterialPage } from '#/api/mp/material';
|
||||||
import { WxAccountSelect } from '#/views/mp/components';
|
import { WxAccountSelect } from '#/views/mp/components';
|
||||||
|
|
||||||
import ImageTable from './modules/image-table.vue';
|
import {
|
||||||
|
useGridFormSchema,
|
||||||
|
useImageGridColumns,
|
||||||
|
useVideoGridColumns,
|
||||||
|
useVoiceGridColumns,
|
||||||
|
} from './modules/data';
|
||||||
import { UploadType } from './modules/upload';
|
import { UploadType } from './modules/upload';
|
||||||
import UploadFile from './modules/UploadFile.vue';
|
import UploadFile from './modules/UploadFile.vue';
|
||||||
import UploadVideo from './modules/UploadVideo.vue';
|
import UploadVideo from './modules/UploadVideo.vue';
|
||||||
import VideoTable from './modules/video-table.vue';
|
|
||||||
import VoiceTable from './modules/voice-table.vue';
|
|
||||||
|
|
||||||
defineOptions({ name: 'MpMaterial' });
|
defineOptions({ name: 'MpMaterial' });
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
const { hasAccessByCodes } = useAccess();
|
||||||
|
|
||||||
const type = ref<UploadType>(UploadType.Image); // 素材类型
|
const type = ref<UploadType>(UploadType.Image); // 素材类型
|
||||||
const loading = ref(false); // 遮罩层
|
const showCreateVideo = ref(false); // 是否新建视频的弹窗
|
||||||
const list = ref<any[]>([]); // 数据列表
|
|
||||||
const total = ref(0); // 总条数
|
|
||||||
|
|
||||||
const accountId = ref(-1);
|
const accountId = ref(-1);
|
||||||
provide('accountId', accountId);
|
provide('accountId', accountId);
|
||||||
|
|
||||||
// TODO @dlyan @AI:这里是不是应该都用 grid;类似 yudao-ui-admin-vben-v5/apps/web-ele/src/views/mp/autoReply/index.vue
|
// 根据类型获取对应的列配置
|
||||||
const queryParams = reactive({
|
const getColumnsByType = () => {
|
||||||
accountId,
|
switch (type.value) {
|
||||||
pageNo: 1,
|
case UploadType.Image: {
|
||||||
pageSize: 10,
|
return useImageGridColumns();
|
||||||
permanent: true,
|
}
|
||||||
}); // 查询参数
|
case UploadType.Video: {
|
||||||
const showCreateVideo = ref(false); // 是否新建视频的弹窗
|
return useVideoGridColumns();
|
||||||
|
}
|
||||||
/** 侦听公众号变化 */
|
case UploadType.Voice: {
|
||||||
function onAccountChanged(id: number) {
|
return useVoiceGridColumns();
|
||||||
accountId.value = id;
|
}
|
||||||
queryParams.accountId = id;
|
default: {
|
||||||
queryParams.pageNo = 1;
|
return [];
|
||||||
getList();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** 查询列表 */
|
|
||||||
async function getList() {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const data = await getMaterialPage({
|
|
||||||
...queryParams,
|
|
||||||
type: type.value,
|
|
||||||
});
|
|
||||||
list.value = data.list;
|
|
||||||
total.value = data.total;
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useGridFormSchema(),
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
columns: getColumnsByType(),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
pagerConfig: {},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
const finalAccountId = formValues?.accountId ?? accountId.value;
|
||||||
|
if (!finalAccountId || finalAccountId === -1) {
|
||||||
|
return { list: [], total: 0 };
|
||||||
|
}
|
||||||
|
return await getMaterialPage({
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
type: type.value,
|
||||||
|
permanent: true,
|
||||||
|
accountId: finalAccountId,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
autoLoad: false,
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
|
height: type.value === UploadType.Image ? 220 : 'auto',
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
refresh: true,
|
||||||
|
search: true,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<MpMaterialApi.Material>,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 当 tab 切换时,更新 Grid 的 columns 和 rowConfig
|
||||||
|
async function onTabChange() {
|
||||||
|
const columns = getColumnsByType();
|
||||||
|
await gridApi.setGridOptions({
|
||||||
|
columns,
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
|
height: type.value === UploadType.Image ? 220 : 'auto',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await gridApi.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
async function handleAccountChange(id: number) {
|
||||||
function handleQuery() {
|
accountId.value = id;
|
||||||
queryParams.pageNo = 1;
|
// 同步设置表单值
|
||||||
getList();
|
gridApi.formApi.setValues({ accountId: id });
|
||||||
|
await gridApi.formApi.submitForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理 tab 切换 */
|
async function handleRefresh() {
|
||||||
function onTabChange() {
|
await gridApi.query();
|
||||||
// 提前清空数据,避免 tab 切换后显示垃圾数据
|
|
||||||
list.value = [];
|
|
||||||
total.value = 0;
|
|
||||||
// 从第一页开始查询
|
|
||||||
handleQuery();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理删除操作 */
|
/** 处理删除操作 */
|
||||||
@@ -86,7 +127,7 @@ async function handleDelete(id: number) {
|
|||||||
try {
|
try {
|
||||||
await deletePermanentMaterial(id);
|
await deletePermanentMaterial(id);
|
||||||
message.success('删除成功');
|
message.success('删除成功');
|
||||||
await getList();
|
handleRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
@@ -98,18 +139,12 @@ async function handleDelete(id: number) {
|
|||||||
<template #doc>
|
<template #doc>
|
||||||
<DocAlert title="公众号素材" url="https://doc.iocoder.cn/mp/material/" />
|
<DocAlert title="公众号素材" url="https://doc.iocoder.cn/mp/material/" />
|
||||||
</template>
|
</template>
|
||||||
<div class="h-full">
|
<Grid class="material-grid">
|
||||||
<!-- 搜索工作栏 -->
|
<template #form-accountId>
|
||||||
<Card class="h-[10%]" :bordered="false">
|
<WxAccountSelect @change="handleAccountChange" />
|
||||||
<Form :model="queryParams" layout="inline">
|
</template>
|
||||||
<Form.Item label="公众号">
|
<template #toolbar-actions>
|
||||||
<WxAccountSelect @change="onAccountChanged" />
|
<Tabs v-model:active-key="type" class="w-full" @change="onTabChange">
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card :bordered="false" class="mt-4 h-auto">
|
|
||||||
<Tabs v-model:active-key="type" @change="onTabChange">
|
|
||||||
<!-- tab 1:图片 -->
|
<!-- tab 1:图片 -->
|
||||||
<Tabs.TabPane :key="UploadType.Image">
|
<Tabs.TabPane :key="UploadType.Image">
|
||||||
<template #tab>
|
<template #tab>
|
||||||
@@ -118,33 +153,6 @@ async function handleDelete(id: number) {
|
|||||||
图片
|
图片
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<!-- 列表 -->
|
|
||||||
<ImageTable
|
|
||||||
:key="`image-${type}`"
|
|
||||||
:list="list"
|
|
||||||
:loading="loading"
|
|
||||||
@delete="handleDelete"
|
|
||||||
@refresh="getList"
|
|
||||||
>
|
|
||||||
<template #toolbar-tools>
|
|
||||||
<UploadFile
|
|
||||||
v-if="hasAccessByCodes(['mp:material:upload-permanent'])"
|
|
||||||
:type="UploadType.Image"
|
|
||||||
@uploaded="getList"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</ImageTable>
|
|
||||||
<!-- 分页组件 -->
|
|
||||||
<div class="mt-4 flex justify-end">
|
|
||||||
<Pagination
|
|
||||||
v-model:current="queryParams.pageNo"
|
|
||||||
v-model:page-size="queryParams.pageSize"
|
|
||||||
:total="total"
|
|
||||||
show-size-changer
|
|
||||||
@change="getList"
|
|
||||||
@show-size-change="getList"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
|
|
||||||
<!-- tab 2:语音 -->
|
<!-- tab 2:语音 -->
|
||||||
@@ -155,33 +163,6 @@ async function handleDelete(id: number) {
|
|||||||
语音
|
语音
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<!-- 列表 -->
|
|
||||||
<VoiceTable
|
|
||||||
:key="`voice-${type}`"
|
|
||||||
:list="list"
|
|
||||||
:loading="loading"
|
|
||||||
@delete="handleDelete"
|
|
||||||
@refresh="getList"
|
|
||||||
>
|
|
||||||
<template #toolbar-tools>
|
|
||||||
<UploadFile
|
|
||||||
v-if="hasAccessByCodes(['mp:material:upload-permanent'])"
|
|
||||||
:type="UploadType.Voice"
|
|
||||||
@uploaded="getList"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</VoiceTable>
|
|
||||||
<!-- 分页组件 -->
|
|
||||||
<div class="mt-4 flex justify-end">
|
|
||||||
<Pagination
|
|
||||||
v-model:current="queryParams.pageNo"
|
|
||||||
v-model:page-size="queryParams.pageSize"
|
|
||||||
:total="total"
|
|
||||||
show-size-changer
|
|
||||||
@change="getList"
|
|
||||||
@show-size-change="getList"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
|
|
||||||
<!-- tab 3:视频 -->
|
<!-- tab 3:视频 -->
|
||||||
@@ -192,40 +173,80 @@ async function handleDelete(id: number) {
|
|||||||
视频
|
视频
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<!-- 列表 -->
|
|
||||||
<VideoTable
|
|
||||||
:key="`video-${type}`"
|
|
||||||
:list="list"
|
|
||||||
:loading="loading"
|
|
||||||
@delete="handleDelete"
|
|
||||||
@refresh="getList"
|
|
||||||
>
|
|
||||||
<template #toolbar-tools>
|
|
||||||
<Button
|
|
||||||
v-if="hasAccessByCodes(['mp:material:upload-permanent'])"
|
|
||||||
type="primary"
|
|
||||||
@click="showCreateVideo = true"
|
|
||||||
>
|
|
||||||
新建视频
|
|
||||||
</Button>
|
|
||||||
</template>
|
|
||||||
</VideoTable>
|
|
||||||
<!-- 新建视频的弹窗 -->
|
|
||||||
<UploadVideo v-model:open="showCreateVideo" @uploaded="getList" />
|
|
||||||
<!-- 分页组件 -->
|
|
||||||
<div class="mt-4 flex justify-end">
|
|
||||||
<Pagination
|
|
||||||
v-model:current="queryParams.pageNo"
|
|
||||||
v-model:page-size="queryParams.pageSize"
|
|
||||||
:total="total"
|
|
||||||
show-size-changer
|
|
||||||
@change="getList"
|
|
||||||
@show-size-change="getList"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Card>
|
</template>
|
||||||
</div>
|
<template #toolbar-tools>
|
||||||
|
<UploadFile
|
||||||
|
v-if="
|
||||||
|
hasAccessByCodes(['mp:material:upload-permanent']) &&
|
||||||
|
type === UploadType.Image
|
||||||
|
"
|
||||||
|
:type="UploadType.Image"
|
||||||
|
@uploaded="handleRefresh"
|
||||||
|
/>
|
||||||
|
<UploadFile
|
||||||
|
v-if="
|
||||||
|
hasAccessByCodes(['mp:material:upload-permanent']) &&
|
||||||
|
type === UploadType.Voice
|
||||||
|
"
|
||||||
|
:type="UploadType.Voice"
|
||||||
|
@uploaded="handleRefresh"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
v-if="
|
||||||
|
hasAccessByCodes(['mp:material:upload-permanent']) &&
|
||||||
|
type === UploadType.Video
|
||||||
|
"
|
||||||
|
type="primary"
|
||||||
|
@click="showCreateVideo = true"
|
||||||
|
>
|
||||||
|
新建视频
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 图片列的slot -->
|
||||||
|
<template #image="{ row }">
|
||||||
|
<div class="flex items-center justify-center" style="height: 192px">
|
||||||
|
<img
|
||||||
|
:src="row.url"
|
||||||
|
class="object-contain"
|
||||||
|
style="display: block; max-width: 100%; max-height: 192px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 语音列的slot -->
|
||||||
|
<template #voice="{ row }">
|
||||||
|
<audio :src="row.url" controls style="width: 160px"></audio>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 视频列的slot -->
|
||||||
|
<template #video="{ row }">
|
||||||
|
<video
|
||||||
|
:src="row.url"
|
||||||
|
controls
|
||||||
|
style="width: 200px; height: 150px"
|
||||||
|
></video>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 操作列的slot -->
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: '删除',
|
||||||
|
icon: ACTION_ICON.delete,
|
||||||
|
color: 'danger',
|
||||||
|
auth: ['mp:material:delete'],
|
||||||
|
onClick: () => handleDelete(row.id),
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- 新建视频的弹窗 -->
|
||||||
|
<UploadVideo v-model:open="showCreateVideo" @uploaded="handleRefresh" />
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { MpMaterialApi } from '#/api/mp/material';
|
import type { MpMaterialApi } from '#/api/mp/material';
|
||||||
|
|
||||||
@@ -131,3 +132,14 @@ export function useImageGridColumns(): VxeTableGridOptions<MpMaterialApi.Materia
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 列表的搜索表单 */
|
||||||
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'accountId',
|
||||||
|
label: '公众号',
|
||||||
|
component: 'Input',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import type { MpMaterialApi } from '#/api/mp/material';
|
|
||||||
|
|
||||||
import { nextTick, onMounted, watch } from 'vue';
|
|
||||||
|
|
||||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
|
||||||
|
|
||||||
import { useImageGridColumns } from './data';
|
|
||||||
import { $t } from '@vben/locales';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
list: MpMaterialApi.Material[];
|
|
||||||
loading: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
delete: [v: number];
|
|
||||||
refresh: [];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const columns = useImageGridColumns();
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid<MpMaterialApi.Material>({
|
|
||||||
gridOptions: {
|
|
||||||
border: true,
|
|
||||||
columns,
|
|
||||||
keepSource: true,
|
|
||||||
pagerConfig: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
rowConfig: {
|
|
||||||
keyField: 'id',
|
|
||||||
isHover: true,
|
|
||||||
height: 220,
|
|
||||||
},
|
|
||||||
toolbarConfig: {
|
|
||||||
refresh: true,
|
|
||||||
},
|
|
||||||
showOverflow: 'tooltip',
|
|
||||||
proxyConfig: {
|
|
||||||
ajax: {
|
|
||||||
query: async () => {
|
|
||||||
// 数据由父组件管理,触发刷新事件后返回当前数据
|
|
||||||
emit('refresh');
|
|
||||||
// 返回当前数据,避免覆盖
|
|
||||||
return {
|
|
||||||
list: Array.isArray(props.list) ? props.list : [],
|
|
||||||
total: props.list?.length || 0,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
enabled: true,
|
|
||||||
autoLoad: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateGridData(data: MpMaterialApi.Material[]) {
|
|
||||||
if (gridApi.grid?.loadData) {
|
|
||||||
gridApi.grid.loadData(data);
|
|
||||||
} else {
|
|
||||||
gridApi.setGridOptions({ data });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.list,
|
|
||||||
async (list: MpMaterialApi.Material[]) => {
|
|
||||||
const data = Array.isArray(list) ? list : [];
|
|
||||||
await nextTick();
|
|
||||||
updateGridData(data);
|
|
||||||
},
|
|
||||||
{ immediate: true, flush: 'post' },
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.loading,
|
|
||||||
(loading: boolean) => {
|
|
||||||
gridApi.setLoading(loading);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
/** 初始化 */
|
|
||||||
onMounted(async () => {
|
|
||||||
await nextTick();
|
|
||||||
updateGridData(Array.isArray(props.list) ? props.list : []);
|
|
||||||
gridApi.setLoading(props.loading);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Grid class="image-table-grid mt-4 pb-0">
|
|
||||||
<template #toolbar-tools>
|
|
||||||
<slot name="toolbar-tools"></slot>
|
|
||||||
</template>
|
|
||||||
<template #image="{ row }">
|
|
||||||
<div class="flex items-center justify-center" style="height: 192px">
|
|
||||||
<img
|
|
||||||
:src="row.url"
|
|
||||||
class="object-contain"
|
|
||||||
style="display: block; max-width: 100%; max-height: 192px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #actions="{ row }">
|
|
||||||
<TableAction
|
|
||||||
:actions="[
|
|
||||||
{
|
|
||||||
label: $t('common.delete'),
|
|
||||||
type: 'link',
|
|
||||||
danger: true,
|
|
||||||
icon: ACTION_ICON.DELETE,
|
|
||||||
auth: ['mp:material:delete'],
|
|
||||||
popConfirm: {
|
|
||||||
title: '确定要删除该图片吗?',
|
|
||||||
confirm: () => emit('delete', row.id!),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Grid>
|
|
||||||
</template>
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
|
||||||
import type { MpMaterialApi } from '#/api/mp/material';
|
|
||||||
|
|
||||||
import { nextTick, watch } from 'vue';
|
|
||||||
|
|
||||||
import { $t } from '@vben/locales';
|
|
||||||
import { openWindow } from '@vben/utils';
|
|
||||||
|
|
||||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
|
||||||
import { WxVideoPlayer } from '#/views/mp/components';
|
|
||||||
|
|
||||||
import { useVideoGridColumns } from './data';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
list: MpMaterialApi.Material[];
|
|
||||||
loading: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
delete: [v: number];
|
|
||||||
refresh: [];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const columns = useVideoGridColumns();
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
|
||||||
gridOptions: {
|
|
||||||
border: true,
|
|
||||||
columns,
|
|
||||||
keepSource: true,
|
|
||||||
pagerConfig: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
rowConfig: {
|
|
||||||
keyField: 'id',
|
|
||||||
isHover: true,
|
|
||||||
},
|
|
||||||
toolbarConfig: {
|
|
||||||
refresh: true,
|
|
||||||
},
|
|
||||||
showOverflow: 'tooltip',
|
|
||||||
proxyConfig: {
|
|
||||||
ajax: {
|
|
||||||
query: async () => {
|
|
||||||
// 数据由父组件管理,触发刷新事件后返回当前数据
|
|
||||||
emit('refresh');
|
|
||||||
// 返回当前数据,避免覆盖
|
|
||||||
return {
|
|
||||||
list: Array.isArray(props.list) ? props.list : [],
|
|
||||||
total: props.list?.length || 0,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
enabled: true,
|
|
||||||
autoLoad: false,
|
|
||||||
},
|
|
||||||
} as VxeTableGridOptions<MpMaterialApi.Material>,
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateGridData(data: MpMaterialApi.Material[]) {
|
|
||||||
if (gridApi.grid?.loadData) {
|
|
||||||
gridApi.grid.loadData(data);
|
|
||||||
} else {
|
|
||||||
gridApi.setGridOptions({ data });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.list,
|
|
||||||
async (list: MpMaterialApi.Material[]) => {
|
|
||||||
const data = Array.isArray(list) ? list : [];
|
|
||||||
await nextTick();
|
|
||||||
updateGridData(data);
|
|
||||||
},
|
|
||||||
{ immediate: true, flush: 'post' },
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.loading,
|
|
||||||
(loading: boolean) => {
|
|
||||||
gridApi.setLoading(loading);
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Grid class="mt-4">
|
|
||||||
<template #toolbar-tools>
|
|
||||||
<slot name="toolbar-tools"></slot>
|
|
||||||
</template>
|
|
||||||
<template #video="{ row }">
|
|
||||||
<WxVideoPlayer v-if="row.url" :url="row.url" />
|
|
||||||
</template>
|
|
||||||
<template #actions="{ row }">
|
|
||||||
<TableAction
|
|
||||||
:actions="[
|
|
||||||
{
|
|
||||||
label: '下载',
|
|
||||||
type: 'link',
|
|
||||||
icon: ACTION_ICON.DOWNLOAD,
|
|
||||||
onClick: () => openWindow(row.url),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: $t('common.delete'),
|
|
||||||
type: 'link',
|
|
||||||
danger: true,
|
|
||||||
icon: ACTION_ICON.DELETE,
|
|
||||||
auth: ['mp:material:delete'],
|
|
||||||
popConfirm: {
|
|
||||||
title: '确定要删除该视频吗?',
|
|
||||||
confirm: () => emit('delete', row.id!),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Grid>
|
|
||||||
</template>
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
|
||||||
import type { MpMaterialApi } from '#/api/mp/material';
|
|
||||||
|
|
||||||
import { nextTick, watch } from 'vue';
|
|
||||||
|
|
||||||
import { $t } from '@vben/locales';
|
|
||||||
import { openWindow } from '@vben/utils';
|
|
||||||
|
|
||||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
|
||||||
import { WxVoicePlayer } from '#/views/mp/components';
|
|
||||||
|
|
||||||
import { useVoiceGridColumns } from './data';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
list: MpMaterialApi.Material[];
|
|
||||||
loading: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
delete: [v: number];
|
|
||||||
refresh: [];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const columns = useVoiceGridColumns();
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
|
||||||
gridOptions: {
|
|
||||||
border: true,
|
|
||||||
columns,
|
|
||||||
keepSource: true,
|
|
||||||
pagerConfig: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
rowConfig: {
|
|
||||||
keyField: 'id',
|
|
||||||
isHover: true,
|
|
||||||
},
|
|
||||||
toolbarConfig: {
|
|
||||||
refresh: true,
|
|
||||||
},
|
|
||||||
showOverflow: 'tooltip',
|
|
||||||
proxyConfig: {
|
|
||||||
ajax: {
|
|
||||||
query: async () => {
|
|
||||||
// 数据由父组件管理,触发刷新事件后返回当前数据
|
|
||||||
emit('refresh');
|
|
||||||
// 返回当前数据,避免覆盖
|
|
||||||
return {
|
|
||||||
list: Array.isArray(props.list) ? props.list : [],
|
|
||||||
total: props.list?.length || 0,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
enabled: true,
|
|
||||||
autoLoad: false,
|
|
||||||
},
|
|
||||||
} as VxeTableGridOptions<MpMaterialApi.Material>,
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateGridData(data: MpMaterialApi.Material[]) {
|
|
||||||
if (gridApi.grid?.loadData) {
|
|
||||||
gridApi.grid.loadData(data);
|
|
||||||
} else {
|
|
||||||
gridApi.setGridOptions({ data });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.list,
|
|
||||||
async (list: MpMaterialApi.Material[]) => {
|
|
||||||
const data = Array.isArray(list) ? list : [];
|
|
||||||
await nextTick();
|
|
||||||
updateGridData(data);
|
|
||||||
},
|
|
||||||
{ immediate: true, flush: 'post' },
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.loading,
|
|
||||||
(loading: boolean) => {
|
|
||||||
gridApi.setLoading(loading);
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Grid class="mt-4">
|
|
||||||
<template #toolbar-tools>
|
|
||||||
<slot name="toolbar-tools"></slot>
|
|
||||||
</template>
|
|
||||||
<template #voice="{ row }">
|
|
||||||
<WxVoicePlayer v-if="row.url" :url="row.url" />
|
|
||||||
</template>
|
|
||||||
<template #actions="{ row }">
|
|
||||||
<TableAction
|
|
||||||
:actions="[
|
|
||||||
{
|
|
||||||
label: '下载',
|
|
||||||
type: 'link',
|
|
||||||
icon: ACTION_ICON.DOWNLOAD,
|
|
||||||
onClick: () => openWindow(row.url),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: $t('common.delete'),
|
|
||||||
type: 'link',
|
|
||||||
danger: true,
|
|
||||||
icon: ACTION_ICON.DELETE,
|
|
||||||
auth: ['mp:material:delete'],
|
|
||||||
popConfirm: {
|
|
||||||
title: '确定要删除该语音吗?',
|
|
||||||
confirm: () => emit('delete', row.id!),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Grid>
|
|
||||||
</template>
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { MpMaterialApi } from '#/api/mp/material';
|
import type { MpMaterialApi } from '#/api/mp/material';
|
||||||
|
|
||||||
import { reactive, ref, watch } from 'vue';
|
import { computed, watch } from 'vue';
|
||||||
|
|
||||||
import { NewsType } from '@vben/constants';
|
import { NewsType } from '@vben/constants';
|
||||||
import { IconifyIcon } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
@@ -33,16 +33,6 @@ const emit = defineEmits<{
|
|||||||
(e: 'selectMaterial', item: any): void;
|
(e: 'selectMaterial', item: any): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const loading = ref(false); // 遮罩层
|
|
||||||
const total = ref(0); // 总条数
|
|
||||||
const list = ref<any[]>([]); // 数据列表
|
|
||||||
// TODO @dlyan @AI:这里是不是应该都用 grid;
|
|
||||||
const queryParams = reactive({
|
|
||||||
accountId: props.accountId,
|
|
||||||
pageNo: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
}); // 查询参数
|
|
||||||
|
|
||||||
const voiceGridColumns: VxeTableGridOptions<MpMaterialApi.Material>['columns'] =
|
const voiceGridColumns: VxeTableGridOptions<MpMaterialApi.Material>['columns'] =
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -122,6 +112,99 @@ const videoGridColumns: VxeTableGridOptions<MpMaterialApi.Material>['columns'] =
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Image Grid
|
||||||
|
const [ImageGrid, imageGridApi] = useVbenVxeGrid({
|
||||||
|
gridOptions: {
|
||||||
|
columns: [],
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, { accountId }) => {
|
||||||
|
const finalAccountId = accountId ?? props.accountId;
|
||||||
|
if (!finalAccountId) {
|
||||||
|
return { list: [], total: 0 };
|
||||||
|
}
|
||||||
|
return await getMaterialPage({
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
accountId: finalAccountId,
|
||||||
|
type: 'image',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
autoLoad: false,
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'mediaId',
|
||||||
|
isHover: true,
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<MpMaterialApi.Material>,
|
||||||
|
});
|
||||||
|
|
||||||
|
// News Grid
|
||||||
|
const [NewsGrid, newsGridApi] = useVbenVxeGrid({
|
||||||
|
gridOptions: {
|
||||||
|
columns: [],
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, { accountId }) => {
|
||||||
|
const finalAccountId = accountId ?? props.accountId;
|
||||||
|
if (!finalAccountId) {
|
||||||
|
return { list: [], total: 0 };
|
||||||
|
}
|
||||||
|
if (props.newsType === NewsType.Published) {
|
||||||
|
const data = await getFreePublishPage({
|
||||||
|
accountId: finalAccountId,
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
});
|
||||||
|
data.list.forEach((item: any) => {
|
||||||
|
const articles = item.content.newsItem;
|
||||||
|
articles.forEach((article: any) => {
|
||||||
|
article.picUrl = article.thumbUrl;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
const data = await getDraftPage({
|
||||||
|
accountId: finalAccountId,
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
});
|
||||||
|
data.list.forEach((draft: any) => {
|
||||||
|
const articles = draft.content.newsItem;
|
||||||
|
articles.forEach((article: any) => {
|
||||||
|
article.picUrl = article.thumbUrl;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
autoLoad: false,
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'mediaId',
|
||||||
|
isHover: true,
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<any>,
|
||||||
|
});
|
||||||
|
|
||||||
const [VoiceGrid, voiceGridApi] = useVbenVxeGrid({
|
const [VoiceGrid, voiceGridApi] = useVbenVxeGrid({
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
border: true,
|
border: true,
|
||||||
@@ -135,7 +218,7 @@ const [VoiceGrid, voiceGridApi] = useVbenVxeGrid({
|
|||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
ajax: {
|
ajax: {
|
||||||
query: async ({ page }, { accountId }) => {
|
query: async ({ page }, { accountId }) => {
|
||||||
const finalAccountId = accountId ?? queryParams.accountId;
|
const finalAccountId = accountId ?? props.accountId;
|
||||||
if (!finalAccountId) {
|
if (!finalAccountId) {
|
||||||
return { list: [], total: 0 };
|
return { list: [], total: 0 };
|
||||||
}
|
}
|
||||||
@@ -171,7 +254,7 @@ const [VideoGrid, videoGridApi] = useVbenVxeGrid({
|
|||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
ajax: {
|
ajax: {
|
||||||
query: async ({ page }, { accountId }) => {
|
query: async ({ page }, { accountId }) => {
|
||||||
const finalAccountId = accountId ?? queryParams.accountId;
|
const finalAccountId = accountId ?? props.accountId;
|
||||||
if (finalAccountId === undefined || finalAccountId === null) {
|
if (finalAccountId === undefined || finalAccountId === null) {
|
||||||
return { list: [], total: 0 };
|
return { list: [], total: 0 };
|
||||||
}
|
}
|
||||||
@@ -194,91 +277,164 @@ const [VideoGrid, videoGridApi] = useVbenVxeGrid({
|
|||||||
} as VxeTableGridOptions<MpMaterialApi.Material>,
|
} as VxeTableGridOptions<MpMaterialApi.Material>,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 从 Grid 获取数据
|
||||||
|
const imageList = computed(() => {
|
||||||
|
try {
|
||||||
|
const tableData = imageGridApi.grid?.getTableData();
|
||||||
|
return (tableData?.tableData as MpMaterialApi.Material[]) || [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const imageLoading = computed(() => {
|
||||||
|
return imageGridApi.grid?.loading || false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const imageTotal = computed(() => {
|
||||||
|
try {
|
||||||
|
const proxyInfo = imageGridApi.grid?.getProxyInfo();
|
||||||
|
return proxyInfo?.pager?.total || 0;
|
||||||
|
} catch {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const imageCurrentPage = computed({
|
||||||
|
get: () => {
|
||||||
|
try {
|
||||||
|
return imageGridApi.grid?.pagerConfig?.currentPage || 1;
|
||||||
|
} catch {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: (value: number) => {
|
||||||
|
imageGridApi.grid?.commitProxy('page', { currentPage: value });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const imagePageSize = computed({
|
||||||
|
get: () => {
|
||||||
|
try {
|
||||||
|
return imageGridApi.grid?.pagerConfig?.pageSize || 10;
|
||||||
|
} catch {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: (value: number) => {
|
||||||
|
imageGridApi.grid?.commitProxy('page', { pageSize: value, currentPage: 1 });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const newsList = computed(() => {
|
||||||
|
try {
|
||||||
|
const tableData = newsGridApi.grid?.getTableData();
|
||||||
|
return (tableData?.tableData as any[]) || [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const newsLoading = computed(() => {
|
||||||
|
return newsGridApi.grid?.loading || false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const newsTotal = computed(() => {
|
||||||
|
try {
|
||||||
|
const proxyInfo = newsGridApi.grid?.getProxyInfo();
|
||||||
|
return proxyInfo?.pager?.total || 0;
|
||||||
|
} catch {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const newsCurrentPage = computed({
|
||||||
|
get: () => {
|
||||||
|
try {
|
||||||
|
return newsGridApi.grid?.pagerConfig?.currentPage || 1;
|
||||||
|
} catch {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: (value: number) => {
|
||||||
|
newsGridApi.grid?.commitProxy('page', { currentPage: value });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const newsPageSize = computed({
|
||||||
|
get: () => {
|
||||||
|
try {
|
||||||
|
return newsGridApi.grid?.pagerConfig?.pageSize || 10;
|
||||||
|
} catch {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: (value: number) => {
|
||||||
|
newsGridApi.grid?.commitProxy('page', { pageSize: value, currentPage: 1 });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
function selectMaterialFun(item: any) {
|
function selectMaterialFun(item: any) {
|
||||||
emit('selectMaterial', item);
|
emit('selectMaterial', item);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMaterialPageFun() {
|
// 监听 accountId 变化
|
||||||
const data = await getMaterialPage({
|
|
||||||
...queryParams,
|
|
||||||
type: props.type,
|
|
||||||
});
|
|
||||||
list.value = data.list;
|
|
||||||
total.value = data.total;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getFreePublishPageFun() {
|
|
||||||
const data = await getFreePublishPage(queryParams);
|
|
||||||
data.list.forEach((item: any) => {
|
|
||||||
const articles = item.content.newsItem;
|
|
||||||
articles.forEach((article: any) => {
|
|
||||||
article.picUrl = article.thumbUrl;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
list.value = data.list;
|
|
||||||
total.value = data.total;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getDraftPageFun() {
|
|
||||||
const data = await getDraftPage(queryParams);
|
|
||||||
data.list.forEach((draft: any) => {
|
|
||||||
const articles = draft.content.newsItem;
|
|
||||||
articles.forEach((article: any) => {
|
|
||||||
article.picUrl = article.thumbUrl;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
list.value = data.list;
|
|
||||||
total.value = data.total;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getPage() {
|
|
||||||
if (props.type === 'voice') {
|
|
||||||
await voiceGridApi.reload({ accountId: queryParams.accountId });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (props.type === 'video') {
|
|
||||||
await videoGridApi.reload({ accountId: queryParams.accountId });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
if (props.type === 'news' && props.newsType === NewsType.Published) {
|
|
||||||
await getFreePublishPageFun();
|
|
||||||
} else if (props.type === 'news' && props.newsType === NewsType.Draft) {
|
|
||||||
await getDraftPageFun();
|
|
||||||
} else {
|
|
||||||
await getMaterialPageFun();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.accountId,
|
() => props.accountId,
|
||||||
(accountId) => {
|
(accountId) => {
|
||||||
queryParams.accountId = accountId;
|
switch (props.type) {
|
||||||
queryParams.pageNo = 1;
|
case 'image': {
|
||||||
getPage();
|
imageGridApi.reload({ accountId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'news': {
|
||||||
|
newsGridApi.reload({ accountId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'video': {
|
||||||
|
videoGridApi.reload({ accountId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'voice': {
|
||||||
|
voiceGridApi.reload({ accountId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 监听 type 变化
|
||||||
watch(
|
watch(
|
||||||
() => props.type,
|
() => props.type,
|
||||||
() => {
|
() => {
|
||||||
queryParams.pageNo = 1;
|
switch (props.type) {
|
||||||
getPage();
|
case 'image': {
|
||||||
|
imageGridApi.reload({ accountId: props.accountId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'news': {
|
||||||
|
newsGridApi.reload({ accountId: props.accountId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'video': {
|
||||||
|
videoGridApi.reload({ accountId: props.accountId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'voice': {
|
||||||
|
voiceGridApi.reload({ accountId: props.accountId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 监听 newsType 变化
|
||||||
watch(
|
watch(
|
||||||
() => props.newsType,
|
() => props.newsType,
|
||||||
() => {
|
() => {
|
||||||
if (props.type === 'news') {
|
if (props.type === 'news') {
|
||||||
queryParams.pageNo = 1;
|
newsGridApi.reload({ accountId: props.accountId });
|
||||||
getPage();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -287,38 +443,41 @@ watch(
|
|||||||
<template>
|
<template>
|
||||||
<div class="pb-30px">
|
<div class="pb-30px">
|
||||||
<!-- 类型:image -->
|
<!-- 类型:image -->
|
||||||
<div v-if="props.type === 'image'">
|
<div v-if="props.type === 'image'" class="image-grid-wrapper">
|
||||||
<div
|
<ImageGrid>
|
||||||
class="mx-auto w-full columns-1 [column-gap:10px] md:columns-2 lg:columns-3 xl:columns-4 2xl:columns-5"
|
<template #default>
|
||||||
v-loading="loading"
|
<div
|
||||||
>
|
class="mx-auto w-full columns-1 [column-gap:10px] md:columns-2 lg:columns-3 xl:columns-4 2xl:columns-5"
|
||||||
<div
|
v-loading="imageLoading"
|
||||||
class="mb-2.5 break-inside-avoid border border-[#eaeaea] p-2.5"
|
>
|
||||||
v-for="item in list"
|
<div
|
||||||
:key="item.mediaId"
|
class="mb-2.5 break-inside-avoid border border-[#eaeaea] p-2.5"
|
||||||
>
|
v-for="item in imageList"
|
||||||
<img class="w-full" :src="item.url" />
|
:key="item.mediaId"
|
||||||
<p class="truncate text-center text-xs leading-[30px]">
|
>
|
||||||
{{ item.name }}
|
<img class="w-full" :src="item.url" />
|
||||||
</p>
|
<p class="truncate text-center text-xs leading-[30px]">
|
||||||
<ElRow class="flex justify-center pt-2.5">
|
{{ item.name }}
|
||||||
<ElButton type="success" @click="selectMaterialFun(item)">
|
</p>
|
||||||
选择
|
<ElRow class="flex justify-center pt-2.5">
|
||||||
<IconifyIcon icon="lucide:circle-check" />
|
<ElButton type="success" @click="selectMaterialFun(item)">
|
||||||
</ElButton>
|
选择
|
||||||
</ElRow>
|
<IconifyIcon icon="lucide:circle-check" />
|
||||||
</div>
|
</ElButton>
|
||||||
</div>
|
</ElRow>
|
||||||
<!-- 分页组件 -->
|
</div>
|
||||||
<ElPagination
|
</div>
|
||||||
background
|
<!-- 分页组件 -->
|
||||||
layout="prev, pager, next, sizes, total"
|
<ElPagination
|
||||||
:total="total"
|
background
|
||||||
v-model:current-page="queryParams.pageNo"
|
layout="prev, pager, next, sizes, total"
|
||||||
v-model:page-size="queryParams.pageSize"
|
:total="imageTotal"
|
||||||
@current-change="getMaterialPageFun"
|
v-model:current-page="imageCurrentPage"
|
||||||
@size-change="getMaterialPageFun"
|
v-model:page-size="imagePageSize"
|
||||||
/>
|
class="mt-4"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</ImageGrid>
|
||||||
</div>
|
</div>
|
||||||
<!-- 类型:voice -->
|
<!-- 类型:voice -->
|
||||||
<div v-else-if="props.type === 'voice'">
|
<div v-else-if="props.type === 'voice'">
|
||||||
@@ -349,37 +508,50 @@ watch(
|
|||||||
</VideoGrid>
|
</VideoGrid>
|
||||||
</div>
|
</div>
|
||||||
<!-- 类型:news -->
|
<!-- 类型:news -->
|
||||||
<div v-else-if="props.type === 'news'">
|
<div v-else-if="props.type === 'news'" class="news-grid-wrapper">
|
||||||
<div
|
<NewsGrid>
|
||||||
class="mx-auto w-full columns-1 [column-gap:10px] md:columns-2 lg:columns-3 xl:columns-4 2xl:columns-5"
|
<template #default>
|
||||||
v-loading="loading"
|
<div
|
||||||
>
|
class="mx-auto w-full columns-1 [column-gap:10px] md:columns-2 lg:columns-3 xl:columns-4 2xl:columns-5"
|
||||||
<div
|
v-loading="newsLoading"
|
||||||
class="mb-2.5 break-inside-avoid border border-[#eaeaea] p-2.5"
|
>
|
||||||
v-for="item in list"
|
<div
|
||||||
:key="item.mediaId"
|
class="mb-2.5 break-inside-avoid border border-[#eaeaea] p-2.5"
|
||||||
>
|
v-for="item in newsList"
|
||||||
<div v-if="item.content && item.content.newsItem">
|
:key="item.mediaId"
|
||||||
<WxNews :articles="item.content.newsItem" />
|
>
|
||||||
<ElRow class="flex justify-center pt-2.5">
|
<div v-if="item.content && item.content.newsItem">
|
||||||
<ElButton type="success" @click="selectMaterialFun(item)">
|
<WxNews :articles="item.content.newsItem" />
|
||||||
选择
|
<ElRow class="flex justify-center pt-2.5">
|
||||||
<IconifyIcon icon="lucide:circle-check" />
|
<ElButton type="success" @click="selectMaterialFun(item)">
|
||||||
</ElButton>
|
选择
|
||||||
</ElRow>
|
<IconifyIcon icon="lucide:circle-check" />
|
||||||
|
</ElButton>
|
||||||
|
</ElRow>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<!-- 分页组件 -->
|
||||||
</div>
|
<ElPagination
|
||||||
<!-- 分页组件 -->
|
background
|
||||||
<ElPagination
|
layout="prev, pager, next, sizes, total"
|
||||||
background
|
:total="newsTotal"
|
||||||
layout="prev, pager, next, sizes, total"
|
v-model:current-page="newsCurrentPage"
|
||||||
:total="total"
|
v-model:page-size="newsPageSize"
|
||||||
v-model:current-page="queryParams.pageNo"
|
class="mt-4"
|
||||||
v-model:page-size="queryParams.pageSize"
|
/>
|
||||||
@current-change="getMaterialPageFun"
|
</template>
|
||||||
@size-change="getMaterialPageFun"
|
</NewsGrid>
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.image-grid-wrapper :deep(.vxe-grid--body-wrapper) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.news-grid-wrapper :deep(.vxe-grid--body-wrapper) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { provide, reactive, ref } from 'vue';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { MpMaterialApi } from '#/api/mp/material';
|
||||||
|
|
||||||
|
import { computed, provide, ref } from 'vue';
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
import { useAccess } from '@vben/access';
|
||||||
import { confirm, DocAlert, Page } from '@vben/common-ui';
|
import { confirm, DocAlert, Page } from '@vben/common-ui';
|
||||||
@@ -7,83 +10,117 @@ import { IconifyIcon } from '@vben/icons';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
ElButton,
|
ElButton,
|
||||||
ElCard,
|
|
||||||
ElForm,
|
|
||||||
ElFormItem,
|
|
||||||
ElLoading,
|
ElLoading,
|
||||||
ElMessage,
|
ElMessage,
|
||||||
ElPagination,
|
|
||||||
ElTabPane,
|
ElTabPane,
|
||||||
ElTabs,
|
ElTabs,
|
||||||
} from 'element-plus';
|
} from 'element-plus';
|
||||||
|
|
||||||
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { deletePermanentMaterial, getMaterialPage } from '#/api/mp/material';
|
import { deletePermanentMaterial, getMaterialPage } from '#/api/mp/material';
|
||||||
import { WxAccountSelect } from '#/views/mp/components';
|
import { WxAccountSelect } from '#/views/mp/components';
|
||||||
|
|
||||||
import ImageTable from './modules/image-table.vue';
|
import {
|
||||||
|
useGridFormSchema,
|
||||||
|
useImageGridColumns,
|
||||||
|
useVideoGridColumns,
|
||||||
|
useVoiceGridColumns,
|
||||||
|
} from './modules/data';
|
||||||
import { UploadType } from './modules/upload';
|
import { UploadType } from './modules/upload';
|
||||||
import UploadFile from './modules/UploadFile.vue';
|
import UploadFile from './modules/UploadFile.vue';
|
||||||
import UploadVideo from './modules/UploadVideo.vue';
|
import UploadVideo from './modules/UploadVideo.vue';
|
||||||
import VideoTable from './modules/video-table.vue';
|
|
||||||
import VoiceTable from './modules/voice-table.vue';
|
|
||||||
|
|
||||||
defineOptions({ name: 'MpMaterial' });
|
defineOptions({ name: 'MpMaterial' });
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
const { hasAccessByCodes } = useAccess();
|
||||||
|
|
||||||
const type = ref<UploadType>(UploadType.Image); // 素材类型
|
const type = ref<UploadType>(UploadType.Image); // 素材类型
|
||||||
const loading = ref(false); // 遮罩层
|
const showCreateVideo = ref(false); // 是否新建视频的弹窗
|
||||||
const list = ref<any[]>([]); // 数据列表
|
|
||||||
const total = ref(0); // 总条数
|
|
||||||
|
|
||||||
const accountId = ref(-1);
|
const accountId = ref(-1);
|
||||||
provide('accountId', accountId);
|
provide('accountId', accountId);
|
||||||
|
|
||||||
// TODO @dlyan @AI:这里是不是应该都用 grid;类似 yudao-ui-admin-vben-v5/apps/web-ele/src/views/mp/autoReply/index.vue
|
// 根据类型获取对应的列配置
|
||||||
const queryParams = reactive({
|
const getColumnsByType = () => {
|
||||||
accountId,
|
switch (type.value) {
|
||||||
pageNo: 1,
|
case UploadType.Image: {
|
||||||
pageSize: 10,
|
return useImageGridColumns();
|
||||||
permanent: true,
|
}
|
||||||
}); // 查询参数
|
case UploadType.Video: {
|
||||||
const showCreateVideo = ref(false); // 是否新建视频的弹窗
|
return useVideoGridColumns();
|
||||||
|
}
|
||||||
/** 侦听公众号变化 */
|
case UploadType.Voice: {
|
||||||
function onAccountChanged(id: number) {
|
return useVoiceGridColumns();
|
||||||
accountId.value = id;
|
}
|
||||||
queryParams.accountId = id;
|
default: {
|
||||||
queryParams.pageNo = 1;
|
return [];
|
||||||
getList();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** 查询列表 */
|
|
||||||
async function getList() {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const data = await getMaterialPage({
|
|
||||||
...queryParams,
|
|
||||||
type: type.value,
|
|
||||||
});
|
|
||||||
list.value = data.list;
|
|
||||||
total.value = data.total;
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
formOptions: {
|
||||||
|
schema: useGridFormSchema(),
|
||||||
|
},
|
||||||
|
gridOptions: {
|
||||||
|
columns: getColumnsByType(),
|
||||||
|
height: 'auto',
|
||||||
|
keepSource: true,
|
||||||
|
pagerConfig: {},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }, formValues) => {
|
||||||
|
const finalAccountId = formValues?.accountId ?? accountId.value;
|
||||||
|
if (!finalAccountId || finalAccountId === -1) {
|
||||||
|
return { list: [], total: 0 };
|
||||||
|
}
|
||||||
|
return await getMaterialPage({
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
type: type.value,
|
||||||
|
permanent: true,
|
||||||
|
accountId: finalAccountId,
|
||||||
|
...formValues,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
autoLoad: false,
|
||||||
|
},
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
|
height: type.value === UploadType.Image ? 220 : 'auto',
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
refresh: true,
|
||||||
|
search: true,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<MpMaterialApi.Material>,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 当 tab 切换时,更新 Grid 的 columns 和 rowConfig
|
||||||
|
async function onTabChange() {
|
||||||
|
const columns = getColumnsByType();
|
||||||
|
await gridApi.setGridOptions({
|
||||||
|
columns,
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
|
height: type.value === UploadType.Image ? 220 : 'auto',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await gridApi.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
async function handleAccountChange(id: number) {
|
||||||
function handleQuery() {
|
accountId.value = id;
|
||||||
queryParams.pageNo = 1;
|
// 同步设置表单值
|
||||||
getList();
|
gridApi.formApi.setValues({ accountId: id });
|
||||||
|
await gridApi.formApi.submitForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理 tab 切换 */
|
async function handleRefresh() {
|
||||||
function onTabChange() {
|
await gridApi.query();
|
||||||
// 提前清空数据,避免 tab 切换后显示垃圾数据
|
|
||||||
list.value = [];
|
|
||||||
total.value = 0;
|
|
||||||
// 从第一页开始查询
|
|
||||||
handleQuery();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理删除操作 */
|
/** 处理删除操作 */
|
||||||
@@ -96,24 +133,11 @@ async function handleDelete(id: number) {
|
|||||||
try {
|
try {
|
||||||
await deletePermanentMaterial(id);
|
await deletePermanentMaterial(id);
|
||||||
ElMessage.success('删除成功');
|
ElMessage.success('删除成功');
|
||||||
await getList();
|
handleRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
loadingInstance.close();
|
loadingInstance.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 分页改变事件 */
|
|
||||||
function handlePageChange(page: number) {
|
|
||||||
queryParams.pageNo = page;
|
|
||||||
getList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 每页条数改变事件 */
|
|
||||||
function handleSizeChange(pageSize: number) {
|
|
||||||
queryParams.pageSize = pageSize;
|
|
||||||
queryParams.pageNo = 1;
|
|
||||||
getList();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -121,18 +145,12 @@ function handleSizeChange(pageSize: number) {
|
|||||||
<template #doc>
|
<template #doc>
|
||||||
<DocAlert title="公众号素材" url="https://doc.iocoder.cn/mp/material/" />
|
<DocAlert title="公众号素材" url="https://doc.iocoder.cn/mp/material/" />
|
||||||
</template>
|
</template>
|
||||||
<div class="h-full">
|
<Grid class="material-grid">
|
||||||
<!-- 搜索工作栏 -->
|
<template #form-accountId>
|
||||||
<ElCard class="h-[10%]" shadow="never">
|
<WxAccountSelect @change="handleAccountChange" />
|
||||||
<ElForm :model="queryParams" :inline="true">
|
</template>
|
||||||
<ElFormItem label="公众号" class="w-52">
|
<template #toolbar-actions>
|
||||||
<WxAccountSelect @change="onAccountChanged" />
|
<ElTabs v-model="type" class="w-full" @tab-change="onTabChange">
|
||||||
</ElFormItem>
|
|
||||||
</ElForm>
|
|
||||||
</ElCard>
|
|
||||||
|
|
||||||
<ElCard shadow="never" class="mt-4 h-auto">
|
|
||||||
<ElTabs v-model="type" @tab-change="onTabChange">
|
|
||||||
<!-- tab 1:图片 -->
|
<!-- tab 1:图片 -->
|
||||||
<ElTabPane :name="UploadType.Image">
|
<ElTabPane :name="UploadType.Image">
|
||||||
<template #label>
|
<template #label>
|
||||||
@@ -141,34 +159,6 @@ function handleSizeChange(pageSize: number) {
|
|||||||
图片
|
图片
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<!-- 列表 -->
|
|
||||||
<ImageTable
|
|
||||||
:key="`image-${type}`"
|
|
||||||
:list="list"
|
|
||||||
:loading="loading"
|
|
||||||
@delete="handleDelete"
|
|
||||||
@refresh="getList"
|
|
||||||
>
|
|
||||||
<template #toolbar-tools>
|
|
||||||
<UploadFile
|
|
||||||
v-if="hasAccessByCodes(['mp:material:upload-permanent'])"
|
|
||||||
:type="UploadType.Image"
|
|
||||||
@uploaded="getList"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</ImageTable>
|
|
||||||
<!-- 分页组件 -->
|
|
||||||
<div class="mt-4 flex justify-end">
|
|
||||||
<ElPagination
|
|
||||||
v-model:current-page="queryParams.pageNo"
|
|
||||||
v-model:page-size="queryParams.pageSize"
|
|
||||||
:total="total"
|
|
||||||
:page-sizes="[10, 20, 50, 100]"
|
|
||||||
layout="total, sizes, prev, pager, next, jumper"
|
|
||||||
@current-change="handlePageChange"
|
|
||||||
@size-change="handleSizeChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ElTabPane>
|
</ElTabPane>
|
||||||
|
|
||||||
<!-- tab 2:语音 -->
|
<!-- tab 2:语音 -->
|
||||||
@@ -179,34 +169,6 @@ function handleSizeChange(pageSize: number) {
|
|||||||
语音
|
语音
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<!-- 列表 -->
|
|
||||||
<VoiceTable
|
|
||||||
:key="`voice-${type}`"
|
|
||||||
:list="list"
|
|
||||||
:loading="loading"
|
|
||||||
@delete="handleDelete"
|
|
||||||
@refresh="getList"
|
|
||||||
>
|
|
||||||
<template #toolbar-tools>
|
|
||||||
<UploadFile
|
|
||||||
v-if="hasAccessByCodes(['mp:material:upload-permanent'])"
|
|
||||||
:type="UploadType.Voice"
|
|
||||||
@uploaded="getList"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</VoiceTable>
|
|
||||||
<!-- 分页组件 -->
|
|
||||||
<div class="mt-4 flex justify-end">
|
|
||||||
<ElPagination
|
|
||||||
v-model:current-page="queryParams.pageNo"
|
|
||||||
v-model:page-size="queryParams.pageSize"
|
|
||||||
:total="total"
|
|
||||||
:page-sizes="[10, 20, 50, 100]"
|
|
||||||
layout="total, sizes, prev, pager, next, jumper"
|
|
||||||
@current-change="handlePageChange"
|
|
||||||
@size-change="handleSizeChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ElTabPane>
|
</ElTabPane>
|
||||||
|
|
||||||
<!-- tab 3:视频 -->
|
<!-- tab 3:视频 -->
|
||||||
@@ -217,41 +179,80 @@ function handleSizeChange(pageSize: number) {
|
|||||||
视频
|
视频
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<!-- 列表 -->
|
|
||||||
<VideoTable
|
|
||||||
:key="`video-${type}`"
|
|
||||||
:list="list"
|
|
||||||
:loading="loading"
|
|
||||||
@delete="handleDelete"
|
|
||||||
@refresh="getList"
|
|
||||||
>
|
|
||||||
<template #toolbar-tools>
|
|
||||||
<ElButton
|
|
||||||
v-if="hasAccessByCodes(['mp:material:upload-permanent'])"
|
|
||||||
type="primary"
|
|
||||||
@click="showCreateVideo = true"
|
|
||||||
>
|
|
||||||
新建视频
|
|
||||||
</ElButton>
|
|
||||||
</template>
|
|
||||||
</VideoTable>
|
|
||||||
<!-- 新建视频的弹窗 -->
|
|
||||||
<UploadVideo v-model:open="showCreateVideo" @uploaded="getList" />
|
|
||||||
<!-- 分页组件 -->
|
|
||||||
<div class="mt-4 flex justify-end">
|
|
||||||
<ElPagination
|
|
||||||
v-model:current-page="queryParams.pageNo"
|
|
||||||
v-model:page-size="queryParams.pageSize"
|
|
||||||
:total="total"
|
|
||||||
:page-sizes="[10, 20, 50, 100]"
|
|
||||||
layout="total, sizes, prev, pager, next, jumper"
|
|
||||||
@current-change="handlePageChange"
|
|
||||||
@size-change="handleSizeChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ElTabPane>
|
</ElTabPane>
|
||||||
</ElTabs>
|
</ElTabs>
|
||||||
</ElCard>
|
</template>
|
||||||
</div>
|
<template #toolbar-tools>
|
||||||
|
<UploadFile
|
||||||
|
v-if="
|
||||||
|
hasAccessByCodes(['mp:material:upload-permanent']) &&
|
||||||
|
type === UploadType.Image
|
||||||
|
"
|
||||||
|
:type="UploadType.Image"
|
||||||
|
@uploaded="handleRefresh"
|
||||||
|
/>
|
||||||
|
<UploadFile
|
||||||
|
v-if="
|
||||||
|
hasAccessByCodes(['mp:material:upload-permanent']) &&
|
||||||
|
type === UploadType.Voice
|
||||||
|
"
|
||||||
|
:type="UploadType.Voice"
|
||||||
|
@uploaded="handleRefresh"
|
||||||
|
/>
|
||||||
|
<ElButton
|
||||||
|
v-if="
|
||||||
|
hasAccessByCodes(['mp:material:upload-permanent']) &&
|
||||||
|
type === UploadType.Video
|
||||||
|
"
|
||||||
|
type="primary"
|
||||||
|
@click="showCreateVideo = true"
|
||||||
|
>
|
||||||
|
新建视频
|
||||||
|
</ElButton>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 图片列的slot -->
|
||||||
|
<template #image="{ row }">
|
||||||
|
<div class="flex items-center justify-center" style="height: 192px">
|
||||||
|
<img
|
||||||
|
:src="row.url"
|
||||||
|
class="object-contain"
|
||||||
|
style="display: block; max-width: 100%; max-height: 192px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 语音列的slot -->
|
||||||
|
<template #voice="{ row }">
|
||||||
|
<audio :src="row.url" controls style="width: 160px"></audio>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 视频列的slot -->
|
||||||
|
<template #video="{ row }">
|
||||||
|
<video
|
||||||
|
:src="row.url"
|
||||||
|
controls
|
||||||
|
style="width: 200px; height: 150px"
|
||||||
|
></video>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 操作列的slot -->
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: '删除',
|
||||||
|
icon: ACTION_ICON.delete,
|
||||||
|
color: 'danger',
|
||||||
|
auth: ['mp:material:delete'],
|
||||||
|
onClick: () => handleDelete(row.id),
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- 新建视频的弹窗 -->
|
||||||
|
<UploadVideo v-model:open="showCreateVideo" @uploaded="handleRefresh" />
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { MpMaterialApi } from '#/api/mp/material';
|
import type { MpMaterialApi } from '#/api/mp/material';
|
||||||
|
|
||||||
@@ -131,3 +132,14 @@ export function useImageGridColumns(): VxeTableGridOptions<MpMaterialApi.Materia
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 列表的搜索表单 */
|
||||||
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'accountId',
|
||||||
|
label: '公众号',
|
||||||
|
component: 'Input',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import type { MpMaterialApi } from '#/api/mp/material';
|
|
||||||
|
|
||||||
import { nextTick, onMounted, watch } from 'vue';
|
|
||||||
|
|
||||||
import { $t } from '@vben/locales';
|
|
||||||
|
|
||||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
|
||||||
|
|
||||||
import { useImageGridColumns } from './data';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
list: MpMaterialApi.Material[];
|
|
||||||
loading: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
delete: [v: number];
|
|
||||||
refresh: [];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const columns = useImageGridColumns();
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid<MpMaterialApi.Material>({
|
|
||||||
gridOptions: {
|
|
||||||
border: true,
|
|
||||||
columns,
|
|
||||||
keepSource: true,
|
|
||||||
pagerConfig: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
rowConfig: {
|
|
||||||
keyField: 'id',
|
|
||||||
isHover: true,
|
|
||||||
height: 220,
|
|
||||||
},
|
|
||||||
toolbarConfig: {
|
|
||||||
refresh: true,
|
|
||||||
},
|
|
||||||
showOverflow: 'tooltip',
|
|
||||||
proxyConfig: {
|
|
||||||
ajax: {
|
|
||||||
query: async () => {
|
|
||||||
// 数据由父组件管理,触发刷新事件后返回当前数据
|
|
||||||
emit('refresh');
|
|
||||||
// 返回当前数据,避免覆盖
|
|
||||||
return {
|
|
||||||
list: Array.isArray(props.list) ? props.list : [],
|
|
||||||
total: props.list?.length || 0,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
enabled: true,
|
|
||||||
autoLoad: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateGridData(data: MpMaterialApi.Material[]) {
|
|
||||||
if (gridApi.grid?.loadData) {
|
|
||||||
gridApi.grid.loadData(data);
|
|
||||||
} else {
|
|
||||||
gridApi.setGridOptions({ data });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.list,
|
|
||||||
async (list: MpMaterialApi.Material[]) => {
|
|
||||||
const data = Array.isArray(list) ? list : [];
|
|
||||||
await nextTick();
|
|
||||||
updateGridData(data);
|
|
||||||
},
|
|
||||||
{ immediate: true, flush: 'post' },
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.loading,
|
|
||||||
(loading: boolean) => {
|
|
||||||
gridApi.setLoading(loading);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
/** 初始化 */
|
|
||||||
onMounted(async () => {
|
|
||||||
await nextTick();
|
|
||||||
updateGridData(Array.isArray(props.list) ? props.list : []);
|
|
||||||
gridApi.setLoading(props.loading);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Grid class="image-table-grid mt-4 pb-0">
|
|
||||||
<template #toolbar-tools>
|
|
||||||
<slot name="toolbar-tools"></slot>
|
|
||||||
</template>
|
|
||||||
<template #image="{ row }">
|
|
||||||
<div class="flex items-center justify-center" style="height: 192px">
|
|
||||||
<img
|
|
||||||
:src="row.url"
|
|
||||||
class="object-contain"
|
|
||||||
style="display: block; max-width: 100%; max-height: 192px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #actions="{ row }">
|
|
||||||
<TableAction
|
|
||||||
:actions="[
|
|
||||||
{
|
|
||||||
label: $t('common.delete'),
|
|
||||||
type: 'danger',
|
|
||||||
link: true,
|
|
||||||
icon: ACTION_ICON.DELETE,
|
|
||||||
auth: ['mp:material:delete'],
|
|
||||||
popConfirm: {
|
|
||||||
title: '确定要删除该图片吗?',
|
|
||||||
confirm: () => emit('delete', row.id!),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Grid>
|
|
||||||
</template>
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
|
||||||
import type { MpMaterialApi } from '#/api/mp/material';
|
|
||||||
|
|
||||||
import { nextTick, watch } from 'vue';
|
|
||||||
|
|
||||||
import { $t } from '@vben/locales';
|
|
||||||
import { openWindow } from '@vben/utils';
|
|
||||||
|
|
||||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
|
||||||
import { WxVideoPlayer } from '#/views/mp/components';
|
|
||||||
|
|
||||||
import { useVideoGridColumns } from './data';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
list: MpMaterialApi.Material[];
|
|
||||||
loading: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
delete: [v: number];
|
|
||||||
refresh: [];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const columns = useVideoGridColumns();
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
|
||||||
gridOptions: {
|
|
||||||
border: true,
|
|
||||||
columns,
|
|
||||||
keepSource: true,
|
|
||||||
pagerConfig: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
rowConfig: {
|
|
||||||
keyField: 'id',
|
|
||||||
isHover: true,
|
|
||||||
},
|
|
||||||
toolbarConfig: {
|
|
||||||
refresh: true,
|
|
||||||
},
|
|
||||||
showOverflow: 'tooltip',
|
|
||||||
proxyConfig: {
|
|
||||||
ajax: {
|
|
||||||
query: async () => {
|
|
||||||
// 数据由父组件管理,触发刷新事件后返回当前数据
|
|
||||||
emit('refresh');
|
|
||||||
// 返回当前数据,避免覆盖
|
|
||||||
return {
|
|
||||||
list: Array.isArray(props.list) ? props.list : [],
|
|
||||||
total: props.list?.length || 0,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
enabled: true,
|
|
||||||
autoLoad: false,
|
|
||||||
},
|
|
||||||
} as VxeTableGridOptions<MpMaterialApi.Material>,
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateGridData(data: MpMaterialApi.Material[]) {
|
|
||||||
if (gridApi.grid?.loadData) {
|
|
||||||
gridApi.grid.loadData(data);
|
|
||||||
} else {
|
|
||||||
gridApi.setGridOptions({ data });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.list,
|
|
||||||
async (list: MpMaterialApi.Material[]) => {
|
|
||||||
const data = Array.isArray(list) ? list : [];
|
|
||||||
await nextTick();
|
|
||||||
updateGridData(data);
|
|
||||||
},
|
|
||||||
{ immediate: true, flush: 'post' },
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.loading,
|
|
||||||
(loading: boolean) => {
|
|
||||||
gridApi.setLoading(loading);
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Grid class="mt-4">
|
|
||||||
<template #toolbar-tools>
|
|
||||||
<slot name="toolbar-tools"></slot>
|
|
||||||
</template>
|
|
||||||
<template #video="{ row }">
|
|
||||||
<WxVideoPlayer v-if="row.url" :url="row.url" />
|
|
||||||
</template>
|
|
||||||
<template #actions="{ row }">
|
|
||||||
<TableAction
|
|
||||||
:actions="[
|
|
||||||
{
|
|
||||||
label: '下载',
|
|
||||||
type: 'primary',
|
|
||||||
link: true,
|
|
||||||
icon: ACTION_ICON.DOWNLOAD,
|
|
||||||
onClick: () => openWindow(row.url),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: $t('common.delete'),
|
|
||||||
type: 'danger',
|
|
||||||
link: true,
|
|
||||||
icon: ACTION_ICON.DELETE,
|
|
||||||
auth: ['mp:material:delete'],
|
|
||||||
popConfirm: {
|
|
||||||
title: '确定要删除该视频吗?',
|
|
||||||
confirm: () => emit('delete', row.id!),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Grid>
|
|
||||||
</template>
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
|
||||||
import type { MpMaterialApi } from '#/api/mp/material';
|
|
||||||
|
|
||||||
import { nextTick, watch } from 'vue';
|
|
||||||
|
|
||||||
import { $t } from '@vben/locales';
|
|
||||||
import { openWindow } from '@vben/utils';
|
|
||||||
|
|
||||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
|
||||||
import { WxVoicePlayer } from '#/views/mp/components';
|
|
||||||
|
|
||||||
import { useVoiceGridColumns } from './data';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
list: MpMaterialApi.Material[];
|
|
||||||
loading: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
delete: [v: number];
|
|
||||||
refresh: [];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const columns = useVoiceGridColumns();
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
|
||||||
gridOptions: {
|
|
||||||
border: true,
|
|
||||||
columns,
|
|
||||||
keepSource: true,
|
|
||||||
pagerConfig: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
rowConfig: {
|
|
||||||
keyField: 'id',
|
|
||||||
isHover: true,
|
|
||||||
},
|
|
||||||
toolbarConfig: {
|
|
||||||
refresh: true,
|
|
||||||
},
|
|
||||||
showOverflow: 'tooltip',
|
|
||||||
proxyConfig: {
|
|
||||||
ajax: {
|
|
||||||
query: async () => {
|
|
||||||
// 数据由父组件管理,触发刷新事件后返回当前数据
|
|
||||||
emit('refresh');
|
|
||||||
// 返回当前数据,避免覆盖
|
|
||||||
return {
|
|
||||||
list: Array.isArray(props.list) ? props.list : [],
|
|
||||||
total: props.list?.length || 0,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
enabled: true,
|
|
||||||
autoLoad: false,
|
|
||||||
},
|
|
||||||
} as VxeTableGridOptions<MpMaterialApi.Material>,
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateGridData(data: MpMaterialApi.Material[]) {
|
|
||||||
if (gridApi.grid?.loadData) {
|
|
||||||
gridApi.grid.loadData(data);
|
|
||||||
} else {
|
|
||||||
gridApi.setGridOptions({ data });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.list,
|
|
||||||
async (list: MpMaterialApi.Material[]) => {
|
|
||||||
const data = Array.isArray(list) ? list : [];
|
|
||||||
await nextTick();
|
|
||||||
updateGridData(data);
|
|
||||||
},
|
|
||||||
{ immediate: true, flush: 'post' },
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.loading,
|
|
||||||
(loading: boolean) => {
|
|
||||||
gridApi.setLoading(loading);
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Grid class="mt-4">
|
|
||||||
<template #toolbar-tools>
|
|
||||||
<slot name="toolbar-tools"></slot>
|
|
||||||
</template>
|
|
||||||
<template #voice="{ row }">
|
|
||||||
<WxVoicePlayer v-if="row.url" :url="row.url" />
|
|
||||||
</template>
|
|
||||||
<template #actions="{ row }">
|
|
||||||
<TableAction
|
|
||||||
:actions="[
|
|
||||||
{
|
|
||||||
label: '下载',
|
|
||||||
type: 'primary',
|
|
||||||
link: true,
|
|
||||||
icon: ACTION_ICON.DOWNLOAD,
|
|
||||||
onClick: () => openWindow(row.url),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: $t('common.delete'),
|
|
||||||
type: 'danger',
|
|
||||||
link: true,
|
|
||||||
icon: ACTION_ICON.DELETE,
|
|
||||||
auth: ['mp:material:delete'],
|
|
||||||
popConfirm: {
|
|
||||||
title: '确定要删除该语音吗?',
|
|
||||||
confirm: () => emit('delete', row.id!),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Grid>
|
|
||||||
</template>
|
|
||||||
Reference in New Issue
Block a user