Преглед изворни кода

📦 对接话题修改接口

快乐的梦鱼 пре 11 часа
родитељ
комит
919b181738

+ 92 - 0
src/api/light/OfficialApi.ts

@@ -66,6 +66,39 @@ export class TopicItem extends DataModel<TopicItem> {
   readCountInWxa = 0;
 }
 
+export class WechatTopicItem extends DataModel<WechatTopicItem> {
+  constructor() {
+    super(WechatTopicItem, '贴图话题');
+    this.setNameMapperCase('Camel', 'Snake');
+    this._convertTable = {
+      id: { clientSide: 'number', serverSide: 'number', clientSideRequired: true },
+      villageId: { clientSide: 'number', serverSide: 'number' },
+      status: { clientSide: 'number', serverSide: 'number' },
+      weigh: { clientSide: 'number', serverSide: 'number' },
+    };
+  }
+
+  id!: number;
+  /** 村落ID: 0=通用 */
+  villageId = 0;
+  /** 名称 */
+  name = '';
+  /** 图片 */
+  image = '';
+  /** 状态: 0=禁用, 1=启用 */
+  status = 0;
+  /** 权重 */
+  weigh = 0;
+  /** 创建时间 */
+  createtime = '';
+  /** 更新时间 */
+  updatetime = '';
+  /** 删除时间 */
+  deletetime = '';
+  /** 状态文本 */
+  statusText = '';
+}
+
 export class TopicListSummary extends DataModel<TopicListSummary> {
   constructor() {
     super(TopicListSummary, '话题汇总');
@@ -424,6 +457,65 @@ export class OfficialApi extends AppServerRequestModule<DataModel> {
   }
 
   /**
+   * 获取贴图话题列表
+   */
+  async getWechatTopicList(params: {
+    villageId?: number;
+    status?: number;
+    page?: number;
+    pageSize?: number;
+    keywords?: string;
+  }) {
+    const res = await this.post<{
+      data: WechatTopicItem[],
+      total: number,
+      per_page: number,
+      current_page: number,
+      last_page: number,
+    }>('/village/topic/wechatTopicList', '获取贴图话题列表', {
+      village_id: params.villageId,
+      status: params.status,
+      page: params.page,
+      pageSize: params.pageSize,
+      keywords: params.keywords,
+    });
+    const data = res.requireData();
+    return {
+      list: transformArrayDataModel<WechatTopicItem>(WechatTopicItem, transformSomeToArray(data.data), '贴图话题列表', true),
+      total: data.total,
+    };
+  }
+
+  /**
+   * 新增/编辑贴图话题
+   * @param params.id 修改时必填
+   */
+  async saveWechatTopic(params: {
+    id?: number;
+    villageId: number;
+    name: string;
+    image?: string;
+    status?: number;
+    weigh?: number;
+  }) {
+    return (await this.post<KeyValue>('/village/topic/saveTopic', '保存贴图话题', {
+      id: params.id,
+      village_id: params.villageId,
+      name: params.name,
+      image: params.image,
+      status: params.status,
+      weigh: params.weigh,
+    })).requireData();
+  }
+
+  /**
+   * 删除贴图话题
+   */
+  async delWechatTopic(id: number) {
+    await this.post<KeyValue>('/village/topic/delTopic', '删除贴图话题', { id });
+  }
+
+  /**
    * 获取个人升级套餐列表
    */
   async getStaffList(page: number, pageSize: number) {

+ 2 - 1
src/components/basic/ImageButton.vue

@@ -21,7 +21,8 @@ import Touchable from '../feedback/Touchable.vue';
 import FlexCol from '../layout/FlexCol.vue';
 import type { ImageProps } from './Image.vue';
 import Image from './Image.vue';
-import Text, { type TextProps } from './Text.vue';
+import Text from './Text.vue';
+import type { TextProps } from './Text';
 
 const props = withDefaults(defineProps<ImageProps & {
   activeOpacity?: number;

+ 2 - 1
src/components/display/block/ImageBlock.vue

@@ -57,7 +57,8 @@ import BackgroundBox from './BackgroundBox.vue';
 import VideoMark from '../../images/icons/video-mark.png';
 import Touchable from '@/components/feedback/Touchable.vue';
 import Image from '@/components/basic/Image.vue';
-import Text, { type TextProps } from '@/components/basic/Text.vue';
+import Text from '@/components/basic/Text.vue';
+import type { TextProps } from '@/components/basic/Text';
 import type { PropType } from 'vue';
 
 const theme = useTheme();

+ 1 - 1
src/components/display/block/ImageBlock2.vue

@@ -54,7 +54,7 @@ import Touchable from '@/components/feedback/Touchable.vue';
 import type { FlexProps } from '../../layout/FlexView.vue';
 import IconTextBlock from './IconTextBlock.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
-import type { TextProps } from '@/components/basic/Text.vue';
+import type { TextProps } from '@/components/basic/Text';
 
 export interface ImageBlock2Props extends Partial<FlexProps> {
   /**

+ 3 - 2
src/components/display/block/TextLeftRightBlock.vue

@@ -10,9 +10,10 @@ export default {}
 </script>
 
 <script setup lang="ts">
-import Text, { type TextProps } from '../../basic/Text.vue';
-import type { PropType } from 'vue';
+import Text from '../../basic/Text.vue';
 import Touchable from '@/components/feedback/Touchable.vue';
+import type { PropType } from 'vue';
+import type { TextProps } from '@/components/basic/Text';
 
 defineEmits([ 'click' ])
 defineProps({	

+ 5 - 5
src/components/dynamic/x/DynamicXNodeChild.vue

@@ -21,28 +21,28 @@
     v-for="(childNode, index) in node.nodes" :key="childNode.name || index"
     :node="childNode"
   >
-    <template #nodeRender="{ node }"><slot name="nodeRender" :node="node" /></template>
+    <template #nodeRender="{ node }"><slot v-if="node.visible!==false" name="nodeRender" :node="node" /></template>
     <block v-for="(childNode2, index) in childNode.nodes" :key="childNode2.name || index">
       <template #[`renderChild-${index}`]><!-- 傻xUniapp会自动加上插槽后缀,此处适应其写法 -->
       
         <!-- Level 1 -->
         <DynamicXNodeComponent :node="childNode2">
-          <template #nodeRender="{ node }"><slot name="nodeRender" :node="node" /></template>
+          <template #nodeRender="{ node }"><slot v-if="node.visible!==false" name="nodeRender" :node="node" /></template>
           <block v-for="(childNode3, index) in childNode2.nodes" :key="childNode3.name || index">
             <template #[`renderChild-${index}`]>
               <!-- Level 2 -->
               <DynamicXNodeComponent :node="childNode3">
-                <template #nodeRender="{ node }"><slot name="nodeRender" :node="node" /></template>
+                <template #nodeRender="{ node }"><slot v-if="node.visible!==false" name="nodeRender" :node="node" /></template>
                 <block v-for="(childNode4, index) in childNode3.nodes" :key="childNode3.name || index">
                   <template #[`renderChild-${index}`]>
                     <!-- Level 3 -->
                     <DynamicXNodeComponent :node="childNode4">
-                      <template #nodeRender="{ node }"><slot name="nodeRender" :node="node" /></template>
+                      <template #nodeRender="{ node }"><slot v-if="node.visible!==false" name="nodeRender" :node="node" /></template>
                       <block v-for="(childNode5, index) in childNode4.nodes" :key="childNode5.name || index">
                         <template #[`renderChild-${index}`]>
                           <!-- Level 4 -->
                           <DynamicXNodeComponent :node="childNode5">
-                            <template #nodeRender="{ node }"><slot name="nodeRender" :node="node" /></template>
+                            <template #nodeRender="{ node }"><slot v-if="node.visible!==false" name="nodeRender" :node="node" /></template>
 
 
 

+ 1 - 0
src/env.d.ts

@@ -2,6 +2,7 @@
 
 declare module 'cax';
 declare module 'pinyinjs';
+declare module 'crypto-js';
 
 declare module '*.vue' {
   import { DefineComponent } from 'vue'

+ 8 - 3
src/pages/home/village/introd/card.vue

@@ -137,7 +137,7 @@
                 <FlexRow align="center" gap="gap.md">
                   <IconButton icon="help-filling" @click="navTo('/pages/article/details', { id: 7021, modelId: 18, showRecommend: false })" />
                   <Button 
-                    v-if="(villageInfoLoader.content.value?.vipLevel || 0) < 4" 
+                    v-if="!villageInfoLoader.content.value?.vipLevelIsTop" 
                     icon="https://xy.wenlvti.net/app_static/images/village/IconUser.png" 
                     radius="radius.lgr" 
                     :padding="[10, 30]" 
@@ -434,6 +434,7 @@ const villageInfoLoader = useSimpleDataLoader(async () => {
     sizeText: `${FormatUtils.formatSize(village?.storageUsed || 0)}/${FormatUtils.formatSize(village?.storageLimit || 0)}`,
     sizeLimit: (village?.storageLimit || 0) >= 0,
     level: village?.level || 1,
+    vipLevelIsTop: (village?.vipLevel || 0) >= 4, //是否是顶级政务版(政贤连心)
     vipLevel: village?.vipLevel || 0,
     vipLevelText: village?.vipName ? `已升级 · ${village?.vipName}` : '',
     rankText: village?.rank.toString() || '',
@@ -522,8 +523,11 @@ const staffInfoRef = ref<InstanceType<typeof StaffInfoDialog>>();
 
 async function checkIsStaff(needTopic?: boolean) {
   assertNotNull(villageStore.currentVillage?.id)
-  if (isDevEnv)
+  if (isDevEnv 
+    || villageInfoLoader.content.value?.vipLevelIsTop //要求:顶级政务版(政贤连心,4级)不需要升级
+  )
     return true;
+
   const isAdmin = needTopic ? 
     await OfficialApi.checkTopicRule(villageStore.currentVillage.id)
     : (authStore.isAdmin || isStaff.value);
@@ -657,8 +661,9 @@ defineExpose({
       } else if (name === 'backRefreshSkin') {
         await villageStore.reloadVillageInfo();
         await pageThemeLoader.load();
+      } else if (name === 'backRefreshTags') {
         await tagsLoader.load();  
-      }      
+      }       
     }, 1500);
   },
 });

+ 85 - 17
src/pages/home/village/introd/config/config-tags.vue

@@ -45,17 +45,49 @@ import Dialog from '@/components/dialog/Dialog.vue';
 import Field from '@/components/form/Field.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
 import FlexRow from '@/components/layout/FlexRow.vue';
-import { alert } from '@/components/dialog/CommonRoot';
-import { back } from '@/components/utils/PageAction';
-import { onMounted, ref } from 'vue';
+import { back, callTopOnPageBack } from '@/components/utils/PageAction';
+import { ref } from 'vue';
 import { defaultTags } from '../data/DefaultTag';
 import DragSortList from '@/common/components/DragSortList.vue';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
+import OfficialApi, { WechatTopicItem } from '@/api/light/OfficialApi';
+import { toast, loading, hideLoading } from '@/components/utils/DialogAction';
 
-const tags = ref<{
+interface TagItem {
+  id?: number;
   title: string;
   icon: string;
   disabled: boolean;
-}[]>([]);
+}
+
+const tags = ref<TagItem[]>([]);
+const originalTags = ref<WechatTopicItem[]>([]);
+
+const loadTags = async () => {
+  try {
+    const { list } = await OfficialApi.getWechatTopicList({
+      villageId: querys.value.villageId,
+    });
+    originalTags.value = list;
+    tags.value = list.length > 0
+      ? list.map(item => ({
+          id: item.id,
+          title: item.name,
+          icon: item.image,
+          disabled: false,
+        }))
+      : defaultTags.map(title => ({ title, icon: '', disabled: false }));
+  } catch (e) {
+    toast('加载话题列表失败');
+    tags.value = defaultTags.map(title => ({ title, icon: '', disabled: false }));
+  }
+};
+
+const { querys } = useLoadQuerys({
+  villageId: 0,
+}, async () => {
+  await loadTags();
+});
 
 const addTagVisible = ref(false);
 const addTagTitle = ref('');
@@ -76,21 +108,57 @@ const handleAddTag = () => {
   addTagVisible.value = true;
 }
 
-const handleSave = () => {
-  //TODO: saveTags();
-  alert({ content: '缺少接口,敬请期待' })
+const handleSave = async () => {
+  const villageId = querys.value.villageId;
+  const tasks: Promise<unknown>[] = [];
+
+  // 删除:原列表中存在但当前列表已移除的
+  for (const original of originalTags.value) {
+    if (!tags.value.some(tag => tag.id === original.id)) {
+      tasks.push(OfficialApi.delWechatTopic(original.id));
+    }
+  }
+
+  // 保存:新增 或 名称/顺序发生变化的
+  tags.value.forEach((tag, index) => {
+    if (tag.id == null) {
+      tasks.push(OfficialApi.saveWechatTopic({
+        villageId,
+        name: tag.title,
+        weigh: index,
+        status: 1,
+      }));
+    } else {
+      const original = originalTags.value.find(item => item.id === tag.id);
+      if (original && (original.name !== tag.title || original.weigh !== index)) {
+        tasks.push(OfficialApi.saveWechatTopic({
+          id: tag.id,
+          villageId,
+          name: tag.title,
+          weigh: index,
+          status: original.status,
+        }));
+      }
+    }
+  });
+
+  if (tasks.length > 0) {
+    loading('保存中');
+    try {
+      await Promise.all(tasks);
+      toast('保存成功');
+    } catch (e) {
+      toast('保存失败');
+      return;
+    } finally {
+      hideLoading();
+    }
+  }
+
+  await callTopOnPageBack('backRefreshTags', {});
 }
 
 const handleCancel = () => {
   back();
 }
-
-onMounted(() => {
-  //TODO: getTags(); 如果为空则使用默认话题列表 defaultTags
-  tags.value = defaultTags.map(item => ({
-    title: item,
-    icon: '',
-    disabled: false,
-  }));
-})
 </script>

+ 2 - 2
src/pages/home/village/introd/data/DarkCard.json

@@ -110,7 +110,7 @@
         },
         {
           "name": "VILLAGE:CONTENT:Collect",
-          "type": "Block:GridButtonBlock4",
+          "type": "Block:GridButtonBlock",
           "props": {
             "title": "魅力乡源",
             "titleRightButtons": [
@@ -173,7 +173,7 @@
         },
         {
           "name": "VILLAGE:CONTENT:Games",
-          "type": "Block:GridButtonBlock4",
+          "type": "Block:GridButtonBlock",
           "props": {
             "title": "活力乡源",
             "items": [

+ 3 - 1
src/pages/home/village/introd/data/TestCard.json

@@ -153,7 +153,7 @@
                               "id": 1,
                               "title": "口述纪实馆",
                               "image": "https://xy.wenlvti.net/app_static/images/village/speical/IconSpeackerStore.png",
-                              "tags": [ "登录" ],
+                              "tags": [ "进入" ],
                               "props": {
                                 "width": "170rpx",
                                 "height": "220rpx",
@@ -211,6 +211,7 @@
             {
               "name": "VILLAGE:CONTENT:Collect",
               "type": "Block:GridButtonBlock",
+              "visible": false,
               "props": {
                 "title": "魅力乡源",
                 "titleRightButtons": [
@@ -276,6 +277,7 @@
             {
               "name": "VILLAGE:CONTENT:Games",
               "type": "Block:GridButtonBlock",
+              "visible": false,
               "props": {
                 "title": "活力乡源",
                 "items": [