abpcoder преди 1 седмица
родител
ревизия
c80ba8458b

+ 0 - 46
components.d.ts

@@ -32,53 +32,7 @@ declare module 'vue' {
     IeTabIeTab: typeof import('./src/components/ie-tab/ie-tab.vue')['default']
     IeTableIeTable: typeof import('./src/components/ie-table/ie-table.vue')['default']
     IeTabsSwiperIeTabsSwiper: typeof import('./src/components/ie-tabs-swiper/ie-tabs-swiper.vue')['default']
-    MDragMDrag: typeof import('./src/components/m-drag/m-drag.vue')['default']
-    MDragMDragVue2: typeof import('./src/components/m-drag/m-drag-vue2.vue')['default']
-    MxBottomButtonsMxBottomButtons: typeof import('./src/components/mx-bottom-buttons/mx-bottom-buttons.vue')['default']
-    MxBuyVipMxBuyVip: typeof import('./src/components/mx-buy-vip/mx-buy-vip.vue')['default']
-    MxConditionDropdownMxConditionDropdown: typeof import('./src/components/mx-condition-dropdown/mx-condition-dropdown.vue')['default']
-    MxConditionDropdownMxConditionDropdownItem: typeof import('./src/components/mx-condition-dropdown/mx-condition-dropdown-item.vue')['default']
-    MxConditionDropdownMxConditionDropdownPopup: typeof import('./src/components/mx-condition-dropdown/mx-condition-dropdown-popup.vue')['default']
-    MxConditionMxCondition: typeof import('./src/components/mx-condition/mx-condition.vue')['default']
-    MxConditionMxConditionDropdown: typeof import('./src/components/mx-condition/mx-condition-dropdown.vue')['default']
-    MxCountDownMxCountDown: typeof import('./src/components/mx-count-down/mx-count-down.vue')['default']
-    MxEchartsMxEcharts: typeof import('./src/components/mx-echarts/mx-echarts.vue')['default']
-    MxFormItemMxFormItem: typeof import('./src/components/mx-form-item/mx-form-item.vue')['default']
-    MxIndexMenusMxIndexMenus: typeof import('./src/components/mx-index-menus/mx-index-menus.vue')['default']
-    MxIndexMenusMxIndexMenusItem: typeof import('./src/components/mx-index-menus/mx-index-menus-item.vue')['default']
-    MxIndexMenusMxIndexPagedMenus: typeof import('./src/components/mx-index-menus/mx-index-paged-menus.vue')['default']
-    MxLoginFormItemMxLoginFormItem: typeof import('./src/components/mx-login-form-item/mx-login-form-item.vue')['default']
-    MxNavBarMxNavBar: typeof import('./src/components/mx-nav-bar/mx-nav-bar.vue')['default']
-    MxPaperComponentsMxPaperCompletion: typeof import('./src/components/mx-paper/components/mx-paper-completion.vue')['default']
-    MxPaperComponentsMxPaperNavigatorPopup: typeof import('./src/components/mx-paper/components/mx-paper-navigator-popup.vue')['default']
-    MxPaperComponentsMxPaperProgress: typeof import('./src/components/mx-paper/components/mx-paper-progress.vue')['default']
-    MxPaperComponentsMxPaperTabItem: typeof import('./src/components/mx-paper/components/mx-paper-tab-item.vue')['default']
-    MxPaperMxPaper: typeof import('./src/components/mx-paper/mx-paper.vue')['default']
-    MxPickerMxPicker: typeof import('./src/components/mx-picker/mx-picker.vue')['default']
-    MxPopupTemplateMxPopupTemplate: typeof import('./src/components/mx-popup-template/mx-popup-template.vue')['default']
-    MxProgressMxProgress: typeof import('./src/components/mx-progress/mx-progress.vue')['default']
-    MxQuestionComponentsMxQuestionCollect: typeof import('./src/components/mx-question/components/mx-question-collect.vue')['default']
-    MxQuestionComponentsMxQuestionCorrect: typeof import('./src/components/mx-question/components/mx-question-correct.vue')['default']
-    MxQuestionComponentsMxQuestionCorrectPopup: typeof import('./src/components/mx-question/components/mx-question-correct-popup.vue')['default']
-    MxQuestionComponentsMxQuestionNavigator: typeof import('./src/components/mx-question/components/mx-question-navigator.vue')['default']
-    MxQuestionComponentsMxQuestionParse: typeof import('./src/components/mx-question/components/mx-question-parse.vue')['default']
-    MxQuestionComponentsMxQuestionScoreSubjective: typeof import('./src/components/mx-question/components/mx-question-score-subjective.vue')['default']
-    MxQuestionComponentsMxQuestionStatistic: typeof import('./src/components/mx-question/components/mx-question-statistic.vue')['default']
-    MxQuestionContentComponentsMxQuestionPlainOption: typeof import('./src/components/mx-question-content/components/mx-question-plain-option.vue')['default']
-    MxQuestionContentComponentsMxQuestionPlainOptionGroup: typeof import('./src/components/mx-question-content/components/mx-question-plain-option-group.vue')['default']
-    MxQuestionContentComponentsMxQuestionSubjective: typeof import('./src/components/mx-question-content/components/mx-question-subjective.vue')['default']
-    MxQuestionContentMxQuestionContent: typeof import('./src/components/mx-question-content/mx-question-content.vue')['default']
-    MxQuestionMxQuestion: typeof import('./src/components/mx-question/mx-question.vue')['default']
-    MxSearchMxSearch: typeof import('./src/components/mx-search/mx-search.vue')['default']
-    MxStepsMxSteps: typeof import('./src/components/mx-steps/mx-steps.vue')['default']
-    MxSubmitLayoutMxSubmitLayout: typeof import('./src/components/mx-submit-layout/mx-submit-layout.vue')['default']
-    MxSubsectionMxSubsection: typeof import('./src/components/mx-subsection/mx-subsection.vue')['default']
-    MxTabsSwiperMxTabsSwiper: typeof import('./src/components/mx-tabs-swiper/mx-tabs-swiper.vue')['default']
-    MxTagButtonMxTagButton: typeof import('./src/components/mx-tag-button/mx-tag-button.vue')['default']
-    MxVideoMxVideo: typeof import('./src/components/mx-video/mx-video.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
-    VipGuideMoreVipGuideMore: typeof import('./src/components/vip-guide-more/vip-guide-more.vue')['default']
-    VueSvgIconsVueSvgIcons: typeof import('./src/components/vue-svg-icons/vue-svg-icons.vue')['default']
   }
 }

+ 169 - 152
src/components/ie-picker/ie-picker.vue

@@ -1,203 +1,220 @@
 <template>
-  <view class="w-full" @click="handleClick">
-    <view class="flex items-center gap-x-6 justify-start" :style="customStyle">
-      <view v-if="matchValue || customLabel" class="text-[15px] h-[24px] leading-[26px] text-fore-title flex-1 min-w-1 ellipsis-1" :style="getValueStyle"
-        :class="{ 'text-[#dce4f6]': disabled || readonly }">
-        <slot :label="label">
-          {{ label }}
-        </slot>
-      </view>
-      <view v-else class="text-[15px] h-[24px] leading-[26px] text-[#c0c4cc] flex-1 min-w-1 ellipsis-1" :style="getPlaceholderStyle">{{ placeholder }}</view>
-      <slot name="right">
-        <view v-if="!readonly && showArrow" class="transition-all duration-300">
-          <uv-icon :name="icon" size="15" color="#B3B3B3" />
+    <view :class="width" @click="handleClick">
+        <view class="flex items-center gap-x-6 justify-start" :style="customStyle">
+            <view v-if="matchValue || customLabel"
+                  class="text-[15px] h-[24px] leading-[26px] flex-1 min-w-1 ellipsis-1"
+                  :style="getValueStyle"
+                  :class="[(disabled || readonly) ? 'text-[#dce4f6]': color]">
+                <slot :label="label">
+                    {{ label }}
+                </slot>
+            </view>
+            <view v-else class="text-[15px] h-[24px] leading-[26px] text-[#c0c4cc] flex-1 min-w-1 ellipsis-1"
+                  :style="getPlaceholderStyle">{{ placeholder }}
+            </view>
+            <slot name="right">
+                <view v-if="!readonly && showArrow" class="transition-all duration-300">
+                    <uv-icon :name="icon" size="15" :color="iconColor"/>
+                </view>
+            </slot>
         </view>
-      </slot>
     </view>
-  </view>
-  <!-- #ifdef H5 -->
-  <teleport to="body">
-    <!-- #endif -->
-    <!-- #ifdef MP-WEIXIN -->
-    <root-portal externalClass="theme-ie">
-      <!-- #endif -->
-      <uv-picker ref="pickerRef" :showToolbar="false" :columns="columns" :defaultIndex="defaultIndex" :round="16"
-        activeColor="#31A0FC" :keyName="keyLabel" :title="title" @change="handleChange" @close="onClose">
-        <template #toolbar>
-          <view class="theme-ie">
-            <view class="flex items-center justify-between pt-20">
-              <view class="px-46 py-20 text-28 text-fore-light" @click="handleCancel">取消</view>
-              <text class="text-30 text-fore-title font-bold">{{ title }}</text>
-              <view class="px-46 py-20 text-28 text-fore-title" @click="handleConfirm">确认</view>
-            </view>
-          </view>
-        </template>
-      </uv-picker>
-      <!-- #ifdef MP-WEIXIN -->
-    </root-portal>
-    <!-- #endif -->
     <!-- #ifdef H5 -->
-  </teleport>
-  <!-- #endif -->
+    <teleport to="body">
+        <!-- #endif -->
+        <!-- #ifdef MP-WEIXIN -->
+        <root-portal externalClass="theme-ie">
+            <!-- #endif -->
+            <uv-picker ref="pickerRef" :showToolbar="false" :columns="columns" :defaultIndex="defaultIndex" :round="16"
+                       activeColor="#31A0FC" :keyName="keyLabel" :title="title" @change="handleChange" @close="onClose">
+                <template #toolbar>
+                    <view class="theme-ie">
+                        <view class="flex items-center justify-between pt-20">
+                            <view class="px-46 py-20 text-28 text-fore-light" @click="handleCancel">取消</view>
+                            <text class="text-30 text-fore-title font-bold">{{ title }}</text>
+                            <view class="px-46 py-20 text-28 text-fore-title" @click="handleConfirm">确认</view>
+                        </view>
+                    </view>
+                </template>
+            </uv-picker>
+            <!-- #ifdef MP-WEIXIN -->
+        </root-portal>
+        <!-- #endif -->
+        <!-- #ifdef H5 -->
+    </teleport>
+    <!-- #endif -->
 </template>
 <script lang="ts" setup>
 defineOptions({
-  options: {
-    virtualHost: true
-  }
+    options: {
+        virtualHost: true
+    }
 });
 const modelValue = defineModel<string | number>('modelValue', {
-  default: ''
+    default: ''
 });
 const props = defineProps({
-  placeholder: {
-    type: String,
-    default: '请选择',
-  },
-  customLabel: {
-    type: String,
-    default: '',
-  },
-  title: {
-    type: String,
-    default: '',
-  },
-  icon: {
-    type: String,
-    default: 'arrow-right',
-  },
-  list: {
-    type: Array as PropType<any[]>,
-    default: () => [],
-  },
-  keyLabel: {
-    type: String,
-    default: 'label',
-  },
-  keyValue: {
-    type: String,
-    default: 'value',
-  },
-  disabled: {
-    type: Boolean,
-    default: false,
-  },
-  readonly: {
-    type: Boolean,
-    default: false,
-  },
-  customStyle: {
-    type: Object,
-    default: () => ({}),
-  },
-  placeholderStyle: {
-    type: Object,
-    default: () => ({}),
-  },
-  fontSize: {
-    type: Number,
-    default: 15,
-  },
-  showArrow: {
-    type: Boolean,
-    default: true,
-  },
+    placeholder: {
+        type: String,
+        default: '请选择',
+    },
+    customLabel: {
+        type: String,
+        default: '',
+    },
+    title: {
+        type: String,
+        default: '',
+    },
+    icon: {
+        type: String,
+        default: 'arrow-right',
+    },
+    iconColor: {
+        type: String,
+        default: '#B3B3B3'
+    },
+    list: {
+        type: Array as PropType<any[]>,
+        default: () => [],
+    },
+    keyLabel: {
+        type: String,
+        default: 'label',
+    },
+    keyValue: {
+        type: String,
+        default: 'value',
+    },
+    disabled: {
+        type: Boolean,
+        default: false,
+    },
+    readonly: {
+        type: Boolean,
+        default: false,
+    },
+    customStyle: {
+        type: Object,
+        default: () => ({}),
+    },
+    width: {
+        type: String,
+        default: 'w-full'
+    },
+    color: {
+        type: String,
+        default: 'text-fore-title'
+    },
+    placeholderStyle: {
+        type: Object,
+        default: () => ({}),
+    },
+    fontSize: {
+        type: Number,
+        default: 15,
+    },
+    showArrow: {
+        type: Boolean,
+        default: true,
+    },
 });
 const defaultIndex = ref([0]);
 const label = ref('');
 const isOpen = ref(false);
 const matchValue = ref(false);
 const columns = computed(() => {
-  return [props.list];
+    return [props.list];
 });
 const getPlaceholderStyle = computed(() => {
-  return {
-    ...props.placeholderStyle,
-    fontSize: props.fontSize + 'px'
-  }
+    return {
+        ...props.placeholderStyle,
+        fontSize: props.fontSize + 'px'
+    }
 });
 const getValueStyle = computed(() => {
-  return {
-    fontSize: props.fontSize + 'px'
-  }
+    return {
+        fontSize: props.fontSize + 'px'
+    }
 });
 const init = () => {
-  if (props.customLabel) {
-    label.value = props.customLabel;
-    matchValue.value = true;
-    return;
-  }
+    if (props.customLabel) {
+        label.value = props.customLabel;
+        matchValue.value = true;
+        return;
+    }
 
-  if (modelValue.value !== null && modelValue.value !== undefined && modelValue.value !== '') {
-    const index = props.list.findIndex(item => item[props.keyValue] == modelValue.value);
-    if (index !== -1) {
-      defaultIndex.value = [index];
-      label.value = props.list[index][props.keyLabel];
-      matchValue.value = true;
+    if (modelValue.value !== null && modelValue.value !== undefined && modelValue.value !== '') {
+        const index = props.list.findIndex(item => item[props.keyValue] == modelValue.value);
+        if (index !== -1) {
+            defaultIndex.value = [index];
+            label.value = props.list[index][props.keyLabel];
+            matchValue.value = true;
+        } else {
+            // 如果找不到匹配项,重置状态
+            defaultIndex.value = [0];
+            label.value = modelValue.value + '';
+            matchValue.value = true;
+        }
     } else {
-      // 如果找不到匹配项,重置状态
-      defaultIndex.value = [0];
-      label.value = modelValue.value + '';
-      matchValue.value = true;
+        defaultIndex.value = [0];
+        label.value = props.placeholder;
+        matchValue.value = false;
     }
-  } else {
-    defaultIndex.value = [0];
-    label.value = props.placeholder;
-    matchValue.value = false;
-  }
 }
 
 // 使用 watch 替代 watchEffect,避免循环更新
-watch([() => modelValue.value, () => props.list], init, { immediate: true });
+watch([() => modelValue.value, () => props.list], init, {immediate: true});
 
 const pickerRef = ref();
 
 const emit = defineEmits<{
-  (e: 'change', value: number, selectedItem: any): void;
-  (e: 'click'): void;
+    (e: 'change', value: number, selectedItem: any): void;
+    (e: 'click'): void;
 }>();
 
 const handleConfirm = () => {
-  const { value } = pickerRef.value.manualConfirm();
-  const oldValue = modelValue.value;
-  const newValue = value[0][props.keyValue];
+    const {value} = pickerRef.value.manualConfirm();
+    const oldValue = modelValue.value;
+    const newValue = value[0][props.keyValue];
 
-  // 更新 modelValue
-  modelValue.value = newValue;
+    // 更新 modelValue
+    modelValue.value = newValue;
 
-  // 手动更新显示状态,确保界面同步
-  const selectedItem = value[0];
-  label.value = selectedItem[props.keyLabel];
-  matchValue.value = true;
+    // 手动更新显示状态,确保界面同步
+    const selectedItem = value[0];
+    label.value = selectedItem[props.keyLabel];
+    matchValue.value = true;
 
-  // 发出 change 事件
-  if (oldValue !== newValue) {
-    emit('change', newValue, selectedItem);
-  }
+    // 发出 change 事件
+    if (oldValue !== newValue) {
+        emit('change', newValue, selectedItem);
+    }
 
-  pickerRef.value.close();
+    pickerRef.value.close();
 }
 const handleCancel = () => {
-  pickerRef.value.close();
+    pickerRef.value.close();
+}
+const handleChange = () => {
 }
-const handleChange = () => { }
 
 const handleClick = () => {
-  emit('click');
-  if (props.disabled || props.readonly) {
-    return;
-  }
-  if (!props.list.length) {
-    uni.$ie.showToast('暂无数据');
-    return;
-  }
-  init();
-  isOpen.value = true;
-  pickerRef.value.open();
+    emit('click');
+    if (props.disabled || props.readonly) {
+        return;
+    }
+    if (!props.list.length) {
+        uni.$ie.showToast('暂无数据');
+        return;
+    }
+    init();
+    isOpen.value = true;
+    pickerRef.value.open();
 }
 
 const onClose = () => {
-  isOpen.value = false;
+    isOpen.value = false;
 }
 </script>
 <style lang="scss" scoped></style>

+ 64 - 60
src/components/ie-table/ie-table.vue

@@ -1,115 +1,119 @@
 <template>
-  <view class="">
-    <view class="table-header">
-      <view class="table-header-cell" v-for="item in tableColumns" :key="item.prop" :style="getHeaderStyle(item)">
-        {{ item.label }}
-      </view>
-    </view>
-    <view class="table-body">
-      <block v-if="data.length">
-        <view class="table-row" :class="{ 'sibling-border-top': getTableConfig.border }" v-for="(row, index) in data"
-          :key="getRowKey(row, index)" @click="handleRowClick(row)">
-          <view class="table-row-cell" v-for="item in tableColumns" :key="item.prop" :style="getCellStyle(item)">
-            <view v-if="item.type === 'index'">
-              {{ index + 1 }}
+    <view class="">
+        <view class="table-header">
+            <view class="table-header-cell" v-for="item in tableColumns" :key="item.prop" :style="getHeaderStyle(item)">
+                {{ item.label }}
             </view>
-            <template v-else-if="item.slot">
-              <slot :name="item.slot" :item="row" :index="index"></slot>
-            </template>
-            <text v-else>{{ getCellValue(row, item.prop) }}</text>
-          </view>
         </view>
-      </block>
-      <view v-else class="no-data">{{ getTableConfig.emptyText }}</view>
+        <view class="table-body">
+            <block v-if="data.length">
+                <view class="table-row" :class="{ 'sibling-border-top': getTableConfig.border }"
+                      v-for="(row, index) in data"
+                      :key="getRowKey(row, index)" @click="handleRowClick(row)">
+                    <view class="table-row-cell" v-for="item in tableColumns" :key="item.prop"
+                          :style="getCellStyle(item)">
+                        <view v-if="item.type === 'index'">
+                            {{ index + 1 }}
+                        </view>
+                        <template v-else-if="item.slot">
+                            <slot :name="item.slot" :item="row" :index="index"></slot>
+                        </template>
+                        <text v-else>{{ getCellValue(row, item.prop) }}</text>
+                    </view>
+                </view>
+            </block>
+            <view v-else class="no-data">{{ getTableConfig.emptyText }}</view>
+        </view>
     </view>
-  </view>
 </template>
 
 <script lang="ts" setup generic="T extends Record<string, any>">
-import { TableColumnConfig, TableConfig } from '@/types';
-import { CSSProperties } from 'vue';
+import {TableColumnConfig, TableConfig} from '@/types';
+import {CSSProperties} from 'vue';
 
 // 使用泛型定义props
 interface Props<T> {
-  tableConfig: TableConfig;
-  tableColumns: TableColumnConfig[];
-  data: T[];
-  cellStyle: CSSProperties;
+    tableConfig: TableConfig;
+    tableColumns: TableColumnConfig[];
+    data: T[];
+    cellStyle: CSSProperties;
+    headerStyle: CSSProperties;
 }
 
 const props = defineProps<Props<any>>();
 
 // 使用泛型定义emits
 const emit = defineEmits<{
-  rowClick: [row: T]
+    rowClick: [row: T]
 }>();
 
 const getTableConfig = computed(() => {
-  return {
-    ...{
-      border: true,
-      stripe: false,
-      emptyText: '暂无数据',
-      loading: false,
-      rowKey: 'id'
-    },
-    ...props.tableConfig
-  };
+    return {
+        ...{
+            border: true,
+            stripe: false,
+            emptyText: '暂无数据',
+            loading: false,
+            rowKey: 'id'
+        },
+        ...props.tableConfig
+    };
 });
 
 const getHeaderStyle = (item: TableColumnConfig) => {
-  return {
-    flex: item.flex ? item.flex : 1,
-    minWidth: '1px',
-    textAlign: item.headerAlign ? item.headerAlign : 'center'
-  };
+    return {
+        flex: item.flex ? item.flex : 1,
+        minWidth: '1px',
+        textAlign: item.headerAlign ? item.headerAlign : 'center',
+        ...props.headerStyle
+    };
 };
 
 const getCellStyle = (item: TableColumnConfig) => {
-  return {
-    flex: item.flex ? item.flex : 1,
-    minWidth: '1px',
-    flexShrink: 0,
-    width: '100%',
-    textAlign: item.align ? item.align : 'center',
-    ...props.cellStyle
-  };
+    return {
+        flex: item.flex ? item.flex : 1,
+        minWidth: '1px',
+        flexShrink: 0,
+        width: '100%',
+        textAlign: item.align ? item.align : 'center',
+        ...props.cellStyle
+    };
 };
 
 const handleRowClick = (row: any) => {
-  emit('rowClick', row);
+    emit('rowClick', row);
 };
 
 // 安全地获取行key
 const getRowKey = (row: any, index: number) => {
-  const rowKey = getTableConfig.value.rowKey;
-  return row[rowKey] || index;
+    const rowKey = getTableConfig.value.rowKey;
+    return row[rowKey] || index;
 };
 
 // 安全地获取单元格值
 const getCellValue = (row: any, prop: string) => {
-  return row[prop] || '';
+    return row[prop] || props.tableConfig.defaultValue || '';
 };
 </script>
 
 <style lang="scss" scoped>
 .table-header {
-  @apply flex items-center bg-[#EBF9FF] rounded-5;
+    @apply flex items-center bg-[#EBF9FF] rounded-5 overflow-hidden;
 }
 
 .table-header-cell {
-  @apply flex-1 px-20 py-20 text-30 text-fore-light;
+    @apply flex-1 px-20 py-20 text-30 text-fore-light;
 }
 
 .table-row {
-  @apply flex items-center;
+    @apply flex items-center;
 }
 
 .table-row-cell {
-  @apply px-20 py-20 text-28 text-fore-title;
+    @apply px-20 py-20 text-28 text-fore-title;
 }
 
 .no-data {
-  @apply mt-16 bg-[#F6F8FA] text-center py-50 text-26 text-fore-light rounded-5;
+    @apply mt-16 bg-[#F6F8FA] text-center py-50 text-26 text-fore-light rounded-5;
 }
 </style>

+ 1 - 1
src/main.ts

@@ -95,7 +95,7 @@ export function createApp() {
                 disabledColor: {default: 'var(--back-light)'},
                 customStyle: {
                     default: () => ({
-                        height: '70rpx',
+                        height: '30px',
                         paddingLeft: '40rpx',
                         paddingRight: '40rpx',
                         borderRadius: '24rpx'

+ 2 - 2
src/pagesOther/pages/voluntary/index/components/voluntary-form-major.vue

@@ -8,7 +8,7 @@
                     {{ data.majorName }}
                 </view>
                 <view class="mt-8 w-fit text-20 text-info border border-solid border-info rounded-4 px-10 py-4">
-                    {{ data.groupName }}
+                    {{ data.majorGroup }}
                 </view>
             </view>
         </view>
@@ -22,7 +22,7 @@ import {SelectedCollegeMajorWithRules} from "@/types/voluntary";
 const props = defineProps<{
     customClass?: string;
 }>();
-const data = inject(VOLUNTARY_FORM) || {} as SelectedCollegeMajorWithRules
+const data = inject(VOLUNTARY_FORM) || ref({} as SelectedCollegeMajorWithRules)
 </script>
 
 <style scoped>

+ 49 - 3
src/pagesOther/pages/voluntary/index/index.vue

@@ -28,7 +28,7 @@
 
 import config from "@/config";
 import VoluntaryForm from "@/pagesOther/pages/voluntary/index/components/voluntary-form.vue";
-import {SelectedCollegeMajorWithRules, VoluntaryDto, VoluntaryModel} from "@/types/voluntary";
+import {SelectedCollegeMajorWithRules, VoluntaryDto, VoluntaryModel, VoluntaryResult} from "@/types/voluntary";
 import {VOLUNTARY_FORM, VOLUNTARY_MODEL} from "@/types/injectionSymbols";
 import {useTransferPage} from "@/hooks/useTransferPage";
 import {routes} from "@/common/routes";
@@ -42,7 +42,53 @@ const {transferTo} = useTransferPage<any, VoluntaryDto>()
 
 const handleSubmit = async () => {
     await form.value?.validate()
-    const bigData = {data: data.value, model: model.value}
+    // make request
+    await nextTick()
+    const result: VoluntaryResult = {
+        "universityCode": "12302",
+        "majorCode": "690390",
+        "majorEnrollCode": "68508",
+        "majorGroup": "专业B组",
+        "majorName": "集成电路技术",
+        "majorDirection": "",
+        "enumPickType": "Danger",
+        "enrollRate": 53,
+        "enrollRateText": "风险极高",
+        "tips": null,
+        "histories": [
+            {
+                "year": 2025,
+                "score": "489.0",
+                "plan": 15,
+                "enroll": 15,
+                "diff": -12,
+                "ruleContent": '语数外180(语100*0.6+数100*0.6+外100*0.6)+职业技能420(技能展示300*1.4)',
+                "application": 3.1,
+                "admission": 1
+            },
+            {
+                "year": 2024,
+                "score": "",
+                "plan": null,
+                "enroll": 20
+            },
+            {
+                "year": 2023,
+                "score": "504.0",
+                "plan": null,
+                "enroll": 32
+            }
+        ],
+        "skill": {
+            year: 2025,
+            cultureScore: 230,
+            cultureRule: "语数外",
+            enrollScore: 489,
+            skillScore: 259, // 反向测技能分
+            diff: -23 // 负数表示低于skillScore,正数高于
+        }
+    }
+    const bigData: VoluntaryDto = {data: data.value, model: model.value, result}
     transferTo(routes.voluntaryResult, {bigData})
 }
 
@@ -54,7 +100,7 @@ onMounted(async () => {
         majorAncestors: "交通运输大类>铁道运输类",
         majorId: "68526",
         majorName: "铁道交通运营管理",
-        groupName: '专业组一',
+        majorGroup: '专业组一',
         // notice: "",
         universityId: "20949",
         universityLogo: "https://mingxuejingbang.oss-cn-beijing.aliyuncs.com/ie/universityLog/23b6da550a584ea6b60886c6ae97b610.jpg",

+ 6 - 2
src/pagesOther/pages/voluntary/result/components/plus/voluntary-result-card.vue

@@ -1,6 +1,6 @@
 <template>
-    <view class="p-25 flex justify-between items-start gap-20 rounded-xl border border-solid border-sky-300"
-          style="background: linear-gradient(to bottom, #EAF8FF 0%, #FFFFFF 36.82%)">
+    <view class="px-25 py-20 flex justify-between items-start gap-20 rounded-xl border border-solid border-sky-300"
+          :class="customClass" style="background: linear-gradient(to bottom, #EAF8FF 0%, #FFFFFF 36.82%)">
         <slot name="title">
             <voluntary-result-tag :tag="tag"/>
         </slot>
@@ -15,6 +15,10 @@ defineProps({
     tag: {
         type: String,
         default: ''
+    },
+    customClass: {
+        type: String,
+        default: ''
     }
 })
 </script>

+ 4 - 3
src/pagesOther/pages/voluntary/result/components/plus/voluntary-result-title.vue

@@ -1,7 +1,8 @@
 <template>
-    <view class="relative">
-        <view class="pl-10 text-28 text-fore-title font-bold relative z-10">{{ title }}</view>
-        <view class="w-90 h-20 rounded-full absolute -bottom-3 bg-gradient-to-br from-cyan-300 to-white"/>
+    <view class="relative flex justify-between items-center">
+        <view class="min-w-1 flex-1 pl-10 text-28 text-fore-title font-bold relative z-10">{{ title }}</view>
+        <view class="w-90 h-20 rounded-full absolute -bottom-3 bg-gradient-to-br from-sky-300 to-white"/>
+        <slot name="right"/>
     </view>
 </template>
 

+ 20 - 3
src/pagesOther/pages/voluntary/result/components/voluntary-result-analysis.vue

@@ -7,7 +7,7 @@
         <voluntary-result-card tag="评估">
             <view class="flex-1 text-23 leading-38 text-fore-title">
                 根据历年数据对比分析,预估目前成绩报考该院校/专业,录取风险属于
-                <text class="text-danger">较高风险</text>
+                <text :class="rateClass">{{result.enrollRateText}}</text>
             </view>
         </voluntary-result-card>
     </view>
@@ -17,8 +17,25 @@
 import VoluntaryResultTitle from "@/pagesOther/pages/voluntary/result/components/plus/voluntary-result-title.vue";
 import IeEchart from "@/pagesOther/components/ie-echart/ie-echart.vue";
 import VoluntaryResultCard from "@/pagesOther/pages/voluntary/result/components/plus/voluntary-result-card.vue";
+import {VOLUNTARY_RESULT} from "@/types/injectionSymbols";
+import {VoluntaryResult} from "@/types/voluntary";
+import {sleep} from "@/uni_modules/uv-ui-tools/libs/function";
 
 const option = ref({})
+const result = inject(VOLUNTARY_RESULT) || ref({} as VoluntaryResult)
+
+const rateClass = computed(() => {
+    switch (result.value.enumPickType) {
+        case 'Danger':
+            return 'text-danger'
+        case 'Normal':
+            return 'text-primary'
+        case 'Safety':
+            return 'text-success'
+    }
+    return ''
+})
+
 const drawRing = (rate: number | null) => {
     const baseGauge = {
         type: 'gauge',
@@ -191,8 +208,8 @@ const drawRing = (rate: number | null) => {
 
 
 onMounted(async () => {
-    await nextTick()
-    drawRing(81)
+    await sleep(200)
+    drawRing(result.value.enrollRate)
 })
 </script>
 

+ 46 - 0
src/pagesOther/pages/voluntary/result/components/voluntary-result-compare.vue

@@ -0,0 +1,46 @@
+<template>
+    <view class="p-28 bg-white rounded-xl">
+        <voluntary-result-title title="专业报录比">
+            <template #right>
+                <ie-picker v-model="year" :list="years" icon="arrow-down" width="" color="text-primary"
+                           icon-color="primary"/>
+            </template>
+        </voluntary-result-title>
+        <view class="pt-28 px-100 flex justify-between items-center">
+            <view class="flex flex-col items-center gap-18">
+                <view class="px-25 py-10 bg-primary text-white font-bold text-28 rounded">报考人数</view>
+                <view class="text-60 text-fore-title">{{currentHistory.application||'-'}}</view>
+            </view>
+            <view class="flex flex-col items-center gap-18">
+                <view class="h-48"></view>
+                <view class="text-60 text-fore-title">:</view>
+            </view>
+            <view class="flex flex-col items-center gap-18">
+                <view class="px-25 py-10 bg-primary text-white font-bold text-28 rounded">计划人数</view>
+                <view class="text-60 text-fore-title">{{currentHistory.admission||'-'}}</view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script setup lang="ts">
+import VoluntaryResultTitle from "@/pagesOther/pages/voluntary/result/components/plus/voluntary-result-title.vue";
+import {VOLUNTARY_RESULT} from "@/types/injectionSymbols";
+import {VoluntaryResult} from "@/types/voluntary";
+
+const result = inject(VOLUNTARY_RESULT) || ref({} as VoluntaryResult)
+const historyList = computed(() => result?.value.histories || [])
+const years = computed(() => historyList.value.map(h => ({label: h.year + '年', value: h.year})))
+const year = ref<string | number>('')
+const currentHistory = computed(() => historyList.value.find(h => h.year == year.value) || {})
+
+watch(historyList, (list) => {
+    if (list.length) {
+        year.value = list[0].year
+    }
+}, {immediate: true})
+</script>
+
+<style scoped>
+
+</style>

+ 18 - 0
src/pagesOther/pages/voluntary/result/components/voluntary-result-disclaimer.vue

@@ -0,0 +1,18 @@
+<template>
+    <view class="p-28 bg-white rounded-xl">
+        <view class="flex items-center gap-10">
+            <uv-icon name="info-circle" color="warning"/>
+            <text class="font-bold text-fore-title text-24">免责申明</text>
+        </view>
+        <view class="mt-20 text-24 leading-38 text-fore-title">
+            系统提供的数据源于院校招生章程与官方文件,仅供您参考。实际录取结果受当年招生计划、报考人数、试题难度等多重因素影响,建议您结合当年实际情况慎重决策。
+        </view>
+    </view>
+</template>
+
+<script setup lang="ts">
+</script>
+
+<style scoped>
+
+</style>

+ 68 - 1
src/pagesOther/pages/voluntary/result/components/voluntary-result-history.vue

@@ -1,8 +1,75 @@
 <template>
-
+    <view class="p-28 bg-white rounded-xl">
+        <voluntary-result-title title="招录数据"/>
+        <uv-gap height="15"/>
+        <ie-table :table-columns="columns" :data="list" :table-config="tableConf" :header-style="headerStyle"
+                  :cell-style="cellStyle">
+            <template #diff="{item}">
+                <text :class="getDiffOpt(item).clazz">{{ getDiffOpt(item).text }}</text>
+            </template>
+        </ie-table>
+        <uv-gap height="15"/>
+        <voluntary-result-card custom-class="flex-col">
+            <template #title>
+                <view class="flex gap-6 items-end">
+                    <voluntary-result-tag tag="录取规则" />
+                    <view class="text-primary text-24">({{recentHistory.year}}年)</view>
+                </view>
+            </template>
+            <view class="flex-1 text-23 leading-38 text-fore-title">{{recentHistory.ruleContent}}</view>
+        </voluntary-result-card>
+    </view>
 </template>
 
 <script setup lang="ts" name="VoluntaryResultHistory">
+import VoluntaryResultTitle from "@/pagesOther/pages/voluntary/result/components/plus/voluntary-result-title.vue";
+import {VOLUNTARY_RESULT} from "@/types/injectionSymbols";
+import {VoluntaryResult, VoluntaryResultHistory} from "@/types/voluntary";
+import VoluntaryResultCard from "@/pagesOther/pages/voluntary/result/components/plus/voluntary-result-card.vue";
+import _ from "lodash";
+import VoluntaryResultTag from "@/pagesOther/pages/voluntary/result/components/plus/voluntary-result-tag.vue";
+
+const result = inject(VOLUNTARY_RESULT) || ref({} as VoluntaryResult)
+const columns = [{
+    label: '年份',
+    prop: 'year'
+}, {
+    label: '计划',
+    prop: 'plan'
+}, {
+    label: '最低分',
+    prop: 'score'
+}, {
+    label: '线差',
+    prop: 'diff',
+    slot: 'diff'
+}]
+const tableConf = {
+    defaultValue: '-'
+}
+const headerStyle = {
+    fontSize: '28rpx',
+    color: 'var(--fore-title)',
+    paddingTop: '15rpx',
+    paddingBottom: '15rpx',
+    backgroundColor: '#B2E7FF'
+}
+const cellStyle = {
+    fontSize: '28rpx',
+    color: 'var(--fore-title)',
+    paddingTop: '15rpx',
+    paddingBottom: '15rpx'
+}
+
+const list = computed(() => result?.value.histories || [])
+const recentHistory = computed(() => _.first(list.value) || {} as VoluntaryResultHistory)
+
+const getDiffOpt = (item: VoluntaryResultHistory) => {
+    if (item.diff === null || item.diff === undefined) return {text: '-'}
+    if (item.diff < 0) return {clazz: 'text-danger', text: '低' + Math.abs(item.diff) + '分'}
+    if (item.diff > 0) return {clazz: 'text-success', text: '高' + Math.abs(item.diff) + '分'}
+    if (item.diff === 0) return {clazz: 'text-primary', text: '持平'}
+}
 </script>
 
 <style scoped>

+ 49 - 6
src/pagesOther/pages/voluntary/result/components/voluntary-result-score.vue

@@ -6,8 +6,15 @@
         </view>
         <voluntary-result-card tag="分析">
             <view class="flex-1 text-23 leading-38 text-fore-title">
-                该专业考试总分为<text class="text-primary">600</text>分,您当前成绩为<text class="text-primary">340</text>分。
-                参考<text>2025</text>年录取分数线,预估还需再提升<text class="text-primary">11</text>分即可达标!
+                该专业考试总分为
+                <text class="text-primary">{{ sum }}</text>
+                分, 您当前成绩为
+                <text class="text-primary">{{ score }}</text>
+                分。 参考
+                <text>{{ recentHistory.year }}</text>
+                年录取分数线, {{ tip.prefix }}
+                <text v-if="tip.middle" :class="tip.middleClass">{{ tip.middle }}</text>
+                {{ tip.suffix }}
             </view>
         </voluntary-result-card>
     </view>
@@ -17,12 +24,22 @@
 import VoluntaryResultTitle from "@/pagesOther/pages/voluntary/result/components/plus/voluntary-result-title.vue";
 import VoluntaryResultProgress from "@/pagesOther/pages/voluntary/result/components/plus/voluntary-result-progress.vue";
 import VoluntaryResultCard from "@/pagesOther/pages/voluntary/result/components/plus/voluntary-result-card.vue";
-import {VOLUNTARY_FORM, VOLUNTARY_MODEL} from "@/types/injectionSymbols";
-import {VoluntaryScoreItem} from "@/types/voluntary";
+import {VOLUNTARY_FORM, VOLUNTARY_MODEL, VOLUNTARY_RESULT} from "@/types/injectionSymbols";
+import {VoluntaryResultHistory, VoluntaryScoreItem} from "@/types/voluntary";
 import _ from "lodash";
 
 const model = inject(VOLUNTARY_MODEL)
 const data = inject(VOLUNTARY_FORM)
+const result = inject(VOLUNTARY_RESULT)
+
+interface RichText {
+    prefix: string;
+    middle: string;
+    middleClass: string;
+    suffix: string;
+}
+
+const emptyRichText: RichText = {prefix: '', middle: '', middleClass: '', suffix: ''}
 
 const progressList = computed(() => {
     const scores = model?.value
@@ -30,8 +47,8 @@ const progressList = computed(() => {
     if (!scores || !form) return []
     const results: VoluntaryScoreItem[] = []
 
-    form.rules.forEach(i => {
-        i.details.forEach(r => {
+    form.rules?.forEach(i => {
+        i.details?.forEach(r => {
             results.push({
                 label: r.label,
                 score: Number(scores[r.fieldName]),
@@ -52,6 +69,32 @@ const progressList = computed(() => {
     })
     return results
 })
+const sum = computed(() => _.sumBy(progressList.value, 'total'))
+const score = computed(() => _.sumBy(progressList.value, 'score'))
+const recentHistory = computed(() => _.first(result?.value.histories) || {} as VoluntaryResultHistory)
+const tip = computed(() => {
+    const diff = recentHistory.value.diff
+    if (diff === undefined || diff === null) return emptyRichText
+    if (diff < 0) return {
+        prefix: '预估还需再提升',
+        middle: Math.abs(diff) + '',
+        middleClass: 'text-danger',
+        suffix: '分即可达标!'
+    }
+    if (diff > 0) return {
+        prefix: '预估已经超过',
+        middle: Math.abs(diff) + '',
+        middleClass: 'text-success',
+        suffix: '分,已达标!'
+    }
+    if (diff === 0) return {
+        prefix: '预估已经与之',
+        middle: '持平',
+        middleClass: 'text-primary',
+        suffix: ',继续努力!'
+    }
+    return emptyRichText
+})
 </script>
 
 <style scoped>

+ 70 - 0
src/pagesOther/pages/voluntary/result/components/voluntary-result-skill.vue

@@ -0,0 +1,70 @@
+<template>
+    <view class="p-28 bg-white rounded-xl">
+        <voluntary-result-title title="职业技能分析"/>
+        <view class="mt-20 flex flex-col gap-20">
+            <view class="flex items-center justify-between gap-20">
+                <view class="flex-1 px-12 py-16 rounded-lg bg-sky-100 text-28 text-fore-title">{{
+                        skill.year
+                    }}录取分:{{ skill.enrollScore }}
+                </view>
+                <view class="flex-1 px-12 py-16 rounded-lg bg-sky-100 text-28 text-fore-title flex items-center">
+                    文化素质分
+                    <uv-icon name="question-circle" color="primary" custom-class="mx-3" @click="handleCultureTip"/>
+                    :{{ skill.cultureScore }}
+                </view>
+            </view>
+            <view class="bg-back-light p-28 rounded-lg text-24 leading-38 text-fore-title flex justify-between gap-28">
+                <view class="flex-1">
+                    根据
+                    <text class="text-primary">{{ skill.year }}</text>
+                    年该校录取情况,预估考取本专业需要职业技能分:
+                </view>
+                <view class="flex flex-col items-center">
+                    <view>
+                        <text class="text-36 text-primary">{{ skill.skillScore }}</text>
+                        分
+                    </view>
+                    <view class="flex items-center gap-5">
+                        <uv-icon :name="passedOption.icon" :color="passedOption.color"/>
+                        <text :class="passedOption.textColor">{{ passedOption.text }}</text>
+                    </view>
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script setup lang="ts">
+import VoluntaryResultTitle from "@/pagesOther/pages/voluntary/result/components/plus/voluntary-result-title.vue";
+import {VOLUNTARY_RESULT} from "@/types/injectionSymbols";
+import {VoluntaryResult, VoluntaryResultSkill} from "@/types/voluntary";
+
+const result = inject(VOLUNTARY_RESULT) || ref({} as VoluntaryResult)
+const skill = computed(() => result?.value.skill || {} as VoluntaryResultSkill)
+const passedOption = computed(() => {
+    return skill.value.diff < 0 ? {
+        icon: 'close-circle-fill',
+        color: 'error',
+        text: '未达成',
+        textColor: 'text-danger'
+    } : {
+        icon: 'checkmark-circle-fill',
+        color: 'success',
+        text: '已达成',
+        textColor: 'text-success'
+    }
+})
+
+const handleCultureTip = () => {
+    uni.$ie.showModal({
+        title: '提示',
+        content: skill.value.cultureRule,
+        showCancel: false,
+        confirmText: '知道了'
+    })
+}
+</script>
+
+<style scoped>
+
+</style>

+ 13 - 3
src/pagesOther/pages/voluntary/result/result.vue

@@ -9,6 +9,10 @@
             <voluntary-form-major custom-class="bg-white mt-0"/>
             <voluntary-result-analysis />
             <voluntary-result-score />
+            <voluntary-result-history/>
+            <voluntary-result-compare/>
+            <voluntary-result-skill/>
+            <voluntary-result-disclaimer/>
         </view>
         <ie-safe-toolbar :height="84" :shadow="false">
             <view class="px-30 py-16">
@@ -22,14 +26,19 @@
 
 import {useTransferPage} from "@/hooks/useTransferPage";
 import {VoluntaryDto} from "@/types/voluntary";
-import {VOLUNTARY_FORM, VOLUNTARY_MODEL} from "@/types/injectionSymbols";
+import {VOLUNTARY_FORM, VOLUNTARY_MODEL, VOLUNTARY_RESULT} from "@/types/injectionSymbols";
 import VoluntaryFormMajor from "@/pagesOther/pages/voluntary/index/components/voluntary-form-major.vue";
 import VoluntaryResultAnalysis from "@/pagesOther/pages/voluntary/result/components/voluntary-result-analysis.vue";
 import VoluntaryResultScore from "@/pagesOther/pages/voluntary/result/components/voluntary-result-score.vue";
+import VoluntaryResultHistory from "@/pagesOther/pages/voluntary/result/components/voluntary-result-history.vue";
+import VoluntaryResultCompare from "@/pagesOther/pages/voluntary/result/components/voluntary-result-compare.vue";
+import VoluntaryResultSkill from "@/pagesOther/pages/voluntary/result/components/voluntary-result-skill.vue";
+import VoluntaryResultDisclaimer from "@/pagesOther/pages/voluntary/result/components/voluntary-result-disclaimer.vue";
 
 const {prevData} = useTransferPage<VoluntaryDto, any>()
-const data = computed(() => prevData.value.data)
-const model = computed(() => prevData.value.model)
+const data = computed(() => prevData.value.data || {})
+const model = computed(() => prevData.value.model || {})
+const result = computed(() => prevData.value.result || {})
 
 const handleSubmit = async () => {
 
@@ -37,6 +46,7 @@ const handleSubmit = async () => {
 
 provide(VOLUNTARY_FORM, data)
 provide(VOLUNTARY_MODEL, model)
+provide(VOLUNTARY_RESULT, result)
 // 为了让子组件触发页面滚动
 onPageScroll(() => { })
 </script>

+ 1 - 0
src/types/index.ts

@@ -50,6 +50,7 @@ export interface TableConfig {
   emptyText?: string;
   loading?: boolean;
   rowKey?: string;
+  defaultValue?: string;
 }
 
 export interface TableColumnConfig {

+ 2 - 1
src/types/injectionSymbols.ts

@@ -46,4 +46,5 @@ export const CLOSE_VIP_POPUP = Symbol('CLOSE_VIP_POPUP') as InjectionKey<() => v
 * 计算录取概率
 * */
 export const VOLUNTARY_FORM = Symbol('VOLUNTARY_FORM') as InjectionKey<Ref<Voluntary.SelectedCollegeMajorWithRules>>
-export const VOLUNTARY_MODEL = Symbol('VOLUNTARY_MODEL') as InjectionKey<Ref<Voluntary.VoluntaryModel>>
+export const VOLUNTARY_MODEL = Symbol('VOLUNTARY_MODEL') as InjectionKey<Ref<Voluntary.VoluntaryModel>>
+export const VOLUNTARY_RESULT = Symbol('VOLUNTARY_RESULT') as InjectionKey<Ref<Voluntary.VoluntaryResult>>

+ 38 - 1
src/types/voluntary.ts

@@ -3,6 +3,7 @@ import {DictItem} from "@/types/index";
 
 export type EnumInputType = 'Score' | 'Number' | 'Text' | 'Radio' | 'Picker' | 'Checkbox' | 'Eyesight';
 export type EnumRuleCategory = 'Enroll' | 'Special';
+export type EnumPickType = 'Danger' | 'Normal' | 'Safety';
 
 export interface EnrollRuleItem {
     defaultValue?: string | number;
@@ -33,7 +34,7 @@ export interface EnrollRule {
 }
 
 export interface SelectedCollegeMajorWithRules extends SelectedUniversityMajor {
-    groupName: string;
+    majorGroup: string;
     rules: EnrollRule[];
 }
 
@@ -41,9 +42,45 @@ export interface VoluntaryModel extends Record<string, string | number | null> {
 
 }
 
+export interface VoluntaryResultHistory {
+    "year": string | number;
+    "score": string | number;
+    "plan"?: number | null;
+    "enroll"?: number | null;
+    "diff"?: number | null; // 负表示低于录取分;正数表示高于录取分
+    "ruleContent"?: string;
+    "application"?: number | null;
+    "admission"?: number | null;
+}
+
+export interface VoluntaryResultSkill {
+    year: string | number;
+    cultureScore: number;
+    cultureRule: string;
+    enrollScore: number;
+    skillScore: number; // 反向测技能分
+    diff: number; // 负数表示低于skillScore,正数高于
+}
+
+export interface VoluntaryResult {
+    "universityCode": string;
+    "majorCode": string;
+    "majorEnrollCode": string;
+    "majorGroup": string;
+    "majorName": string;
+    "majorDirection": string;
+    "enumPickType": EnumPickType;
+    "enrollRate": number | null;
+    "enrollRateText": string;
+    "tips": string | null;
+    "histories": VoluntaryResultHistory[],
+    "skill": VoluntaryResultSkill
+}
+
 export interface VoluntaryDto {
     data: SelectedCollegeMajorWithRules;
     model: VoluntaryModel;
+    result: VoluntaryResult;
 }
 
 export interface VoluntaryScoreItem {