Prechádzať zdrojové kódy

完成单选器选择

yuanmingze 2 mesiacov pred
rodič
commit
f97f82ceff

+ 2 - 0
components.d.ts

@@ -9,8 +9,10 @@ declare module 'vue' {
   export interface GlobalComponents {
     AuthInputItem: typeof import('./src/components/auth/AuthInputItem.vue')['default']
     BaseAuthDialog: typeof import('./src/components/BaseAuthDialog/index.vue')['default']
+    FormField: typeof import('./src/components/FormField/index.vue')['default']
     WdActionSheet: typeof import('@wot-ui/ui/components/wd-action-sheet/wd-action-sheet.vue')['default']
     WdButton: typeof import('@wot-ui/ui/components/wd-button/wd-button.vue')['default']
+    WdCell: typeof import('@wot-ui/ui/components/wd-cell/wd-cell.vue')['default']
     WdCheckbox: typeof import('@wot-ui/ui/components/wd-checkbox/wd-checkbox.vue')['default']
     WdConfigProvider: typeof import('@wot-ui/ui/components/wd-config-provider/wd-config-provider.vue')['default']
     WdDatetimePicker: typeof import('@wot-ui/ui/components/wd-datetime-picker/wd-datetime-picker.vue')['default']

+ 110 - 0
src/components/FormField/index.vue

@@ -0,0 +1,110 @@
+<template>
+  <view class="form-field">
+    <!-- 横向 -->
+    <view v-if="layout === 'horizontal'" class="form-field-horizontal">
+      <view class="form-field-label" :style="{ width: labelWidth }">
+        <view class="form-field-required">
+          <text v-if="required"> * </text>
+        </view>
+        <text class="form-field-title">
+          {{ label }}
+        </text>
+      </view>
+      <view class="form-field-body-right">
+        <slot />
+      </view>
+    </view>
+
+    <!-- 纵向 -->
+    <view v-else class="form-field-vertical">
+      <view class="form-field-label">
+        <view class="form-field-required">
+          <text v-if="required"> * </text>
+        </view>
+        <text class="form-field-title">
+          {{ label }}
+        </text>
+        <text v-if="description" class="form-field-description">
+          {{ description }}
+        </text>
+      </view>
+      <view class="form-field-body-left">
+        <slot />
+      </view>
+    </view>
+  </view>
+</template>
+<script setup lang="ts">
+type FormFieldLayout = 'horizontal' | 'vertical'
+
+interface Props {
+  label: string
+  required?: boolean
+  layout?: FormFieldLayout
+  labelWidth?: string
+  description?: string
+}
+
+withDefaults(defineProps<Props>(), {
+  required: false,
+  layout: 'horizontal',
+  labelWidth: '220rpx',
+  description: '',
+})
+</script>
+
+<style lang="scss" scoped>
+.form-field {
+  background-color: #fff;
+  .form-field-horizontal {
+    display: flex;
+    align-items: center;
+    text-align: left;
+    padding: 20rpx 28rpx;
+
+    .form-field-body-right {
+      line-height: 36rpx;
+      display: flex;
+      align-items: center;
+      justify-content: flex-end;
+      flex: 1;
+    }
+  }
+
+  .form-field-vertical {
+    padding-bottom: 20rpx;
+    display: flex;
+    flex-direction: column;
+    .form-field-label {
+      padding: 24rpx 28rpx;
+    }
+
+    .form-field-body-left {
+      padding: 0 28rpx;
+      display: flex;
+      align-items: center;
+      justify-content: flex-start;
+      flex: 1;
+    }
+  }
+
+  .form-field-title {
+    color: #303133;
+    font-size: 32rpx;
+    white-space: normal;
+    word-break: break-all;
+  }
+
+  .form-field-label {
+    display: flex;
+    align-items: center;
+    line-height: 36rpx;
+  }
+
+  .form-field-required {
+    margin-right: 8rpx;
+    color: #fa3534;
+    font-size: 30rpx;
+  }
+}
+</style>

+ 3 - 0
src/main.ts

@@ -6,6 +6,8 @@ import { setAuthHandlers } from '@/services/request'
 import { setupStore } from '@/stores'
 import { useUserStoreWithOut } from '@/stores/modules/user'
 
+import FormField from '@/components/FormField/index.vue'
+
 setAuthHandlers({
   getAccessToken: () => useUserStoreWithOut().access_token,
   onUnauthorized: () => useUserStoreWithOut().logout(),
@@ -15,6 +17,7 @@ setAuthHandlers({
 import App from './App.vue'
 export function createApp() {
   const app = createSSRApp(App)
+  app.component('FormField', FormField)
 
   setupStore(app)
   return {

+ 37 - 45
src/pages-mine/personal-card/components/UserInfoCard.vue

@@ -52,7 +52,6 @@
     <wd-picker v-model:visible="pickerShow" :columns="pickerColumns" @confirm="pickerConfirm" />
   </view>
 </template>
-
 <script setup lang="ts">
 import { computed, ref } from 'vue'
 
@@ -61,22 +60,15 @@ import type { PersonalBusinessCardResponse } from '@/services/modules/mine/perso
 
 type PickerType = 'gender' | 'degree'
 
-interface PickerConfirmEvent {
-  selectedItems?: DictItem | DictItem[]
-  value?: string | string[]
+interface Props {
+  disabled?: boolean
+  genderColumns: DictItem[]
+  degreeColumns: DictItem[]
 }
 
-const props = withDefaults(
-  defineProps<{
-    title?: string
-    disabled: false
-    genderColumns: DictItem[]
-    degreeColumns: DictItem[]
-  }>(),
-  {
-    title: '基本信息',
-  }
-)
+const props = withDefaults(defineProps<Props>(), {
+  disabled: false,
+})
 
 const model = defineModel<PersonalBusinessCardResponse>({
   required: true,
@@ -84,50 +76,50 @@ const model = defineModel<PersonalBusinessCardResponse>({
 
 const pickerType = ref<PickerType>('gender')
 const pickerShow = ref(false)
-const pickerColumns = ref<DictItem[]>([])
-
-const genderLabel = computed<string>({
-  get() {
-    if (!model.value.gender) return ''
-
-    const current = props.genderColumns.find((item) => item.value === model.value.gender)
-    return current?.label ?? ''
-  },
-  set() {
-    // readonly input 占位 setter,避免 v-model 绑定 readonly computed 报警告
-  },
-})
 
-const degreeLabel = computed<string>({
-  get() {
-    if (!model.value.degree) return ''
+const pickerColumns = computed<DictItem[]>(() =>
+  pickerType.value === 'gender' ? props.genderColumns : props.degreeColumns
+)
 
-    const current = props.degreeColumns.find((item) => item.value === model.value.degree)
-    return current?.label ?? ''
-  },
-  set() {
-    // readonly input 占位 setter,避免 v-model 绑定 readonly computed 报警告
-  },
-})
+const getDictLabel = (columns: DictItem[], value?: string | number) => {
+  if (!value) return ''
 
-function showPicker(type: PickerType) {
+  return columns.find((item) => item.value === value)?.label ?? ''
+}
+
+const createLabelComputed = (type: PickerType) =>
+  computed<string>({
+    get: () =>
+      getDictLabel(
+        type === 'gender' ? props.genderColumns : props.degreeColumns,
+        type === 'gender' ? model.value.gender : model.value.degree
+      ),
+
+    set: () => {},
+  })
+
+const genderLabel = createLabelComputed('gender')
+
+const degreeLabel = createLabelComputed('degree')
+
+const showPicker = (type: PickerType): void => {
   if (props.disabled) return
+
   pickerType.value = type
-  pickerColumns.value = type === 'gender' ? props.genderColumns : props.degreeColumns
   pickerShow.value = true
 }
 
-function pickerConfirm(event: PickerConfirmEvent) {
-  const currentValue = Array.isArray(event.value) ? event.value[0] : event.value
+const pickerConfirm = (event: PickerConfirmEvent): void => {
+  const value = Array.isArray(event.value) ? event.value[0] : event.value
 
-  if (!currentValue) return
+  if (!value) return
 
   if (pickerType.value === 'gender') {
-    model.value.gender = currentValue
+    model.value.gender = value
     return
   }
 
-  model.value.degree = currentValue
+  model.value.degree = value
 }
 </script>
 

+ 1 - 1
src/pages-task/task-entry-page/index.vue

@@ -115,7 +115,7 @@ const getListTaskTypeIds = async () => {
 
 const toTask = (item: TaskItem) => {
   uni.navigateTo({
-    url: `/pages-task/task-form/index?taskTitle=${item.title}&taskId=${item.id}`,
+    url: `/pages-task/task-form/index?taskId=${item.id}&taskTitle=${item.title}`,
   })
 }
 onLoad(async (options: PageLoadOptions = {}) => {

+ 61 - 0
src/pages-task/task-form/components/SingleSelect.vue

@@ -0,0 +1,61 @@
+<template>
+  <view class="single-select">
+    <wd-cell
+      :placeholder="'请选择' + taskFieldConfig.taskFiledValue"
+      :title="taskFieldConfig.taskFiledValue"
+      :required="taskFieldConfig.isMustfill === '1'"
+      @click="pickerClick"
+      is-link
+      :value="label"
+    />
+
+    <wd-picker
+      v-model="pickerValue"
+      v-model:visible="pickerShow"
+      :columns="dict"
+      @confirm="handleSelect"
+    />
+  </view>
+</template>
+
+<script setup lang="ts">
+import type { DictItem } from '@/services/modules/common/type'
+import type { TaskFieldConfigItem, TaskFieldDictMap } from '@/services/modules/task/taskFrom/type'
+
+interface Props {
+  taskFieldConfig: TaskFieldConfigItem
+  taskFieldDict: TaskFieldDictMap
+  disabled?: boolean
+  selectDisabled?: boolean
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  disabled: false,
+  selectDisabled: false,
+})
+
+const label = defineModel<string>('label')
+
+const value = defineModel<string | number>('value')
+
+const dict = ref<DictItem[]>([])
+
+const pickerShow = ref(false)
+
+const pickerValue = ref<(string | number)[]>([])
+
+const pickerClick = () => {
+  const dictName = props.taskFieldConfig.dictGroupName
+  dict.value = dictName ? (props.taskFieldDict[dictName] ?? []) : []
+  pickerShow.value = true
+}
+
+const handleSelect = (option: PickerConfirmEvent) => {
+  const selected = option.selectedItems[0]
+  pickerValue.value = [selected.value]
+  label.value = selected.label
+  value.value = selected.value
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 15 - 0
src/pages-task/task-form/composables/useNavigation.ts

@@ -0,0 +1,15 @@
+import { onMounted } from 'vue'
+
+export const useNavigation = () => {
+  const setNavigationTitle = (title?: string) => {
+    if (!title) return
+
+    uni.setNavigationBarTitle({
+      title,
+    })
+  }
+
+  return {
+    setNavigationTitle,
+  }
+}

+ 56 - 0
src/pages-task/task-form/composables/useTaskForm.ts

@@ -0,0 +1,56 @@
+import { ref } from 'vue'
+
+import { getTaskContentConfigByTaskTypeIdApi } from '@/services/modules/task/taskFrom'
+import type { TaskFieldConfigItem, TaskFieldDictMap } from '@/services/modules/task/taskFrom/type'
+
+interface DynamicFormState {
+  value: Record<
+    string,
+    {
+      label: string | number | boolean | null
+      value: string | number | boolean | null
+    }
+  >
+}
+
+export const useTaskForm = () => {
+  const form = ref<DynamicFormState>({
+    value: {},
+  })
+
+  const taskFieldConfigList = ref<TaskFieldConfigItem[]>([])
+
+  const taskFieldDict = ref<TaskFieldDictMap>({})
+
+  const initFormState = (config: TaskFieldConfigItem[]) => {
+    const value: DynamicFormState['value'] = {}
+
+    config.forEach((item) => {
+      value[item.id] = {
+        label: null,
+        value: null,
+      }
+    })
+
+    form.value = {
+      value,
+    }
+  }
+
+  const loadTaskForm = async (taskTypeId: string) => {
+    const res = await getTaskContentConfigByTaskTypeIdApi(taskTypeId)
+
+    taskFieldConfigList.value = res.data.config
+
+    taskFieldDict.value = res.data.dict
+
+    initFormState(res.data.config)
+  }
+
+  return {
+    form,
+    taskFieldConfigList,
+    taskFieldDict,
+    loadTaskForm,
+  }
+}

+ 32 - 0
src/pages-task/task-form/composables/useTaskNotice.ts

@@ -0,0 +1,32 @@
+import { ref } from 'vue'
+
+const NOTICE_MAP: Record<string, string> = {
+  '13': '请以合法方式获取任务所需信息且不得侵犯任何商业秘密,否则将因此承担民/刑事责任。',
+  '14': '请勿统方,否则您将可能被医疗机构处罚并反馈至相关企业且被企业追究法律责任。',
+  '81': '注意该任务仅支持采集已购买产品的终端!',
+  '99999': '测试qaqaq',
+}
+
+export const useTaskNotice = () => {
+  const showNotice = ref(false)
+  const noticeText = ref('')
+
+  const setNotice = (taskId: string) => {
+    const text = NOTICE_MAP[taskId]
+
+    if (!text) {
+      showNotice.value = false
+      noticeText.value = ''
+      return
+    }
+
+    showNotice.value = true
+    noticeText.value = text
+  }
+
+  return {
+    showNotice,
+    noticeText,
+    setNotice,
+  }
+}

+ 33 - 60
src/pages-task/task-form/index.vue

@@ -9,32 +9,40 @@
         background-color="#fdf6ec "
       />
     </view>
+    <view class="task-form-fields">
+      <view class="task-form-field" v-for="item in taskFieldConfigList" :key="item.id">
+        <template v-if="isSingleSelect(item.taskFiledType)">
+          <SingleSelect
+            :key="item.id"
+            :taskFieldConfig="item"
+            :taskFieldDict="taskFieldDict"
+            v-model:label="form.value[item.id].label"
+            v-model:value="form.value[item.id].value"
+          />
+        </template>
+      </view>
+    </view>
+
+    <wd-button @click="submit">主要按钮</wd-button>
   </view>
 </template>
 
 <script setup lang="ts">
-import { computed, ref } from 'vue'
-
 import { onLoad } from '@dcloudio/uni-app'
 
-import { getTaskContentConfigByTaskTypeIdApi } from '@/services/modules/task/taskFrom/index.ts'
+import SingleSelect from './components/SingleSelect.vue'
+import { useNavigation } from './composables/useNavigation'
+import { useTaskForm } from './composables/useTaskForm'
+import { useTaskNotice } from './composables/useTaskNotice'
 
-interface DynamicFormState {
-  values: {
-    [key: string]: {
-      label: string
-      value: string | number | boolean
-    }
-  }
-}
+const { setNavigationTitle } = useNavigation()
 
-const form = ref<DynamicFormState>({
-  values: {},
-})
+const { showNotice, noticeText, setNotice } = useTaskNotice()
+
+const { form, taskFieldConfigList, taskFieldDict, loadTaskForm } = useTaskForm()
 
-const getTaskContentConfigByTaskTypeId = async (id: string) => {
-  const res = await getTaskContentConfigByTaskTypeIdApi(id)
-  console.log('res', res)
+const isSingleSelect = (taskFiledType: string) => {
+  return ['domain', 'select'].includes(taskFiledType)
 }
 
 type PageLoadOptions = {
@@ -42,52 +50,15 @@ type PageLoadOptions = {
   taskId?: string
 }
 
-const setNavigationTitle = (title?: string) => {
-  if (!title) return
-
-  uni.setNavigationBarTitle({
-    title,
-  })
+const submit = () => {
+  console.log('form.value', form.value)
 }
 
-const showNotice = ref(false)
-const noticeText = ref('')
-
-const setNotice = () => {
-  const showNoticeTaskIds = ['13', '14', '81', '99999']
-
-  let text = ''
-  if (!showNoticeTaskIds.includes(taskId.value)) return
-  switch (taskId.value) {
-    case '13':
-      text = '请以合法方式获取任务所需信息且不得侵犯任何商业秘密,否则将因此承担民/刑事责任。'
-      break
-    case '14':
-      text = '请勿统方,否则您将可能被医疗机构处罚并反馈至相关企业且被企业追究法律责任。'
-      break
-    case '81':
-      text = '注意该任务仅支持采集已购买产品的终端!'
-      break
-    case '99999':
-      text = '测试qaqaq'
-      break
-    default:
-      break
-  }
-
-  showNotice.value = true
-  noticeText.value = text
-}
-
-const taskId = ref('')
-
 onLoad(async (options: PageLoadOptions = {}) => {
   setNavigationTitle(options.taskTitle)
-  taskId.value = options.taskId ?? ''
-
-  setNotice()
-
-  await getTaskContentConfigByTaskTypeId('99999')
+  const taskId = options.taskId ?? ''
+  setNotice(taskId)
+  await loadTaskForm('99999')
 })
 </script>
 
@@ -95,9 +66,11 @@ onLoad(async (options: PageLoadOptions = {}) => {
 .task-form {
   background: #f2f2f2;
   min-height: 100vh;
-  padding: 20rpx 28rpx;
 
   .workbench-notice {
+    background-color: #fff;
+    padding: 0 10rpx;
+    margin: 20rpx 0;
   }
 }
 </style>

+ 0 - 10
src/pages/information/components/academic/InformationSearchBar.vue

@@ -50,16 +50,6 @@ const emit = defineEmits<{
   search: [{ region: string; keyword: string }]
 }>()
 
-interface PickerItem {
-  label: string
-  value: string
-}
-
-interface PickerConfirmEvent {
-  selectedItems: PickerItem[]
-  value: string[]
-}
-
 const region = ref('')
 const keyword = ref('')
 const visible = ref(false)

+ 4 - 0
src/types/wotui-global.d.ts

@@ -0,0 +1,4 @@
+declare interface PickerConfirmEvent {
+  selectedItems: PickerItem[]
+  value: string[]
+}