Bläddra i källkod

⚙️ 更新组件库

快乐的梦鱼 3 veckor sedan
förälder
incheckning
d875a54940

+ 1 - 3
src/components/basic/BackgroundImageButton.vue

@@ -4,9 +4,7 @@
     :innerStyle="style"
     @click="emit('click')"
   >
-    <BackgroundBox >
-      <slot />
-    </BackgroundBox>
+    <slot />
   </Touchable>
 </template>
 

+ 8 - 0
src/components/form/CascadePicker.vue

@@ -62,6 +62,7 @@ const props = withDefaults(defineProps<CascadePickerProps>(), {
 
 const pickerVisibleCols = ref<CascadePickerItem[][]>([]);
 const pickerSelectIndex = ref<number[]>([]);
+const currentText = ref<string>('');
 
 function bindChange(e: any) {
   const val = e.detail.value as number[];
@@ -94,6 +95,7 @@ function bindChange(e: any) {
 
   emit('update:value', resultValue);
   emit('selectTextChange', selectText.join(' '));
+  currentText.value = selectText.join(' ');
 }
 function loadCols() {
   const selectText : string[] = [];
@@ -111,6 +113,7 @@ function loadCols() {
     } 
   }
   emit('selectTextChange', selectText.join(' '), true);
+  currentText.value = selectText.join(' ');
 }
 
 watch(() => props.value, (v) => {
@@ -120,6 +123,11 @@ onMounted(() => {
   loadCols();
 })
 
+defineExpose({
+  getSelectedText: () => {
+    return currentText.value;
+  },
+});
 defineOptions({
   options: {
     styleIsolation: "shared",

+ 8 - 4
src/components/form/CascadePickerField.vue

@@ -13,9 +13,9 @@
       @confirm="onConfirm"
     />
     <CascadePicker 
+      ref="pickerRef"
       v-bind="props"
-      v-model:value="tempValue" 
-      @selectTextChange="onSelectTextChange"
+      v-model:value="tempValue"
     />
   </Popup>
   <Text
@@ -90,7 +90,7 @@ const props = withDefaults(defineProps<CascadePickerFieldProps>(), {
 });
 
 const popupShow = ref(false);
-
+const pickerRef = ref();
 const {
   value,
   updateValue,
@@ -105,7 +105,6 @@ const {
 );
 
 const {
-  onSelectTextChange,
   onCancel,
   onConfirm,
   selectText,
@@ -117,6 +116,11 @@ const {
   emit as any,
   [],
   props.shouldUpdateValueImmediately,
+  (v) => {
+    if (!v || v.length === 0)
+      return '';
+    return pickerRef.value?.getSelectedText() ?? '';
+  },
   undefined,
   popupShow,
 );

+ 6 - 14
src/components/form/CascaderField.vue

@@ -16,7 +16,6 @@
       v-bind="props"
       :modelValue="tempValue"
       @update:modelValue="(v:any) => tempValue = v"
-      @selectTextChange="onSelectTextChange"
       @pickEnd="onPickEnd"
     />
     <slot name="footer">
@@ -35,7 +34,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, toRef, watch } from 'vue';
+import { ref, toRef } from 'vue';
 import { useFieldChildValueInjector } from './FormContext';
 import { usePickerFieldTempStorageData } from './PickerUtils';
 import type { CascaderProps } from './Cascader.vue';
@@ -124,7 +123,6 @@ const {
 );
 
 const {
-  onSelectTextChange,
   onCancel,
   onConfirm,
   selectText,
@@ -136,6 +134,11 @@ const {
   emit as any,
   [],
   props.shouldUpdateValueImmediately,
+  (v) => {
+    if (!v || v.length === 0)
+      return '';
+    return getCascaderText(v, props.valueKey, props.textKey, props.childrenKey, props.data);
+  },
   props.beforeConfirm,
   popupShow,
 );
@@ -145,17 +148,6 @@ function onPickEnd() {
     onConfirm();
 }
 
-watch(tempValue, (v) => {
-  if (!popupShow.value)
-    onSelectTextChange(getCascaderText(
-      v, 
-      props.valueKey, 
-      props.textKey, 
-      props.childrenKey, 
-      props.data
-    ), true);
-}, { immediate: true })
-
 defineExpose({
   confirm: onConfirm,
   cancel: onCancel,

+ 5 - 2
src/components/form/DatePicker.vue

@@ -110,6 +110,8 @@ const columns = computed(() => {
   return cols;
 });
 
+let forceUpdate = true;
+
 // 更新选中的值
 function updateValue(v: number[]) {
   if (props.modelValue || v.length > 0) {
@@ -130,10 +132,11 @@ function updateValue(v: number[]) {
       }
       emit('update:modelValue', date);
     }
-    emit('selectTextChange', DateUtils.formatDate(date, 'yyyy-MM-dd'));
+    emit('selectTextChange', DateUtils.formatDate(date, 'yyyy-MM-dd'), forceUpdate);
   } else {
-    emit('selectTextChange', '');
+    emit('selectTextChange', '', forceUpdate);
   }
+  forceUpdate = false;
 }
 onMounted(() => updateValue([]));
 

+ 3 - 2
src/components/form/DatePickerField.vue

@@ -15,7 +15,6 @@
     <DatePicker 
       v-bind="props"
       v-model="tempValue"
-      @selectTextChange="onSelectTextChange"
     />
   </Popup>
   <Text
@@ -38,6 +37,7 @@ import DatePicker from './DatePicker.vue';
 import { usePickerFieldTempStorageData } from './PickerUtils';
 import Text, { type TextProps } from '../basic/Text.vue';
 import { usePickerFieldInstance, type PickerFieldInstance } from './Picker';
+import { DateUtils } from '@imengyu/imengyu-utils';
 
 export interface DatePickerFieldProps extends Omit<DatePickerProps, 'modelValue'> {
   modelValue?: Date;
@@ -105,7 +105,6 @@ const {
 );
 
 const {
-  onSelectTextChange,
   onCancel,
   onConfirm,
   selectText,
@@ -117,10 +116,12 @@ const {
   emit as any,
   new Date(),
   props.shouldUpdateValueImmediately,
+  (v) => DateUtils.formatDate(v, 'yyyy-MM-dd'),
   undefined,
   popupShow,
 );
 
+
 defineExpose<PickerFieldInstance>(usePickerFieldInstance(popupShow));
 defineOptions({
   options: {

+ 5 - 2
src/components/form/DateTimePicker.vue

@@ -167,6 +167,8 @@ const columns = computed(() => {
   return cols;
 });
 
+let forceUpdate = true;
+
 // 更新选中的值
 function updateValue(v: number[]) {
   if (props.modelValue || v.length > 0) {
@@ -198,10 +200,11 @@ function updateValue(v: number[]) {
       }
       emit('update:modelValue', date);
     }
-    emit('selectTextChange', DateUtils.formatDate(date, 'yyyy-MM-dd HH:mm:ss')); 
+    emit('selectTextChange', DateUtils.formatDate(date, 'yyyy-MM-dd'), forceUpdate);
   } else {
-    emit('selectTextChange', '');
+    emit('selectTextChange', '', forceUpdate);
   }
+  forceUpdate = false;
 }
 onMounted(() => updateValue([]));
 

+ 2 - 2
src/components/form/DateTimePickerField.vue

@@ -15,7 +15,6 @@
     <DateTimePicker 
       v-bind="props"
       v-model="tempValue"
-      @selectTextChange="onSelectTextChange"
     />
   </Popup>
   <Text
@@ -38,6 +37,7 @@ import DateTimePicker from './DateTimePicker.vue';
 import { usePickerFieldTempStorageData } from './PickerUtils';
 import Text, { type TextProps } from '../basic/Text.vue';
 import { usePickerFieldInstance, type PickerFieldInstance } from './Picker';
+import { DateUtils } from '@imengyu/imengyu-utils';
 
 export interface DateTimePickerFieldProps extends Omit<DateTimePickerProps, 'modelValue'> {
   modelValue?: Date;
@@ -108,7 +108,6 @@ const {
 );
 
 const {
-  onSelectTextChange,
   onCancel,
   onConfirm,
   selectText,
@@ -120,6 +119,7 @@ const {
   emit as any,
   new Date(),
   props.shouldUpdateValueImmediately,
+  (v) => DateUtils.formatDate(v, 'yyyy-MM-dd HH:mm:ss'),
   undefined,
   popupShow,
 );

+ 7 - 3
src/components/form/Field.vue

@@ -179,7 +179,7 @@
         <text :style="themeStyles.extraMessageText.value">{{extraMessage}}</text>
       </FlexRow>
       <text v-if="showWordLimit" :style="themeStyles.wordLimitText.value">{{wordLimitText}}</text>
-
+      <slot name="extra" />
     </FlexCol>
     <!-- 清除按钮 -->
     <IconButton
@@ -709,9 +709,13 @@ onMounted(() => {
 });
 
 function emitChangeText(text: string) {
-  emit('update:modelValue', text);
+  let value: number | string = text;
+  if (props.type === 'number')
+    value = Number(text);
+   
+  emit('update:modelValue', value);
   inputValue.value = text;
-  formItemContext.onFieldChange(text);
+  formItemContext.onFieldChange(value);
 }
 function doFormatter(text: string) {
   switch (props.type) {

+ 12 - 0
src/components/form/Picker.vue

@@ -59,6 +59,7 @@ const emit = defineEmits([ 'update:value', 'selectTextChange' ]);
 const props = withDefaults(defineProps<PickerProps>(), {
   pickerHeight: 300,
   pickerWidth: 750,
+  columns: () => [],
 });
 
 const loaded = ref(false);
@@ -117,6 +118,17 @@ onMounted(() => {
 const themeStyles = themeContext.useThemeStyles({
 });
 
+defineExpose({
+  refresh: loadValues,
+  getSelectedText: () => {
+    return pickerSelectIndex.value.map((p, i) => {
+      const cols = props.columns[i];
+      if (!cols || cols.length === 0) 
+        return null;
+      return cols[p]?.text ?? cols[0]?.text ?? null;
+    }).join(' ');
+  },
+});
 defineOptions({
   options: {
     styleIsolation: "shared",

+ 7 - 2
src/components/form/PickerField.vue

@@ -13,9 +13,9 @@
       @confirm="onConfirm"
     />
     <Picker 
+      ref="pickerRef"
       v-bind="props"
       v-model:value="tempValue"
-      @selectTextChange="onSelectTextChange"
     />
   </Popup>
   <Text
@@ -93,6 +93,7 @@ const props = withDefaults(defineProps<PickerFieldProps>(), {
 });
 
 const popupShow = ref(false);
+const pickerRef = ref();
 
 const {
   value,
@@ -108,7 +109,6 @@ const {
 );
 
 const {
-  onSelectTextChange,
   onCancel,
   onConfirm,
   selectText,
@@ -120,6 +120,11 @@ const {
   emit as any,
   [],
   props.shouldUpdateValueImmediately,
+  (v) => {
+    if (!v || v.length === 0)
+      return '';
+    return pickerRef.value?.getSelectedText() ?? '';
+  },
   undefined,
   popupShow,
 );

+ 28 - 36
src/components/form/PickerUtils.ts

@@ -1,18 +1,18 @@
-import { nextTick, ref, watch, type Ref } from "vue";
+import { onMounted, ref, watch, type Ref } from "vue";
 
 /**
  * 选择器字段临时存储数据的组合式函数
  * 用于管理选择器组件的临时值、选择文本和交互逻辑
  * 
  * @template T - 值的类型
- * @param 当前值的响应式引用
- * @param 更新值的回调函数
- * @param 关闭弹窗的回调函数
- * @param 事件发射器函数
- * @param 默认的新值
- * @param 是否立即更新值
- * @param 确认前的回调函数,返回true时取消确认
- * @param 弹窗显示状态的响应式引用
+ * @param value - 当前值的响应式引用
+ * @param updateValue - 更新值的回调函数
+ * @param closePopup - 关闭弹窗的回调函数
+ * @param emit - 事件发射器函数
+ * @param defaultNewValue - 默认的新值
+ * @param shouldUpdateValueImmediately - 是否立即更新值至绑定值
+ * @param beforeConfirm - 确认前的回调函数,返回true时取消确认
+ * @param popupShow - 弹窗显示状态的响应式引用
  */
 export function usePickerFieldTempStorageData<T>(
   value: Ref<T>, 
@@ -21,36 +21,25 @@ export function usePickerFieldTempStorageData<T>(
   emit: (name: string, d?: any) => void,
   defaultNewValue: T,
   shouldUpdateValueImmediately: boolean,
+  requireFormat: (value: T) => string,
   beforeConfirm?: ((value: T) => Promise<boolean>) | undefined,
   popupShow?: Ref<boolean>,
 ) {
 
-  let tempSelectText = '';
-  let tempLastSelectText = '';
   // 临时值的响应式引用,初始值为当前值或默认新值
   const tempValue = ref(value.value ?? defaultNewValue) as Ref<T>;
+  const beforeConfirmValue = ref(value.value ?? defaultNewValue) as Ref<T>;
   // 显示的文本的响应式引用
   const selectText = ref('');
 
   /**
-   * 当选择文本变化时的处理函数
-   * @param 新的选择文本
-   * @param 是否强制更新显示的选择文本
-   */
-  function onSelectTextChange(t: string, forceUpdate = false) {
-    tempSelectText = t;
-    emit('selectTextChange', t);
-    if (forceUpdate)
-      selectText.value = t;
-  }
-
-  /**
-   * 取消选择的处理函数
+   * 取消选择。恢复临时值为当前值或默认新值
    */
   function onCancel() {
     closePopup();
+    tempValue.value = beforeConfirmValue.value;
+    updateValue(tempValue.value);
     emit('cancel');
-    selectText.value = tempLastSelectText;
   }
 
   /**
@@ -61,13 +50,13 @@ export function usePickerFieldTempStorageData<T>(
     // 如果有确认前回调且返回true,则取消确认
     if (beforeConfirm && await beforeConfirm(tempValue.value))
       return;
-    selectText.value = tempSelectText;
     updateValue(tempValue.value);
     emit('confirm', value.value);
   }
 
   watch(value, (v) => {
     tempValue.value = v;
+    selectText.value = requireFormat(v);
   });
   watch(tempValue, (v) => {
     emit('tempValueChange', tempValue.value);
@@ -79,19 +68,22 @@ export function usePickerFieldTempStorageData<T>(
   if (popupShow) {
     watch(popupShow, (v) => {
       if (v) {
-        // 弹窗显示时,记录当前的选择文本作为上一次的选择文本
-        nextTick(() => {
-          tempLastSelectText = tempSelectText;
-        });
+        beforeConfirmValue.value = tempValue.value;
       }
-    })
+    });
   }
 
+  onMounted(() => {
+    if (value.value)
+      selectText.value = requireFormat(value.value);
+    else
+      selectText.value = '';
+  });
+
   return {
-    onSelectTextChange, // 选择文本变化处理函数
-    onCancel, // 取消处理函数
-    onConfirm, // 确认处理函数
-    selectText, // 显示的选择文本
-    tempValue, // 临时值
+    onCancel,
+    onConfirm,
+    selectText,
+    tempValue,
   }
 }

+ 5 - 0
src/components/form/SignatureField.vue

@@ -75,6 +75,9 @@ const props = withDefaults(defineProps<SignatureFieldProps>(), {
     width: '100%',
     height: '400rpx',
   }),
+  borderStyle: 'dashed',
+  borderWidth: 1,
+  borderColor: '#ddd',
 });
 const emit = defineEmits<{
   (e: 'update:modelValue', value: string | null): void;
@@ -90,6 +93,8 @@ const {
   (v) => emit('update:modelValue', v ?? null),
   undefined,
   () => {
+    console.log('11111');
+    
     popupShow.value = true;
   },
   props.initalValue,

+ 18 - 3
src/components/form/TimePickerField.vue

@@ -15,7 +15,6 @@
     <TimePicker 
       v-bind="props"
       v-model="tempValue"
-      @selectTextChange="onSelectTextChange"
     />
   </Popup>
   <Text
@@ -29,7 +28,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, toRef } from 'vue';
+import { computed, ref, toRef } from 'vue';
 import { useFieldChildValueInjector } from './FormContext';
 import type { TimePickerProps } from './TimePicker.vue';
 import Popup from '../dialog/Popup.vue';
@@ -38,6 +37,7 @@ import TimePicker from './TimePicker.vue';
 import { usePickerFieldTempStorageData } from './PickerUtils';
 import Text, { type TextProps } from '../basic/Text.vue';
 import { usePickerFieldInstance, type PickerFieldInstance } from './Picker';
+import { DateUtils } from '@imengyu/imengyu-utils';
 
 export interface TimePickerFieldProps extends Omit<TimePickerProps, 'modelValue'> {
   modelValue?: Date;
@@ -100,8 +100,18 @@ const {
   props.initalValue,
 );
 
+const format = computed(() => {
+  const formats = []
+  if (props.showHours)
+    formats.push('HH');
+  if (props.showMinute)
+    formats.push('mm');
+  if (props.showSecond)
+    formats.push('ss');
+  return formats.join(':');
+});
+
 const {
-  onSelectTextChange,
   onCancel,
   onConfirm,
   selectText,
@@ -113,6 +123,11 @@ const {
   emit as any,
   new Date(),
   props.shouldUpdateValueImmediately,
+  (v) => {
+    if (!v)
+      return '';
+    return DateUtils.formatDate(v, format.value);
+  },
   undefined,
   popupShow,
 );

+ 6 - 1
src/components/form/Uploader.ts

@@ -1,5 +1,9 @@
 export interface UploaderItem {
   /**
+   * 文件显示名称。若为空则从文件名中获取
+   */
+  name?: string;
+  /**
    * 上传文件源路径
    */
   filePath: string;
@@ -72,8 +76,9 @@ export interface UploaderAction {
   }, message?: string) => void;
 }
 
-export function stringUrlToUploaderItem(url: string): UploaderItem {
+export function stringUrlToUploaderItem(url: string, displayName: string): UploaderItem {
   return {
+    name: displayName,
     filePath: url,
     uploadedPath: url,
     state: 'success',

+ 31 - 9
src/components/form/Uploader.vue

@@ -175,9 +175,11 @@ export interface UploaderProps {
    * 选择文件类型
    * * image:图片
    * * video:视频
+   * * file: 文件(仅H5)
+   * * select:根据用户选择(图片/视频)
    * @default 'image'
    */
-  chooseType?: 'image'|'video'|'file'|'';
+  chooseType?: 'image'|'video'|'file'|'select'|'';
   /**
    * 是否是从消息中选择文件
    * @default true
@@ -338,14 +340,12 @@ function onUploadPress() {
             },
           ],
           showCancel: true,
-          onSelect(index, name) {
-          },
         }).then((index) => {
           if (index === 0) {
-            chooseLocal();
+            chooseLocal(props.chooseType);
           } else if (index === 1) {
             uni.chooseMessageFile({
-              type: props.chooseType || 'all',
+              type: props.chooseType === 'select' ? 'all' : (props.chooseType || 'all'),
               count: props.maxUploadCount - currentUpladList.value.length,
               success: (res) => {
                 LogUtils.printLog(TAG, 'info', 'chooseMessageFile', res);
@@ -359,15 +359,15 @@ function onUploadPress() {
           }
         });
       } else {
-        chooseLocal();
+        chooseLocal(props.chooseType);
       }
       //#endif
       //#ifndef MP
-      chooseLocal();
+      chooseLocal(props.chooseType);
       //#endif
 
-      function chooseLocal() {
-        switch (props.chooseType) {
+      function chooseLocal(type: UploaderProps['chooseType']) {
+        switch (type) {
           case 'video':
             uni.chooseVideo().then((res) => handleFiles([
               {
@@ -379,6 +379,28 @@ function onUploadPress() {
           case 'file':
             uni.chooseFile().then((res) => handleFiles(res.tempFiles as { path: string; size: number; }[])).catch(reject);
             break;
+          case 'select':
+            actionSheet({
+              title: '您想上传哪种类型的文件?',
+              actions: [
+                {
+                  name: '照片',
+                },
+                {
+                  name: '视频',
+                },
+              ],
+              showCancel: true,
+              onSelect(index, name) {
+              },
+            }).then((index) => {
+              if (index === 0) {
+                chooseLocal('image');
+              } else if (index === 1) {
+                chooseLocal('video');
+              }
+            });
+            break;
           default:
           case 'image':
             uni.chooseImage({

+ 1 - 1
src/components/form/UploaderListItem.vue

@@ -59,7 +59,7 @@
     <FlexRow v-if="isListStyle" :flex="1" align="center">
       <Width :size="20" />
       <FlexCol :flex="1">
-        <Text :fontSize="26" wrap wordBreak="break-all" :text="StringUtils.path.getFileName(item.filePath)" />
+        <Text :fontSize="26" wrap wordBreak="break-all" :text="item.name || StringUtils.path.getFileName(item.filePath)" />
         <Text :fontSize="22" :text="item.message" />
         <Height :size="10" /> 
         <Progress :progressColor="selectStyleType(item.state, 'notstart', {

+ 237 - 0
src/pages/third/yunexamine/service/request/main.js

@@ -0,0 +1,237 @@
+import { useAppStore } from '../../store/index.js'
+import {
+	encodeRedirectUrl
+} from '../../common/util.js'
+import Request from './request.js'
+import Config from '../config.js'
+import {
+	wechatAutoLogin
+} from '../api/user.js';
+let appStore = null;
+
+function getStore() {
+  if (!appStore)
+    appStore = useAppStore();
+  return appStore;
+}
+
+export const http = new Request();
+
+// 设置全局配置
+http.setConfig((config) => {
+	config.baseUrl = Config.baseUrl;
+	config.header = {
+		'Content-Type': 'application/json;charset=UTF-8'
+	};
+
+	return config
+})
+
+
+// 请求之前拦截器
+http.interceptor.beforeRequest((config, cancel) => {
+	console.log('请求前拦截', config.url, config)
+  getStore();
+
+	// 登录校验
+	let accessToken = appStore.getAccessToken;
+	let activityId = appStore.getActivityId;
+	console.log('get.accessToken', accessToken, activityId);
+	if (!config.muteLogin) {
+		if (!accessToken) {
+			console.error('needLogin request');
+			cancel('登录过期,尝试重试'); // 取消请求
+			console.info('needLogin request', 'silentReload');
+			silentReload(config.curd === 'save');
+			return;
+		}
+
+		config.url += '?token=' + accessToken;
+		// config.header['Cookie'] = 'token=' + accessToken;  // uploadFile时,不能传header,慎用这种方式
+	}
+	config.url += (config.url.indexOf('?') === -1 ? '?' : '&') + 'activity_id=' + activityId;
+
+	return config;
+})
+
+// 请求之后拦截器 
+http.interceptor.afterRequest((response) => {
+	console.log('请求后拦截', response);
+  getStore();
+
+	// 系统错误
+	if (response.statusCode === 500) {
+		console.error('系统错误');
+		setTimeout(() => {
+			uni.showToast({
+				title: '系统错误',
+				icon: 'none',
+				mask: true,
+				duration: 2000
+			})
+		}, 50);
+		return [response];
+	}
+
+	let data = response.data;
+
+	// 网络错误
+	if (!data) {
+		console.error('网络错误');
+		setTimeout(() => {
+			uni.showToast({
+				title: '网络错误',
+				icon: 'none',
+				mask: true,
+				duration: 2000
+			})
+		}, 50)
+		return [response];
+	}
+
+	if (typeof data === 'string') {
+		data = JSON.parse(data)
+	}
+
+	// 正常请求
+	if (data.code === 1) {
+		return [0, data.data || data.msg];
+	}
+
+	// 系统错误
+	console.warn('invalid request', data.msg);
+	switch (data.code) {
+		case 401: // 认证失败
+			console.error('needLogin 401');
+			appStore.delAccessToken(); // 删除token
+			console.info('needLogin 401', 'silentReload');
+			silentReload(response.config.curd === 'save');
+			break;
+		case 402: // 小程序自动登录失败
+			break;
+		default: // 错误提示
+			setTimeout(() => {
+				uni.showToast({
+					title: data.msg || '网络错误',
+					icon: 'none',
+					mask: true,
+					duration: 2000
+				})
+			}, 50)
+			if (data.code === 403) { // 活动无效
+				appStore.delAccessToken(); // 删除token
+				appStore.delActivityId(); // 删除活动ID
+				smartLogin('');
+			}
+			break;
+	}
+
+	return [response];
+})
+
+/**
+ * 获取 login code
+ */
+export function silentReload(warning = false, redirectUrl = null) {
+  getStore();
+	if (warning) {
+		setTimeout(() => {
+			uni.showToast({
+				title: '鉴权失败,请重新操作',
+				icon: 'none',
+				mask: true,
+				duration: 2000
+			})
+		}, 50)
+		return false
+	}
+	if (appStore.isLock('silentReload')) {
+		return false
+	}
+	console.info('needLogin silentReload')
+	appStore.lock('silentReload')
+	if (redirectUrl === null) {
+		const pages = getCurrentPages()
+		const currentPage = pages[pages.length - 1]
+		redirectUrl = encodeRedirectUrl(currentPage.route, currentPage.options)
+	}
+
+	// #ifdef  MP-WEIXIN
+	// 微信小程序自动登录
+	uni.reLaunch({
+		url: '/pages/third/yunexamine/pages/user/auto_login?redirect=' + redirectUrl
+	})
+	// #endif
+
+	// #ifdef  H5
+	// 跳到登录页面或活动列表页
+	smartLogin(redirectUrl)
+	// #endif
+
+	return true
+}
+
+/**
+ * 获取 login code
+ */
+export function smartLogin(redirectUrl, loginCheck, ignoreConfig) {
+  getStore();
+	if (appStore.getActivityId) { // 有缓存的活动ID
+		if (!loginCheck) {
+			// 直接到登录页面
+			uni.reLaunch({
+				url: '/pages/third/yunexamine/pages/user/login?redirect=' + redirectUrl
+			})
+		}
+
+		return true;
+	}
+	const globalConfig = appStore.getGlobalConfig
+
+	// 配置未正确加载
+	if (!globalConfig || Object.keys(globalConfig).length === 0) {
+		if (ignoreConfig) {
+			console.log('load config fail')
+			setTimeout(() => {
+				uni.reLaunch({
+					url: '/pages/third/yunexamine/pages/user/tips?cont=配置加载失败,请尝试重新进入'
+				})
+			}, 50)
+		} else {
+			console.log('wait to load config')
+			setTimeout(() => {
+				smartLogin(redirectUrl, false, true)
+			}, 800)
+		}
+		return true;
+	}
+
+	// 存在默认活动
+	if (globalConfig.default_activity) {
+		if (!loginCheck) {
+			// 直接到登录页面
+			uni.reLaunch({
+				url: '/pages/third/yunexamine/pages/user/login?redirect=' + redirectUrl
+			})
+		}
+		return true;
+	}
+
+	// 可以选择活动
+	if (globalConfig.select_activity) {
+		// 直接到活动列表
+		uni.reLaunch({
+			url: '/pages/third/yunexamine/pages/home/dashboard'
+		})
+		return true;
+	}
+
+	// 需要扫码进入
+	setTimeout(() => {
+		uni.reLaunch({
+			url: '/pages/third/yunexamine/pages/user/tips'
+		})
+	}, 50)
+
+	return true
+}