快乐的梦鱼 месяцев назад: 2
Родитель
Сommit
452d54eb97
37 измененных файлов с 480 добавлено и 101 удалено
  1. 1 1
      src/common/components/SimplePageListLoader.vue
  2. 11 0
      src/common/components/dynamicf/ComponentRender.vue
  3. 6 0
      src/common/components/form/Recorder.vue
  4. 18 7
      src/common/components/form/RichTextEditor.vue
  5. 1 1
      src/common/composeabe/LoaderCommon.ts
  6. 1 1
      src/common/composeabe/SimplePageListLoader.ts
  7. 1 0
      src/components/data/DefaultIcon.json
  8. 4 0
      src/components/dynamic/DynamicForm.ts
  9. 2 0
      src/components/dynamic/DynamicForm.vue
  10. 32 1
      src/components/dynamic/DynamicFormControl.vue
  11. 2 3
      src/components/dynamic/nest/DynamicFormItemContainer.vue
  12. 18 3
      src/components/dynamic/wrappers/PickerAddressField.vue
  13. 14 3
      src/components/dynamic/wrappers/PickerCityField.vue
  14. 5 0
      src/components/dynamic/wrappers/PickerIdField.ts
  15. 2 2
      src/components/dynamic/wrappers/PickerIdField.vue
  16. 1 0
      src/components/dynamic/wrappers/PickerLonlat.vue
  17. 0 2
      src/components/form/CalendarField.vue
  18. 12 3
      src/components/form/CascaderField.vue
  19. 10 6
      src/components/form/Field.vue
  20. 3 1
      src/components/form/Form.vue
  21. 26 0
      src/components/form/FormContext.ts
  22. 9 1
      src/components/form/PickerField.vue
  23. 3 1
      src/components/form/Radio.vue
  24. 2 0
      src/components/form/RadioGroup.vue
  25. 10 1
      src/components/form/Uploader.vue
  26. 3 3
      src/components/theme/Theme.ts
  27. 9 0
      src/pages.json
  28. 140 0
      src/pages/dig/admin/preview.vue
  29. 19 7
      src/pages/dig/components/CollectModuleList.vue
  30. 5 1
      src/pages/dig/components/TaskList.vue
  31. 20 11
      src/pages/dig/forms/common.vue
  32. 14 0
      src/pages/dig/forms/composeable/TaskEntryForm.ts
  33. 8 20
      src/pages/dig/forms/data/overview.ts
  34. 10 2
      src/pages/dig/forms/list.vue
  35. 2 0
      src/pages/dig/forms/task.vue
  36. 10 2
      src/pages/dig/index.vue
  37. 46 18
      src/pages/user/login.vue

+ 1 - 1
src/common/components/SimplePageListLoader.vue

@@ -5,7 +5,7 @@
     || (loader.loadStatus.value == 'nomore' && !$slots.empty)" 
     :status="loader.loadStatus.value" 
   />
-  <slot v-else-if="loader.loadStatus.value == 'nomore' && $slots.empty" name="empty" />
+  <slot v-else-if="loader.loadStatus.value == 'empty' && $slots.empty" name="empty" />
   <Loadmore 
     v-else-if="loader.loadStatus.value == 'error'"
     status="loadmore" 

+ 11 - 0
src/common/components/dynamicf/ComponentRender.vue

@@ -5,6 +5,8 @@
     <RichTextEditor
       ref="itemRef"
       :modelValue="modelValue"
+      :disabled="disabled"
+      :readonly="readonly"
       @update:modelValue="onValueChanged"
       v-bind="params"
     />
@@ -13,6 +15,7 @@
     <Recorder
       ref="itemRef"
       :modelValue="modelValue"
+      :disabled="disabled || readonly"
       @update:modelValue="onValueChanged"
       v-bind="params"
     />
@@ -49,6 +52,14 @@ const props = defineProps({
     type: String,
     default: '',
   },
+  disabled: {
+    type: Boolean,
+    default: false,
+  },
+  readonly: {
+    type: Boolean,
+    default: false,
+  },
 });
 const emit = defineEmits(['update:modelValue']);
 

+ 6 - 0
src/common/components/form/Recorder.vue

@@ -11,6 +11,10 @@ const props = defineProps({
     type: String,
     default: null 
   },
+  disabled: {
+    type: Boolean,
+    default: false,
+  },
 });
 
 
@@ -57,6 +61,8 @@ manager.onStop((result) => {
 })
 
 function startRecord() {
+  if (props.disabled)
+    return;
   if (state.value)
     return;
   uni.showLoading();

+ 18 - 7
src/common/components/form/RichTextEditor.vue

@@ -1,15 +1,15 @@
 <template>
-  <view class="d-flex flex-col">
-    <view class="richtext-preview-box" @click="edit">
+  <FlexCol>
+    <view class="richtext-preview-box" @click="() => !disabled && !readonly ? edit() : preview()">
       <Parse v-if="modelValue" :content="modelValue" containerStyle="max-height:400px" />
       <Text v-else color="text.second">{{placeholder}}</Text>
     </view>
-    <view class="d-flex flex-row gap-sss align-center mt-2">
-      <Button icon="browse" text="预览" size="small" @click="preview" />
-      <Button icon="edit" text="编辑" size="small" @click="edit" type="primary" />
+    <FlexRow :gap="20" align="center">
+      <Button v-if="!disabled" icon="browse" text="预览" size="small" @click="preview" />
+      <Button v-if="!disabled && !readonly" icon="edit" text="编辑" size="small" @click="edit" type="primary" />
       <Text v-if="maxLength > 0">{{ modelValue?.length || 0 }}/{{ maxLength }} 字</Text>
-    </view>
-  </view>
+    </FlexRow>
+  </FlexCol>
 </template>
 
 <script setup lang="ts">
@@ -18,6 +18,8 @@ import { navTo } from '@/components/utils/PageAction';
 import Parse from '@/components/display/parse/Parse.vue';
 import Button from '@/components/basic/Button.vue';
 import Text from '@/components/basic/Text.vue';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import FlexRow from '@/components/layout/FlexRow.vue';
 
 const props = defineProps({	
   modelValue: { 
@@ -32,6 +34,14 @@ const props = defineProps({
     type: String,
     default: '未编写内容,点击编写',
   },
+  disabled: {
+    type: Boolean,
+    default: false,
+  },
+  readonly: {
+    type: Boolean,
+    default: false,
+  },
 })
 const emit = defineEmits(['update:modelValue'])
 let editorOpened = false;
@@ -71,5 +81,6 @@ onPageShow(() => {
 .richtext-preview-box {
   flex: 1;
   min-height: 400rpx;
+  margin-bottom: 15rpx;
 }
 </style>

+ 1 - 1
src/common/composeabe/LoaderCommon.ts

@@ -1,6 +1,6 @@
 import type { Ref } from "vue";
 
-export type LoaderLoadType = 'loading' | 'finished' | 'nomore' | 'error';
+export type LoaderLoadType = 'loading' | 'finished' | 'nomore' | 'error' | 'empty';
 
 export interface ILoaderCommon<P> {
   loadError: Ref<string>;

+ 1 - 1
src/common/composeabe/SimplePageListLoader.ts

@@ -43,7 +43,7 @@ export function useSimplePageListLoader<T, P = any>(
       const res = (await loader(page.value, pageSize, lastParams));
       list.value = list.value.concat(res.list as T[]);
       total.value = res.total;
-      loadStatus.value = res.list.length > 0 ? 'finished' : 'nomore';
+      loadStatus.value = res.list.length > 0 ? 'finished' : (total.value > 0 ? 'nomore' : 'empty');
       loadError.value = '';
       loading = false;
     } catch(e) {

Разница между файлами не показана из-за своего большого размера
+ 1 - 0
src/components/data/DefaultIcon.json


+ 4 - 0
src/components/dynamic/DynamicForm.ts

@@ -30,6 +30,10 @@ export interface IDynamicFormOptions {
    */
   disabled?: boolean;
   /**
+   * 是否只读表单
+   */
+  readonly?: boolean;
+  /**
    * 是否屏蔽所有子条目空错误。默认否
    * @default false
    */

+ 2 - 0
src/components/dynamic/DynamicForm.vue

@@ -5,6 +5,8 @@
     v-bind="finalOptions.formAdditionaProps"
     :model="model || {}"
     :rules="finalOptions.formRules"
+    :disabled="finalOptions.disabled"
+    :readonly="finalOptions.readonly"
     @submit="(e) => emit('submit', e)"
     @submitFailed="() => emit('finishFailed')"
   >

+ 32 - 1
src/components/dynamic/DynamicFormControl.vue

@@ -20,6 +20,8 @@
     :showBottomBorder="!isLast"
     :required="Boolean(item.rules?.length)"
     :rules="item.rules"
+    :disabled="disabled"
+    :readonly="readonly"
     v-bind="{ 
       ...params,
       ...extraDefine?.itemProps || {},
@@ -36,6 +38,8 @@
     :showBottomBorder="!isLast"
     :requireChildRef="() => itemRef"
     :rules="item.rules"
+    :disabled="disabled"
+    :readonly="readonly"
     v-bind="{ 
       ...extraDefine?.itemProps || {},
       ...item.formProps,
@@ -50,6 +54,7 @@
         <Stepper
           ref="itemRef"
           :modelValue="model"
+          :disabled="disabled || readonly"
           @update:modelValue="onValueChanged"
           v-bind="params"
         />
@@ -58,6 +63,7 @@
         <Switch
           ref="itemRef"
           :modelValue="model"
+          :disabled="disabled || readonly"
           @update:modelValue="onValueChanged"
           v-bind="params"
         />
@@ -66,6 +72,7 @@
         <RadioValue
           ref="itemRef"
           :modelValue="model"
+          :disabled="disabled || readonly"
           @update:modelValue="onValueChanged"
           v-bind="(params as any as RadioValueProps)"
         />
@@ -74,6 +81,7 @@
         <RadioIdField
           ref="itemRef"
           :modelValue="model"
+          :disabled="disabled || readonly"
           @update:modelValue="onValueChanged"
           v-bind="(params as any as RadioIdFieldProps)"
         />
@@ -83,6 +91,7 @@
           <NaPickerField 
             ref="itemRef"
             :modelValue="model"
+            :disabled="disabled || readonly"
             @update:modelValue="onValueChanged"
             v-bind="(params as any as PickerFieldProps)"
           />
@@ -92,6 +101,7 @@
         <Rate
           ref="itemRef"
           :modelValue="model"
+          :disabled="disabled || readonly"
           @update:modelValue="onValueChanged"
           v-bind="(params as any as RateProps)"
         />
@@ -100,6 +110,8 @@
         <UploaderField
           ref="itemRef"
           :modelValue="model"
+          :disabled="disabled"
+          :readonly="readonly"
           @update:modelValue="onValueChanged"
           v-bind="(params as any as UploaderFieldProps)"
         />
@@ -108,6 +120,8 @@
         <PickerIdField 
           ref="itemRef"
           :modelValue="model"
+          :disabled="disabled"
+          :readonly="readonly"
           @update:modelValue="onValueChanged"
           v-bind="(params as any as PickerIdFieldProps)"
         />
@@ -116,6 +130,8 @@
         <PickerCityField
           ref="itemRef"
           :modelValue="model"
+          :disabled="disabled"
+          :readonly="readonly"
           @update:modelValue="onValueChanged"
           v-bind="(params as any)"
         />
@@ -124,6 +140,8 @@
         <PickerAddressField
           ref="itemRef"
           :modelValue="model"
+          :disabled="disabled"
+          :readonly="readonly"
           @update:modelValue="onValueChanged"
           v-bind="(params as any)"
         />
@@ -132,6 +150,7 @@
         <PickerLonlat
           ref="itemRef"
           :modelValue="model"
+          :disabled="disabled || readonly"
           @update:modelValue="(v:any) => onValueChanged(v)"
           v-bind="params"
         />
@@ -140,6 +159,7 @@
         <CheckBox
           ref="itemRef"
           :modelValue="model"
+          :disabled="disabled || readonly"
           @update:modelValue="onValueChanged"
           v-bind="params"
         />
@@ -148,6 +168,7 @@
         <CheckBoxList
           ref="itemRef"
           :modelValue="model"
+          :disabled="disabled || readonly"
           @update:modelValue="onValueChanged"
           v-bind="(params)"
         />
@@ -156,6 +177,7 @@
         <CheckBoxTreeList
           ref="itemRef"
           :modelValue="model"
+          :disabled="disabled || readonly"
           @update:modelValue="onValueChanged"
           v-bind="(params as any as CheckBoxTreeListProps)"
         />
@@ -164,6 +186,7 @@
         <CheckBoxToInt
           ref="itemRef"
           :modelValue="model"
+          :disabled="disabled || readonly"
           @update:modelValue="onValueChanged"
           v-bind="params"
         />
@@ -201,6 +224,7 @@
       <template v-else-if="item.type === 'button'">
         <Button
           ref="itemRef"
+          :disabled="disabled || readonly"
           v-bind="params"
         />
       </template>
@@ -224,13 +248,15 @@
         :item="item"
         :name="name"
         :isLast="isLast"
+        :disabled="disabled"
+        :readonly="readonly"
       />
     </slot>
   </Field>
 </template>
 
 <script setup lang="ts">
-import { computed, inject, onBeforeUnmount, onMounted, ref, watch, type PropType, type Ref } from 'vue';
+import { computed, inject, onBeforeUnmount, onMounted, ref, type PropType, type Ref } from 'vue';
 import type { IDynamicFormItem, IDynamicFormItemCallback, IDynamicFormObject, IDynamicFormOptions, IDynamicFormRef } from '.';
 import Field from '../form/Field.vue';
 import Stepper from '../form/Stepper.vue';
@@ -260,6 +286,7 @@ import Button from '../basic/Button.vue';
 import Alert from '../feedback/Alert.vue';
 import Image from '../basic/Image.vue';
 import CheckBoxTreeList, { type CheckBoxTreeListProps } from './wrappers/CheckBoxTreeList.vue';
+import { useInjectFormContext, useInjectFormItemContext } from '../form/FormContext';
 
 export interface FormCeilProps {
   model: unknown,
@@ -334,6 +361,10 @@ const finalOptions = inject<Ref<IDynamicFormOptions>>('finalOptions');
 const globalParams = inject<Ref<IDynamicFormObject>>('globalParams');
 const formRef = inject<IDynamicFormRef>('formRef');
 const formName = inject('formName', '');
+const context = useInjectFormItemContext();
+const formContext = useInjectFormContext();
+const disabled = computed(() => props.disabled || formContext?.disabled.value || context?.disabled.value);
+const readonly = computed(() => formContext?.readonly.value || context?.readonly.value);
 
 function evaluateCallback(val: unknown|IDynamicFormItemCallback<unknown>) {
   if (typeof val === 'object' && typeof (val as IDynamicFormItemCallback<unknown>).callback === 'function')

+ 2 - 3
src/components/dynamic/nest/DynamicFormItemContainer.vue

@@ -292,16 +292,15 @@
 </template>
 
 <script lang="ts" setup>
-import { inject, type PropType, type Ref, toRefs, computed, provide } from 'vue';
+import { inject, type Ref, toRefs, computed, provide } from 'vue';
 import type { Rules } from 'async-validator';
+import type { IDynamicFormItem, IDynamicFormItemCallback, IDynamicFormObject, IDynamicFormOptions, IDynamicFormRef, IEvaluateCallback } from '..';
 import DynamicFormItemNormal, { type FormCeilProps } from '../DynamicFormControl.vue';
 import FormGroup from '../group/FormGroup.vue';
 import FormArrayGroup from '../group/FormArrayGroup.vue';;
 import Col, { type ColProps } from '@/components/layout/grid/Col.vue';
 import Row from '@/components/layout/grid/Row.vue';
 import DynamicFormCheckEmpty from './DynamicFormCheckEmpty.vue';
-import type { IDynamicFormItem, IDynamicFormItemCallback, IDynamicFormObject, IDynamicFormOptions, IDynamicFormRef, IEvaluateCallback } from '..';
-import Button from '@/components/basic/Button.vue';
 import DynamicFormItemContainerFuckMp from './DynamicFormItemContainerFuckMp.vue';
 
 /**

+ 18 - 3
src/components/dynamic/wrappers/PickerAddressField.vue

@@ -1,11 +1,12 @@
 <template>
-  <FlexRow width="100%" justify="space-between" align="center">
+  <FlexRow width="100%" align="center">
     <input 
       :value="modelValue"
-      placeholder="输入地址或者选择地图地址"
+      :placeholder="readonly ? '未填写' : '输入地址或者选择地图地址'"
       @input="handleInput"
     />
-    <Button type="primary" size="mini" icon="map" @click="selectFromMap">地图选择</Button>
+    <Button v-if="!disabled && !readonly" type="primary" size="mini" icon="map" @click="selectFromMap">地图选择</Button>
+    <view v-else></view>
   </FlexRow>
 </template>
 
@@ -19,6 +20,20 @@ const props = defineProps({
     type: String,
     default: '',
   },
+  /**
+   * 是否禁用
+   */
+  disabled: {
+    type: Boolean,
+    default: false,
+  },
+  /**
+   * 是否只读
+   */
+  readonly: {
+    type: Boolean,
+    default: false,
+  },
   loadFormattedAddress: {
     type: Function as PropType<(latlon: [number,number]) => Promise<string>>,
     default: null,

+ 14 - 3
src/components/dynamic/wrappers/PickerCityField.vue

@@ -1,5 +1,5 @@
 <template>
-  <FlexRow width="100%" justify="space-between" align="center">
+  <FlexRow width="100%" align="center">
     <CascaderField
       v-if="ChinaCityData.data.value"
       ref="fieldRef"
@@ -8,11 +8,11 @@
       textKey="name"
       :valueKey="stringValue ? 'name' : 'code'"
       childrenKey="children"
-      placeholder="请选择省市区" 
       :data="(ChinaCityData.data.value as CascaderItem[]) || []"
       v-bind="$attrs"
+      :placeholder="readonly ? '未填写' : ($attrs.placeholder as string || '请选择省市区')" 
     />
-    <Button type="primary" size="mini" icon="map" @click="selectCityFromMap">地图选择</Button>
+    <Button v-if="!disabled && !readonly" type="primary" size="mini" icon="map" @click="selectCityFromMap">地图选择</Button>
   </FlexRow>
 </template>
 
@@ -29,6 +29,17 @@ const props = defineProps({
     type: Array as PropType<string[]>,
     default: () => [],
   },
+  /**
+   * 是否禁用
+   */
+  disabled: {
+    type: Boolean,
+    default: false,
+  },
+  readonly: {
+    type: Boolean,
+    default: false,
+  },
   loadCityData: {
     type: Function as PropType<() => Promise<CascaderItem[]>>,
     default: () => Promise.resolve([]),

+ 5 - 0
src/components/dynamic/wrappers/PickerIdField.ts

@@ -11,6 +11,11 @@ export interface PickerIdFieldProps extends Omit<PickerFieldProps, 'columns'> {
    */
   disabled?: boolean;
   /**
+   * 是否只读,只读状态下不能选择数据,默认的请选择选项变为“未选择”。
+   * @default false
+   */
+  readonly?: boolean,
+  /**
    * 加载选项数据
    * @returns 
    */

+ 2 - 2
src/components/dynamic/wrappers/PickerIdField.vue

@@ -10,8 +10,8 @@
 
 <script setup lang="ts">
 import PickerField from '@/components/form/PickerField.vue';
-import type { PickerIdFieldProps, PickerIdFieldOption } from './PickerIdField';
 import { useDataLoader } from '@/components/composeabe/DataLoader';
+import type { PickerIdFieldProps, PickerIdFieldOption } from './PickerIdField';
 
 const props = defineProps<PickerIdFieldProps>();
 const emit = defineEmits(['update:modelValue']);
@@ -20,7 +20,7 @@ const loader = useDataLoader<PickerIdFieldOption[]>(async () => {
   if (res.length === 0)
     return [];
   return ([{
-    text: '请选择',
+    text: props.readonly ? '未选择' : '请选择',
     value: '',
   }] as PickerIdFieldOption[]).concat(res);
 }, {

+ 1 - 0
src/components/dynamic/wrappers/PickerLonlat.vue

@@ -2,6 +2,7 @@
   <Button
     type="primary"
     size="small"
+    :touchable="!disabled"
     :text="props.modelValue ? `${FormatUtils.formatCoordinates(props.modelValue[0], props.modelValue[1])}` : '请选择经纬度'"
     @click="openPicker"
   />

+ 0 - 2
src/components/form/CalendarField.vue

@@ -115,8 +115,6 @@ const confirmDisabled = computed(() => {
   }
 })
 
-
-
 const popupShow = ref(false);
 
 const {

+ 12 - 3
src/components/form/CascaderField.vue

@@ -26,18 +26,19 @@
   <Text 
     v-if="showSelectText"
     :size="30"
-    :color="selectText ? 'text.content' : 'text.second'"
+    :color="selectText && !(readonly || disabled) ? 'text.content' : 'text.second'"
     :text="selectText || placeholder" 
     :maxWidth="300"
+    textAlign="left"
     v-bind="textProps"
   />
 </template>
 
 <script setup lang="ts">
-import { nextTick, onMounted, ref, toRef, watch } from 'vue';
+import { ref, toRef, watch } from 'vue';
 import { useFieldChildValueInjector } from './FormContext';
 import { usePickerFieldTempStorageData } from './PickerUtils';
-import type { CascaderItem, CascaderProps } from './Cascader.vue';
+import type { CascaderProps } from './Cascader.vue';
 import Popup from '../dialog/Popup.vue';
 import Cascader from './Cascader.vue';
 import Height from '../layout/space/Height.vue';
@@ -46,6 +47,14 @@ import PopupTitle from '../dialog/PopupTitle.vue';
 import { getCascaderText } from './CascaderUtils';
 
 export interface CascaderFieldProps extends Omit<CascaderProps, 'modelValue'> {
+  /**
+   * 是否禁用
+   */
+  disabled?: boolean,
+  /**
+   * 是否只读
+   */
+  readonly?: boolean,
   
   modelValue?: (string|number)[];
   /**

+ 10 - 6
src/components/form/Field.vue

@@ -162,7 +162,7 @@
       </FlexRow>
       <!-- 额外的提示信息 -->
       <FlexRow 
-        v-if="extraMessage"
+        v-if="extraMessage && !readonly"
         :gap="10"
         :innerStyle="themeStyles.extraMessage.value"
         align="center"
@@ -194,7 +194,7 @@
 </template>
 
 <script setup lang="ts">
-import { computed, inject, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue';
+import { computed, inject, onBeforeUnmount, onMounted, provide, ref, toRef, watch } from 'vue';
 import { propGetThemeVar, useTheme, type TextStyle, type ViewStyle } from '../theme/ThemeDefine';
 import { FormItemContextContextKey, propGetFormContext, type FormContext, type FormItemContext, type FormItemInternalContext } from './FormContext';
 import { DynamicColor, DynamicSize, DynamicVar, selectStyleType } from '../theme/ThemeTools';
@@ -527,8 +527,8 @@ const props = withDefaults(defineProps<FieldProps>(), {
   showLabel: () => propGetFormContext()?.showLabel.value ?? true,
   showRequiredBadge: () => propGetFormContext()?.fieldProps.value?.showRequiredBadge ?? true,
   showRightArrow: () => propGetFormContext()?.fieldProps.value?.showRightArrow ?? false,
-  disabled: false,
-  readonly: false,
+  disabled: () => propGetFormContext()?.disabled.value ?? false,
+  readonly: () => propGetFormContext()?.readonly.value ?? false,
   autoHeight: false,
   maxLength: 100,
   modelValue: undefined,
@@ -588,6 +588,8 @@ const formItemContext : FormItemContext = {
   setOnClickListener(listener: (() => void)|undefined) {
     childOnClickListener.value = listener;
   },
+  disabled: toRef(props, 'disabled'),
+  readonly: toRef(props, 'readonly'),
 }
 provide(FormItemContextContextKey, formItemContext);
 
@@ -740,10 +742,12 @@ function onClear() {
   emit('clear'); 
 }
 function onClick() {
-  childOnClickListener.value?.();
-  emit('click');
   if (props.resetErrorOnClick)
     fieldInstance.clearValidate();
+  if (props.disabled || props.readonly)
+    return;
+  childOnClickListener.value?.();
+  emit('click');
 }
 
 //#region 标签输入框事件处理

+ 3 - 1
src/components/form/Form.vue

@@ -173,7 +173,7 @@ function accessFormModel(keyName: string, isSet: boolean|undefined, setValue: un
 } 
 const { 
   labelFlex, inputFlex, colon, labelAlign, labelPosition, labelWidth, model, rules,
-  validateTrigger, showLabel, addRequireMark, name, fieldProps,
+  validateTrigger, showLabel, addRequireMark, name, fieldProps, disabled, readonly,
 } = toRefs(props);
 
 //Context
@@ -224,6 +224,8 @@ const formContext : FormContext = {
   inputFlex,
   showLabel,
   fieldProps,
+  disabled,
+  readonly,
 };
 
 function getItemRule(name: string) {

+ 26 - 0
src/components/form/FormContext.ts

@@ -44,6 +44,14 @@ export type FormItemContext = {
    * @returns 表单组件中的当前值
    */
   getFormModelValue(): any;
+  /**
+   * 表单项是否禁用的状态
+   */
+  disabled: Ref<boolean|undefined>,
+  /**
+   * 表单项是否只读的状态
+   */
+  readonly: Ref<boolean|undefined>,
 };
 export type FormItemInternalContext = {
   /**
@@ -82,6 +90,8 @@ export type FormContext = {
   showLabel: Ref<boolean|undefined>;
   name: Ref<string|undefined>;
   fieldProps: Ref<FieldProps|undefined>,
+  disabled: Ref<boolean|undefined>,
+  readonly: Ref<boolean|undefined>,
   getItemValue: (item: FormItemInternalContext) => unknown;
   getItemRequieed: (item: FormItemInternalContext) => boolean;
 };
@@ -147,6 +157,7 @@ export function useFieldChildValueInjector<T>(
 ) {
   const cellContext = useCellContext();
   const context = useInjectFormItemContext();
+  const formContext = useInjectFormContext();
   const shadowRefValue = ref(propsModelValue.value ?? context.getFormModelValue() ?? initialValue) as Ref<T>;
 
   const value = computed(() => {
@@ -179,6 +190,9 @@ export function useFieldChildValueInjector<T>(
       cellContext.setOnClickListener(fieldClick);
   }
 
+  const disabled = computed(() => formContext.disabled.value || context.disabled.value);
+  const readonly = computed(() => formContext.readonly.value || context.readonly.value);
+
   return {
     /**
      * 临时值
@@ -189,5 +203,17 @@ export function useFieldChildValueInjector<T>(
      * 表单项上下文
      */
     context,
+    /**
+     * 表单上下文
+     */
+    formContext,
+    /**
+     * 指示顶层由表单和表单项设置的禁用状态
+     */
+    disabled,
+    /**
+     * 指示顶层由表单和表单项设置的只读状态
+     */
+    readonly,
   }
 }

+ 9 - 1
src/components/form/PickerField.vue

@@ -21,7 +21,7 @@
   <Text
     v-if="showSelectText"
     :size="30"
-    :color="selectText ? 'text.content' : 'text.second'"
+    :color="selectText && !(readonly || disabled) ? 'text.content' : 'text.second'"
     :text="selectText || placeholder" 
     :maxWidth="300"
     v-bind="textProps"
@@ -68,6 +68,14 @@ export interface PickerFieldProps extends Omit<PickerProps, 'value'> {
    * 显示的文本属性
    */
   textProps?: TextProps,
+  /**
+   * 是否禁用
+   */
+  disabled?: boolean,
+  /**
+   * 是否只读
+   */
+  readonly?: boolean,
 }
 
 const emit = defineEmits([ 'update:modelValue', 'cancel', 'confirm', 'selectTextChange', 'tempValueChange' ]);

+ 3 - 1
src/components/form/Radio.vue

@@ -160,6 +160,8 @@ const props = withDefaults(defineProps<RadioBoxProps>(), {
   icon: 'check-mark',
 });
 
+const disabled = computed(() => props.disabled || groupContext?.topDisabled.value);
+
 const themeContext = useTheme();
 const themeStyles = themeContext.useThemeStyles({
   checkBox: {
@@ -191,7 +193,7 @@ const value = computed(() => {
 });
 
 function switchOn() {
-  if (props.disabled)
+  if (disabled.value)
     return;
   groupContext!.onValueChange(props.name, true);
 }

+ 2 - 0
src/components/form/RadioGroup.vue

@@ -32,6 +32,7 @@ export interface RadioBoxGroupToggleOptions {
 }
 export interface RadioBoxGroupContextInfo {
   value: Ref<string|number|boolean>,
+  topDisabled: Ref<boolean>,
   onValueChange: (name: string|number|boolean|undefined, v: boolean) => void;
   onAddItem: (name: string, disabled: boolean) => void;
 }
@@ -51,6 +52,7 @@ const {
 
 provide<RadioBoxGroupContextInfo>('RadioBoxGroupContext', {
   value: value as any,
+  topDisabled: toRef(props, 'disabled'),
   onValueChange,
   onAddItem: (name, disabled) => {
     allCheckNames.push(name);

+ 10 - 1
src/components/form/Uploader.vue

@@ -47,9 +47,12 @@
             />
           </slot>
         </template>
-        <slot v-if="currentUpladList.length < maxUploadCount" name="addButton" :onUploadPress="onUploadPress" :itemSize="itemSize">
+        <slot v-if="currentUpladList.length < maxUploadCount && !disabled" name="addButton" :onUploadPress="onUploadPress" :itemSize="itemSize">
           <UploaderListAddItem :itemSize="itemSize" :style="itemStyle" @click="onUploadPress" :isListStyle="props.listType === 'list'" />
         </slot>
+        <slot v-if="readonly && currentUpladList.length === 0">
+          <Text color="text.second">暂无上传文件</Text>
+        </slot>
       </FlexView>
       <slot v-else name="addButton" :onUploadPress="onUploadPress" :itemSize="itemSize">
         <UploaderListAddItem :itemSize="itemSize" :style="itemStyle" @click="onUploadPress" :isListStyle="props.listType === 'list'" />
@@ -70,6 +73,7 @@ import FlexView from '../layout/FlexView.vue';
 import FlexCol from '../layout/FlexCol.vue';
 import type { UploaderAction, UploaderItem } from './Uploader';
 import { LogUtils } from '@imengyu/imengyu-utils';
+import Text from '../basic/Text.vue';
 
 const themeContext = useTheme();
 const TAG = 'Uploader';
@@ -91,6 +95,11 @@ export interface UploaderProps {
    */
   disabled?: boolean;
   /**
+   * 是否只读,只读状态下不会显示上传按钮,也不能删除已上传的文件。
+   * @default false
+   */
+  readonly?: boolean;
+  /**
    * 是否显示文件已上传列表。
    * @default true
    */

+ 3 - 3
src/components/theme/Theme.ts

@@ -36,9 +36,9 @@ export const DefaultTheme : ThemeConfig = {
       dark: '#212121',
       white: '#FFFFFF',
       black: '#000000',
-      grey: '#dddddd',
-      lightGrey: '#cccccc',
-      darkGrey: '#999999',
+      grey: '#999999',
+      lightGrey: '#c3c3c3',
+      darkGrey: '#8e8e8e',
       skeleton: 'rgba(46,50,56, 0.05)',
     },
     pressed: {

+ 9 - 0
src/pages.json

@@ -4,6 +4,8 @@
       "path": "pages/user/login",
       "style": {
         "navigationBarTitleText": "登录",
+        "navigationStyle": "custom",
+        "navigationBarTextStyle": "black",
         "enablePullDownRefresh": false
       }
     },
@@ -58,6 +60,13 @@
       }
     },
     {
+      "path": "pages/dig/admin/preview",
+      "style": {
+        "navigationBarTitleText": "村落预览",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
       "path": "pages/dig/forms/task",
       "style": {
         "navigationBarTitleText": "村社文化资源挖掘平台",

+ 140 - 0
src/pages/dig/admin/preview.vue

@@ -0,0 +1,140 @@
+<template>
+  <FlexCol :padding="30" :gap="20">
+    <ImageSwiper
+      :images="imageList"
+      :radius="20"
+      round
+      mode="widthFix"
+      :width="690" 
+    />
+
+    <FlexCol v-if="overviewLoader.content.value">
+      <SubTitle :title="overviewLoader.content.value.villageName || ''" />
+      <IntroBlock 
+        small
+        :descItems="[
+          {
+            label: '村落类型',
+            value: overviewLoader.content.value.villageTypeText ,
+          },
+          {
+            label: '地址',
+            value: overviewLoader.content.value.cityAddress?.join(' '),
+          },
+        ]"
+      />
+      <FlexCol :margin="[20, 0, 0, 0]">
+        <Parse :content="overviewLoader.content.value.overview" :tagStyle="commonParserStyle" />
+        <Text v-if="!overviewLoader.content.value.overview" fontConfig="subText">暂无采编简内容</Text>
+      </FlexCol>
+    </FlexCol>
+
+    <FlexCol :gap="20">
+      <CollectModuleList
+        :villageId="querys.villageId"
+        :villageVolunteerId="querys.villageVolunteerId"
+        :taskName="''"
+        :taskTitle="''"
+        :taskPid="0"
+        isView
+      />
+    </FlexCol>
+
+    <FlexCol :margin="[20, 0, 30, 0]">
+      <SubTitle title="地理位置" />
+      <FlexCol :radius="20" backgroundColor="white" :margin="[20, 0, 0, 0]">
+        <map 
+          id="map"
+          :latitude="center[1]"
+          :longitude="center[0]"
+          :markers="markers"
+          :scale="15"
+          :style="{
+            width: '100%',
+            height: '350rpx',
+          }"
+        />
+      </FlexCol>
+    </FlexCol>
+
+    <XBarSpace />
+  </FlexCol>
+</template>
+
+<script setup lang="ts">
+import { computed, ref } from 'vue';
+import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
+import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
+import { useCollectStore } from '@/store/collect';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import CollectModuleList from '../components/CollectModuleList.vue';
+import XBarSpace from '@/components/layout/space/XBarSpace.vue';
+import ImageSwiper from '@/common/components/parts/ImageSwiper.vue';
+import VillageInfoApi, { CommonInfoModel } from '@/api/inhert/VillageInfoApi';
+import SubTitle from '@/components/display/title/SubTitle.vue';
+import IntroBlock from '@/pages/article/common/IntroBlock.vue';
+import Parse from '@/components/display/parse/Parse.vue';
+import Text from '@/components/basic/Text.vue';
+import commonParserStyle from '@/common/style/commonParserStyle';
+import ImagesUrls from '@/common/config/ImagesUrls';
+
+const { querys } = useLoadQuerys({ 
+  villageId: 0,  
+  villageName: '',
+  villageVolunteerId: 0,
+});
+
+const { getCollectModuleId } = useCollectStore();
+
+const center = ref([118.15723, 24.48147]);
+const markers = ref<any>([]);
+  
+const imageList = computed(() => {
+  const content = overviewLoader.content.value;
+  if (!content) return [];
+  if (content.images && content.images.length > 0) 
+    return content.images;
+  if (content.image)
+    return [content.image];
+  return ["https://mn.wenlvti.net/app_static/xiangan/banner_dig_1.jpg" ];
+});
+const overviewLoader = useSimpleDataLoader<CommonInfoModel>(async () => {
+  const subType = 'overview';
+  const collectModuleId = getCollectModuleId('overview');
+  const list = await VillageInfoApi.getList(
+    collectModuleId, subType, undefined, undefined,
+    querys.value.villageId, querys.value.villageVolunteerId);
+
+  if (list.length > 0) {
+    const info =  (await VillageInfoApi.getInfo(
+      collectModuleId,
+      subType,
+      undefined,
+      undefined,
+      querys.value.villageId,
+      querys.value.villageVolunteerId,
+      undefined,
+      list[0].id,
+    )) as CommonInfoModel
+     if (info.longitude && info.latitude) {
+      center.value = [Number(info.longitude), Number(info.latitude)];
+    } else {
+      center.value = [118.11593, 24.467580];
+    }
+    markers.value = [
+      {
+        id: 1,
+        latitude: center.value[1],
+        longitude: center.value[0],
+        iconPath: ImagesUrls.IconMarker,
+        width: 40,
+        height: 40,
+      }
+    ];
+
+    return info;
+  }
+  return new CommonInfoModel();
+});
+
+</script>

+ 19 - 7
src/pages/dig/components/CollectModuleList.vue

@@ -15,6 +15,7 @@
         :title="item.title"
         :desc="item.desc"
         :extra="item.extra"
+        :goButtonText="isView ? '查看' : '去完成'"
         :enable="typeof item.enable === 'string' ? canCollect(item.enable) : item.enable"
         @click="handleClick(item)"
       />
@@ -35,17 +36,22 @@ import FlexCol from '@/components/layout/FlexCol.vue';
 import Image from '@/components/basic/Image.vue';
 import TaskList from '../components/TaskList.vue';
 import VillageApi from '@/api/inhert/VillageApi';
+import { useAuthStore } from '@/store/auth';
 
-const { goForm } = useTaskEntryForm();
+const { goForm, goPreviewForm } = useTaskEntryForm();
+const authStore = useAuthStore();
 const { canCollect, canCollectCatalog, getCollectModuleInternalNameById } = useCollectStore();
 
-const props = defineProps<{
+const props = withDefaults(defineProps<{
   villageId: number,
   villageVolunteerId: number,
   taskName: string,
   taskTitle: string,
   taskPid: number,
-}>();
+  isView?: boolean,
+}>(), {
+  isView: false,
+});
 
 async function loadList() {
   if (props.taskName) {
@@ -57,7 +63,7 @@ async function loadList() {
   if (props.taskPid >= 0) {
     const res = (await VillageApi.getCatalogList(
       props.villageId, 
-      props.villageVolunteerId,
+      authStore.isAdmin ? undefined : props.villageVolunteerId,
       props.taskPid
     ));
     if (res.length === 0)
@@ -69,7 +75,7 @@ async function loadList() {
           const formDefine = collectModuleInternalName ? getVillageInfoForm(collectModuleInternalName, -1) : undefined;
           return {
             ...item,
-            extra: item.total >= 0 ? `采集数:${item.total}` : '',
+            extra: item.total >= 0 ? `已采编 ${item.total}` : '',
             enable: canCollectCatalog(item.id),
             catalogItem: item,
             goForm: collectModuleInternalName ? [ 
@@ -87,6 +93,7 @@ async function loadList() {
                   taskName: '',
                   taskTitle: item.title,
                   taskPid: item.id,
+                  isView: props.isView,
                 })
               } else {
                 alert({
@@ -114,7 +121,7 @@ const currentTaskDefItems = ref<TaskMenuDefItem[]>([]);
 const currentTaskBanner = ref('');
 const handleClick = (item: TaskMenuDefItem) => {
   
-  if (!item.enable) {
+  if (!item.enable && !authStore.isAdmin) {
     uni.showToast({
       title: '您没有完成任务的权限,如需要请联系管理员',
       icon: 'none',
@@ -122,8 +129,12 @@ const handleClick = (item: TaskMenuDefItem) => {
     });
     return;
   }
+
   if (item.goForm instanceof Array) {
-    goForm(...item.goForm);
+    if (props.isView)
+      goPreviewForm(...item.goForm);
+    else
+      goForm(...item.goForm);
     return;
   }
   else if (item.goForm) {
@@ -132,6 +143,7 @@ const handleClick = (item: TaskMenuDefItem) => {
       taskName: item.goForm.name,
       taskTitle: item.title,
       taskPid: (item as any).id,
+      isView: props.isView,
     })
     return;
   }

+ 5 - 1
src/pages/dig/components/TaskList.vue

@@ -33,6 +33,10 @@ defineProps({
     type: String,
     default: '',
   },
+  goButtonText: {
+    type: String,
+    default: '去完成',
+  },
 })
 </script>
 
@@ -58,7 +62,7 @@ defineProps({
         :radius="40" 
         :touchable="enable"
         @click="$emit('click')"
-        :text="enable ? '去完成' : '未开放'"
+        :text="enable ? goButtonText : '未开放'"
       />  
     </FlexRow>
   </FlexRow>

+ 20 - 11
src/pages/dig/forms/common.vue

@@ -16,7 +16,8 @@
           :globalParams="querys"
         />
         <Height :height="20" />
-        <Button type="primary" :loading="loading" @click="submit">提交</Button>
+        <Button v-if="querys.isView" type="primary" :loading="loading" @click="backPrev(false)">返回</Button>
+        <Button v-else type="primary" :loading="loading" @click="submit">提交</Button>
       </FlexCol>
       <XBarSpace />
     </CommonRoot>
@@ -72,6 +73,7 @@ const { querys } = useLoadQuerys({
   subKey: '',
   subTitle: '',
   id: 0,
+  isView: false,
 }, async (querys) => {
   loading.value = true;
   if (querys.subTitle) {
@@ -90,7 +92,9 @@ const { querys } = useLoadQuerys({
     formModel.value = new model() as any;
     formDefine.value = {
       ...formDefine.value,
-      ...forms(formRef as any)
+      ...forms(formRef as any),
+      readonly: querys.isView,
+      disabled: querys.isView,
     };
     if (querys.id >= 0) {
       let findId = querys.id;
@@ -126,23 +130,28 @@ const { querys } = useLoadQuerys({
   } finally {
     loading.value = false;
   }
-    
-  try {
-    formData = await loadLocalSave(formData as CommonInfoModel);
-    if (formData)
-      formModel.value = formData as any;
-  } catch (e) {
-    console.log(e);
-    showError(e, '加载本地保存数据失败');
+
+  if (!querys.isView) {
+    try {
+      formData = await loadLocalSave(formData as CommonInfoModel);
+    } catch (e) {
+      console.log(e);
+      showError(e, '加载本地保存数据失败');
+    }
   }
+  
+  if (formData)
+    formModel.value = formData as any;
 
   await nextTick();
   await waitTimeOut(400);
   formRef.value.initDefaultValuesToModel();
 
   console.log(formModel.value);
+
   await waitTimeOut(1000);
-  canSaveNow = true;
+  if (!querys.isView)
+    canSaveNow = true;
 });
 
 async function submit() {

+ 14 - 0
src/pages/dig/forms/composeable/TaskEntryForm.ts

@@ -19,9 +19,23 @@ export function useTaskEntryForm() {
       subTitle,
     })
   }
+  function goPreviewForm(subType: string, subId: number, subKey = 'type', type = 'list', subTitle = '', catalogId : number|undefined = undefined) {
+    navTo('/pages/dig/forms/' + type, {
+      id: type === 'common' ? 1 : undefined,
+      villageId: querys.value.villageId,  
+      villageVolunteerId: querys.value.villageVolunteerId,  
+      catalogId,
+      subType,
+      subId,
+      subKey,
+      subTitle,
+      isView: true,
+    })
+  }
 
   return {
     goForm,
+    goPreviewForm,
   }
 }
 

+ 8 - 20
src/pages/dig/forms/data/overview.ts

@@ -8,6 +8,7 @@ import type { FieldProps } from "@/components/form/Field.vue";
 import type { UploaderFieldProps } from "@/components/form/UploaderField.vue";
 import type { RowProps } from "@/components/layout/grid/Row.vue";
 import type { GroupForm } from "../forms";
+import { villageCommonContent } from "./common";
 
 export const villageInfoOverviewForm : GroupForm = {
   [1]: [CommonInfoModel, (form) => ({
@@ -75,7 +76,7 @@ export const villageInfoOverviewForm : GroupForm = {
       },
     ]
   }), { title: '行政区划', typeName: '', order: 1 }],
-  [5]: [CommonInfoModel, () => ({
+  [5]: [CommonInfoModel, (f) => ({
     formItems: [
       {
         name: '',
@@ -103,28 +104,15 @@ export const villageInfoOverviewForm : GroupForm = {
               maxLength: 300,
               showWordLimit: true, 
             } as FieldProps,
-          }
-        ]
-      },
-      {
-        name: '',
-        type: 'flat-group',
-        childrenColProps: { span: 24 },
-        children: [
-          {
-            label: '相关视频(可选)',
-            name: 'video',
-            type: 'uploader',
-            defaultValue: '',
-            additionalProps: {
-              upload: useAliOssUploadCo('xiangyuan/cultural/video'),
-              chooseType: 'video',
-              maxFileSize: 1024 * 1024 * 20,
-              single: true,
-            } as UploaderFieldProps,
           },
         ]
       },
+      ...(villageCommonContent(f, { 
+        title: '综述',
+        showContent: false,
+        showTitle: false,
+        noType: true,
+      })).formItems
     ] 
   }), { title: '综述', typeName: '', order: 2 }],
   [2]: [CommonInfoModel, () => ({

+ 10 - 2
src/pages/dig/forms/list.vue

@@ -7,7 +7,7 @@
         :innerStyle="{ width: '460rpx' }"
         @confirm="search"
       />
-      <Button type="primary" @click="newData">+ 新增</Button>
+      <Button v-if="!querys.isView" type="primary" @click="newData">+ 新增</Button>
     </FlexRow>
     <Height :height="20" />
     <FlexCol :gap="20">
@@ -42,7 +42,8 @@
     </FlexCol>
     <SimplePageListLoader :loader="listLoader" :noEmpty="true">
       <template #empty>
-        <Empty image="search" text="这里还没有数据,快来编写完善吧!">
+        <Empty v-if="querys.isView" image="search" text="暂无数据,等待志愿者编写中..." />
+        <Empty v-else image="search" text="这里还没有数据,快来编写完善吧!">
           <Button type="primary" :text="`+ 新增${subTitle}数据`" @click="newData" />
         </Empty>
       </template>
@@ -72,6 +73,7 @@ import Height from '@/components/layout/space/Height.vue';
 import H4 from '@/components/typography/H4.vue';
 import Touchable from '@/components/feedback/Touchable.vue';
 import XBarSpace from '@/components/layout/space/XBarSpace.vue';
+import { alert } from '@/components/dialog/CommonRoot';
 
 const subTitle = ref('');
 const searchText = ref('');
@@ -121,6 +123,10 @@ const listLoader = useSimplePageListLoader<{
 });
 
 function newData() {
+  if (querys.value.isView) {
+    alert({ title: '提示', content: "查看模式下不能新增数据" });
+    return;
+  }
   navTo('common', { 
     id: -1,
     villageId: querys.value.villageId,  
@@ -140,6 +146,7 @@ function goDetail(id: number) {
     subKey: querys.value.subKey,
     subId: querys.value.subId,
     subTitle: querys.value.subTitle,
+    isView: querys.value.isView,
   });
 }
 function search() {
@@ -154,6 +161,7 @@ const { querys } = useLoadQuerys({
   subKey: '',
   subId: 0,
   subTitle: '',
+  isView: false,
 }, async (querys) => {
   if (querys.subTitle) {
     subTitle.value = querys.subTitle;

+ 2 - 0
src/pages/dig/forms/task.vue

@@ -6,6 +6,7 @@
       :taskName="querys.taskName"
       :taskTitle="querys.taskTitle"
       :taskPid="querys.taskPid"
+      :isView="querys.isView"
     />
   </FlexCol>
 </template>
@@ -21,5 +22,6 @@ const { querys } = useLoadQuerys({
   taskName: '',
   taskTitle: '',
   taskPid: 0,
+  isView: false,
 });
 </script>

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

@@ -37,8 +37,9 @@
                 />
                 <H3>{{ item.villageName }}</H3>
               </FlexRow>
-              <FlexRow align="center" :gap="20">
-                <Button v-if="authStore.isAdmin" type="primary" size="small" icon="work-filling" @click="goManagePage(item)">管理</Button>
+              <FlexRow align="center" :gap="10">
+                <Button v-if="authStore.isAdmin" type="primary" size="small" @click="goManagePage(item)">管理</Button>
+                <Button v-if="authStore.isAdmin" size="small" @click="goPreviewDigPage(item)">预览</Button>
                 <Button type="default" size="small" icon="edit-filling" @click="goSubmitDigPage(item)">采编</Button>
               </FlexRow>
             </FlexRow>
@@ -105,4 +106,11 @@ function goManagePage(item: VillageListItem) {
     level: volunteerInfoLoader.content.value?.level,
   })
 }
+function goPreviewDigPage(item: VillageListItem) {
+  navTo('./dig/admin/preview', { 
+    villageName: item.villageName,
+    villageId: item.villageId,
+    villageVolunteerId: item.villageVolunteerId,
+  })
+}
 </script>

+ 46 - 18
src/pages/user/login.vue

@@ -1,12 +1,28 @@
 <template>
   <CommonRoot>
-    <view class="p-3">
-      <FlexCol center v-if="type == 'mobile'">
-        <image class="width-300 m-5" :src="baseLogo" mode="widthFix"></image>
+    <FlexCol>
+      <StatusBarSpace />
+
+      <FlexCol center>
+        <Height :size="200" />
+        <Image
+          :width="200"
+          :src="baseLogo" 
+          mode="widthFix"
+        />
+        <Height :size="60" />
+        <Text bold :fontSize="40">您好!欢迎使用村社文化资源挖掘平台</Text>
+        <Height :size="60" />
+      </FlexCol>
+
+      <FlexCol center v-if="type == 'mobile'" :padding="40">
         <Form 
           :model="loginFormModel"
           :rules="loginFormRules"
           labelWidth="140rpx"
+          :fieldProps="{
+            fieldStyle: fieldStyle,
+          }"
         >
           <Field
             label="用户名"
@@ -21,8 +37,13 @@
             placeholder="请输入密码(6-16位)"
           />
           <Field
-            label="登录为"
+            label="角色"
             name="loginType"
+            required
+            :fieldStyle="{ 
+              ...fieldStyle,
+              backgroundColor: 'transparent' 
+            }"
           >
             <FlexRow align="center">
               <RadioGroup>
@@ -34,27 +55,19 @@
         </Form>
 
         <Height :size="25" />
-        <Button type="primary" block text="登录" @click="loginMobile" />
+        <Button type="primary" block size="large" text="登录" @click="loginMobile" />
         <Height :size="20" />
-        <Button type="default" block text="微信登录" @click="type='wechat'" />
+        <Button type="text" block text="返回" @click="type='wechat'" />
       </FlexCol>
 
-      <FlexCol center v-if="type == 'wechat'">
-        <image 
-          class="width-300"
-          :src="baseLogo" 
-          mode="widthFix"
-        />
-        <view class="mt-3 mb-3">
-          <text class="text-align-center">请登录后进入平台继续操作</text>
-        </view>
+      <FlexCol center v-if="type == 'wechat'" :padding="40">
         <!-- #ifdef MP-WEIXIN -->
-        <Button type="primary" block text="微信登录" @click="loginWechat" />
+        <Button type="primary" block size="large" text="微信登录" icon="wechat-white" :iconProps="{color: 'white'}" @click="loginWechat" />
         <Height :size="20" />
         <!-- #endif -->
-        <Button type="default" block text="用户名密码登录" @click="type='mobile'" />
+        <Button type="default" block size="large" text="用户名密码登录" @click="type='mobile'" />
       </FlexCol>
-    </view>
+    </FlexCol>
   </CommonRoot>
 </template>
 
@@ -75,10 +88,17 @@ import type { Rules } from 'async-validator';
 import { closeToast, toast } from '@/components/dialog/CommonRoot';
 import FlexRow from '@/components/layout/FlexRow.vue';
 import CommonRoot from '@/components/dialog/CommonRoot.vue';
+import StatusBarSpace from '@/components/layout/space/StatusBarSpace.vue';
+import Image from '@/components/basic/Image.vue';
+import Text from '@/components/basic/Text.vue';
+import { useTheme } from '@/components/theme/ThemeDefine';
+import { DynamicColor } from '@/components/theme/ThemeTools';
+import type { back } from '@/components/utils/PageAction';
 
 const type = ref('wechat');
 const authStore = useAuthStore();
 const collectStore = useCollectStore();
+const themeContext = useTheme();
 
 const loginFormModel = ref({
   mobile: '',
@@ -100,6 +120,14 @@ const loginFormRules : Rules = {
   },
 }
 
+const fieldStyle = themeContext.useThemeStyle({
+  backgroundColor: '#ececec',
+  paddingVertical: '30rpx',
+  paddingHorizontal: '25rpx',
+  borderRadius: '20rpx',
+  marginBottom: '20rpx',
+});
+
 function loginWechat() {
 
   toast({