Prechádzať zdrojové kódy

修改细节问题,增加调试错误日志上报

快乐的梦鱼 1 mesiac pred
rodič
commit
79ccb51dc8

+ 20 - 2
src/App.vue

@@ -1,18 +1,21 @@
 <script setup lang="ts">
 import AppConfig, { isTestEnv } from '@/common/config/AppCofig'
-import { onLaunch } from '@dcloudio/uni-app'
+import { onError, onLaunch } from '@dcloudio/uni-app'
 import { useAuthStore } from './store/auth'
 import { configTheme } from './components/theme/ThemeDefine';
 import { getCurrentPageUrl, navTo } from './components/utils/PageAction';
-import { RequestApiConfig } from '@imengyu/imengyu-utils';
+import { RequestApiConfig, RequestApiError } from '@imengyu/imengyu-utils';
 import ApiCofig from './common/config/ApiCofig';
 import { useAppInit } from './common/composeabe/AppInit';
 import MemoryTimeOut from './common/composeabe/MemoryTimeOut';
+import BugReporter, { type BugDetailDeviceInfo } from './common/BugReporter';
+import { BugReporterAbstractionUniapp } from './common/BugReporter/impl/BugReporterAbstractionUniapp';
 
 const authStore = useAuthStore();
 const { init } = useAppInit();
 const redirectThrottle = new MemoryTimeOut('RedirectThrottle', 50000);
 
+
 onLaunch(async () => {
   console.log('App Launch');
 
@@ -29,9 +32,24 @@ onLaunch(async () => {
     }
   }
     
+  await BugReporter.checkAndReportBug();
   await init();
 });
 
+//配置BugReporter
+BugReporter.config({
+  abstractionLayer: new BugReporterAbstractionUniapp(),
+  serverUrl: ApiCofig.bugReport.server,
+  appId: ApiCofig.bugReport.appId,
+  appKey: ApiCofig.bugReport.appKey,
+});
+
+onError((err) => {
+  if (!((err as any) instanceof RequestApiError)) {
+    BugReporter.reportError(err);
+  }
+});
+
 //设置请求基础地址
 RequestApiConfig.setConfig({
   ...RequestApiConfig.getConfig(),

+ 27 - 12
src/api/RequestModules.ts

@@ -6,6 +6,7 @@
  * * 自定义错误报告处理函数。
  */
 
+import BugReporter from "@/common/BugReporter";
 import AppCofig, { isDev } from "../common/config/AppCofig";
 import ApiCofig from "@/common/config/ApiCofig";
 import { 
@@ -22,8 +23,10 @@ import type { DataModel, KeyValue, NewDataModel } from "@imengyu/js-request-tran
 /**
  * 不报告错误的 code
  */
-const notReportErrorCode = [401] as number[];
+const notReportErrorCode = [401,403,404] as number[];
 const notReportMessages = [
+  /请登录/g,
+  /认领/g,
   /请授权绑定手机号/g,
 ] as RegExp[];
 function matchNotReportMessage(str: string) {
@@ -202,18 +205,30 @@ export function reportError<T extends DataModel>(instance: RequestCoreInstance<T
     }
   } else {    
     let errMsg = '';
-    if (response instanceof RequestApiError)
-      errMsg = response.errorMessage + '。';
-      
-    errMsg += '服务出现了异常,请稍后重试或联系客服。';
-    errMsg += '版本:' + AppCofig.version;
+    if (response instanceof RequestApiError) {
+      BugReporter.reportRequestBug({
+        errorType: response.errorType,
+        errorMessage: response.errorMessage,
+        apiName: response.apiName,
+        apiUrl: response.apiUrl,
+        rawRequest: response.rawRequest,
+        rawData: response.rawData,
+        code: response.code.toString(),
+        errorCodeMessage: response.errorCodeMessage,
+        data: response.rawData,
+      });
+    } else  {
+      errMsg += '服务出现了异常,请稍后重试或联系客服。';
+      errMsg += '版本:' + AppCofig.version;
 
-    uni.showModal({
-      title: '抱歉',
-      content: errMsg,
-      showCancel: false,
-    });
-}
+      uni.showModal({
+        title: '抱歉',
+        content: errMsg,
+        showCancel: false,
+      });
+      BugReporter.reportError(response);
+    }
+  }
 }
 
 /**

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

@@ -200,6 +200,7 @@ export class VillageApi extends AppServerRequestModule<DataModel> {
   }
 
   async getClaimedVallageList(volunteerId?: string) {
+    
     return (this.get('/village/village/getVillageList', '获取已认领村落', {
       village_volunteer_id: volunteerId,
     })) 
@@ -207,6 +208,7 @@ export class VillageApi extends AppServerRequestModule<DataModel> {
       .catch(e => { throw e });
   }
   async getCanClaimVallageList() {
+    
     return (this.get('/village/village/getClaimList', '可认领村落列表', {
       main_body_id: 2,
     })) 
@@ -219,6 +221,7 @@ export class VillageApi extends AppServerRequestModule<DataModel> {
     }, '认领村落')) ;
   }
   async getVallageList(level?: number, status?: number) {
+    
     return (this.get('/village/village/getList', '村落列表', {
       history_level: level,
       status,
@@ -227,6 +230,7 @@ export class VillageApi extends AppServerRequestModule<DataModel> {
       .catch(e => { throw e });
   }
   async getVolunteerInfo() {
+    
     return (await this.post('/village/volunteer/getInfo', {
     }, '获取志愿者信息', undefined, VolunteerInfo)).data as VolunteerInfo
   }

+ 57 - 0
src/common/BugReporter/impl/BugReporterAbstractionUniapp.ts

@@ -0,0 +1,57 @@
+/**
+ * BUG数据提交工具层
+ * 
+ * Copyright © 2025 imengyu.top imengyu-bugreport-server
+ */
+
+import Constants, { isProd } from '@/common/config/AppCofig';
+import type { BugDetailDeviceInfo, BugReporterAbstractionLayer } from '../index'
+
+const tagString = '[BugReporter] ';
+const errStorageClientDataKey = 'BugReporterClientData';
+
+export class BugReporterAbstractionUniapp implements BugReporterAbstractionLayer {
+  log(str: string): void {
+    console.log(tagString + str);
+  }
+  async getDeviceInfo(): Promise<BugDetailDeviceInfo> {
+    let dataStorage = await this.getStorage(errStorageClientDataKey);
+    if (!dataStorage) {
+      const res = await uni.getSystemInfo({}) as unknown as Record<string, unknown>;
+
+      dataStorage = {
+        deviceName: res.mode + '/' + res.model + '/' + res.brand + '/' + res.pixelRatio,
+        deviceVersion: res.system + '/' + res.version + '/SDKVersion: ' + res.SDKVersion,
+        appVersion: Constants.version,
+      } as BugDetailDeviceInfo;
+      
+      await this.setStorage(errStorageClientDataKey, dataStorage);
+    }
+    return dataStorage as BugDetailDeviceInfo;
+  }
+  async enable(): Promise<boolean> {
+    return isProd;
+  }
+  async doPost(url: string, body: unknown): Promise<void> {
+    await uni.request({
+      url,
+      data: body as AnyObject,
+      method: 'POST'
+    });
+  }
+  async getStorage(key: string): Promise<unknown> {
+    try {
+      const str = await uni.getStorage({ key }) as unknown as Record<string, string>;
+      return str.data ? JSON.parse(str.data) : null;
+    } catch {
+      return null;
+    }
+  }
+  async setStorage(key: string, value: unknown): Promise<void> {
+    await uni.setStorage({ key, data: JSON.stringify(value) });
+  }
+  async reomveStorage(key: string): Promise<void> {
+    await uni.removeStorage({ key });
+  }
+}
+

+ 393 - 0
src/common/BugReporter/index.ts

@@ -0,0 +1,393 @@
+/**
+ * BUG数据提交工具层
+ * 
+ * Copyright © 2025 imengyu.top imengyu-bugreport-server
+ */
+
+export interface BugReporterConfig {
+  /**
+   * 设置抽象层以 BugReporter 正常工作
+   */
+  abstractionLayer: BugReporterAbstractionLayer,
+  /**
+   * 错误收集服务器URL
+   */
+  serverUrl: string,
+  /**
+   * 应用ID
+   */
+  appId: number,
+  /**
+   * 应用KEY
+   */
+  appKey: string,
+  /**
+   * 是否立即提交错误(通常用于调试),否则为定时收集错误发送。默认:false
+   */
+  reportImmediately?: boolean,
+  /**
+   * 定时收集错误模式时错误数量超过此数值,进行提交。默认:8
+   */
+  delayMaxCount?: number,
+  /**
+   * 定时收集错误模式时, 超过指定时间(天),则进行提交。默认:2
+   */
+  delayTime?: number,
+  /**
+   * 在应用一次运行中,如果相同数量的错误出现次数超出此阈值,则强制提交一次错误。默认:4
+   */
+  errorSameRunTimeLimit?: number,
+}
+export interface BugReporterAbstractionLayer {
+  /**
+   * 获取设备信息钩子
+   */
+  getDeviceInfo: () => Promise<BugDetailDeviceInfo>;
+  /**
+   * 输出调试日志的钩子函数,这个函数返回false,将不启用错误提交功能
+   * 通常用于在开发时禁止提交错误到服务器
+   */
+  log: (str: string) => void;
+  /**
+   * 是否启用
+   */
+  enable: () => Promise<boolean>;
+  /**
+   * 自定义HTTP请求钩子
+   */
+  doPost: (url: string, body: unknown) => Promise<void>;
+  /**
+   * 自定义获取存储钩子
+   */
+  getStorage: (key: string) => Promise<unknown>;
+  /**
+   * 自定义设置存储钩子
+   */
+  setStorage: (key: string, value: unknown) => Promise<void>;
+  /**
+   * 自定义删除存储钩子
+   */
+  reomveStorage: (key: string) => Promise<void>;
+}
+
+//========================================================
+
+export function stringHash(str: string) : string {
+  let hash = 0, i, chr;
+  if (str.length === 0) return hash.toString();
+  for (i = 0; i < str.length; i++) {
+    chr   = str.charCodeAt(i);
+    hash  = ((hash << 5) - hash) + chr;
+    hash |= 0; // Convert to 32bit integer
+  }
+  return hash.toString();
+}
+function pad(num: number|string, n: number) : string {
+  let str = num.toString();
+  let len = str.length;
+  while (len < n) {
+    str = "0" + num;
+    len++;
+  }
+  return str;
+}
+function formatDate(date: Date, formatStr?: string) {
+  let str = formatStr ? formatStr : "YYYY-MM-dd HH:ii:ss";
+  str = str.replace(/yyyy|YYYY/, date.getFullYear().toString());
+  str = str.replace(/MM/, pad(date.getMonth() + 1, 2));
+  str = str.replace(/M/, (date.getMonth() + 1).toString());
+  str = str.replace(/dd|DD/, pad(date.getDate(), 2));
+  str = str.replace(/d/, date.getDate().toString());
+  str = str.replace(/HH/, pad(date.getHours(), 2));
+  str = str.replace(/hh/, pad(date.getHours() > 12 ? date.getHours() - 12 : date.getHours(), 2));
+  str = str.replace(/mm/, pad(date.getMinutes(), 2));
+  str = str.replace(/ii/, pad(date.getMinutes(), 2));
+  str = str.replace(/ss/, pad(date.getSeconds(), 2));
+  return str;
+}
+
+//========================================================
+
+const errStorageKey = 'BugReporterStorage';
+const errStorageCountKey = 'BugReporterStorageCount';
+const errStorageTimeKey = 'BugReporterStorageLastSubmitTime';
+const errStorageCustomKey = 'BugReporterStorageData';
+
+//========================================================
+
+const customDataArr = new Map<string, string>();
+const onceTimeHashCheck = new Map<string, number>();
+const defaultConfig : BugReporterConfig = {
+  abstractionLayer: {} as BugReporterAbstractionLayer,
+  serverUrl: '',
+  appId: 0,
+  appKey: '',
+  reportImmediately: false,
+  delayMaxCount: 8,
+  delayTime: 2,
+  errorSameRunTimeLimit: 4,
+};
+let abstractionLayer : BugReporterAbstractionLayer = {} as BugReporterAbstractionLayer;
+let config : BugReporterConfig = {} as BugReporterConfig;
+
+//========================================================
+
+const BugReporter = {
+  /**
+   * 配置
+   */
+  config(configData: BugReporterConfig) : void {
+    abstractionLayer = configData.abstractionLayer;
+    config = {
+      ...defaultConfig,
+      ...configData,
+    };
+    if (!abstractionLayer)
+      throw new Error('[BugReporter] You have not configured abstractionLayer, so you cannot use BugReporter.');
+    if (!configData.serverUrl)
+      abstractionLayer.log('serverUrl is not configured!');
+    if (!configData.appId)
+      abstractionLayer.log('appId is not configured!');
+    if (!configData.appKey)
+      abstractionLayer.log('appKey is empty!');
+  },
+  /**
+   * 添加与错误一并提交的自定义数据。注意,自定义数据不是持久保存的,在应用关闭后会丢失
+   * @param key 
+   * @param value 
+   */
+  addCustomData(key: string, value: string) : void {
+    customDataArr.set(key, value);
+  },
+  /**
+   * 获取之前设置的自定义数据
+   * @param key 
+   */
+  getCustomData(key: string) : string|null {
+    return customDataArr.get(key) || null;
+  },
+  /**
+   * 删除与错误一并提交的自定义数据
+   * @param key 
+   */
+  deleteCustomData(key: string) : void {
+    customDataArr.delete(key);
+  },
+  /**
+   * 设置与错误一并提交的用户ID数据
+   * @param userId 
+   */
+  async setUserId(userId: string) : Promise<void> {
+    await abstractionLayer.setStorage(errStorageCustomKey + 'UserId', userId);
+  },
+  /**
+   * 获取之前设置的用户ID
+   * @returns 
+   */
+  async getUserId(): Promise<string | null> {
+    return await abstractionLayer.getStorage(errStorageCustomKey + 'UserId') as string|null;
+  },
+  /**
+   * 程序启动时收集错误信息并提交
+   */
+  async checkAndReportBug() : Promise<boolean> {
+    if(!await abstractionLayer.enable())
+      return false;
+      
+    const errorCount = await abstractionLayer.getStorage(errStorageCountKey) as number || 0;
+    const submitLastTime = new Date(await abstractionLayer.getStorage(errStorageTimeKey) as string);
+  
+    if (
+      errorCount > 0 && 
+      (
+        errorCount > (config.delayMaxCount as number) 
+        || new Date().getTime() - submitLastTime.getTime() > (config.delayTime as number) * 1000 * 60 * 60 * 24
+      )
+    ) {
+      abstractionLayer.log('The number or time exceeds the threshold, now submit bug ');
+      abstractionLayer.log('Last submit time ' + formatDate(submitLastTime));
+      abstractionLayer.log('Error count ' + errorCount + '/' + config.delayMaxCount);
+  
+      this.reportBugsInStorage();
+      return true;
+    } else {
+      abstractionLayer.log('No need to submit bug now');
+    }
+   
+    return false;
+  },
+  /**
+   * 提交在暂存区中的错误信息
+   */
+  async reportBugsInStorage() : Promise<void> {
+    if(!await abstractionLayer.enable())
+      return ;
+   
+    const errorCount = await abstractionLayer.getStorage(errStorageCountKey) as number || 0;
+    abstractionLayer.log('Error count ' + errorCount + '/' + config.delayMaxCount);
+
+    //开始提交
+    const bugArr = [] as unknown[];
+    for(let i = 0; i < errorCount; i++) {
+      const obj = await abstractionLayer.getStorage(errStorageKey + i);
+      if (obj && typeof obj === 'object' && typeof (obj as Record<string, unknown>).errorType === 'number')
+        bugArr.push(obj);
+    }
+
+    abstractionLayer.log('Submit error now, count ' + bugArr.length + ' storage count: ' + errorCount);
+
+    try {
+      //立即提交错误
+      abstractionLayer.doPost(config.serverUrl, {
+        appId: config.appId,
+        app_key: config.appKey,
+        data: {
+          errorList: bugArr
+        }
+      })
+    } catch (e) {
+      abstractionLayer.log('Failed to report bug, will submit next time error: ' + e);
+      
+      //太多错误堆积,需要删除一些
+      if (errorCount > 256) {
+        abstractionLayer.log('Too many errors, clear some errors ' + (errorCount / 3));
+        for(let i = 0; i < errorCount / 3; i++)
+          abstractionLayer.reomveStorage(errStorageKey + i);
+      }
+      return;
+    }
+
+    //清除之前存储的数据
+    for(let i = 0; i < errorCount; i++)
+      abstractionLayer.reomveStorage(errStorageKey + i);
+    abstractionLayer.setStorage(errStorageCountKey, 0);
+    abstractionLayer.log('Clear bug storage count: ' + errorCount);
+    abstractionLayer.setStorage(errStorageTimeKey, new Date().toISOString()); //设置当前时间
+  },
+  /**
+   * 提交Bug
+   * @param type 0 普通Bug 1 请求Bug 2 崩溃Bug  3 其他
+   * @param hash 对这个错误信息进行归类的hash,当相同hash错误发生时,将在后台系统中归类显示,有助于您查找Bug
+   * @param summary 对这个错误进行简要描述的文案,有助于您查找Bug
+   * @param data 主要错误信息
+   * @param extendData 附加错误信息
+   */
+  async reportBug(type: number, hash: string, summary: string, data: unknown, extendData?: Record<string, string>) : Promise<void> {
+    if(!await abstractionLayer.enable())
+      return;
+
+    //获取设备信息
+    const deviceInfo = await abstractionLayer.getDeviceInfo();
+  
+    //追加自定义数据
+    const finalExtendData : Record<string, string> = extendData || {};
+    customDataArr.forEach((v, k) => finalExtendData[k] = v);
+
+    //检查本次运行错误数量
+    let forceReportThisTime = false;
+    const onceTimeCount = onceTimeHashCheck.get(hash);
+    if (onceTimeCount) {
+      onceTimeHashCheck.set(hash, onceTimeCount + 1);
+      if (onceTimeCount > (config.errorSameRunTimeLimit as number))
+        forceReportThisTime = true;
+    } else {
+      onceTimeHashCheck.set(hash, 1);
+    }
+  
+    //错误对象
+    const errorObj = {
+      errorHash: hash,
+      errorSummary: summary,
+      errorData: JSON.stringify(data),
+      errorDeviceName: deviceInfo.deviceName,
+      errorDeviceVersion: deviceInfo.deviceVersion,
+      errorAppVersion: deviceInfo.appVersion,
+      errorUserId: await this.getUserId(),
+      errorAdditionData: JSON.stringify(finalExtendData),
+      errorTime: formatDate(new Date()),
+      errorType: type
+    };
+  
+    if (config.reportImmediately) {
+      try {
+        //立即提交错误
+        abstractionLayer.doPost(config.serverUrl, {
+          appId: config.appId,
+          appKey: config.appKey,
+          data: {
+            errorList: [ errorObj ]
+          }
+        });
+      } catch (e) {
+        abstractionLayer.log('Failed to report bug. error: ' + e);
+      }
+    } else {
+      //存储当前条目至存储中
+      const errorCount = await abstractionLayer.getStorage(errStorageCountKey) as number || 0;
+      abstractionLayer.setStorage(errStorageKey + errorCount, errorObj);//设置存储
+      abstractionLayer.setStorage(errStorageCountKey, errorCount + 1);//数量+1
+      
+      //检查相关阈值,并提交错误
+      if (forceReportThisTime)
+        this.reportBugsInStorage();
+      else
+        await this.checkAndReportBug();
+    }
+  },
+  /**
+   * 提交请求类错误
+   * @param data 主要错误信息
+   * @param extendData 附加错误信息
+   */
+  async reportRequestBug(data: BugDetailErrorRequestData, extendData?: Record<string, string>) : Promise<void> {
+    await this.reportBug(1, stringHash(data.errorMessage), `请求错误:${data.errorMessage}`, data, extendData);
+  },
+  /**
+   * 提交崩溃类错误
+   * @param data 主要错误信息
+   * @param extendData 附加错误信息
+   */
+  async reportCrashBug(data: BugDetailErrorCrashData, extendData?: Record<string, string>) : Promise<void> {
+    await this.reportBug(2, stringHash(data.errorMessage), `崩溃:${data.errorMessage}`, data, extendData);
+  },
+  /**
+   * 提交普通错误
+   * @param e 主要错误信息
+   * @param extendData 附加错误信息
+   */
+  async reportError(e: Error|string, extendData?: Record<string, string>) : Promise<void> {
+    await this.reportBug(2, 
+      stringHash(typeof e === 'string' ? e : (e.message || ('' + e))), 
+      '' + e, 
+      (e instanceof Error ? e.stack : ''), 
+      extendData
+    );
+  }
+}
+
+export interface BugDetailDeviceInfo {
+  deviceName: string;
+  deviceVersion: string;
+  appVersion: string;
+}
+export interface BugDetailErrorRequestData {
+  apiName: string;
+  apiUrl: string;
+  code: string;
+  errorType: string;
+  errorMessage: string;
+  errorCodeMessage: string;
+  data: unknown;
+  rawData: unknown;
+  rawRequest: unknown;
+}
+export interface BugDetailErrorCrashData {
+  deviceName: string;
+  deviceRom: string;
+  deviceSystemVersion: string;
+  errorCode: string;
+  errorMessage: string;
+  errorStack: string;
+}
+
+export default BugReporter;

+ 1 - 0
src/common/components/SimplePageContentLoader.vue

@@ -103,6 +103,7 @@ function handleLoad() {
 
 <style lang="scss">
 .loader-view {
+  position: relative;
   min-height: 200rpx;
 
   &.center {

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

@@ -1,13 +1,18 @@
 import { useCollectStore } from "@/store/collect";
+import BugReporter from "../BugReporter";
+import { useAuthStore } from "@/store/auth";
 
 export function useAppInit() {
   
   const collectStore = useCollectStore();
+  const authStore = useAuthStore();
 
   return {
     async init() {
       //加载采集板块信息
       await collectStore.loadCollectableModules();
+      //设置日志用户ID
+      BugReporter.setUserId(authStore.userInfo?.id.toString() || '');
     },
   }
 }

+ 5 - 0
src/common/config/ApiCofig.ts

@@ -8,4 +8,9 @@ export default {
   amapServerKey: '8fd09264c33678141f609588c432df0e',
   mainBodyId: 1,
   platformId: 330,
+  bugReport: {
+    server: 'https://update-server1.imengyu.top/bug-submit',
+    appId: 1,
+    appKey: 'PSzTR2h8c547pNZGERsH3pJRTPhexzc6WSZwdGrQdJFHmXR2',
+  }
 }

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

@@ -1,9 +1,13 @@
+const buildTime = `${__BUILD_TIMESTAMP__}`
+const buildInfo = `${__BUILD_GUID__}`
 
 /**
  * 说明:应用静态配置
  */
 export default {
   version: '1.0.0',
+  buildTime,
+  buildInfo,
   appId: 'wx954621c03f2fa912',
   qqMapKey: 'TOIBZ-CA4WB-OFQUF-J3XG4-EEB2J-DXBX7',
   amapKey: '34eb1d57f93720a871bd11a90af0c91c',
@@ -26,7 +30,8 @@ export default {
 export function configAiMap() {
 }
 
-export const isDev = process.env.NODE_ENV === 'development';
+export const isDev = import.meta.env.DEV;
+export const isProd = import.meta.env.PROD;
 
 const accountInfo = uni.getAccountInfoSync();
 export const envVersion = accountInfo.miniProgram.envVersion;

+ 1 - 1
src/components/basic/ActivityIndicator.vue

@@ -24,7 +24,7 @@ export interface ActivityIndicatorProps {
    */
   color?: string,
   /**
-   * 加载中圆圈颜色
+   * 加载中圆圈大小
    */
   size?: string|number,
   /**

+ 5 - 5
src/components/dialog/DialogRoot.vue

@@ -15,7 +15,7 @@ export interface DialogAlertOptions extends Omit<DialogProps, 'show'> {
 }
 export interface DialogAlertRoot {
   confirm(options: DialogAlertOptions): Promise<boolean>;
-  alert(options: DialogAlertOptions): Promise<void>;
+  alert(options: DialogAlertOptions): Promise<string|undefined>;
 }
 
 const show = ref(false);
@@ -56,15 +56,15 @@ defineExpose<DialogAlertRoot>({
 
     const onConfirm = _options.onConfirm;
 
-    return new Promise<void>((resolve) => {
+    return new Promise<string|undefined>((resolve) => {
       (_options as any).onClose = () => {
         show.value = false;
-        resolve();
+        resolve(undefined);
       };
-      _options.onConfirm = () => {
+      _options.onConfirm = (buttonName?: string) => {
         show.value = false;
         onConfirm?.();
-        resolve();
+        resolve(buttonName);
       };
       options.value = _options;
       show.value = true;

+ 0 - 4
src/pages/home/discover/index.vue

@@ -49,17 +49,13 @@ import { navTo } from '@/components/utils/PageAction';
 import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
 import Box from '@/common/components/parts/Box.vue';
 import SimplePageContentLoader from '@/common/components/SimplePageContentLoader.vue';
-import Image from '@/components/basic/Image.vue';
-import Text from '@/components/basic/Text.vue';
 import Loadmore from '@/components/display/loading/Loadmore.vue';
-import Touchable from '@/components/feedback/Touchable.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
 import Height from '@/components/layout/space/Height.vue';
 import ProvideVar from '@/components/theme/ProvideVar.vue';
 import Grid from '@/components/layout/grid/Grid.vue';
 import GridItem from '@/components/layout/grid/GridItem.vue';
 import VillageInfoApi from '@/api/inhert/VillageInfoApi';
-import AppCofig from '@/common/config/AppCofig';
 import Tag from '@/components/display/Tag.vue';
 import ImageBlock2 from '@/components/display/block/ImageBlock2.vue';
 

+ 2 - 2
src/pages/index.vue

@@ -63,13 +63,13 @@ const tabIndex = ref(0);
 onShareAppMessage(() => {
   return {
     title: '村社文化资源挖掘平台',
-    path: '/pages/index/index',
+    path: '/pages/index',
   }
 })
 onShareTimeline(() => {
   return {
     title: '村社文化资源挖掘平台',
-    path: '/pages/index/index',
+    path: '/pages/index',
   }
 })
 </script>

+ 58 - 0
src/pages/user/debug/DebugButton.vue

@@ -0,0 +1,58 @@
+<template>
+  <Touchable direction="column" center :padding="40" :gap="10" @click="showBuildInfo">
+    <Text 
+      color="text.second"
+      :fontSize="22"
+      :text="`软件版本 ${AppCofig.version}`"
+    />
+  </Touchable>
+</template>
+
+<script setup lang="ts">
+import { DateUtils } from '@imengyu/imengyu-utils';
+import { alert } from '@/components/dialog/CommonRoot';
+import Text from '@/components/basic/Text.vue';
+import Touchable from '@/components/feedback/Touchable.vue';
+import BugReporter from '@/common/BugReporter';
+import AppCofig, { isDev, isTestEnv } from '@/common/config/AppCofig';
+
+const showAct = isDev || isTestEnv;
+
+function showBuildInfo() {
+  alert({
+    title: '关于程序',
+    content: '版本: ' + AppCofig.version + (showAct ?
+     ('\n构建时间:' + DateUtils.formatDate(new Date(parseInt(AppCofig.buildTime)), 'yyyy-MM-dd HH:mm:ss') + 
+        ' (' + AppCofig.buildTime + ')' + 
+     '\n构建GUID:' + AppCofig.buildInfo) : ''),
+    icon: 'prompt-filling',
+    iconColor: 'primary',
+    customButtons: showAct ? [
+      {
+        text: '测试提交BUG(1)',
+        name: 'testBug',
+      },
+      {
+        text: '测试异常BUG(2)',
+        name: 'testBug2',
+      },
+    ] : [],
+    bottomVertical: true,
+    width: 560,
+  }).then((res) => {
+    if (res == 'testBug') {
+      uni.showToast({
+        title: '测试提交BUG(1)',
+        icon: 'none',
+      });
+      BugReporter.reportError(new Error('测试提交BUG(1)'));
+    } else if (res == 'testBug2') {
+      uni.showToast({
+        title: '测试异常BUG(2)',
+        icon: 'none',
+      });
+      throw new Error('测试异常BUG(2)');
+    }
+  });
+}
+</script>

+ 4 - 25
src/pages/user/index.vue

@@ -37,22 +37,16 @@
       <Cell icon="/static/images/user/icon-chat.png" title="商务合作" showArrow touchable @click="navTo('/pages/home/about/contract')" />
       <Cell v-if="userInfo" icon="/static/images/user/icon-quit.png" title="退出登录" showArrow touchable @click="doLogout" />
     </CellGroup>
-    <Touchable direction="column" center :padding="40" :gap="10" @click="showBuildInfo">
-      <Text 
-        color="text.second"
-        :fontSize="22"
-        :text="`软件版本 ${AppCofig.version}`"
-      />
-    </Touchable>
+    <DebugButton />
   </FlexCol>
 </template>
 
 <script setup lang="ts">
 import { computed } from 'vue';
 import { navTo } from '@/components/utils/PageAction';
-import { alert, confirm } from '@/components/dialog/CommonRoot';
-import { DateUtils } from '@imengyu/imengyu-utils';
+import { confirm } from '@/components/dialog/CommonRoot';
 import { useAuthStore } from '@/store/auth';
+import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
 import UserHead from '@/static/images/user/avatar.png';
 import CellGroup from '@/components/basic/CellGroup.vue';
 import Cell from '@/components/basic/Cell.vue';
@@ -61,30 +55,15 @@ import H4 from '@/components/typography/H4.vue';
 import Height from '@/components/layout/space/Height.vue';
 import Touchable from '@/components/feedback/Touchable.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
-import Text from '@/components/basic/Text.vue';
 import AppCofig from '@/common/config/AppCofig';
-import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
 import VillageApi from '@/api/inhert/VillageApi';
+import DebugButton from './debug/DebugButton.vue';
 
 const authStore = useAuthStore();
 const userInfo = computed(() => authStore.isLogged ? authStore.userInfo : null);
 const isBindWx = computed(() => Boolean(userInfo.value?.openId));
 const volunteerInfoLoader = useSimpleDataLoader(async () => await VillageApi.getVolunteerInfo(), true);
-const buildTime = `${__BUILD_TIMESTAMP__}`
-const buildInfo = `${__BUILD_GUID__}`
 
-function showBuildInfo() {
-  alert({
-    title: '关于程序',
-    content: '版本: ' + AppCofig.version +
-     '\n构建时间:' + DateUtils.formatDate(new Date(parseInt(buildTime)), 'yyyy-MM-dd HH:mm:ss') + 
-        ' (' + buildTime + ')' + 
-     '\n构建GUID:' + buildInfo,
-     icon: 'prompt-filling',
-     iconColor: 'primary',
-     width: 560,
-  })
-}
 function goUserProfile() {
   userInfo.value ? navTo('/pages/user/update/profile') : navTo('/pages/user/login');
 }