Ver código fonte

📦 发布页面对接

快乐的梦鱼 2 semanas atrás
pai
commit
853ee8edf5

+ 0 - 7
package-lock.json

@@ -30,7 +30,6 @@
         "crypto-js": "^4.2.0",
         "openai": "^6.35.0",
         "pinia": "^3.0.1",
-        "sse.js": "^2.8.0",
         "tslib": "^2.8.1",
         "vue": "3.5.22",
         "vue-i18n": "9.14.5",
@@ -13913,12 +13912,6 @@
       "license": "BSD-3-Clause",
       "peer": true
     },
-    "node_modules/sse.js": {
-      "version": "2.8.0",
-      "resolved": "https://registry.npmmirror.com/sse.js/-/sse.js-2.8.0.tgz",
-      "integrity": "sha512-35RyyFYpzzHZgMw9D5GxwADbL6gnntSwW/rKXcuIy1KkYCPjW6oia0moNdNRhs34oVHU1Sjgovj3l7uIEZjrKA==",
-      "license": "Apache-2.0"
-    },
     "node_modules/stack-utils": {
       "version": "2.0.6",
       "resolved": "https://registry.npmmirror.com/stack-utils/-/stack-utils-2.0.6.tgz",

+ 0 - 1
package.json

@@ -57,7 +57,6 @@
     "crypto-js": "^4.2.0",
     "openai": "^6.35.0",
     "pinia": "^3.0.1",
-    "sse.js": "^2.8.0",
     "tslib": "^2.8.1",
     "vue": "3.5.22",
     "vue-i18n": "9.14.5",

+ 9 - 6
src/App.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import AppConfig, { isTestEnv } from '@/common/config/AppCofig'
+import AppConfig, { isProd, isTestEnv } from '@/common/config/AppCofig'
 import { useAuthStore } from './store/auth'
 import { useAppInit } from './common/composeabe/AppInit';
 import { BugReporterAbstractionUniapp } from './common/BugReporter/impl/BugReporterAbstractionUniapp';
@@ -11,6 +11,7 @@ import { RequestApiConfig, RequestApiError } from '@imengyu/imengyu-utils';
 import ApiCofig from './common/config/ApiCofig';
 import MemoryTimeOut from './components/composeabe/MemoryTimeOut';
 import BugReporter from './common/BugReporter';
+import '@/common/style/icons';
 
 const authStore = useAuthStore();
 const { init } = useAppInit();
@@ -50,11 +51,13 @@ BugReporter.config({
   appKey: ApiCofig.bugReport.appKey,
 });
 
-onError((err) => {
-  if (!((err as any) instanceof RequestApiError)) {
-    BugReporter.reportError(err);
-  }
-});
+if (isProd) {
+  onError((err) => {
+    if (!((err as any) instanceof RequestApiError)) {
+      BugReporter.reportError(err);
+    }
+  });
+}
 
 //设置请求基础地址
 RequestApiConfig.setConfig({

+ 4 - 3
src/api/agent/Agent.ts

@@ -2,8 +2,8 @@ import { DataModel, type KeyValue } from "@imengyu/js-request-transform";
 import { appendGetUrlParams, assertNotNull, RandomUtils } from "@imengyu/imengyu-utils";
 import { useAuthStore } from "@/store/auth";
 import { RestfulApi } from "../restful";
-import { SSE } from "sse.js";
-import OpenAI from "openai";
+import { SSE } from "@/pages/chat/core/speical/ssemp";
+import type OpenAI from "openai";
 import { CommonPageResult } from "@/api/restful/types";
 import { AgentChatFile } from "./AgentChatFile";
 
@@ -243,7 +243,8 @@ export class AgentApi extends RestfulApi<AgentChatDummy> {
       },
       method: 'POST',
       payload: JSON.stringify({ options }),
-      withCredentials: false
+      withCredentials: false,
+      timeout: 60000,
     });
   }
 

+ 1 - 1
src/api/auth/UserApi.ts

@@ -144,7 +144,7 @@ export class UserApi extends AppServerRequestModule<DataModel> {
     return (await this.post('/ich/inheritor/refresh', '刷新token', {}, undefined, LoginResult)).data as LoginResult;
   }
   async checkUserAuthed() {
-    return await this.post('/village/village/getVillageList', '检查用户是否登录', {});
+    
   }  
 
   

+ 1 - 1
src/api/inhert/VillageApi.ts

@@ -271,7 +271,7 @@ export class VillageApi extends AppServerRequestModule<DataModel> {
     return res.data as VolunteerInfo;
   }
   async getVolunteerInfoById(id: number) {
-    const res = await this.post('/village/volunteer/getInfo', '获取志愿者信息', {
+    const res = await this.post('/village/volunteer/getInfo', '通过ID获取志愿者信息', {
       id,
     }, undefined, VolunteerInfo);
     return res.data as VolunteerInfo;

+ 5 - 1
src/common/composeabe/AppInit.ts

@@ -14,7 +14,11 @@ export function useAppInit() {
   return {
     async init() {
       //加载采集板块信息
-      await collectStore.loadCollectableModules();
+      try {
+        await collectStore.loadCollectableModules();
+      } catch (error) {
+        console.error('加载采集板块信息失败', error);
+      }
       //设置日志用户ID
       BugReporter.setUserId(authStore.userInfo?.id.toString() || '');
     },

+ 1 - 1
src/common/config/AppCofig.ts

@@ -8,7 +8,7 @@ export default {
   version: '1.0.0',
   buildTime,
   buildInfo,
-  appId: 'wx954621c03f2fa912',
+  appId: 'wx7e264ff5483995e9',
   qqMapKey: 'TOIBZ-CA4WB-OFQUF-J3XG4-EEB2J-DXBX7',
   amapKey: '34eb1d57f93720a871bd11a90af0c91c',
   defaultLonLat: [ 118.161270, 24.529196 ],

+ 19 - 0
src/common/data/CollectableModulesNameMapping.ts

@@ -0,0 +1,19 @@
+export const CollectableModulesNameMapping : Record<string, string> = {
+  'overview': '村落概况',
+  'distribution': '建筑分布',
+  'building': '传统建筑',
+  'folk_culture': '民俗文化',
+  'food_product': '美食物产',
+  'route': '旅游路线',
+  'travel_guide': '旅游导览',
+  'element': '环境要素',
+  'environment': '环境格局',
+  'relic': '文物古迹',
+  'cultural': '历史文化',
+  'figure': '历史人物',
+  'ich': '非遗',
+  'story': '掌故轶事',
+  'spots': '风景名胜',
+  'speaker': '口述者',
+  'collect': '随手记',
+}

Diferenças do arquivo suprimidas por serem muito extensas
+ 5 - 0
src/common/style/icons.ts


+ 1 - 1
src/manifest.json

@@ -50,7 +50,7 @@
     "quickapp" : {},
     /* 小程序特有相关 */
     "mp-weixin" : {
-        "appid" : "wx954621c03f2fa912",
+        "appid" : "wx7e264ff5483995e9",
         "setting" : {
             "urlCheck" : false,
             "es6" : true,

+ 131 - 114
src/pages.json

@@ -68,22 +68,6 @@
       }
     },
     {
-      "path": "pages/home/post/publish",
-      "style": {
-        "navigationBarTitleText": "发布微信贴图",
-        "navigationStyle": "custom",
-        "enablePullDownRefresh": false
-      }
-    },
-    {
-      "path": "pages/home/post/detail",
-      "style": {
-        "navigationBarTitleText": "微信贴图详情",
-        "navigationStyle": "custom",
-        "enablePullDownRefresh": false
-      }
-    },
-    {
       "path": "pages/home/village/rank/volunteer",
       "style": {
         "navigationBarTitleText": "志愿者排名",
@@ -99,83 +83,6 @@
         "enablePullDownRefresh": false
       }
     },
-    {
-      "path": "pages/dig/details",
-      "style": {
-        "navigationBarTitleText": "村社文化资源挖掘平台-详情",
-        "enablePullDownRefresh": false
-      }
-    },
-    {
-      "path": "pages/dig/admin/index",
-      "style": {
-        "navigationBarTitleText": "村社文化资源挖掘平台-管理员",
-        "enablePullDownRefresh": true
-      }
-    },
-    {
-      "path": "pages/dig/admin/volunteer",
-      "style": {
-        "navigationBarTitleText": "村社文化资源挖掘平台-志愿者管理",
-        "enablePullDownRefresh": false
-      }
-    },
-    {
-      "path": "pages/dig/admin/preview",
-      "style": {
-        "navigationBarTitleText": "村落预览",
-        "enablePullDownRefresh": false
-      }
-    },
-    {
-      "path": "pages/dig/admin/review",
-      "style": {
-        "navigationBarTitleText": "注册审核",
-        "enablePullDownRefresh": true
-      }
-    },
-    {
-      "path": "pages/dig/forms/task",
-      "style": {
-        "navigationBarTitleText": "村社文化资源挖掘平台",
-        "enablePullDownRefresh": false
-      }
-    },
-    {
-      "path": "pages/dig/forms/common",
-      "style": {
-        "navigationBarTitleText": "村社文化资源挖掘平台-提交信息",
-        "enablePullDownRefresh": false
-      }
-    },
-    {
-      "path": "pages/dig/forms/list",
-      "style": {
-        "navigationBarTitleText": "信息列表",
-        "enablePullDownRefresh": true
-      }
-    },
-    {
-      "path": "pages/dig/forms/submits",
-      "style": {
-        "navigationBarTitleText": "我的投稿",
-        "enablePullDownRefresh": true
-      }
-    },
-    {
-      "path": "pages/editor/editor",
-      "style": {
-        "navigationBarTitleText": "编辑文章",
-        "enablePullDownRefresh": false
-      }
-    },
-    {
-      "path": "pages/editor/preview",
-      "style": {
-        "navigationBarTitleText": "预览文章",
-        "enablePullDownRefresh": false
-      }
-    },
     
     {
       "path": "pages/article/details",
@@ -227,27 +134,6 @@
       }
     },
     {
-      "path": "pages/dig/sharereg/share-reg-page",
-      "style": {
-        "navigationBarTitleText": "注册成为志愿者",
-        "enablePullDownRefresh": false
-      }
-    },
-    {
-      "path": "pages/dig/sharereg/bind",
-      "style": {
-        "navigationBarTitleText": "绑定志愿者",
-        "enablePullDownRefresh": false
-      }
-    },
-    {
-      "path": "pages/dig/sharereg/bind-wx",
-      "style": {
-        "navigationBarTitleText": "绑定微信",
-        "enablePullDownRefresh": false
-      }
-    },
-    {
       "path": "pages/home/about/about",
       "style": {
         "navigationBarTitleText": "关于我们",
@@ -283,6 +169,137 @@
       }
     }
   ],
+  "subPackages": [
+    {
+      "root": "pages/home/post",
+      "pages": [
+        {
+          "path": "publish",
+          "style": {
+            "navigationBarTitleText": "发布微信贴图",
+            "navigationStyle": "custom",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "detail",
+          "style": {
+            "navigationBarTitleText": "微信贴图详情",
+            "navigationStyle": "custom",
+            "enablePullDownRefresh": false
+          }
+        }
+      ]
+    },
+    {
+      "root": "pages/dig",
+      "pages": [
+        {
+          "path": "details",
+          "style": {
+            "navigationBarTitleText": "村社文化资源挖掘平台-详情",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "admin/index",
+          "style": {
+            "navigationBarTitleText": "村社文化资源挖掘平台-管理员",
+            "enablePullDownRefresh": true
+          }
+        },
+        {
+          "path": "admin/volunteer",
+          "style": {
+            "navigationBarTitleText": "村社文化资源挖掘平台-志愿者管理",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "admin/preview",
+          "style": {
+            "navigationBarTitleText": "村落预览",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "admin/review",
+          "style": {
+            "navigationBarTitleText": "注册审核",
+            "enablePullDownRefresh": true
+          }
+        },
+        {
+          "path": "forms/task",
+          "style": {
+            "navigationBarTitleText": "村社文化资源挖掘平台",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "forms/common",
+          "style": {
+            "navigationBarTitleText": "村社文化资源挖掘平台-提交信息",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "forms/list",
+          "style": {
+            "navigationBarTitleText": "信息列表",
+            "enablePullDownRefresh": true
+          }
+        },
+        {
+          "path": "forms/submits",
+          "style": {
+            "navigationBarTitleText": "我的投稿",
+            "enablePullDownRefresh": true
+          }
+        },
+        {
+          "path": "sharereg/share-reg-page",
+          "style": {
+            "navigationBarTitleText": "注册成为志愿者",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "sharereg/bind",
+          "style": {
+            "navigationBarTitleText": "绑定志愿者",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "sharereg/bind-wx",
+          "style": {
+            "navigationBarTitleText": "绑定微信",
+            "enablePullDownRefresh": false
+          }
+        }
+      ]
+    },
+    {
+      "root": "pages/editor",
+      "pages": [
+        {
+          "path": "editor",
+          "style": {
+            "navigationBarTitleText": "编辑文章",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "preview",
+          "style": {
+            "navigationBarTitleText": "预览文章",
+            "enablePullDownRefresh": false
+          }
+        }
+      ]
+    }
+  ],
   "globalStyle": {
     "navigationBarTextStyle": "white",
     "navigationBarTitleText": "uni-app",

+ 1 - 3
src/pages/chat/components/ChatMessage.vue

@@ -21,9 +21,7 @@
         :class="{ 'expanded': contentExpanded }"
       >
         <!-- 工具图标 -->
-        <i v-if="message.role === 'tool'" class="prefix icon iconfont icon-Jigsaw"></i>
-        <!-- 系统图标 -->
-        <i v-else-if="message.role === 'system'" class="prefix icon iconfont icon-a-ConfusedMind "></i>
+        <Icon v-if="message.role === 'tool'" class="prefix icon" name="ai-tools" />
         <!-- 加载和失败状态 -->
         <ActivityIndicator v-if="message.state === 'loading'" class="prefix icon" />
         <Icon icon="warning" v-else-if="message.state === 'error'" class="prefix icon" />

+ 1 - 1
src/pages/chat/components/ChatMessageContainer.vue

@@ -21,7 +21,7 @@
       <scroll-view
         ref="scrollRectRef"
         scroll-y 
-        :style="{ flex: 1, height: '100%', maxHeight: '100%' }"
+        :style="{ flex: 1, height: '100%', maxHeight: '40vh' }"
         :scroll-top="scrollY"
       >
         <FlexCol gap="gap.sm">

+ 1 - 3
src/pages/chat/core/Chat.ts

@@ -9,7 +9,7 @@ import { useChatStaticMessages } from "./StaticMessages";
 import { useChatContext, type ContextMemoryConfig, type ContextMemorySetting } from "./Context";
 import { useChatTools, type ChatToolsManager } from "./Tools";
 import type { ChatSessionManager } from "../composables/useChatSession";
-import type { SSE } from "sse.js";
+import type { SSE } from "./speical/ssemp";
 import type OpenAI from "openai";
 import { requireNotNull } from "@imengyu/imengyu-utils";
 
@@ -150,9 +150,7 @@ export function useChat(options: {
 
   //新建会话消息构建
   sessionManager.events.on('session-newed', () => {
-    console.log('session-newed', 1);
     messages.value = [staticMessagesManager.getWelcomeMessage()];
-    console.log('session-newed', messages.value);
     config.onNewChat?.(true);
     contextManager.estimateTokenUseage(config.onGetSendOptions());
   });

+ 292 - 0
src/pages/chat/core/speical/ssemp.ts

@@ -0,0 +1,292 @@
+export type SSEMethod = "GET" | "POST" | "PUT" | "DELETE";
+
+export interface SSEOptions {
+  headers?: Record<string, string>;
+  method?: SSEMethod;
+  payload?: string;
+  withCredentials?: boolean;
+  timeout?: number;
+}
+
+type SSEOpenHandler = ((event?: Event) => void) | null;
+type SSEMessageHandler = ((event: MessageEvent<string>) => void) | null;
+type SSEErrorHandler = ((error: unknown) => void) | null;
+
+function createTextDecoder() {
+  if (typeof TextDecoder !== "undefined") {
+    return new TextDecoder("utf-8");
+  }
+  return null;
+}
+
+function decodeBufferChunk(data: unknown, decoder: TextDecoder | null): string {
+  if (typeof data === "string") {
+    return data;
+  }
+
+  if (data instanceof ArrayBuffer) {
+    if (decoder) {
+      return decoder.decode(new Uint8Array(data), { stream: true });
+    }
+    return String.fromCharCode(...new Uint8Array(data));
+  }
+
+  if (ArrayBuffer.isView(data)) {
+    const typedArray = data as Uint8Array;
+    if (decoder) {
+      return decoder.decode(typedArray, { stream: true });
+    }
+    return String.fromCharCode(...typedArray);
+  }
+
+  return "";
+}
+
+function parsePayload(payload?: string): string | Record<string, unknown> | ArrayBuffer | undefined {
+  if (!payload) {
+    return undefined;
+  }
+  try {
+    return JSON.parse(payload);
+  } catch {
+    return payload;
+  }
+}
+
+export class SSE {
+  onopen: SSEOpenHandler = null;
+  onmessage: SSEMessageHandler = null;
+  onerror: SSEErrorHandler = null;
+
+  private requestTask: UniApp.RequestTask | null = null;
+  private abortController: AbortController | null = null;
+  private buffer = "";
+  private lastEventId = "";
+  private closed = false;
+  private opened = false;
+
+  constructor(
+    private readonly url: string,
+    private readonly options: SSEOptions = {},
+  ) {}
+
+  stream() {
+    this.closed = false;
+    this.opened = false;
+    this.buffer = "";
+
+    if (typeof uni !== "undefined" && typeof uni.request === "function") {
+      this.startWithUniRequest();
+      return;
+    }
+
+    this.startWithFetch();
+  }
+
+  close() {
+    this.closed = true;
+
+    if (this.requestTask) {
+      this.requestTask.abort();
+      this.requestTask = null;
+    }
+
+    if (this.abortController) {
+      this.abortController.abort();
+      this.abortController = null;
+    }
+  }
+
+  private emitOpen() {
+    if (this.opened || this.closed) {
+      return;
+    }
+    this.opened = true;
+    this.onopen?.();
+  }
+
+  private emitError(error: unknown) {
+    this.onerror?.(error);
+    if (this.closed) {
+      return;
+    }
+  }
+
+  private dispatchRawEvent(raw: string) {
+    if (!raw.trim()) {
+      return;
+    }
+
+    const lines = raw.split("\n");
+    const dataLines: string[] = [];
+    let eventName = "message";
+
+    for (const line of lines) {
+      if (!line || line.startsWith(":")) {
+        continue;
+      }
+
+      const separator = line.indexOf(":");
+      const field = separator >= 0 ? line.slice(0, separator) : line;
+      let value = separator >= 0 ? line.slice(separator + 1) : "";
+      if (value.startsWith(" ")) {
+        value = value.slice(1);
+      }
+
+      if (field === "data") {
+        dataLines.push(value);
+      } else if (field === "event" && value) {
+        eventName = value;
+      } else if (field === "id") {
+        this.lastEventId = value;
+      }
+    }
+
+    if (dataLines.length === 0) {
+      return;
+    }
+
+    const event = {
+      data: dataLines.join("\n"),
+      type: eventName,
+      lastEventId: this.lastEventId,
+      origin: this.url,
+    } as MessageEvent<string>;
+
+    // 兼容 Chat.ts 的使用方式:统一通过 onmessage 消费数据。
+    this.onmessage?.(event);
+  }
+
+  private consumeSseChunk(chunkText: string) {
+    if (!chunkText || this.closed) {
+      return;
+    }
+
+    this.emitOpen();
+    this.buffer += chunkText.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
+
+    let boundaryIndex = this.buffer.indexOf("\n\n");
+    while (boundaryIndex >= 0) {
+      const rawEvent = this.buffer.slice(0, boundaryIndex);
+      this.buffer = this.buffer.slice(boundaryIndex + 2);
+      this.dispatchRawEvent(rawEvent);
+      boundaryIndex = this.buffer.indexOf("\n\n");
+    }
+  }
+
+  private flushRemainder() {
+    if (this.buffer.trim()) {
+      this.dispatchRawEvent(this.buffer);
+      this.buffer = "";
+    }
+  }
+
+  private startWithUniRequest() {
+    const decoder = createTextDecoder();
+    const method = this.options.method || "GET";
+    const payloadData = parsePayload(this.options.payload);
+
+    const requestTask = uni.request({
+      url: this.url,
+      method,
+      header: this.options.headers,
+      data: payloadData,
+      timeout: this.options.timeout,
+      responseType: "arraybuffer",
+      enableChunked: true,
+      success: (res) => {
+        if (res.statusCode !== 200) {
+          this.emitError(new Error(`请求失败: ${res.statusCode}`));
+          return;
+        }
+        if (this.closed) {
+          return;
+        }
+        const text = decodeBufferChunk(res.data, decoder);
+        this.consumeSseChunk(text);
+        this.flushRemainder();
+      },
+      fail: (error) => {
+        this.emitError(error);
+      },
+    } as UniApp.RequestOptions);
+
+    this.requestTask = requestTask as unknown as UniApp.RequestTask;
+
+    const chunkableTask = this.requestTask as UniApp.RequestTask & {
+      onChunkReceived?: (callback: (result: { data: ArrayBuffer }) => void) => void;
+    };
+    console.log('chunkableTask', chunkableTask);
+    if (typeof chunkableTask.onChunkReceived === "function") {
+      chunkableTask.onChunkReceived((result: { data: ArrayBuffer }) => {
+        if (this.closed) {
+          return;
+        }
+        const text = decodeBufferChunk(result.data, decoder);
+        this.consumeSseChunk(text);
+      });
+    }
+  }
+
+  private async startWithFetch() {
+    if (typeof fetch === "undefined") {
+      this.emitError(new Error("当前环境不支持 fetch 流式读取"));
+      return;
+    }
+
+    this.abortController = new AbortController();
+    const method = this.options.method || "GET";
+
+    try {
+      const response = await fetch(this.url, {
+        method,
+        headers: this.options.headers,
+        body: this.options.payload,
+        credentials: this.options.withCredentials ? "include" : "omit",
+        signal: this.abortController.signal,
+      });
+
+      if (this.closed) {
+        return;
+      }
+
+      if (!response.ok) {
+        throw new Error(`请求失败: ${response.status} ${response.statusText}`);
+      }
+
+      this.emitOpen();
+
+      if (!response.body) {
+        const plainText = await response.text();
+        this.consumeSseChunk(plainText);
+        this.flushRemainder();
+        return;
+      }
+
+      const reader = response.body.getReader();
+      const decoder = createTextDecoder();
+
+      while (!this.closed) {
+        const result = await reader.read();
+        if (result.done) {
+          break;
+        }
+        const chunkText = decodeBufferChunk(result.value, decoder);
+        this.consumeSseChunk(chunkText);
+      }
+
+      if (decoder) {
+        const tail = decoder.decode();
+        if (tail) {
+          this.consumeSseChunk(tail);
+        }
+      }
+      this.flushRemainder();
+    } catch (error) {
+      if (this.closed) {
+        return;
+      }
+      this.emitError(error);
+    }
+  }
+}

+ 0 - 20
src/pages/dig/forms/forms.ts

@@ -27,26 +27,6 @@ export type SingleForm = [NewDataModel, (formRef: Ref<IDynamicFormRef>) => IDyna
 }]
 export type GroupForm = Record<number, SingleForm>
 
-export const CollectableModulesNameMapping : Record<string, string> = {
-  'overview': '村落概况',
-  'distribution': '建筑分布',
-  'building': '传统建筑',
-  'folk_culture': '民俗文化',
-  'food_product': '美食物产',
-  'route': '旅游路线',
-  'travel_guide': '旅游导览',
-  'element': '环境要素',
-  'environment': '环境格局',
-  'relic': '文物古迹',
-  'cultural': '历史文化',
-  'figure': '历史人物',
-  'ich': '非遗',
-  'story': '掌故轶事',
-  'spots': '风景名胜',
-  'speaker': '口述者',
-  'collect': '随手记',
-}
-
 const villageInfoForm : Record<string, GroupForm> = {
   'overview': villageInfoOverviewForm,
   'cultural': villageInfoCulture,

+ 5 - 2
src/pages/dig/index.vue

@@ -130,13 +130,11 @@ import { navTo } from '@/components/utils/PageAction';
 import { useAuthStore } from '@/store/auth';
 import { useCollectStore } from '@/store/collect';
 import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
-import { checkIsNotVolunteerError } from './forms/bind';
 import VillageApi, { VillageListItem } from '@/api/inhert/VillageApi';
 import RequireLogin from '@/common/components/RequireLogin.vue';
 import SimplePageContentLoader from '@/components/loader/SimplePageContentLoader.vue';
 import Button from '@/components/basic/Button.vue';
 import Image from '@/components/basic/Image.vue';
-import SubTitle from '@/components/display/title/SubTitle.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
 import FlexRow from '@/components/layout/FlexRow.vue';
 import H3 from '@/components/typography/H3.vue';
@@ -148,6 +146,7 @@ import Result from '@/components/feedback/Result.vue';
 import Dialog from '@/components/dialog/Dialog.vue';
 import HomeTitle from '@/common/components/parts/HomeTitle.vue';
 import HomeLargeTitle from '@/common/components/parts/HomeLargeTitle.vue';
+import type { RequestApiError } from '@imengyu/imengyu-utils';
 
 const showOnlinePreviewDialog = ref(false);
 const authStore = useAuthStore();
@@ -167,6 +166,10 @@ const volunteerInfoLoader = useSimpleDataLoader(async () => {
 }, true);
 const rankListLoader = useSimpleDataLoader(async () => await VillageApi.getVolunteerRanklist(), true);
 
+function checkIsNotVolunteerError(e: unknown) {
+  return (e as RequestApiError).errorMessage.includes('请认领')
+}
+
 watch(() => authStore.isLogged, (newVal) => {
   if (newVal) {
     villageListLoader.reload();

+ 42 - 28
src/pages/home/index.vue

@@ -106,25 +106,26 @@
     <VillageUserRankList :list="villageUserRankListLoader.content.value ?? []" />
 
     <HomeTitle title="精选记忆" showMore />
-    <MasonryGrid>
-      <MasonryGridItem
-        v-for="(item, i) in recommendLoader.list.value"
-        :key="i"
-        :width="340"
-      >
-        <IndexCommonImageItem
-          :image="item.image"
-          :title="item.title"
-          :desc="item.content ?? ''"
-          :userName="item.nickName ?? ''"
-          :likes="item.likeCount"
-          :isLike="false"
-          @click="goMessageDetails(item)"
-        />
-      </MasonryGridItem>
-    </MasonryGrid>
+    <SimplePageListLoader :loader="recommendLoader">
+      <MasonryGrid>
+        <MasonryGridItem
+          v-for="(item, i) in recommendLoader.list.value"
+          :key="i"
+          :width="340"
+        >
+          <IndexCommonImageItem
+            :image="item.image"
+            :title="item.title"
+            :desc="item.content ?? ''"
+            :userName="item.nickName ?? ''"
+            :likes="item.likeCount"
+            :isLike="false"
+            @click="goMessageDetails(item)"
+          />
+        </MasonryGridItem>
+      </MasonryGrid>
+    </SimplePageListLoader>
 
-    <Loadmore status="nomore" />
     <Height :height="150" />
 
     <Popup 
@@ -182,9 +183,12 @@ import StatusBarSpace from '@/components/layout/space/StatusBarSpace.vue';
 import FollowVillageApi from '@/api/light/FollowVillageApi';
 import LightVillageApi, { VillageListItem } from '@/api/light/LightVillageApi';
 import type { CityItem } from '@/api/map/MapApi';
+import SimplePageListLoader from '@/components/loader/SimplePageListLoader.vue';
+import { useAuthStore } from '@/store/auth';
 
 const emit = defineEmits(['goVillage']);
 
+const authStore = useAuthStore();
 const villageStore = useVillageStore();
 const themeContext = useTheme();
 const searchKeywords = ref('');
@@ -267,6 +271,25 @@ function handleSelectCity(city: CityItem) {
   handleChangedCity(city.name);
 }
 
+async function loadInfo() {
+  const res = await FollowVillageApi.getFollowVillageList({ page: 1, pageSize: 200 });
+  villageStore.setMyFollowVillages(res.list);
+  if (res.list.length > 0) {
+    const currentVillage = villageStore.loadCurrentVillage();
+    if (currentVillage) {
+      villageStore.setCurrentVillage(res.list.find(p => p.id === currentVillage) as VillageListItem || res.list[0]);
+    } else {
+      villageStore.setCurrentVillage(res.list[0]);
+    }
+  }
+}
+
+watch(() => authStore.isLogged, async (newVal) => {
+  if (newVal) {
+    await loadInfo();
+  }
+});
+
 onMounted(async () => {
   try {
     if (currentCity.value) {
@@ -278,15 +301,6 @@ onMounted(async () => {
     console.error(error);
     toast('获取当前位置失败,您可以手动选择城市');
   }
-  const res = await FollowVillageApi.getFollowVillageList({ page: 1, pageSize: 200 });
-  villageStore.setMyFollowVillages(res.list);
-  if (res.list.length > 0) {
-    const currentVillage = villageStore.loadCurrentVillage();
-    if (currentVillage) {
-      villageStore.setCurrentVillage(res.list.find(p => p.id === currentVillage) as VillageListItem || res.list[0]);
-    } else {
-      villageStore.setCurrentVillage(res.list[0]);
-    }
-  }
+  await loadInfo();
 });
 </script>

+ 6 - 4
src/pages/home/light/submit.vue

@@ -61,12 +61,12 @@ import DynamicForm from '@/components/dynamic/DynamicForm.vue';
 import { useAppInit } from '@/common/composeabe/AppInit';
 import { UserApi } from '@/api/auth/UserApi';
 import { useAuthStore } from '@/store/auth';
-import { back, redirectTo } from '@/components/utils/PageAction';
+import { back } from '@/components/utils/PageAction';
 import { closeToast, toast } from '@/components/dialog/CommonRoot';
 import { showError } from '@/common/composeabe/ErrorDisplay';
 import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
 import { onMounted, ref } from 'vue';
-import { getVolunteerForm } from '@/pages/dig/admin/data/volunteer';
+//import { getVolunteerForm } from '@/pages/dig/admin/data/volunteer';
 import VillageApi, { VolunteerInfo } from '@/api/inhert/VillageApi';
 import type { IDynamicFormOptions, IDynamicFormRef } from '@/components/dynamic';
 import { waitTimeOut } from '@imengyu/imengyu-utils';
@@ -89,13 +89,15 @@ onMounted(async () => {
     return;
   }
   await waitTimeOut(1000);
-  registerFormDefine.value = getVolunteerForm({
+  registerFormDefine.value = {
+    formItems: [],
+  }/* getVolunteerForm({
     canSetCatalog: false,
     villageId: querys.value.villageId,
     onlyPassword: false,
     isNew: ref(true),
     formRef: registerFormRef,
-  });
+  }) */;
 });
 
 async function loginWechat() {

+ 81 - 13
src/pages/home/post/publish.vue

@@ -26,10 +26,11 @@
             :padding="[40, 30]"
           >
             <Uploader 
-              v-model="images" 
+              ref="uploader"
               listType="grid"
               :maxUploadCount="9" 
               :upload="uploadImage"
+              @updateList="onUpdateList"
             />
             <Field v-model="title" type="text" placeholder="请输入标题" :maxLength="30" showWordLimit bac />
             <Field v-model="content" type="text" multiline placeholder="请输入内容" :maxLength="1000" rows="10" showWordLimit />
@@ -78,17 +79,19 @@
 import { onMounted, ref, watch } from 'vue';
 import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
 import { Debounce } from '@imengyu/imengyu-utils';
-import { confirm } from '@/components/dialog/CommonRoot';
+import { confirm, toast } from '@/components/dialog/CommonRoot';
+import { useAuthStore } from '@/store/auth';
+import { envVersion } from '@/common/config/AppCofig';
 import CommonContent from '@/api/CommonContent';
 import CommonRoot from '@/components/dialog/CommonRoot.vue';
 import BackgroundBox from '@/components/display/block/BackgroundBox.vue';
 import Field from '@/components/form/Field.vue';
-import Uploader from '@/components/form/Uploader.vue';
+import Uploader, { type UploaderInstance } from '@/components/form/Uploader.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
 import StatusBarSpace from '@/components/layout/space/StatusBarSpace.vue';
 import NavBar from '@/components/nav/NavBar.vue';
 import ProvideVar from '@/components/theme/ProvideVar.vue';
-import type { UploaderAction } from '@/components/form/Uploader';
+import type { UploaderAction, UploaderItem } from '@/components/form/Uploader';
 import Height from '@/components/layout/space/Height.vue';
 import ImageButton from '@/components/basic/ImageButton.vue';
 import FlexRow from '@/components/layout/FlexRow.vue';
@@ -97,23 +100,32 @@ import XBarSpace from '@/components/layout/space/XBarSpace.vue';
 import IconButton from '@/components/basic/IconButton.vue';
 import Popup from '@/components/dialog/Popup.vue';
 import Agent from './agent.vue';
+import LightVillageApi, { PostMessage } from '@/api/light/LightVillageApi';
 
 const { querys } = useLoadQuerys({
   tag: '',
   villageId: 0,
 });
+const authStore = useAuthStore();
 
 const title = ref('');
 const content = ref('');
-const images = ref([] as string[]);
+const images = ref<{
+  url: string;
+  localUrl: string;
+}[]>([]);
+
+const uploader = ref<UploaderInstance>();
 
 const showAgentPopup = ref(false);
 const saveDraftDebunce = new Debounce(1000, () => {
-  uni.setStorageSync('postDraft', {
-    title: title.value,
-    content: content.value,
-    images: images.value,
-  });
+  if (title.value || content.value || images.value.length > 0) {
+    uni.setStorageSync('postDraft', {
+      title: title.value,
+      content: content.value,
+      images: images.value,
+    });
+  }
 });
 
 watch(title, () => saveDraftDebunce.executeWithDelay());
@@ -127,11 +139,20 @@ function loadDraft() {
       content: '您有上次编辑未完成的草稿,是否要从上次的编辑数据继续?',
       confirmText: '继续',
       cancelText: '取消',
+      width: 580,
     }).then((res) => {
       if (res) {
         title.value = draft.title;
         content.value = draft.content;
         images.value = draft.images;
+        uploader.value?.setList(images.value.map((image) => ({
+          url: image.url,
+          type: 'image',
+          filePath: image.localUrl,
+          state: image.url ? 'success' : 'notstart',
+        })));
+      } else {
+        uni.removeStorageSync('postDraft');
       }
     });
   }
@@ -160,13 +181,60 @@ function uploadImage(item: UploaderAction) {
   };
 }
 function publish() {
-  console.log('publish', title.value, content.value, images.value);
+  if (images.value.length === 0) {
+    toast('请上传图片');
+    return;
+  }
+  const data = {
+    title: title.value,
+    content: content.value,
+    images: images.value.map((image) => image.localUrl),
+    tags: [
+      '亮乡源',
+      `${querys.value.tag || '亮乡源'}话题${querys.value.villageId ? `·${querys.value.villageId}` : ''}·${authStore.userInfo?.id || '0'}`
+    ],
+  };
+  console.log('data', data, authStore.userInfo);
+  (uni as any).shareToOfficialAccount({
+    ...data,
+    success: () => {
+      LightVillageApi.publishMessage(new PostMessage().setSelfValues({
+        title: title.value,
+        content: content.value,
+        images: images.value.map((image) => image.url),
+        image: images.value[0].url,
+        path: `/pages/index`,
+      })).then(() => {
+        console.log('发布成功');
+      }).catch((e) => {
+        console.error(e);
+      });
+      uni.removeStorageSync('postDraft');
+      toast('发布成功');
+      setTimeout(() => {
+        uni.navigateBack();
+      }, 1000);
+    },
+    fail: (error: any) => {
+      console.error(error);
+      toast('发布失败');
+    },
+  });
+}
+
+function onUpdateList(list: UploaderItem[]) {
+  images.value = list.map((item) => ({
+    url: item.uploadedPath || '',
+    localUrl: item.filePath,
+  }));
 }
 
 onMounted(() => {
-  loadDraft();
   setTimeout(() => {
-    showAgentPopup.value = true;
+    loadDraft();
+    if (envVersion === 'develop') {
+      showAgentPopup.value = true;
+    }
   }, 1000);
 });
 </script>

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

@@ -253,8 +253,10 @@ import IndexCommonImageItem from '@/common/components/parts/IndexCommonImageItem
 import FollowVillageApi from '@/api/light/FollowVillageApi';
 import LightVillageApi from '@/api/light/LightVillageApi';
 import SimplePageListLoader from '@/components/loader/SimplePageListLoader.vue';
+import { useReqireLogin } from '@/common/composeabe/RequireLogin';
 
 const authStore = useAuthStore();
+const { requireLogin } = useReqireLogin();
 const villageStore = useVillageStore();
 const villageInfoLoader = useSimpleDataLoader(async () => {
   const village = villageStore.currentVillage;  
@@ -316,14 +318,17 @@ const isJoined = computed(() => {
 });
 
 async function onFollow() {
-  if (!villageStore.currentVillage) return;
-  try {
-    await FollowVillageApi.followVillage(villageStore.currentVillage.id);
-    villageStore.myFollowVillages.push(villageStore.currentVillage);
-    toast('关注成功');
-  } catch {
-    toast('关注失败');
-  }
+  if (!villageStore.currentVillage) 
+    return;
+  requireLogin(async () => {
+    try {
+      await FollowVillageApi.followVillage(villageStore.currentVillage!.id);
+      villageStore.myFollowVillages.push(villageStore.currentVillage!);
+      toast('关注成功');
+    } catch {
+      toast('关注失败');
+    }
+  }, '登录后才能关注村庄哦');
 }
 function onUnFollow() {
   if (!villageStore.currentVillage) return;

+ 1 - 1
src/pages/index.vue

@@ -45,7 +45,7 @@ import StatusBarSpace from '@/components/layout/space/StatusBarSpace.vue';
 import NavBar from '@/components/nav/NavBar.vue';
 import TabBar from '@/components/nav/TabBar.vue';
 import TabBarItem from '@/components/nav/TabBarItem.vue';
-import DigIndex from './dig/index.vue';
+import DigIndex from './home/dig.vue';
 import UserIndex from './user/index.vue';
 import HomeIndex from './home/index.vue';
 import DiscoverIndex from './home/discover/index.vue';

+ 2 - 23
src/pages/user/login.vue

@@ -198,37 +198,16 @@ async function loginMobile() {
     await loginAfter(true);
 
   } catch (e) { 
+    console.error(e);
+    
     closeToast()
     showError(e); 
   }
 }
 async function loginAfter(isMobileLogin = false) {
   await waitTimeOut(200);
-
-  //检查是否有志愿者信息,跳转至不同的页面
-  //已认领志愿者,跳转至首页
-  //未认领志愿者,跳转至绑定账号页面
-  if (await checkAndGoBindVolunteer())
-    return;
-
   //刷新用户信息
   await init();
-
-  //如果用户未绑定微信,提示用户绑定微信
-  if (isMobileLogin && !authStore.userInfo?.openId && tipBindWechat.isTimeout()) {
-    tipBindWechat.recordTime();
-
-    if (await confirm({
-      title: '提示',
-      content: '绑定微信账号后登录更方便,是否前往绑定?',
-      confirmText: '前往绑定',
-      cancelText: '稍后绑定',
-      width: 580,
-    })) {
-      uni.redirectTo({ url: '/pages/dig/sharereg/bind-wx?fromLogin=true' });
-      return;
-    }
-  }
   redirectToIndex();
 }
 function redirectToIndex() {

BIN
src/static/EmptyImage.png


+ 1 - 1
src/store/collect.ts

@@ -1,7 +1,7 @@
 import { computed, ref } from 'vue'
 import { defineStore } from 'pinia'
 import { useAuthStore } from './auth';
-import { CollectableModulesNameMapping } from '@/pages/dig/forms/forms';
+import { CollectableModulesNameMapping } from '@/common/data/CollectableModulesNameMapping';
 import VillageApi from '@/api/inhert/VillageApi';
 
 /**