Bläddra i källkod

📦 按要求修改新接口

快乐的梦鱼 2 månader sedan
förälder
incheckning
96a4eacf33

+ 20 - 1
src/App.vue

@@ -31,16 +31,35 @@ import zhCN from 'ant-design-vue/es/locale/zh_CN';
 import { useRedirectLoginPage } from './common/LoginPageRedirect';
 import FooterSmall from './components/FooterSmall.vue';
 import Colors from './assets/scss/vueexp.module.scss';
+import VillageApi from './api/inhert/VillageApi';
+import { useCollectStore } from './stores/collect';
 
 const authStore = useAuthStore();
+const collectStore = useCollectStore();
 const route = useRoute();
 const { checkAndRedirectLoginPage } = useRedirectLoginPage();
 
 onMounted(async () => {
   await authStore.loadLoginState(route);
-  checkAndRedirectLoginPage();
+  if (!checkAndRedirectLoginPage()) {
+    await loadVolunteerInfo();
+  }
 });
 
+async function loadVolunteerInfo() {
+  const res = await VillageApi.getVolunteerInfo();
+  const collectableModules = (res.collectModule as string)?.split(',') || [];
+  const collectableModulesMap = await VillageApi.getCollectModuleMap();
+  const needRemoveKeys = new Set<string>();
+  if (!authStore.isAdmin) {
+    for (const [key,id] of collectableModulesMap)
+      if (!collectableModules.includes(key))
+        needRemoveKeys.add(key);
+  }
+  for (const key of needRemoveKeys)
+    collectableModulesMap.delete(key);
+  collectStore.setCollectableModules(collectableModulesMap);
+}
 
 watch(route, () => {
   window.scrollTo({

+ 14 - 0
src/api/inhert/VillageApi.ts

@@ -170,6 +170,20 @@ export class VillageApi extends AppServerRequestModule<DataModel> {
     return (await this.post('/village/volunteer/getInfo', {
     }, '获取志愿者信息', undefined, VolunteerInfo)).data as VolunteerInfo
   }
+  async getCollectModuleMap() {
+    const res = (await this.post('/village/volunteer/getCollectModuleList', {}, '采集板块列表'))
+    const map = new Map<string, number>();
+    if (!res.data2 || typeof res.data2 !== 'object') 
+      return map;
+    for (const key in res.data2) {
+      if (typeof res.data2[key] !== 'string')
+        throw new Error(`采集板块列表键值对值不是字符串: ${key} -> ${res.data2[key]}`);
+      if (isNaN(Number(key)))
+        throw new Error(`采集板块列表键值对键不是数字: ${key}`);
+      map.set(res.data2[key], Number(key));
+    }
+    return map;
+  }
   async getVolunteerRanklist(category?: number) {
     return (this.post('/village/volunteer/getRanklist', {
       category,

+ 43 - 20
src/api/inhert/VillageInfoApi.ts

@@ -1,4 +1,4 @@
-import { DataModel, transformArrayDataModel, type NewDataModel } from '@imengyu/js-request-transform';
+import { DataModel, transformArrayDataModel } from '@imengyu/js-request-transform';
 import { AppServerRequestModule } from '../RequestModules';
 import CommonContent from '../CommonContent';
 
@@ -48,7 +48,6 @@ export class CommonInfoModel extends DataModel<CommonInfoModel> {
   }
   id !: number;
   cityAddress?: string[];
-
 }
 
 export class VillageEnvInfo extends DataModel<VillageEnvInfo> {
@@ -172,29 +171,40 @@ export class VillageInfoApi extends AppServerRequestModule<DataModel> {
   }
 
   async getInfo<T extends DataModel>(
-    sub: string,
-    subId: number,
+    collectModuleId: number|undefined,
+    subType: string,
+    subId: number|undefined,
+    subKey: string|undefined,
     villageId: number,
     villageVolunteerId: number,
-    id?: number,
+    id?: number, 
     modelClassCreator: (new () => T) = CommonInfoModel as any
   ) {
-    return (await this.post(`/village/${sub}/getInfo`, {
-      type: subId,
-      village_id: villageId,
-      village_volunteer_id: villageVolunteerId,
-      id,
-    }, '获取信息详情', undefined, modelClassCreator)).data as T
+    if (subType === 'overview') {
+      return (await this.post(`/village/${subType}/getInfo`, {
+        type: subId,
+        village_id: villageId,
+        village_volunteer_id: villageVolunteerId,
+        id,
+      }, '获取村落概况', undefined, modelClassCreator)).data as T
+    } else {
+      return (await this.post(`/village/collect/info`, {
+        collect_module_id: collectModuleId,
+        id,
+      }, '通用获取信息详情', undefined, modelClassCreator)).data as T
+    }
   }
   async getList<T extends DataModel = VillageListItem>(
-    sub: string,
+    collectModuleId: number|undefined,
+    subType: string,
     subId: number|undefined,
     subKey: string|undefined,
     villageId: number,
     villageVolunteerId: number,
     modelClassCreator: (new () => T) = VillageListItem as any 
   ) {
-    return (this.post(`/village/${sub}/getList`, {
+    return (this.post(`/village/collect/list`, {
+      collect_module_id: collectModuleId,
       [subKey ? subKey : 'type']: subId,
       village_id: villageId,
       village_volunteer_id: villageVolunteerId,
@@ -203,17 +213,30 @@ export class VillageInfoApi extends AppServerRequestModule<DataModel> {
       .catch(e => { throw e });
   }
   async updateInfo<T extends DataModel>(
-    sub: string,
+    collectModuleId: number|undefined,
+    subType: string,
+    subKey: string,
+    subId: number,
     villageId: number,
     villageVolunteerId: number,
     data: T,
   ) {
-    return (await this.post(`/village/${sub}/save`, {
-      sub,
-      village_id: villageId,
-      village_volunteer_id: villageVolunteerId,
-      ...data.toServerSide(),
-    }, '更新信息详情'));
+    if (subType === 'overview') {
+      return (await this.post(`/village/${subType}/save`, {
+        subType,
+        village_id: villageId,
+        village_volunteer_id: villageVolunteerId,
+        ...data.toServerSide(),
+      }, '更新信息详情'));
+    } else {
+      return (await this.post(`/village/collect/save`, {
+        collect_module_id: collectModuleId,
+        [subKey||'type']: subId,
+        village_id: villageId,
+        village_volunteer_id: villageVolunteerId,
+        ...data.toServerSide(),
+      }, '通用更新信息详情'));
+    }
   }
 }
 

+ 0 - 48
src/assets/scss/main.scss

@@ -301,48 +301,6 @@ $small-banner-height: 445px;
 
 //Card box
 
-.task-list {
-  display: flex;
-  flex-direction: column;
-
-  .item {
-    margin-top: 20px;
-    display: flex;
-    flex-direction: row;
-    align-items: center;
-    padding: 20px;
-    background-color: $box-color;
-    border-radius: 10px;
-    cursor: pointer;
-
-    &:hover {
-      background-color: $box-hover-color;
-    }
-
-    .info {
-      flex: 1;
-      font-size: 20px;
-      margin: 0 20px;
-
-      .desc {
-        font-size: 15px;
-        color: $text-content-color;
-      }
-    }
-
-    .iconfont {
-      width: 50px;
-      height: 50px;
-      border-radius: 50%;
-      border: 1px solid $primary-color;
-      text-align: center;
-      color: $primary-color;
-      font-size: 30px;
-      line-height: 50px;
-      display: inline-block;
-    }
-  }
-}
 
 @media (max-width: 1280px) {
   .main-section {
@@ -398,9 +356,6 @@ $small-banner-height: 445px;
   .form-box {
     padding: 40px;
   }
-  .task-list .item {
-    padding: 15px;
-  }
 }
 @media (max-width: 425px) {
   .main-section {
@@ -453,7 +408,4 @@ $small-banner-height: 445px;
     background-color: transparent;
     box-shadow: none;
   }
-  .task-list .item {
-    padding: 10px;
-  }
 }

+ 2 - 0
src/common/LoginPageRedirect.ts

@@ -12,7 +12,9 @@ export function useRedirectLoginPage() {
     checkAndRedirectLoginPage() {
       if (!authStore.isLogged && !noLoginPages.includes(route.path)) {
         router.replace(loginPage);
+        return true;
       }
+      return false;
     },
   }
 }

+ 39 - 0
src/pages/components/TaskItem.vue

@@ -0,0 +1,39 @@
+<template>
+  <div class="item">
+    <i :class="'iconfont ' + icon"></i>
+    <div class="info">
+      <div class="title">{{title}}</div>
+      <div class="desc">{{desc}}</div>
+    </div>
+    <a-button 
+      type="primary" 
+      :disabled="!enabled" 
+      :title="enabled ? '去编写内容吧' : '当前栏目未开放,请联系管理员认领可采编栏目'"
+      @click="emit('click')"
+    >
+      {{enabled ? '采编' : '待开放'}}
+    </a-button>
+  </div>
+</template>
+
+<script setup lang="ts">
+const emit = defineEmits(['click']);
+const props = defineProps({
+  title: {
+    type: String,
+    default: ''
+  },
+  desc: {
+    type: String,
+    default: ''
+  },
+  icon: {
+    type: String,
+    default: ''
+  },
+  enabled: {
+    type: Boolean,
+    default: false
+  }
+});
+</script>

+ 62 - 0
src/pages/components/TaskList.vue

@@ -0,0 +1,62 @@
+<template>
+  <div class="task-list">
+    <slot />
+  </div>
+</template>
+
+<style lang="scss">
+@use '@/assets/scss/colors.scss' as *;
+
+.task-list {
+  display: flex;
+  flex-direction: column;
+
+  .item {
+    margin-top: 20px;
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    padding: 20px;
+    background-color: $box-color;
+    border-radius: 10px;
+    cursor: pointer;
+
+    &:hover {
+      background-color: $box-hover-color;
+    }
+
+    .info {
+      flex: 1;
+      font-size: 20px;
+      margin: 0 20px;
+
+      .desc {
+        font-size: 15px;
+        color: $text-content-color;
+      }
+    }
+
+    .iconfont {
+      width: 50px;
+      height: 50px;
+      border-radius: 50%;
+      border: 1px solid $primary-color;
+      text-align: center;
+      color: $primary-color;
+      font-size: 30px;
+      line-height: 50px;
+      display: inline-block;
+    }
+  }
+}
+@media (max-width: 768px) {
+  .task-list .item {
+    padding: 15px;
+  }
+}
+@media (max-width: 500px) {
+  .task-list .item {
+    padding: 10px;
+  }
+}
+</style>

+ 2 - 2
src/pages/composeable/TaskEntryForm.ts

@@ -11,7 +11,7 @@ export function useTaskEntryForm() {
     router.push({
       path: '../forms/' + type, 
       query: {
-        id: type === 'common' ? 1 : undefined,
+        //id: type === 'common' ? 1 : undefined,
         villageId: querys.value.villageId,  
         villageVolunteerId: querys.value.villageVolunteerId,  
         subType,
@@ -31,7 +31,7 @@ export function goFormStatic(villageId: number, villageVolunteerId: number, subT
   router.push({
     path: '/forms/' + type, 
     query: {
-      id: type === 'common' ? 1 : undefined,
+      //id: type === 'common' ? 1 : undefined,
       villageId,  
       villageVolunteerId,  
       subType,

+ 85 - 108
src/pages/details.vue

@@ -27,113 +27,87 @@
           </div>
         </div>
 
-        <div class="task-list">
-          <div v-if="canCollect('village')" class="item">
-            <i class="iconfont icon-task-summary"></i>
-            <div class="info">
-              <div class="title">村落概况</div>
-              <div class="desc">探索村落的历史渊源与发生轨迹</div>
-            </div>
-            <a-button type="primary" @click="navTo('task/summary', nextPageData)">去完成</a-button>
-          </div>
-          <div v-if="canCollect('cultural')" class="item">
-            <i class="iconfont icon-task-history"></i>
-            <div class="info">
-              <div class="title">历史文化</div>
-              <div class="desc">传承百年文化遗产和精神财富</div>
-            </div>
-            <a-button type="primary" @click="navTo('task/history', nextPageData)">
-              去完成
-            </a-button>
-          </div>
-          <div v-if="canCollect('ich')" class="item">
-            <i class="iconfont icon-task-custom-1"></i>
-            <div class="info">
-              <div class="title">非物质文化遗产项目</div>
-              <div class="desc">维护文化多样性</div>
-            </div>
-            <a-button type="primary" @click="goForm('ich', 0)">
-              去填写
-            </a-button>
-          </div>
-          <div v-if="canCollect('environment')" class="item">
-            <i class="iconfont icon-task-environment"></i>
-            <div class="info">
-              <div class="title">环境格局</div>
-              <div class="desc">感受自然人文环境之美</div>
-            </div>
-            <a-button type="primary" @click="navTo('task/environment', nextPageData)">
-              去完成
-            </a-button>
-          </div>
-          <div v-if="canCollect('building')" class="item">
-            <i class="iconfont icon-task-building"></i>
-            <div class="info">
-              <div class="title">传统建筑</div>
-              <div class="desc">领略古建筑的独特魅力</div>
-            </div>
-            <a-button type="primary" @click="navTo('task/building', nextPageData)">
-              去完成
-            </a-button>
-          </div>
-          <div v-if="canCollect('folk_culture')" class="item">
-            <i class="iconfont icon-task-custom"></i>
-            <div class="info">
-              <div class="title">民俗文化</div>
-              <div class="desc">体验民间传统习俗与节庆</div>
-            </div>
-            <a-button type="primary" @click="navTo('task/custom', nextPageData)">
-              去完成
-            </a-button>
-          </div>
-          <div v-if="canCollect('food_product')" class="item">
-            <i class="iconfont icon-task-food"></i>
-            <div class="info">
-              <div class="title">美食物产</div>
-              <div class="desc">正宗、传统地方特色美食</div>
-            </div>
-            <a-button type="primary" @click="navTo('task/food', nextPageData)">
-              去完成
-            </a-button>
-          </div>
-          <div v-if="canCollect('food_product')" class="item">
-            <i class="iconfont icon-task-mine"></i>
-            <div class="info">
-              <div class="title">物产资源</div>
-              <div class="desc">特定地域的植物、矿物或手工艺</div>
-            </div>
-            <a-button type="primary" @click="navTo('task/mine', nextPageData)">
-              去完成
-            </a-button>
-          </div>
-          <div v-if="canCollect('route')" class="item">
-            <i class="iconfont icon-task-trip"></i>
-            <div class="info">
-              <div class="title">旅游路线</div>
-              <div class="desc">体验独特的文化魅力</div>
-            </div>
-            <a-button type="primary" @click="navTo('task/trip', nextPageData)">
-              去完成
-            </a-button>
-          </div>
-          <div v-if="!isAdmin && isEmpty" class="item">
-            <i class="iconfont icon-task-summary"></i>
-            <div class="info">
-              <div class="title">您当前没有可完成的任务</div>
-              <div class="desc">请联系管理员认领可采编栏目</div>
-            </div>
-          </div>
-          <!-- <div class="item">
-            <i class="iconfont icon-task-other"></i>
-            <div class="info">
-              <div class="title">其他</div>
-              <div class="desc">更多文化传承相关信息</div>
-            </div>
-            <a-button type="primary" disabled @click="navTo('task/other', nextPageData)">
-              待开放
-            </a-button>
-          </div> -->
-        </div>
+        <TaskList>
+          <Alert
+            v-if="!isAdmin && isEmpty" 
+            class="mt-3"
+            type="warning"
+            showIcon
+            message="您当前没有可完成的任务, 请联系管理员认领可采编栏目" 
+          />
+
+          <TaskItem 
+            title="随手记" 
+            desc="写随手记,记录下村庄文化发现和思考,自动分类。也可点击下方进入指定的分类采集信息" 
+            icon="icon-task-summary" 
+            :enabled="canCollect('collect')"
+            @click="goForm('collect', 1)"
+          />
+
+          <TaskItem 
+            title="村落概况" 
+            desc="探索村落的历史渊源与发生轨迹" 
+            icon="icon-task-summary" 
+            :enabled="canCollect('village')"
+            @click="navTo('task/summary', nextPageData)"
+          />
+          <TaskItem 
+            title="历史文化" 
+            desc="传承百年文化遗产和精神财富" 
+            icon="icon-task-history" 
+            :enabled="canCollect('cultural')"
+            @click="navTo('task/history', nextPageData)"
+          />
+          <TaskItem 
+            title="非物质文化遗产项目" 
+            desc="维护文化多样性" 
+            icon="icon-task-custom-1" 
+            :enabled="canCollect('ich')"
+            @click="goForm('ich', 0)"
+          />
+          <TaskItem 
+            title="环境格局" 
+            desc="感受自然人文环境之美" 
+            icon="icon-task-environment" 
+            :enabled="canCollect('environment')"
+            @click="navTo('task/environment', nextPageData)"
+          />
+          <TaskItem 
+            title="传统建筑" 
+            desc="领略古建筑的独特魅力" 
+            icon="icon-task-building" 
+            :enabled="canCollect('building')"
+            @click="navTo('task/building', nextPageData)"
+          />
+          <TaskItem 
+            title="民俗文化" 
+            desc="体验民间传统习俗与节庆" 
+            icon="icon-task-custom" 
+            :enabled="canCollect('folk_culture')"
+            @click="navTo('task/custom', nextPageData)"
+          />
+          <TaskItem 
+            title="美食物产" 
+            desc="正宗、传统地方特色美食" 
+            icon="icon-task-food" 
+            :enabled="canCollect('food_product')"
+            @click="navTo('task/food', nextPageData)"
+          />
+          <TaskItem 
+            title="物产资源" 
+            desc="特定地域的植物、矿物或手工艺" 
+            icon="icon-task-mine" 
+            :enabled="canCollect('food_product')"
+            @click="navTo('task/mine', nextPageData)"
+          />
+          <TaskItem 
+            title="旅游路线" 
+            desc="体验独特的文化魅力" 
+            icon="icon-task-trip" 
+            :enabled="canCollect('route')"
+            @click="navTo('task/trip', nextPageData)"
+          />
+        </TaskList>
 
       </div>
     </section>
@@ -148,6 +122,9 @@ import { ArrowLeftOutlined } from '@ant-design/icons-vue';
 import { useCollectStore } from '@/stores/collect';
 import { useTaskEntryForm } from './composeable/TaskEntryForm';
 import { useAuthStore } from '@/stores/auth';
+import TaskList from './components/TaskList.vue';
+import TaskItem from './components/TaskItem.vue';
+import { Alert } from 'ant-design-vue';
 
 const authStore = useAuthStore();
 const router = useRouter();
@@ -182,4 +159,4 @@ function navTo(path: string, data: any) {
   object-fit: cover;
 }
 
-</style>
+</style>

+ 12 - 3
src/pages/forms/common.vue

@@ -51,6 +51,7 @@ import { DynamicForm, type IDynamicFormOptions, type IDynamicFormRef } from '@im
 import type { FormInstance } from 'ant-design-vue/es/form/Form';
 import { ArrowLeftOutlined } from '@ant-design/icons-vue';
 import VillageInfoApi from '@/api/inhert/VillageInfoApi';
+import { useCollectStore } from '@/stores/collect';
 
 const loading = ref(false);
 const formRef = ref<IDynamicFormRef>();
@@ -60,6 +61,7 @@ const {
   backAndCallOnPageBack,
   back,
 } = usePageAction();
+const { getCollectModuleId } = useCollectStore();
 
 async function submit() {
   if (!formRef.value)
@@ -77,11 +79,13 @@ async function submit() {
     }
 
     loading.value = true;
-    formModel.value.type = querys.value.subId;
     console.log('Submit form ', formModel.value);
     
     const result = await VillageInfoApi.updateInfo(
+      getCollectModuleId(querys.value.subType),
       querys.value.subType,
+      querys.value.subKey,
+      querys.value.subId,
       querys.value.villageId,
       querys.value.villageVolunteerId,
       formModel.value,
@@ -104,7 +108,6 @@ function backPrev(needRefresh: boolean) {
     needRefresh,
   });
 }
-
 function handleBack() {
   Modal.confirm({
     title: '如果有修改请先提交,未保存的修改将丢失,您确认返回上一页吗?',
@@ -120,21 +123,28 @@ const { querys } = useLoadQuerys({
   villageId: 0,  
   villageVolunteerId: 0,
   subType: '',
+  subKey: '',
   subId: 0,
   id: 0,
 }, async (querys) => {
   loading.value = true;
   if (!formRef.value)
     return;
+
+  await waitTimeOut(400);
+
   let formData = undefined;
   try {
+
     const [model, forms] = getVillageInfoForm(querys.subType, querys.subId);
     formModel.value = new model();
     formOptions.value = forms(formRef as Ref<IDynamicFormRef>);
     if (querys.id !== -1) {
       formData = await VillageInfoApi.getInfo(
+        getCollectModuleId(querys.subType),
         querys.subType, 
         querys.subId,
+        querys.subKey,
         querys.villageId, 
         querys.villageVolunteerId,
         querys.id,
@@ -156,7 +166,6 @@ const { querys } = useLoadQuerys({
   }
     
   await nextTick();
-
   formRef.value.initDefaultValuesToModel();
 });
 

+ 8 - 2
src/pages/forms/list.vue

@@ -19,7 +19,7 @@
             <a-button type="primary" class="ml-2" @click="newData">+ 新增</a-button>
           </div>
         </div>
-        <div class="task-list">
+        <TaskList>
           <div 
             class="item"
             v-for="item in listLoader.list.value"
@@ -32,7 +32,7 @@
               <div class="desc">{{ item.date}}</div>
             </div>
           </div>
-        </div>
+        </TaskList>
         <SimplePageListContentLoader :loader="listLoader" :noEmpty="true" :emptyView="{
           text: '暂无数据,点击右上方按钮新增数据',
           button: false,
@@ -54,8 +54,12 @@ import { ArrowLeftOutlined } from '@ant-design/icons-vue';
 import IconSearch from '@/components/icons/IconSearch.vue';
 import SimpleInput from '@/components/controls/SimpleInput.vue';
 import VillageInfoApi from '@/api/inhert/VillageInfoApi';
+import { useCollectStore } from '@/stores/collect';
+import { waitTimeOut } from '@imengyu/imengyu-utils';
+import TaskList from '../components/TaskList.vue';
 
 const router = useRouter();
+const { getCollectModuleId } = useCollectStore();
 const searchText = ref('');
 const listLoader = useSimplePagerDataLoader<{
   id: number,
@@ -71,7 +75,9 @@ const listLoader = useSimplePagerDataLoader<{
 }>(8, async (page, pageSize, params) => {
   if (!params || !params.subType || !params.villageId || !params.villageVolunteerId)
     throw new Error("未传入参数,当前页面需要参数");
+  await waitTimeOut(400);
   let res = (page == 1 ? await VillageInfoApi.getList(
+    getCollectModuleId(params.subType),
     params.subType,
     params.subId,
     params.subKey,

+ 4 - 11
src/pages/inheritor.vue

@@ -22,7 +22,7 @@
             buttonClick: () => {},
           }"
         >
-          <div class="task-list">
+          <TaskList>
             <div 
               v-for="item in villageListLoader.content.value"
               :key="item.id"
@@ -42,7 +42,7 @@
                 </div>
               </div>
             </div>
-          </div>
+          </TaskList>
         </SimplePageContentLoader>
 
         <h3 class="text-center p-3 mt-3">我的贡献</h3>
@@ -68,20 +68,13 @@ import { useRouter } from 'vue-router';
 import { useSimpleDataLoader, SimplePageContentLoader } from '@imengyu/imengyu-web-shared';
 import { useCollectStore } from '@/stores/collect';
 import { useAuthStore } from '@/stores/auth';
-import { computed } from 'vue';
 import VillageApi, { VillageListItem } from '@/api/inhert/VillageApi';
+import TaskList from './components/TaskList.vue';
 
 const router = useRouter();
 const authStore = useAuthStore();
-
-const collectStore = useCollectStore();
 const villageListLoader = useSimpleDataLoader(async () => await VillageApi.getClaimedVallageList(), true);
-const volunteerInfoLoader = useSimpleDataLoader(async () =>{
-  const res = await VillageApi.getVolunteerInfo();
-  const collectableModules = (volunteerInfoLoader.content.value?.collectModule as string)?.split(',') || [];
-  collectStore.setCollectableModules(collectableModules);
-  return res;
-}, true);
+const volunteerInfoLoader = useSimpleDataLoader(async () => await VillageApi.getVolunteerInfo(), true);
 
 function goSubmitDigPage(item: VillageListItem) {
   router.push({

+ 33 - 41
src/pages/task/building.vue

@@ -1,51 +1,43 @@
 <template>
   <img class="head-img w-100 radius-base" src="https://mn.wenlvti.net/app_static/xiangan/banner_dig_building.jpg" />
-  <div class="task-list">
-    <div v-if="canCollect('distribution')" class="item">
-      <i class="iconfont icon-task-building-1"></i>
-      <div class="info">
-        <div class="title">建筑分布</div>
-        <div class="desc">村落内传统建筑分布情况</div>
-      </div>
-      <a-button type="primary" @click="goForm('distribution', 0)">填写</a-button>
-    </div>
-    <div class="item">
-      <i class="iconfont icon-task-building-2"></i>
-      <div class="info">
-        <div class="title">文物建筑</div>
-        <div class="desc">历史、艺术、科学价值</div>
-      </div>
-      <a-button type="primary" @click="goForm('building', 1, 'nature')">
-        填写
-      </a-button>
-    </div>
-    <div class="item">
-      <i class="iconfont icon-task-building-3"></i>
-      <div class="info">
-        <div class="title">历史建筑</div>
-        <div class="desc">重大历史事件记录</div>
-      </div>
-      <a-button type="primary" @click="goForm('building', 2, 'nature')">
-        填写
-      </a-button>
-    </div>
-    <div class="item">
-      <i class="iconfont icon-task-building-4"></i>
-      <div class="info">
-        <div class="title">重要传统建筑</div>
-        <div class="desc">重要传统建筑的信息</div>
-      </div>
-      <a-button type="primary" @click="goForm('building', 3, 'nature')">
-        填写
-      </a-button>
-    </div>
-  </div>
+  <TaskList>
+    <TaskItem 
+      title="建筑分布" 
+      desc="村落内传统建筑分布情况" 
+      icon="icon-task-building-1" 
+      :enabled="canCollect('distribution')"
+      @click="goForm('distribution', 0)"
+    />
+    <TaskItem 
+      title="文物建筑" 
+      desc="历史、艺术、科学价值" 
+      icon="icon-task-building-2" 
+      :enabled="true"
+      @click="goForm('building', 1, 'nature')"
+    />
+    <TaskItem 
+      title="历史建筑" 
+      desc="重大历史事件记录" 
+      icon="icon-task-building-3" 
+      :enabled="true"
+      @click="goForm('building', 2, 'nature')"
+    />
+    <TaskItem 
+      title="重要传统建筑" 
+      desc="重要传统建筑的信息" 
+      icon="icon-task-building-4" 
+      :enabled="true"
+      @click="goForm('building', 3, 'nature')"
+    />
+  </TaskList>
 </template>
 
 <script setup lang="ts">
 import { useTaskEntryForm } from '../composeable/TaskEntryForm';
 import { useCollectStore } from '@/stores/collect';
+import TaskList from '../components/TaskList.vue';
+import TaskItem from '../components/TaskItem.vue';
 
 const { goForm } = useTaskEntryForm();
 const { canCollect } = useCollectStore();
-</script>
+</script>

+ 40 - 53
src/pages/task/custom.vue

@@ -1,63 +1,50 @@
 <template>
   <img class="head-img w-100 radius-base" src="https://mn.wenlvti.net/app_static/xiangan/banner_dig_custom.jpg" />
-  <div class="task-list">
-    <div class="item">
-      <i class="iconfont icon-task-custom-2"></i>
-      <div class="info">
-        <div class="title">节庆活动</div>
-        <div class="desc">欢庆与传承并重的文化盛宴</div>
-      </div>
-      <a-button type="primary" @click="goForm('folk_culture', 1, 'folk_culture_type')">
-        填写
-      </a-button>
-    </div>
-    <div class="item">
-      <i class="iconfont icon-task-custom-3"></i>
-      <div class="info">
-        <div class="title">祭祀崇礼</div>
-        <div class="desc">对先贤与自然的崇高致敬</div>
-      </div>
-      <a-button type="primary" @click="goForm('folk_culture', 2, 'folk_culture_type')">
-        填写
-      </a-button>
-    </div>
-    <div class="item">
-      <i class="iconfont icon-task-custom-4"></i>
-      <div class="info">
-        <div class="title">婚丧嫁娶</div>
-        <div class="desc">生命礼赞与文化传承的双重奏鸣</div>
-      </div>
-      <a-button type="primary" @click="goForm('folk_culture', 3, 'folk_culture_type')">
-        填写
-      </a-button>
-    </div>
-    <div class="item">
-      <i class="iconfont icon-task-custom-5"></i>
-      <div class="info">
-        <div class="title">地方方言</div>
-        <div class="desc">历史沉淀的语言瑰宝</div>
-      </div>
-      <a-button type="primary" @click="goForm('folk_culture', 4, 'folk_culture_type')">
-        填写
-      </a-button>
-    </div>
-    <div class="item">
-      <i class="iconfont icon-task-custom-6"></i>
-      <div class="info">
-        <div class="title">特色文化</div>
-        <div class="desc">民族精神的鲜明烙印</div>
-      </div>
-      <a-button type="primary" @click="goForm('folk_culture', 5, 'folk_culture_type')">
-        填写
-      </a-button>
-    </div>
-  </div>
+  <TaskList>
+    <TaskItem 
+      title="节庆活动" 
+      desc="欢庆与传承并重的文化盛宴" 
+      icon="icon-task-custom-2" 
+      :enabled="true"
+      @click="goForm('folk_culture', 1, 'folk_culture_type')"
+    />
+    <TaskItem 
+      title="祭祀崇礼" 
+      desc="对先贤与自然的崇高致敬" 
+      icon="icon-task-custom-3" 
+      :enabled="true"
+      @click="goForm('folk_culture', 2, 'folk_culture_type')"
+    />
+    <TaskItem 
+      title="婚丧嫁娶" 
+      desc="生命礼赞与文化传承的双重奏鸣" 
+      icon="icon-task-custom-4" 
+      :enabled="true"
+      @click="goForm('folk_culture', 3, 'folk_culture_type')"
+    />
+    <TaskItem 
+      title="地方方言" 
+      desc="历史沉淀的语言瑰宝" 
+      icon="icon-task-custom-5" 
+      :enabled="true"
+      @click="goForm('folk_culture', 4, 'folk_culture_type')"
+    />
+    <TaskItem 
+      title="特色文化" 
+      desc="民族精神的鲜明烙印" 
+      icon="icon-task-custom-6" 
+      :enabled="true"
+      @click="goForm('folk_culture', 5, 'folk_culture_type')"
+    />
+  </TaskList>
 </template>
 
 <script setup lang="ts">
 import { useCollectStore } from '@/stores/collect';
 import { useTaskEntryForm } from '../composeable/TaskEntryForm';
+import TaskList from '../components/TaskList.vue';
+import TaskItem from '../components/TaskItem.vue';
 
 const { goForm } = useTaskEntryForm();
 const { canCollect } = useCollectStore();
-</script>
+</script>

+ 26 - 34
src/pages/task/environment.vue

@@ -1,44 +1,36 @@
 <template>
   <img class="head-img w-100 radius-base" src="https://mn.wenlvti.net/app_static/xiangan/banner_dig_environment.jpg" />
-  <div class="task-list">
-    <div class="item">
-      <i class="iconfont icon-task-environment-1"></i>
-      <div class="info">
-        <div class="title">自然环境</div>
-        <div class="desc">村落建立与发展历程</div>
-      </div>
-      <a-button type="primary" @click="goForm('environment', 0)">
-        填写
-      </a-button>
-    </div>
-    <div v-if="canCollect('cultural_relic')" class="item">
-      <i class="iconfont icon-task-environment-5"></i>
-      <div class="info">
-        <div class="title">文物古迹</div>
-        <div class="desc">重要历史文献资料</div>
-      </div>
-      <a-button type="primary" @click="goForm('relic', 0)">
-        填写
-      </a-button>
-    </div>
-    <div v-if="canCollect('element')" class="item">
-      <i class="iconfont icon-task-environment-6"></i>
-      <div class="info">
-        <div class="title">历史环境要素</div>
-        <div class="desc">村民口述历史记录</div>
-      </div>
-      <a-button type="primary" @click="goForm('element', 0)">
-        填写
-      </a-button>
-    </div>
-  </div>
+  <TaskList>
+    <TaskItem 
+      title="自然环境" 
+      desc="村落建立与发展历程" 
+      icon="icon-task-environment-1" 
+      :enabled="canCollect('environment')"
+      @click="goForm('environment', 0)"
+    />
+    <TaskItem 
+      title="文物古迹" 
+      desc="重要历史文献资料" 
+      icon="icon-task-environment-5" 
+      :enabled="canCollect('relic')"
+      @click="goForm('relic', 0)"
+    />
+    <TaskItem 
+      title="历史环境要素" 
+      desc="历史环境要素资料" 
+      icon="icon-task-environment-6" 
+      :enabled="canCollect('element')"
+      @click="goForm('element', 0)"
+    />
+  </TaskList>
 </template>
 
 <script setup lang="ts">
 import { useTaskEntryForm } from '../composeable/TaskEntryForm';
 import { useCollectStore } from '@/stores/collect';
+import TaskList from '../components/TaskList.vue';
+import TaskItem from '../components/TaskItem.vue';
 
 const { goForm } = useTaskEntryForm();
 const { canCollect } = useCollectStore();
-</script>
-
+</script>

+ 19 - 23
src/pages/task/food.vue

@@ -1,31 +1,27 @@
 <template>
   <img class="head-img w-100 radius-base" src="https://mn.wenlvti.net/app_static/xiangan/banner_dig_food.jpg" />
-  <div class="task-list">
-    <div class="item">
-      <i class="iconfont icon-task-food-1"></i>
-      <div class="info">
-        <div class="title">农副产品</div>
-        <div class="desc">乡村繁荣的多元支柱</div>
-      </div>
-      <a-button type="primary" @click="goForm('food_product', 1)">
-        填写
-      </a-button>
-    </div>
-    <div class="item">
-      <i class="iconfont icon-task-food-2"></i>
-      <div class="info">
-        <div class="title">特色美食</div>
-        <div class="desc">给味蕾探索带来无限惊喜</div>
-      </div>
-      <a-button type="primary" @click="goForm('food_product', 3)">
-        填写
-      </a-button>
-    </div>
-  </div>
+  <TaskList>
+    <TaskItem 
+      title="农副产品" 
+      desc="乡村繁荣的多元支柱" 
+      icon="icon-task-food-1" 
+      :enabled="true"
+      @click="goForm('food_product', 1, 'food_product_type')"
+    />
+    <TaskItem 
+      title="特色美食" 
+      desc="给味蕾探索带来无限惊喜" 
+      icon="icon-task-food-2" 
+      :enabled="true"
+      @click="goForm('food_product', 3, 'product_type')"
+    />
+  </TaskList>
 </template>
 
 <script setup lang="ts">
 import { useTaskEntryForm } from '../composeable/TaskEntryForm';
+import TaskList from '../components/TaskList.vue';
+import TaskItem from '../components/TaskItem.vue';
 
 const { goForm } = useTaskEntryForm();
-</script>
+</script>

+ 47 - 64
src/pages/task/history.vue

@@ -1,74 +1,57 @@
 <template>
   <img class="head-img w-100 radius-base" src="https://mn.wenlvti.net/app_static/xiangan/banner_dig_history.jpg" />
-  <div class="task-list">
-    <div class="item">
-      <i class="iconfont icon-task-history-1"></i>
-      <div class="info">
-        <div class="title">建村历史</div>
-        <div class="desc">村落建立与发展历程</div>
-      </div>
-      <a-button type="primary" @click="goForm('cultural', 1)">
-        填写
-      </a-button>
-    </div>
-    <div v-if="canCollect('figure')" class="item">
-      <i class="iconfont icon-task-history-2"></i>
-      <div class="info">
-        <div class="title">历史人物</div>
-        <div class="desc">重要历史人物事迹</div>
-      </div>
-      <a-button type="primary" @click="goForm('figure', 0)">
-        填写
-      </a-button>
-    </div>
-    <div class="item">
-      <i class="iconfont icon-task-history-3"></i>
-      <div class="info">
-        <div class="title">历史事件</div>
-        <div class="desc">重大历史事件记录</div>
-      </div>
-      <a-button type="primary" @click="goForm('cultural', 2)">
-        填写
-      </a-button>
-    </div>
-    <div v-if="canCollect('story')" class="item">
-      <i class="iconfont icon-task-history-4"></i>
-      <div class="info">
-        <div class="title">掌故轶事</div>
-        <div class="desc">民间传说与历史故事</div>
-      </div>
-      <a-button type="primary" @click="goForm('story', 0)">
-        填写
-      </a-button>
-    </div>
-    <div class="item">
-      <i class="iconfont icon-task-history-5"></i>
-      <div class="info">
-        <div class="title">历史文献</div>
-        <div class="desc">重要历史文献资料</div>
-      </div>
-      <a-button type="primary" @click="goForm('cultural', 3)">
-        填写
-      </a-button>
-    </div>
-    <div class="item">
-      <i class="iconfont icon-task-history-6"></i>
-      <div class="info">
-        <div class="title">口述历史</div>
-        <div class="desc">村民口述历史记录</div>
-      </div>
-      <a-button type="primary" @click="goForm('cultural', 4)">
-        填写
-      </a-button>
-    </div>
-  </div>
+  <TaskList>
+    <TaskItem 
+      title="建村历史" 
+      desc="村落建立与发展历程" 
+      icon="icon-task-history-1" 
+      :enabled="canCollect('cultural')"
+      @click="goForm('cultural', 1, 'cultural_type')"
+    />
+    <TaskItem 
+      title="历史人物" 
+      desc="重要历史人物事迹" 
+      icon="icon-task-history-2" 
+      :enabled="canCollect('figure')"
+      @click="goForm('figure', 0)"
+    />
+    <TaskItem 
+      title="历史事件" 
+      desc="重大历史事件记录" 
+      icon="icon-task-history-3" 
+      :enabled="canCollect('cultural')"
+      @click="goForm('cultural', 2, 'cultural_type')"
+    />
+    <TaskItem 
+      title="掌故轶事" 
+      desc="民间传说与历史故事" 
+      icon="icon-task-history-4" 
+      :enabled="canCollect('story')"
+      @click="goForm('story', 0)"
+    />
+    <TaskItem 
+      title="历史文献" 
+      desc="重要历史文献资料" 
+      icon="icon-task-history-5" 
+      :enabled="canCollect('cultural')"
+      @click="goForm('cultural', 3)"
+    />
+    <TaskItem 
+      title="口述历史" 
+      desc="村民口述历史记录" 
+      icon="icon-task-history-6" 
+      :enabled="canCollect('cultural')"
+      @click="goForm('cultural', 4)"
+    />
+  </TaskList>
 </template>
 
 <script setup lang="ts">
 import { useCollectStore } from '@/stores/collect';
 import { useTaskEntryForm } from '../composeable/TaskEntryForm';
+import TaskList from '../components/TaskList.vue';
+import TaskItem from '../components/TaskItem.vue';
 
 const { goForm } = useTaskEntryForm();
 const { canCollect } = useCollectStore();
-</script>
-
+</script>

+ 26 - 33
src/pages/task/mine.vue

@@ -1,41 +1,34 @@
 <template>
   <img class="head-img w-100 radius-base" src="https://mn.wenlvti.net/app_static/xiangan/banner_dig_mine.jpg" />
-  <div class="task-list">
-    <div class="item">
-      <i class="iconfont icon-task-mine-1"></i>
-      <div class="info">
-        <div class="title">商业集市</div>
-        <div class="desc">文化交流的开放窗口</div>
-      </div>
-      <a-button type="primary" @click="goForm('food_product', 4)">
-        填写
-      </a-button>
-    </div>
-    <div class="item">
-      <i class="iconfont icon-task-mine-2"></i>
-      <div class="info">
-        <div class="title">服装服饰</div>
-        <div class="desc">艺术与功能交织的时尚篇章</div>
-      </div>
-      <a-button type="primary" @click="goForm('food_product', 5)">
-        填写
-      </a-button>
-    </div>
-    <div class="item">
-      <i class="iconfont icon-task-mine-2"></i>
-      <div class="info">
-        <div class="title">运输工具</div>
-        <div class="desc">历史的运输工具</div>
-      </div>
-      <a-button type="primary" @click="goForm('food_product', 6)">
-        填写
-      </a-button>
-    </div>
-  </div>
+  <TaskList>
+    <TaskItem 
+      title="商业集市" 
+      desc="文化交流的开放窗口" 
+      icon="icon-task-mine-1" 
+      :enabled="true"
+      @click="goForm('food_product', 4, 'product_type')"
+    />
+    <TaskItem 
+      title="服装服饰" 
+      desc="艺术与功能交织的时尚篇章" 
+      icon="icon-task-mine-2" 
+      :enabled="true"
+      @click="goForm('food_product', 5, 'product_type')"
+    />
+    <TaskItem 
+      title="运输工具" 
+      desc="历史的运输工具" 
+      icon="icon-task-mine-2" 
+      :enabled="true"
+      @click="goForm('food_product', 6, 'product_type')"
+    />
+  </TaskList>
 </template>
 
 <script setup lang="ts">
 import { useTaskEntryForm } from '../composeable/TaskEntryForm';
+import TaskList from '../components/TaskList.vue';
+import TaskItem from '../components/TaskItem.vue';
 
 const { goForm } = useTaskEntryForm();
-</script>
+</script>

+ 40 - 53
src/pages/task/summary.vue

@@ -1,65 +1,52 @@
 <template>
   <img class="head-img w-100 radius-base" src="https://mn.wenlvti.net/app_static/xiangan/banner_dig_summary.jpg" />
-  <div class="task-list">
-    <div class="item">
-      <i class="iconfont icon-task-summary-1"></i>
-      <div class="info">
-        <div class="title">行政区划</div>
-        <div class="desc">村落行政区域划分及变迁</div>
-      </div>
-      <a-button type="primary" @click="goForm(1)">
-        填写
-      </a-button>
-    </div>
-    <div class="item">
-      <i class="iconfont icon-task-summary-5"></i>
-      <div class="info">
-        <div class="title">村落综述</div>
-        <div class="desc">村落整体概况介绍</div>
-      </div>
-      <a-button type="primary" @click="goForm(5)">
-        填写
-      </a-button>
-    </div>
-    <div class="item">
-      <i class="iconfont icon-task-summary-2"></i>
-      <div class="info">
-        <div class="title">地理信息</div>
-        <div class="desc">地理位置和自然环境特征</div>
-      </div>
-      <a-button type="primary" @click="goForm(2)">
-        填写
-      </a-button>
-    </div>
-    <div class="item">
-      <i class="iconfont icon-task-summary-3"></i>
-      <div class="info">
-        <div class="title">建设与保护</div>
-        <div class="desc">村落发展与文化遗产保护</div>
-      </div>
-      <a-button type="primary" @click="goForm(3)">
-        填写
-      </a-button>
-    </div>
-    <div class="item">
-      <i class="iconfont icon-task-summary-4"></i>
-      <div class="info">
-        <div class="title">人口与经济</div>
-        <div class="desc">人口与经济情况</div>
-      </div>
-      <a-button type="primary" @click="goForm(4)">
-        填写
-      </a-button>
-    </div>
-  </div>
+  <TaskList>
+    <TaskItem 
+      title="行政区划" 
+      desc="村落行政区域划分及变迁" 
+      icon="icon-task-summary-1" 
+      :enabled="true"
+      @click="goForm(1)"
+    />
+    <TaskItem 
+      title="村落综述" 
+      desc="村落整体概况介绍" 
+      icon="icon-task-summary-5" 
+      :enabled="true"
+      @click="goForm(5)"
+    />
+    <TaskItem 
+      title="地理信息" 
+      desc="地理位置和自然环境特征" 
+      icon="icon-task-summary-2" 
+      :enabled="true"
+      @click="goForm(2)"
+    />
+    <TaskItem 
+      title="建设与保护" 
+      desc="村落发展与文化遗产保护" 
+      icon="icon-task-summary-3" 
+      :enabled="true"
+      @click="goForm(3)"
+    />
+    <TaskItem 
+      title="人口与经济" 
+      desc="人口与经济情况" 
+      icon="icon-task-summary-4" 
+      :enabled="true"
+      @click="goForm(4)"
+    />
+  </TaskList>
 </template>
 
 <script setup lang="ts">
 import { useTaskEntryForm } from '../composeable/TaskEntryForm';
+import TaskList from '../components/TaskList.vue';
+import TaskItem from '../components/TaskItem.vue';
 
 const t = useTaskEntryForm();
 
 function goForm(subId: number) {
   t.goForm('overview', subId, undefined, 'common');
 }
-</script>
+</script>

+ 33 - 39
src/pages/task/trip.vue

@@ -1,48 +1,42 @@
 <template>
   <img class="head-img w-100 radius-base" src="https://mn.wenlvti.net/app_static/xiangan/banner_dig_trip.jpg" />
-  <div class="task-list">
-    <div v-if="canCollect('travel_guide')" class="item">
-      <i class="iconfont icon-task-trip-3"></i>
-      <div class="info">
-        <div class="title">旅游导览</div>
-      </div>
-      <a-button type="primary" @click="goForm('travel_guide', 0)">
-        填写
-      </a-button>
-    </div>
-    <div class="item">
-      <i class="iconfont icon-task-trip-1"></i>
-      <div class="info">
-        <div class="title">游览路线</div>
-      </div>
-      <a-button type="primary" @click="goForm('route', 1)">
-        填写
-      </a-button>
-    </div>
-    <div class="item">
-      <i class="iconfont icon-task-trip-2"></i>
-      <div class="info">
-        <div class="title">活动时间</div>
-      </div>
-      <a-button type="primary" @click="goForm('route', 2)">
-        填写
-      </a-button>
-    </div>
-    <div class="item">
-      <i class="iconfont icon-task-trip-4"></i>
-      <div class="info">
-        <div class="title">路线特色</div>
-      </div>
-      <a-button type="primary" @click="goForm('route', 3)">
-        填写
-      </a-button>
-    </div>
-  </div>
+  <TaskList>
+    <TaskItem 
+      title="旅游导览" 
+      desc="" 
+      icon="icon-task-trip-3" 
+      :enabled="canCollect('travel_guide')"
+      @click="goForm('travel_guide', 0)"
+    />
+    <TaskItem 
+      title="游览路线" 
+      desc="" 
+      icon="icon-task-trip-1" 
+      :enabled="canCollect('route')"
+      @click="goForm('route', 1)"
+    />
+    <TaskItem 
+      title="活动时间" 
+      desc="" 
+      icon="icon-task-trip-2" 
+      :enabled="canCollect('route')"
+      @click="goForm('route', 2)"
+    />
+    <TaskItem 
+      title="路线特色" 
+      desc="" 
+      icon="icon-task-trip-4" 
+      :enabled="canCollect('route')"
+      @click="goForm('route', 3)"
+    />
+  </TaskList>
 </template>
 
 <script setup lang="ts">
 import { useCollectStore } from '@/stores/collect';
 import { useTaskEntryForm } from '../composeable/TaskEntryForm';
+import TaskList from '../components/TaskList.vue';
+import TaskItem from '../components/TaskItem.vue';
 
 const { goForm } = useTaskEntryForm();
 const { canCollect } = useCollectStore();
@@ -56,4 +50,4 @@ const { canCollect } = useCollectStore();
     }
   }
 }
-</style>
+</style>

+ 35 - 4
src/stores/collect.ts

@@ -2,25 +2,56 @@ import { computed, ref } from 'vue'
 import { defineStore } from 'pinia'
 import { useAuthStore } from './auth';
 
+const CollectableModulesNameMapping : Record<string, string> = {
+  'overview': '村落概况',
+  'distribution': '建筑分布',
+  'building': '传统建筑',
+  'folk_culture': '民俗文化',
+  'food_product': '美食物产',
+  'route': '旅游路线',
+  'travel_guide': '旅游导览',
+  'element': '环境要素',
+  'environment': '环境格局',
+  'relic': '文物古迹',
+  'cultural': '历史文化',
+  'figure': '历史人物',
+  'ich': '非遗',
+  'story': '掌故轶事',
+  'village': '风景名胜',
+  'speaker': '口述者',
+  'collect': '随手记',
+}
+
 export const useCollectStore = defineStore('collect', () => {
-  const collectableModules = ref<string[]>([]);
+  const collectableModules = ref(new Map<string, number>());
   const authStore = useAuthStore();
 
-  function setCollectableModules(modules: string[]) {
+
+  function setCollectableModules(modules: Map<string, number>) {
     collectableModules.value = modules;
+    console.log(modules);
+    
   }
   function canCollect(module: string) {
     if (authStore.isAdmin)
       return true;
-    return collectableModules.value.includes(module);
+    if (collectableModules.value.has(CollectableModulesNameMapping[module]))
+      return true;
+    return collectableModules.value.has(module);
+  }
+  function getCollectModuleId(module: string) {
+    if (collectableModules.value.has(CollectableModulesNameMapping[module]))
+      return collectableModules.value.get(CollectableModulesNameMapping[module]);
+    return collectableModules.value.get(module);
   }
   
-  const isEmpty = computed(() => collectableModules.value.length === 0);
+  const isEmpty = computed(() => collectableModules.value.size === 0);
 
   return { 
     isEmpty,
     collectableModules,
     setCollectableModules,
+    getCollectModuleId,
     canCollect,
   }
 })