Merge remote-tracking branch 'yudao/dev' into dev

This commit is contained in:
jason
2025-12-01 20:47:51 +08:00
41 changed files with 451 additions and 261 deletions

View File

@@ -21,6 +21,7 @@ export namespace MallCombinationActivityApi {
limitDuration?: number; // 限制时长 limitDuration?: number; // 限制时长
combinationPrice?: number; // 拼团价格 combinationPrice?: number; // 拼团价格
products: CombinationProduct[]; // 商品列表 products: CombinationProduct[]; // 商品列表
picUrl?: any;
} }
/** 拼团活动所需属性 */ /** 拼团活动所需属性 */

View File

@@ -31,6 +31,7 @@ export namespace MallSeckillActivityApi {
totalStock?: number; // 秒杀总库存 totalStock?: number; // 秒杀总库存
seckillPrice?: number; // 秒杀价格 seckillPrice?: number; // 秒杀价格
products?: SeckillProduct[]; // 秒杀商品列表 products?: SeckillProduct[]; // 秒杀商品列表
picUrl?: any;
} }
} }

View File

@@ -45,7 +45,7 @@ const bpmnInstances = () => (window as any)?.bpmnInstances;
const generateStandardId = (type: string): string => { const generateStandardId = (type: string): string => {
const prefix = type === 'message' ? 'Message_' : 'Signal_'; const prefix = type === 'message' ? 'Message_' : 'Signal_';
const timestamp = Date.now(); const timestamp = Date.now();
const random = Math.random().toString(36).substring(2, 6).toUpperCase(); const random = Math.random().toString(36).slice(2, 6).toUpperCase();
return `${prefix}${timestamp}_${random}`; return `${prefix}${timestamp}_${random}`;
}; };

View File

@@ -153,14 +153,14 @@ watch(
.header-editor { .header-editor {
.header-list { .header-list {
max-height: 400px; max-height: 400px;
overflow-y: auto;
margin-bottom: 16px; margin-bottom: 16px;
overflow-y: auto;
} }
.header-item { .header-item {
display: flex; display: flex;
align-items: center;
gap: 8px; gap: 8px;
align-items: center;
margin-bottom: 12px; margin-bottom: 12px;
.header-key { .header-key {
@@ -168,8 +168,8 @@ watch(
} }
.separator { .separator {
color: #606266;
font-weight: 500; font-weight: 500;
color: #606266;
} }
.header-value { .header-value {

View File

@@ -1,3 +1,4 @@
<!-- eslint-disable prettier/prettier -->
<script lang="ts" setup> <script lang="ts" setup>
import { inject, nextTick, onBeforeUnmount, ref, toRaw, watch } from 'vue'; import { inject, nextTick, onBeforeUnmount, ref, toRaw, watch } from 'vue';
@@ -203,7 +204,7 @@ const updateHttpExtensions = (force = false) => {
? String(!!rawValue) ? String(!!rawValue)
: (rawValue === undefined : (rawValue === undefined
? '' ? ''
: String(rawValue)); : rawValue.toString());
desiredEntries.push([name, persisted]); desiredEntries.push([name, persisted]);
}); });

View File

@@ -13,7 +13,7 @@ import { useGridColumns, useGridFormSchema } from './data';
defineOptions({ name: 'BpmManagerTask' }); defineOptions({ name: 'BpmManagerTask' });
/** 查看历史 */ /** 查看历史 */
function handleHistory(row: BpmTaskApi.TaskManager) { function handleHistory(row: BpmTaskApi.Task) {
router.push({ router.push({
name: 'BpmProcessInstanceDetail', name: 'BpmProcessInstanceDetail',
query: { query: {

View File

@@ -2,8 +2,8 @@ import type { Ref } from 'vue';
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { VxeGridProps, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeGridProps, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MallSpuApi } from '#/api/mall/product/spu';
import type { MallCategoryApi } from '#/api/mall/product/category'; import type { MallCategoryApi } from '#/api/mall/product/category';
import type { MallSpuApi } from '#/api/mall/product/spu';
import { computed } from 'vue'; import { computed } from 'vue';

View File

@@ -133,6 +133,13 @@ export function useFormSchema(): VbenFormSchema[] {
placeholder: '请输入最大砍价金额', placeholder: '请输入最大砍价金额',
}, },
}, },
// TODO @puhui999这里交互不太对可以对比下 element-plus 版本呢
{
fieldName: 'spuId',
label: '砍价商品',
component: 'Input',
rules: 'required',
},
]; ];
} }

View File

@@ -14,6 +14,7 @@ import {
updateBargainActivity, updateBargainActivity,
} from '#/api/mall/promotion/bargain/bargainActivity'; } from '#/api/mall/promotion/bargain/bargainActivity';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { SpuShowcase } from '#/views/mall/product/spu/components';
import { useFormSchema } from '../data'; import { useFormSchema } from '../data';
@@ -21,7 +22,7 @@ defineOptions({ name: 'PromotionBargainActivityForm' });
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
const formData = ref<MallBargainActivityApi.BargainActivity>(); const formData = ref<Partial<MallBargainActivityApi.BargainActivity>>({});
const getTitle = computed(() => { const getTitle = computed(() => {
return formData.value?.id return formData.value?.id
? $t('ui.actionTitle.edit', ['砍价活动']) ? $t('ui.actionTitle.edit', ['砍价活动'])
@@ -49,8 +50,11 @@ const [Modal, modalApi] = useVbenModal({
} }
modalApi.lock(); modalApi.lock();
// 提交表单 // 提交表单
const data = const values = await formApi.getValues();
(await formApi.getValues()) as MallBargainActivityApi.BargainActivity; const data = {
...values,
spuId: formData.value.spuId,
} as MallBargainActivityApi.BargainActivity;
try { try {
await (formData.value?.id await (formData.value?.id
? updateBargainActivity(data) ? updateBargainActivity(data)
@@ -65,7 +69,7 @@ const [Modal, modalApi] = useVbenModal({
}, },
async onOpenChange(isOpen: boolean) { async onOpenChange(isOpen: boolean) {
if (!isOpen) { if (!isOpen) {
formData.value = undefined; formData.value = {};
return; return;
} }
// 加载数据 // 加载数据
@@ -86,8 +90,12 @@ const [Modal, modalApi] = useVbenModal({
</script> </script>
<template> <template>
<!-- TODO @puhui999这里缺少商品的选择 -->
<Modal class="w-2/5" :title="getTitle"> <Modal class="w-2/5" :title="getTitle">
<Form class="mx-4" /> <Form class="mx-4">
<!-- 自定义插槽商品选择 -->
<template #spuId>
<SpuShowcase v-model="formData.spuId" :limit="1" />
</template>
</Form>
</Modal> </Modal>
</template> </template>

View File

@@ -105,11 +105,12 @@ export function useFormSchema(): VbenFormSchema[] {
options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
}, },
}, },
// TODO @puhui999这里交互不太对可以对比下 element-plus 版本呢
{ {
// TODO <!-- TODO @puhui999这里缺少商品的选择 -->
fieldName: 'spuId', fieldName: 'spuId',
label: '拼团商品', label: '拼团商品',
component: 'Input', component: 'Input',
rules: 'required',
}, },
]; ];
} }

View File

@@ -13,13 +13,16 @@ import {
updateCombinationActivity, updateCombinationActivity,
} from '#/api/mall/promotion/combination/combinationActivity'; } from '#/api/mall/promotion/combination/combinationActivity';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { SpuShowcase } from '#/views/mall/product/spu/components';
import { useFormSchema } from '../data'; import { useFormSchema } from '../data';
defineOptions({ name: 'CombinationActivityForm' }); defineOptions({ name: 'CombinationActivityForm' });
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
const formData = ref<MallCombinationActivityApi.CombinationActivity>(); const formData = ref<Partial<MallCombinationActivityApi.CombinationActivity>>(
{},
);
const getTitle = computed(() => { const getTitle = computed(() => {
return formData.value?.id return formData.value?.id
? $t('ui.actionTitle.edit', ['拼团活动']) ? $t('ui.actionTitle.edit', ['拼团活动'])
@@ -47,8 +50,11 @@ const [Modal, modalApi] = useVbenModal({
} }
modalApi.lock(); modalApi.lock();
// 提交表单 // 提交表单
const data = const values = await formApi.getValues();
(await formApi.getValues()) as MallCombinationActivityApi.CombinationActivity; const data = {
...values,
spuId: formData.value.spuId,
} as MallCombinationActivityApi.CombinationActivity;
try { try {
await (formData.value?.id await (formData.value?.id
? updateCombinationActivity(data) ? updateCombinationActivity(data)
@@ -63,7 +69,7 @@ const [Modal, modalApi] = useVbenModal({
}, },
async onOpenChange(isOpen: boolean) { async onOpenChange(isOpen: boolean) {
if (!isOpen) { if (!isOpen) {
formData.value = undefined; formData.value = {};
return; return;
} }
// 加载数据 // 加载数据
@@ -86,6 +92,11 @@ const [Modal, modalApi] = useVbenModal({
<template> <template>
<Modal class="w-3/5" :title="getTitle"> <Modal class="w-3/5" :title="getTitle">
<Form /> <Form>
<!-- 自定义插槽商品选择 -->
<template #spuId>
<SpuShowcase v-model="formData.spuId" :limit="1" />
</template>
</Form>
</Modal> </Modal>
</template> </template>

View File

@@ -111,7 +111,6 @@ function emitActivityChange() {
> >
<Tooltip :title="activity.name"> <Tooltip :title="activity.name">
<div class="relative h-full w-full"> <div class="relative h-full w-full">
<!-- TODO @芋艿 -->
<Image <Image
:src="activity.picUrl" :src="activity.picUrl"
class="h-full w-full rounded-lg object-cover" class="h-full w-full rounded-lg object-cover"

View File

@@ -133,9 +133,8 @@ function handleSliderChange(prop: string) {
</TabPane> </TabPane>
<!-- 每个组件的通用内容 --> <!-- 每个组件的通用内容 -->
<!-- TODO @xingyu装修这里的样式貌似没 ele 版本的好看 -->
<TabPane tab="样式" key="style" force-render> <TabPane tab="样式" key="style" force-render>
<p class="text-lg font-bold">组件样式</p> <div class="mb-2 bg-gray-100 p-2 text-sm">组件样式</div>
<div class="flex flex-col gap-2 rounded-md p-4 shadow-lg"> <div class="flex flex-col gap-2 rounded-md p-4 shadow-lg">
<Form :model="formData"> <Form :model="formData">
<FormItem <FormItem
@@ -181,7 +180,7 @@ function handleSliderChange(prop: string) {
class="mb-0 w-full" class="mb-0 w-full"
> >
<Row> <Row>
<Col :span="11"> <Col :span="19">
<Slider <Slider
v-model:value=" v-model:value="
formData[dataRef.prop as keyof ComponentStyle] formData[dataRef.prop as keyof ComponentStyle]
@@ -192,8 +191,9 @@ function handleSliderChange(prop: string) {
class="mr-4" class="mr-4"
/> />
</Col> </Col>
<Col :span="2"> <Col :span="4">
<InputNumber <InputNumber
class="w-[50px]"
:max="100" :max="100"
:min="0" :min="0"
v-model:value=" v-model:value="

View File

@@ -98,7 +98,7 @@ const handleDeleteComponent = () => {
<component :is="component.id" :property="component.property" /> <component :is="component.id" :property="component.property" />
</div> </div>
<div <div
class="component-wrap absolute -bottom-1 -left-0.5 -right-0.5 -top-1 block h-full w-full" class="component-wrap absolute -bottom-1 -left-0.5 -right-0.5 block h-full w-full"
> >
<!-- 左侧组件名悬浮的小贴条 --> <!-- 左侧组件名悬浮的小贴条 -->
<div class="component-name" v-if="component.name"> <div class="component-name" v-if="component.name">
@@ -109,9 +109,6 @@ const handleDeleteComponent = () => {
class="component-toolbar" class="component-toolbar"
v-if="showToolbar && component.name && active" v-if="showToolbar && component.name && active"
> >
<!-- TODO @xingyu装修按钮少的时候会存在遮住的情况 -->
<!-- TODO @xingyu装修貌似中间的选中框框没全部框柱上面多了点下面少了点 -->
<!-- TODO @xingyu装修从左侧的组件拖拽到中间时没有这个组件的小卡片预览ele 版本是有的 -->
<VerticalButtonGroup size="small"> <VerticalButtonGroup size="small">
<Button <Button
:disabled="!canMoveUp" :disabled="!canMoveUp"
@@ -172,7 +169,6 @@ const handleDeleteComponent = () => {
</div> </div>
</div> </div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
$active-border-width: 2px; $active-border-width: 2px;
$hover-border-width: 1px; $hover-border-width: 1px;

View File

@@ -100,5 +100,4 @@ function handleCloneComponent(component: DiyComponent<any>) {
</Collapse.Panel> </Collapse.Panel>
</Collapse> </Collapse>
</div> </div>
<!-- TODO @xingyu装修ele 里面有一些 style看看是不是都迁移完了特别是 drag-area 是全局样式 -->
</template> </template>

View File

@@ -106,7 +106,6 @@ watch(
</div> </div>
</Carousel> </Carousel>
</template> </template>
<style lang="scss"> <style lang="scss">
// Ant Design Vue Carousel 样式调整 // Ant Design Vue Carousel 样式调整
:deep(.ant-carousel .ant-carousel-dots) { :deep(.ant-carousel .ant-carousel-dots) {

View File

@@ -14,7 +14,7 @@ defineProps<{ property: UserCardProperty }>();
<div class="flex flex-col"> <div class="flex flex-col">
<div class="flex items-center justify-between px-4 py-6"> <div class="flex items-center justify-between px-4 py-6">
<div class="flex flex-1 items-center gap-4"> <div class="flex flex-1 items-center gap-4">
<Avatar :size="60"> <Avatar :size="60" class="flex items-center">
<IconifyIcon icon="ep:avatar" :size="60" /> <IconifyIcon icon="ep:avatar" :size="60" />
</Avatar> </Avatar>
<span class="text-[18px] font-bold">芋道源码</span> <span class="text-[18px] font-bold">芋道源码</span>

View File

@@ -175,8 +175,7 @@ function handleComponentSelected(
index: number = -1, index: number = -1,
) { ) {
// 使用深拷贝避免响应式追踪循环警告 // 使用深拷贝避免响应式追踪循环警告
// TODO @xingyu【装修】这个是必须的么ele 没有哈。 selectedComponent.value = component;
selectedComponent.value = cloneDeep(component);
selectedComponentIndex.value = index; selectedComponentIndex.value = index;
} }
@@ -344,7 +343,7 @@ onMounted(() => {
<!-- 中心设计区域ComponentContainer --> <!-- 中心设计区域ComponentContainer -->
<Col :span="12"> <Col :span="12">
<div <div
class="relative flex max-h-[calc(80vh)] w-full flex-1 flex-col justify-center overflow-y-auto" class="editor-center relative flex max-h-[calc(80vh)] w-full flex-1 flex-col overflow-y-auto"
@click="handlePageSelected" @click="handlePageSelected"
> >
<!-- 手机顶部 --> <!-- 手机顶部 -->
@@ -378,20 +377,20 @@ onMounted(() => {
</div> </div>
<!-- 手机页面编辑区域 --> <!-- 手机页面编辑区域 -->
<div <div
class="min-h-full w-full" class="mx-auto min-h-full w-96 bg-no-repeat"
:style="{ :style="{
// backgroundColor: pageConfigComponent.property.backgroundColor, // backgroundColor: pageConfigComponent.property.backgroundColor,
backgroundImage: `url(${pageConfigComponent.property.backgroundImage})`, backgroundImage: `url(${pageConfigComponent.property.backgroundImage})`,
}" }"
> >
<div <div
class="relative mx-auto my-0 min-h-full w-96 items-center justify-center bg-auto bg-no-repeat" class="relative my-0 min-h-full w-full items-center justify-center bg-auto bg-no-repeat"
> >
<draggable <draggable
v-model="pageComponents" v-model="pageComponents"
:animation="200" :animation="200"
:force-fallback="false" :force-fallback="false"
class="min-h-full w-full" class="min-h-[70vh] w-full"
filter=".component-toolbar" filter=".component-toolbar"
ghost-class="draggable-ghost" ghost-class="draggable-ghost"
group="component" group="component"
@@ -508,5 +507,4 @@ onMounted(() => {
</div> </div>
</PreviewModal> </PreviewModal>
</Page> </Page>
<!-- TODO @xingyu装修这里改造完后类似 web-ele/src/views/mall/promotion/components/diy-editor/index.vue 里的全局样式递推到子组件里的就没没了类似 property-group -->
</template> </template>

View File

@@ -1,7 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { Space } from 'ant-design-vue'; import { Space } from 'ant-design-vue';
// TODO @xingyu【装修】貌似上下移动的按钮被遮住了
/** /**
* 垂直按钮组 * 垂直按钮组
* Ant Design Vue 的按钮组,通过 Space 实现垂直布局 * Ant Design Vue 的按钮组,通过 Space 实现垂直布局

View File

@@ -60,14 +60,10 @@ export function useFormSchema(): VbenFormSchema[] {
rules: 'required', rules: 'required',
defaultValue: PromotionProductScopeEnum.ALL.scope, defaultValue: PromotionProductScopeEnum.ALL.scope,
}, },
// TODO @puhui999 商品选择器优化
{ {
fieldName: 'productSpuIds', fieldName: 'productSpuIds',
label: '商品', label: '商品',
component: 'Input', component: 'Input',
componentProps: {
placeholder: '请选择商品',
},
dependencies: { dependencies: {
triggerFields: ['productScope', 'productScopeValues'], triggerFields: ['productScope', 'productScopeValues'],
show: (model) => show: (model) =>
@@ -84,14 +80,10 @@ export function useFormSchema(): VbenFormSchema[] {
}, },
rules: 'required', rules: 'required',
}, },
// TODO @puhui999 商品分类选择器优化
{ {
fieldName: 'productCategoryIds', fieldName: 'productCategoryIds',
label: '商品分类', label: '商品分类',
component: 'Input', component: 'Input',
componentProps: {
placeholder: '请选择商品分类',
},
dependencies: { dependencies: {
triggerFields: ['productScope', 'productScopeValues'], triggerFields: ['productScope', 'productScopeValues'],
show: (model) => show: (model) =>

View File

@@ -16,11 +16,18 @@ import {
updateCouponTemplate, updateCouponTemplate,
} from '#/api/mall/promotion/coupon/couponTemplate'; } from '#/api/mall/promotion/coupon/couponTemplate';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { ProductCategorySelect } from '#/views/mall/product/category/components';
import { SpuShowcase } from '#/views/mall/product/spu/components';
import { useFormSchema } from '../data'; import { useFormSchema } from '../data';
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
const formData = ref<MallCouponTemplateApi.CouponTemplate>(); const formData = ref<
Partial<MallCouponTemplateApi.CouponTemplate> & {
productCategoryIds?: number | number[];
productSpuIds?: number[];
}
>({});
const getTitle = computed(() => { const getTitle = computed(() => {
return formData.value?.id return formData.value?.id
? $t('ui.actionTitle.edit', ['优惠券模板']) ? $t('ui.actionTitle.edit', ['优惠券模板'])
@@ -64,7 +71,7 @@ const [Modal, modalApi] = useVbenModal({
}, },
async onOpenChange(isOpen: boolean) { async onOpenChange(isOpen: boolean) {
if (!isOpen) { if (!isOpen) {
formData.value = undefined; formData.value = {};
return; return;
} }
// 加载数据 // 加载数据
@@ -75,7 +82,7 @@ const [Modal, modalApi] = useVbenModal({
modalApi.lock(); modalApi.lock();
try { try {
formData.value = await getCouponTemplate(data.id); formData.value = await getCouponTemplate(data.id);
const processedData = await processLoadData(formData.value); const processedData = await processLoadData(formData.value as any);
// 设置到表单 // 设置到表单
await formApi.setValues(processedData); await formApi.setValues(processedData);
} finally { } finally {
@@ -144,6 +151,15 @@ async function processLoadData(
<template> <template>
<Modal :title="getTitle" class="w-2/5"> <Modal :title="getTitle" class="w-2/5">
<Form class="mx-4" /> <Form class="mx-4">
<!-- 自定义插槽商品选择 -->
<template #productSpuIds>
<SpuShowcase v-model="formData.productSpuIds" />
</template>
<!-- 自定义插槽分类选择 -->
<template #productCategoryIds>
<ProductCategorySelect v-model="formData.productCategoryIds" />
</template>
</Form>
</Modal> </Modal>
</template> </template>

View File

@@ -67,8 +67,15 @@ export function useFormSchema(): VbenFormSchema[] {
placeholder: '请输入备注', placeholder: '请输入备注',
rows: 4, rows: 4,
}, },
formItemClass: 'col-span-2',
},
{
fieldName: 'spuIds',
label: '活动商品',
component: 'Input',
rules: 'required',
formItemClass: 'col-span-2',
}, },
// TODO @puhui999少了商品选择~
]; ];
} }

View File

@@ -13,13 +13,18 @@ import {
updateDiscountActivity, updateDiscountActivity,
} from '#/api/mall/promotion/discount/discountActivity'; } from '#/api/mall/promotion/discount/discountActivity';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { SpuShowcase } from '#/views/mall/product/spu/components';
import { useFormSchema } from '../data'; import { useFormSchema } from '../data';
defineOptions({ name: 'DiscountActivityForm' }); defineOptions({ name: 'DiscountActivityForm' });
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
const formData = ref<MallDiscountActivityApi.DiscountActivity>(); const formData = ref<
Partial<MallDiscountActivityApi.DiscountActivity> & {
spuIds?: number[];
}
>({});
const getTitle = computed(() => { const getTitle = computed(() => {
return formData.value?.id return formData.value?.id
? $t('ui.actionTitle.edit', ['限时折扣活动']) ? $t('ui.actionTitle.edit', ['限时折扣活动'])
@@ -69,7 +74,7 @@ const [Modal, modalApi] = useVbenModal({
}, },
async onOpenChange(isOpen: boolean) { async onOpenChange(isOpen: boolean) {
if (!isOpen) { if (!isOpen) {
formData.value = undefined; formData.value = {};
return; return;
} }
// 加载数据 // 加载数据
@@ -91,6 +96,11 @@ const [Modal, modalApi] = useVbenModal({
<template> <template>
<Modal class="w-3/5" :title="getTitle"> <Modal class="w-3/5" :title="getTitle">
<Form /> <Form>
<!-- 自定义插槽商品选择 -->
<template #spuIds>
<SpuShowcase v-model="formData.spuIds" />
</template>
</Form>
</Modal> </Modal>
</template> </template>

View File

@@ -20,7 +20,6 @@ import {
} from '#/api/mall/promotion/diy/template'; } from '#/api/mall/promotion/diy/template';
import { DiyEditor, PAGE_LIBS } from '#/views/mall/promotion/components'; import { DiyEditor, PAGE_LIBS } from '#/views/mall/promotion/components';
// TODO @xingyu【装修】左上角的“基础设施”、“首页”、“我的”切换时中间的编辑器内容没有正确切换。可对比 ele 版本的效果!
/** 装修模板表单 */ /** 装修模板表单 */
defineOptions({ name: 'DiyTemplateDecorate' }); defineOptions({ name: 'DiyTemplateDecorate' });
@@ -28,34 +27,28 @@ const route = useRoute();
const { refreshTab } = useTabs(); const { refreshTab } = useTabs();
const domain = import.meta.env.VITE_MALL_H5_DOMAIN; const domain = import.meta.env.VITE_MALL_H5_DOMAIN;
// 特殊:存储 reset 重置时,当前 selectedTemplateItem 值,从而进行恢复 const DIY_PAGE_INDEX_KEY = 'diy_page_index'; // 特殊:存储 reset 重置时,当前 selectedTemplateItem 值,从而进行恢复
const DIY_PAGE_INDEX_KEY = 'diy_page_index';
const selectedTemplateItem = ref(0); const selectedTemplateItem = ref(0);
// 左上角工具栏操作按钮
const templateItems = ref([ const templateItems = ref([
{ key: 0, name: '基础设置', icon: 'lucide:settings' }, { key: 0, name: '基础设置', icon: 'lucide:settings' },
{ key: 1, name: '首页', icon: 'lucide:home' }, { key: 1, name: '首页', icon: 'lucide:home' },
{ key: 2, name: '我的', icon: 'lucide:user' }, { key: 2, name: '我的', icon: 'lucide:user' },
]); ]); // 左上角工具栏操作按钮
const formData = ref<MallDiyTemplateApi.DiyTemplateProperty>(); const formData = ref<MallDiyTemplateApi.DiyTemplateProperty>();
// 当前编辑的属性
const currentFormData = ref< const currentFormData = ref<
MallDiyPageApi.DiyPage | MallDiyTemplateApi.DiyTemplateProperty MallDiyPageApi.DiyPage | MallDiyTemplateApi.DiyTemplateProperty
>({ >({
property: '', property: '',
} as MallDiyPageApi.DiyPage); } as MallDiyPageApi.DiyPage); // 当前编辑的属性
// templateItem 对应的缓存
const currentFormDataMap = ref< const currentFormDataMap = ref<
Map<string, MallDiyPageApi.DiyPage | MallDiyTemplateApi.DiyTemplateProperty> Map<string, MallDiyPageApi.DiyPage | MallDiyTemplateApi.DiyTemplateProperty>
>(new Map()); >(new Map()); // templateItem 对应的缓存
// 商城 H5 预览地址
const previewUrl = ref(''); const previewUrl = ref(''); // 商城 H5 预览地址
// 模板组件库 const templateLibs = [] as DiyComponentLibrary[]; // 模板组件库
const templateLibs = [] as DiyComponentLibrary[]; const libs = ref<DiyComponentLibrary[]>(templateLibs); // 当前组件库
// 当前组件库
const libs = ref<DiyComponentLibrary[]>(templateLibs);
/** 获取详情 */ /** 获取详情 */
async function getPageDetail(id: any) { async function getPageDetail(id: any) {
@@ -74,23 +67,23 @@ async function getPageDetail(id: any) {
} }
/** 模板选项切换 */ /** 模板选项切换 */
function handleTemplateItemChange(val: any) { function handleTemplateItemChange(valObj: any) {
const changeValue = val.target.value; const val = valObj.target.value;
// 缓存模版编辑数据 // 缓存模版编辑数据
currentFormDataMap.value.set( currentFormDataMap.value.set(
templateItems.value[changeValue]!.name, templateItems.value[selectedTemplateItem.value]?.name || '',
currentFormData.value!, currentFormData.value!,
); );
// 切换模版
selectedTemplateItem.value = changeValue;
// 读取模版缓存 // 读取模版缓存
const data = currentFormDataMap.value.get( const data = currentFormDataMap.value.get(
templateItems.value[changeValue]!.name, templateItems.value[val]?.name || '',
); );
// 切换模版
selectedTemplateItem.value = val;
// 情况一:编辑模板 // 情况一:编辑模板
if (changeValue === 0) { if (val === 0) {
libs.value = templateLibs; libs.value = templateLibs;
currentFormData.value = (isEmpty(data) ? formData.value : data) as currentFormData.value = (isEmpty(data) ? formData.value : data) as
| MallDiyPageApi.DiyPage | MallDiyPageApi.DiyPage
@@ -104,7 +97,7 @@ function handleTemplateItemChange(val: any) {
isEmpty(data) isEmpty(data)
? formData.value!.pages.find( ? formData.value!.pages.find(
(page: MallDiyPageApi.DiyPage) => (page: MallDiyPageApi.DiyPage) =>
page.name === templateItems.value[changeValue]!.name, page.name === templateItems.value[val]?.name,
) )
: data : data
) as MallDiyPageApi.DiyPage | MallDiyTemplateApi.DiyTemplateProperty; ) as MallDiyPageApi.DiyPage | MallDiyTemplateApi.DiyTemplateProperty;

View File

@@ -133,10 +133,6 @@ export function useFormSchema(): VbenFormSchema[] {
component: 'Input', component: 'Input',
rules: 'required', rules: 'required',
formItemClass: 'col-span-2', formItemClass: 'col-span-2',
// TODO @puhui999貌似 renderComponentContent 不需要哇?
renderComponentContent: () => ({
default: () => null,
}),
}, },
]; ];
} }

View File

@@ -42,7 +42,6 @@ function handleEdit(row: MallRewardActivityApi.RewardActivity) {
/** 关闭满减送活动 */ /** 关闭满减送活动 */
async function handleClose(row: MallRewardActivityApi.RewardActivity) { async function handleClose(row: MallRewardActivityApi.RewardActivity) {
// TODO @puhui999这个国际化需要加下哈closing、closeSuccess
const hideLoading = message.loading({ const hideLoading = message.loading({
content: $t('ui.actionMessage.closing', [row.name]), content: $t('ui.actionMessage.closing', [row.name]),
duration: 0, duration: 0,

View File

@@ -29,10 +29,10 @@ const emit = defineEmits(['success']);
const formData = ref<Partial<MallRewardActivityApi.RewardActivity>>({ const formData = ref<Partial<MallRewardActivityApi.RewardActivity>>({
conditionType: PromotionConditionTypeEnum.PRICE.type, conditionType: PromotionConditionTypeEnum.PRICE.type,
productScope: PromotionProductScopeEnum.ALL.scope,
rules: [], rules: [],
}); });
// TODO @puhui999点击“编辑”后会出现 Cannot read properties of null (reading 'type') 报错;
const getTitle = computed(() => { const getTitle = computed(() => {
return formData.value?.id return formData.value?.id
? $t('ui.actionTitle.edit', ['满减送']) ? $t('ui.actionTitle.edit', ['满减送'])

View File

@@ -8,12 +8,14 @@ import { useVbenModal } from '@vben/common-ui';
import { Button, message } from 'ant-design-vue'; import { Button, message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form'; import { useVbenForm } from '#/adapter/form';
import { getSpu } from '#/api/mall/product/spu';
import { import {
createSeckillActivity, createSeckillActivity,
getSeckillActivity, getSeckillActivity,
updateSeckillActivity, updateSeckillActivity,
} from '#/api/mall/promotion/seckill/seckillActivity'; } from '#/api/mall/promotion/seckill/seckillActivity';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { SpuSkuSelect } from '#/views/mall/product/spu/components';
import { useFormSchema } from '../data'; import { useFormSchema } from '../data';
@@ -31,25 +33,37 @@ const spuId = ref<number>();
const spuName = ref<string>(''); const spuName = ref<string>('');
const skuTableData = ref<any[]>([]); const skuTableData = ref<any[]>([]);
// 选择商品(占位函数,实际需要对接商品选择组件) const spuSkuSelectRef = ref(); // 商品选择弹窗 Ref
/** 打开商品选择弹窗 */
const handleSelectProduct = () => { const handleSelectProduct = () => {
message.info('商品选择功能需要对接商品选择组件'); spuSkuSelectRef.value?.open();
// TODO: 打开商品选择弹窗
// 实际使用时需要:
// 1. 打开商品选择弹窗
// 2. 选择商品后调用以下逻辑设置数据:
// spuId.value = selectedSpu.id;
// spuName.value = selectedSpu.name;
// skuTableData.value = selectedSkus.map(sku => ({
// skuId: sku.id,
// skuName: sku.name || '',
// picUrl: sku.picUrl || selectedSpu.picUrl || '',
// price: sku.price || 0,
// stock: 0,
// seckillPrice: 0,
// }));
}; };
/** 选择商品后的回调 */
async function handleSpuSelected(selectedSpuId: number, skuIds?: number[]) {
const spu = await getSpu(selectedSpuId);
if (!spu) return;
spuId.value = spu.id;
spuName.value = spu.name || '';
// 筛选指定的 SKU
const selectedSkus = skuIds
? spu.skus?.filter((sku) => skuIds.includes(sku.id!))
: spu.skus;
skuTableData.value =
selectedSkus?.map((sku) => ({
skuId: sku.id!,
skuName: sku.name || '',
picUrl: sku.picUrl || spu.picUrl || '',
price: sku.price || 0,
stock: 0,
seckillPrice: 0,
})) || [];
}
// ================= end ================= // ================= end =================
const [Form, formApi] = useVbenForm({ const [Form, formApi] = useVbenForm({
@@ -137,10 +151,30 @@ const [Modal, modalApi] = useVbenModal({
await nextTick(); await nextTick();
await formApi.setValues(formData.value); await formApi.setValues(formData.value);
// TODO: 加载商品和 SKU 信息 // 加载商品和 SKU 信息
// 需要调用商品 API 获取 SPU 详情 if (formData.value.spuId) {
// spuId.value = formData.value.spuId; const spu = await getSpu(formData.value.spuId);
// await loadProductDetails(formData.value.spuId, formData.value.products); if (spu) {
spuId.value = spu.id;
spuName.value = spu.name || '';
// 回填 SKU 配置
const products = formData.value.products || [];
skuTableData.value =
spu.skus
?.filter((sku) => products.some((p) => p.skuId === sku.id))
.map((sku) => {
const product = products.find((p) => p.skuId === sku.id);
return {
skuId: sku.id!,
skuName: sku.name || '',
picUrl: sku.picUrl || spu.picUrl || '',
price: sku.price || 0,
stock: product?.stock || 0,
seckillPrice: (product?.seckillPrice || 0) / 100, // 分转元
};
}) || [];
}
}
} finally { } finally {
modalApi.unlock(); modalApi.unlock();
} }
@@ -154,7 +188,6 @@ const [Modal, modalApi] = useVbenModal({
<Form /> <Form />
<!-- 商品选择区域 --> <!-- 商品选择区域 -->
<!-- TODO @puhui999这里缺少商品的选择 -->
<div class="mt-4"> <div class="mt-4">
<div class="mb-2 flex items-center"> <div class="mb-2 flex items-center">
<span class="text-sm font-medium">秒杀活动商品:</span> <span class="text-sm font-medium">秒杀活动商品:</span>
@@ -218,4 +251,11 @@ const [Modal, modalApi] = useVbenModal({
</div> </div>
</div> </div>
</Modal> </Modal>
<!-- 商品选择器弹窗 -->
<SpuSkuSelect
ref="spuSkuSelectRef"
:is-select-sku="true"
@select="handleSpuSelected"
/>
</template> </template>

View File

@@ -133,7 +133,6 @@ function emitActivityChange() {
class="flex h-[60px] w-[60px] cursor-pointer items-center justify-center rounded-lg border border-dashed border-gray-300 hover:border-blue-400" class="flex h-[60px] w-[60px] cursor-pointer items-center justify-center rounded-lg border border-dashed border-gray-300 hover:border-blue-400"
@click="handleOpenActivitySelect" @click="handleOpenActivitySelect"
> >
<!-- TODO @芋艿等待和 /Users/yunai/Java/yudao-ui-admin-vben-v5/apps/web-antd/src/views/mall/product/spu/components/spu-showcase.vue 进一步统一 -->
<IconifyIcon icon="lucide:plus" class="text-xl text-gray-400" /> <IconifyIcon icon="lucide:plus" class="text-xl text-gray-400" />
</div> </div>
</Tooltip> </Tooltip>

View File

@@ -14,7 +14,7 @@ defineOptions({ name: 'TabNews' });
const props = defineProps<{ const props = defineProps<{
modelValue: Reply; modelValue: Reply;
newsType: NewsType; newsType?: NewsType;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{

View File

@@ -4,13 +4,13 @@ import type { MpMaterialApi } from '#/api/mp/material';
import { watch } from 'vue'; import { watch } from 'vue';
import { $t } from '@vben/locales';
import { openWindow } from '@vben/utils'; import { openWindow } from '@vben/utils';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { WxVideoPlayer } from '#/views/mp/components'; import { WxVideoPlayer } from '#/views/mp/components';
import { useVideoGridColumns } from './data'; import { useVideoGridColumns } from './data';
import {$t} from '@vben/locales';
const props = defineProps<{ const props = defineProps<{
list: MpMaterialApi.Material[]; list: MpMaterialApi.Material[];

View File

@@ -7,7 +7,7 @@ import { requestClient } from '#/api/request';
export namespace BpmTaskApi { export namespace BpmTaskApi {
/** 流程任务 */ /** 流程任务 */
export interface Task { export interface Task {
id: number; // 编号 id: string; // 编号
name: string; // 任务名字 name: string; // 任务名字
status: number; // 任务状态 status: number; // 任务状态
createTime: number; // 创建时间 createTime: number; // 创建时间

View File

@@ -8,22 +8,22 @@ import { z } from '#/adapter/form';
export const EVENT_EXECUTION_OPTIONS = [ export const EVENT_EXECUTION_OPTIONS = [
{ {
label: 'start', label: '开始',
value: 'start', value: 'start',
}, },
{ {
label: 'end', label: '结束',
value: 'end', value: 'end',
}, },
]; ];
export const EVENT_OPTIONS = [ export const EVENT_OPTIONS = [
{ label: 'create', value: 'create' }, { label: '创建', value: 'create' },
{ label: 'assignment', value: 'assignment' }, { label: '指派', value: 'assignment' },
{ label: 'complete', value: 'complete' }, { label: '完成', value: 'complete' },
{ label: 'delete', value: 'delete' }, { label: '删除', value: 'delete' },
{ label: 'update', value: 'update' }, { label: '更新', value: 'update' },
{ label: 'timeout', value: 'timeout' }, { label: '超时', value: 'timeout' },
]; ];
/** 新增/修改的表单 */ /** 新增/修改的表单 */

View File

@@ -145,5 +145,6 @@ async function processLoadData(
<template> <template>
<Modal :title="getTitle" class="w-2/5"> <Modal :title="getTitle" class="w-2/5">
<Form class="mx-4" /> <Form class="mx-4" />
<!-- TODO @puhui999这里需要同步下 -->
</Modal> </Modal>
</template> </template>

View File

@@ -3,7 +3,7 @@ import type { MallDiyPageApi } from '#/api/mall/promotion/diy/page';
import type { MallDiyTemplateApi } from '#/api/mall/promotion/diy/template'; import type { MallDiyTemplateApi } from '#/api/mall/promotion/diy/template';
import type { DiyComponentLibrary } from '#/views/mall/promotion/components'; // 商城的 DIY 组件,在 DiyEditor 目录下 import type { DiyComponentLibrary } from '#/views/mall/promotion/components'; // 商城的 DIY 组件,在 DiyEditor 目录下
import { onMounted, reactive, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useTabs } from '@vben/hooks'; import { useTabs } from '@vben/hooks';
@@ -35,7 +35,7 @@ const { refreshTab } = useTabs();
const DIY_PAGE_INDEX_KEY = 'diy_page_index'; // 特殊:存储 reset 重置时,当前 selectedTemplateItem 值,从而进行恢复 const DIY_PAGE_INDEX_KEY = 'diy_page_index'; // 特殊:存储 reset 重置时,当前 selectedTemplateItem 值,从而进行恢复
const selectedTemplateItem = ref(0); const selectedTemplateItem = ref(0);
const templateItems = reactive([ const templateItems = ref([
{ name: '基础设置', icon: 'ep:iphone' }, { name: '基础设置', icon: 'ep:iphone' },
{ name: '首页', icon: 'ep:home-filled' }, { name: '首页', icon: 'ep:home-filled' },
{ name: '我的', icon: 'ep:user-filled' }, { name: '我的', icon: 'ep:user-filled' },
@@ -77,11 +77,13 @@ async function getPageDetail(id: any) {
function handleTemplateItemChange(val: any) { function handleTemplateItemChange(val: any) {
// 缓存模版编辑数据 // 缓存模版编辑数据
currentFormDataMap.value.set( currentFormDataMap.value.set(
templateItems[selectedTemplateItem.value]?.name || '', templateItems.value[selectedTemplateItem.value]?.name || '',
currentFormData.value!, currentFormData.value!,
); );
// 读取模版缓存 // 读取模版缓存
const data = currentFormDataMap.value.get(templateItems[val]?.name || ''); const data = currentFormDataMap.value.get(
templateItems.value[val]?.name || '',
);
// 切换模版 // 切换模版
selectedTemplateItem.value = val; selectedTemplateItem.value = val;
@@ -101,7 +103,7 @@ function handleTemplateItemChange(val: any) {
isEmpty(data) isEmpty(data)
? formData.value!.pages.find( ? formData.value!.pages.find(
(page: MallDiyPageApi.DiyPage) => (page: MallDiyPageApi.DiyPage) =>
page.name === templateItems[val]?.name, page.name === templateItems.value[val]?.name,
) )
: data : data
) as MallDiyPageApi.DiyPage | MallDiyTemplateApi.DiyTemplateProperty; ) as MallDiyPageApi.DiyPage | MallDiyTemplateApi.DiyTemplateProperty;
@@ -114,7 +116,7 @@ async function submitForm() {
}); });
try { try {
// 对所有的 templateItems 都进行保存,有缓存则保存缓存,解决都有修改时只保存了当前所编辑的 templateItem导致装修效果存在差异 // 对所有的 templateItems 都进行保存,有缓存则保存缓存,解决都有修改时只保存了当前所编辑的 templateItem导致装修效果存在差异
for (const [i, templateItem] of templateItems.entries()) { for (const [i, templateItem] of templateItems.value.entries()) {
const data = currentFormDataMap.value.get(templateItem.name) as any; const data = currentFormDataMap.value.get(templateItem.name) as any;
// 情况一:基础设置 // 情况一:基础设置
if (i === 0) { if (i === 0) {
@@ -188,7 +190,7 @@ onMounted(async () => {
:show-navigation-bar="selectedTemplateItem !== 0" :show-navigation-bar="selectedTemplateItem !== 0"
:show-page-config="selectedTemplateItem !== 0" :show-page-config="selectedTemplateItem !== 0"
:show-tab-bar="selectedTemplateItem === 0" :show-tab-bar="selectedTemplateItem === 0"
:title="templateItems[selectedTemplateItem]?.name || ''" :title="templateItems[selectedTemplateItem]?.name ?? ''"
@reset="handleEditorReset" @reset="handleEditorReset"
@save="submitForm" @save="submitForm"
> >

View File

@@ -1,26 +1,19 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, reactive, ref } from 'vue'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MpMaterialApi } from '#/api/mp/material';
import { reactive, ref, watch } from 'vue';
import { NewsType } from '@vben/constants'; import { NewsType } from '@vben/constants';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { formatTime } from '@vben/utils';
import { import { ElButton, ElPagination, ElRow } from 'element-plus';
ElButton,
ElPagination,
ElRow,
ElTable,
ElTableColumn,
} from 'element-plus';
import * as MpDraftApi from '#/api/mp/draft'; import { useVbenVxeGrid } from '#/adapter/vxe-table';
import * as MpFreePublishApi from '#/api/mp/freePublish'; import { getDraftPage } from '#/api/mp/draft';
import * as MpMaterialApi from '#/api/mp/material'; import { getFreePublishPage } from '#/api/mp/freePublish';
import News from '#/views/mp/components/wx-news/wx-news.vue'; import { getMaterialPage } from '#/api/mp/material';
import VideoPlayer from '#/views/mp/components/wx-video-play/wx-video-play.vue'; import { WxNews, WxVideoPlayer, WxVoicePlayer } from '#/views/mp/components';
import VoicePlayer from '#/views/mp/components/wx-voice-play/wx-voice-play.vue';
// TODO @hw代码风格看看 antd 和 ele 是不是统一下; 等antd此组件修改完再调整
/** 微信素材选择 */ /** 微信素材选择 */
defineOptions({ name: 'WxMaterialSelect' }); defineOptions({ name: 'WxMaterialSelect' });
@@ -49,33 +42,163 @@ const queryParams = reactive({
pageSize: 10, pageSize: 10,
}); // 查询参数 }); // 查询参数
/** 选择素材 */ const voiceGridColumns: VxeTableGridOptions<MpMaterialApi.Material>['columns'] =
[
{
field: 'mediaId',
title: '编号',
align: 'center',
minWidth: 160,
},
{
field: 'name',
title: '文件名',
minWidth: 200,
},
{
field: 'voice',
title: '语音',
minWidth: 200,
align: 'center',
slots: { default: 'voice' },
},
{
field: 'createTime',
title: '上传时间',
width: 180,
formatter: 'formatDateTime',
},
{
title: '操作',
width: 140,
fixed: 'right',
align: 'center',
slots: { default: 'actions' },
},
];
const videoGridColumns: VxeTableGridOptions<MpMaterialApi.Material>['columns'] =
[
{
field: 'mediaId',
title: '编号',
minWidth: 160,
},
{
field: 'name',
title: '文件名',
minWidth: 200,
},
{
field: 'title',
title: '标题',
minWidth: 200,
},
{
field: 'introduction',
title: '介绍',
minWidth: 220,
},
{
field: 'video',
title: '视频',
minWidth: 220,
align: 'center',
slots: { default: 'video' },
},
{
field: 'createTime',
title: '上传时间',
width: 180,
formatter: 'formatDateTime',
},
{
title: '操作',
width: 140,
fixed: 'right',
align: 'center',
slots: { default: 'actions' },
},
];
const [VoiceGrid, voiceGridApi] = useVbenVxeGrid({
gridOptions: {
border: true,
columns: voiceGridColumns,
height: 'auto',
keepSource: true,
pagerConfig: {
enabled: true,
pageSize: 10,
},
proxyConfig: {
ajax: {
query: async ({ page }, { accountId }) => {
const finalAccountId = accountId ?? queryParams.accountId;
if (!finalAccountId) {
return { list: [], total: 0 };
}
return await getMaterialPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
accountId: finalAccountId,
type: 'voice',
});
},
},
},
rowConfig: {
keyField: 'mediaId',
isHover: true,
},
toolbarConfig: {
refresh: true,
},
} as VxeTableGridOptions<MpMaterialApi.Material>,
});
const [VideoGrid, videoGridApi] = useVbenVxeGrid({
gridOptions: {
border: true,
columns: videoGridColumns,
height: 'auto',
keepSource: true,
pagerConfig: {
enabled: true,
pageSize: 10,
},
proxyConfig: {
ajax: {
query: async ({ page }, { accountId }) => {
const finalAccountId = accountId ?? queryParams.accountId;
if (finalAccountId === undefined || finalAccountId === null) {
return { list: [], total: 0 };
}
return await getMaterialPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
accountId: finalAccountId,
type: 'video',
});
},
},
},
rowConfig: {
keyField: 'mediaId',
isHover: true,
},
toolbarConfig: {
refresh: true,
},
} as VxeTableGridOptions<MpMaterialApi.Material>,
});
function selectMaterialFun(item: any) { function selectMaterialFun(item: any) {
emit('selectMaterial', item); emit('selectMaterial', item);
} }
/** 获取分页数据 */
async function getPage() {
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;
}
}
/** 获取素材分页 */
async function getMaterialPageFun() { async function getMaterialPageFun() {
const data = await MpMaterialApi.getMaterialPage({ const data = await getMaterialPage({
...queryParams, ...queryParams,
type: props.type, type: props.type,
}); });
@@ -83,9 +206,8 @@ async function getMaterialPageFun() {
total.value = data.total; total.value = data.total;
} }
/** 获取已发布图文分页 */
async function getFreePublishPageFun() { async function getFreePublishPageFun() {
const data = await MpFreePublishApi.getFreePublishPage(queryParams); const data = await getFreePublishPage(queryParams);
data.list.forEach((item: any) => { data.list.forEach((item: any) => {
const articles = item.content.newsItem; const articles = item.content.newsItem;
articles.forEach((article: any) => { articles.forEach((article: any) => {
@@ -96,9 +218,8 @@ async function getFreePublishPageFun() {
total.value = data.total; total.value = data.total;
} }
/** 获取草稿图文分页 */
async function getDraftPageFun() { async function getDraftPageFun() {
const data = await MpDraftApi.getDraftPage(queryParams); const data = await getDraftPage(queryParams);
data.list.forEach((draft: any) => { data.list.forEach((draft: any) => {
const articles = draft.content.newsItem; const articles = draft.content.newsItem;
articles.forEach((article: any) => { articles.forEach((article: any) => {
@@ -109,9 +230,57 @@ async function getDraftPageFun() {
total.value = data.total; total.value = data.total;
} }
onMounted(async () => { async function getPage() {
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(
() => props.accountId,
(accountId) => {
queryParams.accountId = accountId;
queryParams.pageNo = 1;
getPage();
},
{ immediate: true },
);
watch(
() => props.type,
() => {
queryParams.pageNo = 1;
getPage();
},
);
watch(
() => props.newsType,
() => {
if (props.type === 'news') {
queryParams.pageNo = 1;
getPage();
}
},
);
</script> </script>
<template> <template>
@@ -152,90 +321,31 @@ onMounted(async () => {
</div> </div>
<!-- 类型voice --> <!-- 类型voice -->
<div v-else-if="props.type === 'voice'"> <div v-else-if="props.type === 'voice'">
<!-- 列表 --> <VoiceGrid>
<ElTable v-loading="loading" :data="list"> <template #voice="{ row }">
<ElTableColumn label="编号" align="center" prop="mediaId" /> <WxVoicePlayer :url="row.url" />
<ElTableColumn label="文件名" align="center" prop="name" /> </template>
<ElTableColumn label="语音" align="center"> <template #actions="{ row }">
<template #default="scope"> <ElButton type="primary" link @click="selectMaterialFun(row)">
<VoicePlayer :url="scope.row.url" /> 选择
</template> <IconifyIcon icon="lucide:plus" />
</ElTableColumn> </ElButton>
<ElTableColumn </template>
label="上传时间" </VoiceGrid>
align="center"
prop="createTime"
width="180"
:formatter="
(row: any) => formatTime(row.createTime, 'YYYY-MM-DD HH:mm:ss')
"
/>
<ElTableColumn label="操作" align="center" fixed="right">
<template #default="scope">
<ElButton type="primary" link @click="selectMaterialFun(scope.row)">
选择
<IconifyIcon icon="lucide:plus" />
</ElButton>
</template>
</ElTableColumn>
</ElTable>
<!-- 分页组件 -->
<ElPagination
background
layout="prev, pager, next, sizes, total"
:total="total"
v-model:current-page="queryParams.pageNo"
v-model:page-size="queryParams.pageSize"
@current-change="getPage"
@size-change="getPage"
/>
</div> </div>
<!-- 类型video --> <!-- 类型video -->
<div v-else-if="props.type === 'video'"> <div v-else-if="props.type === 'video'">
<!-- 列表 --> <VideoGrid>
<ElTable v-loading="loading" :data="list"> <template #video="{ row }">
<ElTableColumn label="编号" align="center" prop="mediaId" /> <WxVideoPlayer :url="row.url" />
<ElTableColumn label="文件名" align="center" prop="name" /> </template>
<ElTableColumn label="标题" align="center" prop="title" /> <template #actions="{ row }">
<ElTableColumn label="介绍" align="center" prop="introduction" /> <ElButton type="primary" link @click="selectMaterialFun(row)">
<ElTableColumn label="视频" align="center"> 选择
<template #default="scope"> <IconifyIcon icon="lucide:circle-plus" />
<VideoPlayer :url="scope.row.url" /> </ElButton>
</template> </template>
</ElTableColumn> </VideoGrid>
<ElTableColumn
label="上传时间"
align="center"
prop="createTime"
width="180"
:formatter="
(row: any) => formatTime(row.createTime, 'YYYY-MM-DD HH:mm:ss')
"
/>
<ElTableColumn
label="操作"
align="center"
fixed="right"
class-name="small-padding fixed-width"
>
<template #default="scope">
<ElButton type="primary" link @click="selectMaterialFun(scope.row)">
选择
<IconifyIcon icon="lucide:circle-plus" />
</ElButton>
</template>
</ElTableColumn>
</ElTable>
<!-- 分页组件 -->
<ElPagination
background
layout="prev, pager, next, sizes, total"
:total="total"
v-model:current-page="queryParams.pageNo"
v-model:page-size="queryParams.pageSize"
@current-change="getMaterialPageFun"
@size-change="getMaterialPageFun"
/>
</div> </div>
<!-- 类型news --> <!-- 类型news -->
<div v-else-if="props.type === 'news'"> <div v-else-if="props.type === 'news'">
@@ -249,7 +359,7 @@ onMounted(async () => {
:key="item.mediaId" :key="item.mediaId"
> >
<div v-if="item.content && item.content.newsItem"> <div v-if="item.content && item.content.newsItem">
<News :articles="item.content.newsItem" /> <WxNews :articles="item.content.newsItem" />
<ElRow class="flex justify-center pt-2.5"> <ElRow class="flex justify-center pt-2.5">
<ElButton type="success" @click="selectMaterialFun(item)"> <ElButton type="success" @click="selectMaterialFun(item)">
选择 选择

View File

@@ -14,7 +14,7 @@ defineOptions({ name: 'TabNews' });
const props = defineProps<{ const props = defineProps<{
modelValue: Reply; modelValue: Reply;
newsType: NewsType; newsType?: NewsType;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{

View File

@@ -4,6 +4,7 @@ import type { MpMaterialApi } from '#/api/mp/material';
import { watch } from 'vue'; import { watch } from 'vue';
import { $t } from '@vben/locales';
import { openWindow } from '@vben/utils'; import { openWindow } from '@vben/utils';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';

View File

@@ -24,8 +24,8 @@ import {
// VxeOptgroup, // VxeOptgroup,
// VxeOption, // VxeOption,
// VxePulldown, // VxePulldown,
// VxeRadio, VxeRadio,
// VxeRadioButton, VxeRadioButton,
VxeRadioGroup, VxeRadioGroup,
VxeSelect, VxeSelect,
VxeTooltip, VxeTooltip,
@@ -88,8 +88,8 @@ export function initVxeTable() {
// VxeUI.component(VxeOption); // VxeUI.component(VxeOption);
VxeUI.component(VxePager); VxeUI.component(VxePager);
// VxeUI.component(VxePulldown); // VxeUI.component(VxePulldown);
// VxeUI.component(VxeRadio); VxeUI.component(VxeRadio);
// VxeUI.component(VxeRadioButton); VxeUI.component(VxeRadioButton);
VxeUI.component(VxeRadioGroup); VxeUI.component(VxeRadioGroup);
VxeUI.component(VxeSelect); VxeUI.component(VxeSelect);
// VxeUI.component(VxeSwitch); // VxeUI.component(VxeSwitch);

View File

@@ -36,7 +36,9 @@
"downloadTemplateFail": "Download template failed", "downloadTemplateFail": "Download template failed",
"updating": "Updating {0}...", "updating": "Updating {0}...",
"updateSuccess": "Update {0} successfully", "updateSuccess": "Update {0} successfully",
"updateFailed": "Update {0} failed" "updateFailed": "Update {0} failed",
"closing": "Closing {0} ...",
"closeSuccess": "{0} closed successfully"
}, },
"placeholder": { "placeholder": {
"input": "Please enter", "input": "Please enter",

View File

@@ -36,7 +36,9 @@
"downloadTemplateFail": "下载模板失败", "downloadTemplateFail": "下载模板失败",
"updating": "正在更新 {0}...", "updating": "正在更新 {0}...",
"updateSuccess": "更新 {0} 成功", "updateSuccess": "更新 {0} 成功",
"updateFailed": "更新 {0} 失败" "updateFailed": "更新 {0} 失败",
"closing": "正在关闭 {0} ...",
"closeSuccess": "{0} 关闭成功"
}, },
"placeholder": { "placeholder": {
"input": "请输入", "input": "请输入",