Переглянути джерело

完成积分规则页面重构工作

yuanmingze 2 місяців тому
батько
коміт
7778b7c254

+ 3 - 0
src/config/index.ts

@@ -0,0 +1,3 @@
+export default {
+  imgHost: import.meta.env.VITE_BASE_API + '/admin/sys-file/wmkj/',
+}

+ 1 - 1
src/pages-mine/personal-card/components/ProfessionalSkillCard.vue

@@ -52,7 +52,7 @@
 <script setup lang="ts">
 import { computed, ref } from 'vue'
 
-import type { PersonalBusinessCardResponse } from '@/services/modules/minePage/personalCard/type'
+import type { PersonalBusinessCardResponse } from '@/services/modules/mine/personalCard/type'
 
 import { useUserStore } from '@/stores/modules/user'
 

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

@@ -57,7 +57,7 @@
 import { computed, ref } from 'vue'
 
 import type { DictItem } from '@/services/modules/common/type'
-import type { PersonalBusinessCardResponse } from '@/services/modules/minePage/personalCard/type'
+import type { PersonalBusinessCardResponse } from '@/services/modules/mine/personalCard/type'
 
 type PickerType = 'gender' | 'degree'
 

+ 2 - 2
src/pages-mine/personal-card/index.vue

@@ -34,11 +34,11 @@ import type { DictItem } from '@/services/modules/common/type'
 import {
   getPersonalBusinessCardApi,
   savePersonalBusinessCardApi,
-} from '@/services/modules/minePage/personalCard/index'
+} from '@/services/modules/mine/personalCard/index'
 import type {
   PersonalBusinessCardResponse,
   SavePersonalBusinessCardRequest,
-} from '@/services/modules/minePage/personalCard/type'
+} from '@/services/modules/mine/personalCard/type'
 
 import { useUserStore } from '@/stores/modules/user'
 

+ 461 - 3
src/pages-mine/points-rule/index.vue

@@ -1,7 +1,465 @@
 <template>
-  <view>积分规则</view>
+  <page-meta :page-style="descVisible ? 'overflow:hidden;' : 'overflow:visible;'" />
+
+  <view class="rules-box">
+    <image class="rules-top-img" :src="topImgBg" mode="aspectFill" />
+
+    <view class="enterprise-switch" @click="showEnterprisePicker">
+      <view class="enterprise-switch__icon">
+        <wd-icon name="loop" size="34rpx" />
+      </view>
+      <text>切换企业</text>
+    </view>
+
+    <wd-picker
+      v-model:visible="pickerShow"
+      v-model="selectedRuleId"
+      title="切换企业"
+      :columns="enterpriseList"
+      label-key="label"
+      value-key="value"
+      root-portal
+      @confirm="handleEnterpriseConfirm"
+    />
+
+    <view class="table-box">
+      <view class="table-header">
+        <view class="table-tab table-tab--left">获取途径</view>
+        <view class="table-tab">获得积分</view>
+      </view>
+
+      <view class="table-con">
+        <view class="table-bg">
+          <view v-if="loading" class="loading">
+            <view class="loading__item" />
+            <view class="loading__item" />
+            <view class="loading__item" />
+          </view>
+
+          <template v-else-if="sections.length">
+            <view v-for="section in sections" :key="section.title" class="section">
+              <view class="section-title">{{ section.title }}</view>
+
+              <view v-for="item in section.items" :key="item.id" class="table-item">
+                <view class="item-left">
+                  <text class="task-name">{{ item.taskTypeName }}</text>
+
+                  <image
+                    class="tips"
+                    :src="tipsIcon"
+                    mode="aspectFit"
+                    @click.stop="openDesc(item)"
+                  />
+                </view>
+
+                <text class="score-text">+{{ formatScore(item.score) }}积分</text>
+              </view>
+            </view>
+          </template>
+
+          <view v-else class="empty">暂无数据</view>
+        </view>
+      </view>
+    </view>
+
+    <wd-popup
+      v-model="descVisible"
+      position="bottom"
+      root-portal
+      lock-scroll
+      :safe-area-inset-bottom="true"
+      custom-style="border-radius: 40rpx 40rpx 0 0;"
+    >
+      <view class="info">
+        <view class="title">
+          <text>{{ activeTask?.taskTypeName || '任务说明' }}</text>
+          <wd-icon name="close" size="34rpx" @click="descVisible = false" />
+        </view>
+
+        <view class="list">
+          <view class="tit">定义:</view>
+          <view class="cont">{{ activeDesc }}</view>
+        </view>
+
+        <view v-if="activeTask?.taskRemark" class="list">
+          <view class="tit">任务要求:</view>
+          <view class="cont">{{ activeTask.taskRemark }}</view>
+        </view>
+
+        <view class="btn" @click="descVisible = false">知道了</view>
+      </view>
+    </wd-popup>
+  </view>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+import { computed, onMounted, ref } from 'vue'
+
+import config from '@/config'
+
+import { getDictTypeApi } from '@/services/modules/common'
+import type { DictItem } from '@/services/modules/common/type'
+import {
+  getTaskTypeDeptListApi,
+  getTaskTypeListByRuleIdApi,
+} from '@/services/modules/mine/points-rule'
+import type {
+  TaskTypeDeptListResponse,
+  TaskTypeListByRuleIdResponse,
+} from '@/services/modules/mine/points-rule/type'
+
+type TaskRuleItem = TaskTypeListByRuleIdResponse[string][number]
+
+const topImgBg = `${config.imgHost}home-top-bg2.png`
+const tipsIcon = 'https://yy-cloud-oss.oss-cn-beijing.aliyuncs.com/img/tips.png'
+
+const selectedRuleId = ref('')
+const pickerShow = ref(false)
+const loading = ref(false)
+const descVisible = ref(false)
+
+const enterpriseList = ref<TaskTypeDeptListResponse>([])
+const dictList = ref<DictItem[]>([])
+const sections = ref<Array<{ title: string; items: TaskRuleItem[] }>>([])
+const activeTask = ref<TaskRuleItem>()
+
+const activeDesc = computed(() => {
+  const taskName = activeTask.value?.taskTypeName?.trim()
+
+  if (!taskName) return '暂无定义'
+
+  const dict = dictList.value.find((item) => {
+    return [item.label, item.value, item.externalValue].includes(taskName)
+  })
+
+  return dict?.description || dict?.remarks || '暂无定义'
+})
+
+onMounted(() => {
+  initPage()
+})
+
+const initPage = async () => {
+  await Promise.all([loadEnterpriseList(), loadDictList()])
+
+  const firstRuleId = enterpriseList.value[0]?.value
+
+  if (!firstRuleId) return
+
+  selectedRuleId.value = firstRuleId
+  await loadRules(firstRuleId)
+}
+
+const loadEnterpriseList = async () => {
+  try {
+    const res = await getTaskTypeDeptListApi()
+
+    if (res.code === 0) {
+      enterpriseList.value = res.data
+    }
+  } catch (error) {
+    console.log(error)
+    enterpriseList.value = []
+  }
+}
+
+const loadDictList = async () => {
+  try {
+    const res = await getDictTypeApi('task_type_desc')
+
+    if (res.code === 0) {
+      dictList.value = res.data
+    }
+  } catch (error) {
+    console.log(error)
+    dictList.value = []
+  }
+}
+
+const loadRules = async (ruleId: string) => {
+  if (!ruleId) {
+    sections.value = []
+    return
+  }
+
+  loading.value = true
+
+  try {
+    const res = await getTaskTypeListByRuleIdApi(ruleId)
+
+    if (res.code === 0) {
+      sections.value = Object.entries(res.data)
+        .map(([title, items]) => ({ title, items }))
+        .filter((section) => section.items.length > 0)
+    }
+  } catch (error) {
+    console.log(error)
+    sections.value = []
+  } finally {
+    loading.value = false
+  }
+}
+
+const showEnterprisePicker = () => {
+  if (!enterpriseList.value.length) {
+    uni.showToast({
+      title: '暂无可切换企业',
+      icon: 'none',
+    })
+    return
+  }
+
+  pickerShow.value = true
+}
+
+const handleEnterpriseConfirm = async (event: { value: string | string[] }) => {
+  const ruleId = Array.isArray(event.value) ? event.value[0] : event.value
+
+  if (!ruleId) return
+
+  selectedRuleId.value = ruleId
+  activeTask.value = undefined
+  descVisible.value = false
+
+  await loadRules(ruleId)
+}
+
+const openDesc = (task: TaskRuleItem) => {
+  activeTask.value = task
+  descVisible.value = true
+}
+
+const formatScore = (score: TaskRuleItem['score']) => {
+  const value = Number(score)
+
+  return Number.isFinite(value) ? value : score || 0
+}
+</script>
+
+<style lang="scss" scoped>
+.rules-box {
+  position: relative;
+  min-height: 100vh;
+  overflow-x: hidden;
+  background: #019cd0;
+}
+
+.rules-top-img {
+  display: block;
+  width: 750rpx;
+  height: 526rpx;
+}
+
+.enterprise-switch {
+  position: fixed;
+  z-index: 99;
+  right: 0;
+  top: 360rpx;
+  display: flex;
+  align-items: center;
+  height: 76rpx;
+  padding: 0 22rpx 0 18rpx;
+  box-sizing: border-box;
+  border-radius: 999rpx 0 0 999rpx;
+  color: #ffffff;
+  background: linear-gradient(135deg, #2f8cff 0%, #13c2c2 100%);
+  box-shadow:
+    0 12rpx 28rpx rgba(19, 129, 210, 0.32),
+    inset 0 1rpx 0 rgba(255, 255, 255, 0.32);
+
+  text {
+    font-size: 28rpx;
+    font-weight: 700;
+    line-height: 76rpx;
+    white-space: nowrap;
+  }
+}
+
+.enterprise-switch__icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 46rpx;
+  height: 46rpx;
+  margin-right: 10rpx;
+  border-radius: 50%;
+  color: #2f8cff;
+  background: rgba(255, 255, 255, 0.96);
+}
+
+.table-box {
+  min-height: calc(100vh - 526rpx);
+  background: #019cd0;
+}
+
+.table-header {
+  display: flex;
+  align-items: center;
+  height: 120rpx;
+}
+
+.table-tab {
+  width: 50%;
+  color: #ffffff;
+  font-size: 38rpx;
+  font-weight: 800;
+  line-height: 120rpx;
+  text-align: center;
+  letter-spacing: 1rpx;
+  text-shadow: 0 4rpx 12rpx rgba(0, 72, 120, 0.2);
+}
+
+.table-tab--left {
+  border-right: 1rpx solid rgba(255, 255, 255, 0.78);
+}
+
+.table-con {
+  padding: 0 26rpx 50rpx;
+}
+
+.table-bg {
+  min-height: 420rpx;
+  padding: 46rpx;
+  border-radius: 18rpx;
+  background: linear-gradient(180deg, rgba(241, 253, 255, 0.96) 0%, #ffffff 38%), #ffffff;
+  box-shadow:
+    inset 0 8rpx 54rpx rgba(110, 219, 255, 0.42),
+    0 18rpx 36rpx rgba(0, 96, 140, 0.12);
+}
+
+.section + .section {
+  margin-top: 56rpx;
+  padding-top: 42rpx;
+  border-top: 1rpx solid rgba(104, 193, 228, 0.28);
+}
+
+.section-title {
+  position: relative;
+  display: inline-flex;
+  align-items: center;
+  color: #4f3a9f;
+  font-size: 34rpx;
+  font-weight: 800;
+  line-height: 46rpx;
+}
+
+.section-title::before {
+  content: '';
+  width: 10rpx;
+  height: 30rpx;
+  margin-right: 14rpx;
+  border-radius: 999rpx;
+  background: linear-gradient(180deg, #24c6dc 0%, #514a9d 100%);
+}
+
+.table-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-top: 32rpx;
+  margin-left: 32rpx;
+  color: #5f4b8b;
+  font-size: 32rpx;
+  line-height: 44rpx;
+}
+
+.item-left {
+  min-width: 0;
+  flex: 1;
+  display: flex;
+  align-items: center;
+}
+
+.task-name {
+  min-width: 0;
+  overflow: hidden;
+  color: #5b4b8a;
+  font-weight: 500;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.tips {
+  flex: none;
+  width: 36rpx;
+  height: 36rpx;
+  margin-left: 10rpx;
+}
+
+.score-text {
+  flex: none;
+  min-width: 172rpx;
+  margin-left: 24rpx;
+  color: #0b8fc8;
+  font-size: 32rpx;
+  font-weight: 800;
+  line-height: 44rpx;
+  text-align: right;
+  text-shadow: 0 4rpx 12rpx rgba(11, 143, 200, 0.12);
+}
+
+.loading {
+  padding: 20rpx 0;
+}
+
+.loading__item {
+  height: 72rpx;
+  margin-top: 24rpx;
+  border-radius: 12rpx;
+  background: linear-gradient(90deg, #eff9fd 0%, #e0f5fb 50%, #f7fdff 100%);
+}
+
+.empty {
+  padding: 80rpx 0;
+  color: #999999;
+  font-size: 28rpx;
+  text-align: center;
+}
+
+.info {
+  position: relative;
+  padding: 30rpx;
+  background: #ffffff;
+}
+
+.title {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 60rpx;
+
+  text {
+    color: #000000;
+    font-size: 36rpx;
+    font-weight: 600;
+  }
+}
+
+.list {
+  margin: 10rpx 0;
+}
+
+.tit {
+  margin-bottom: 10rpx;
+  color: #333333;
+  font-size: 32rpx;
+}
+
+.cont {
+  min-height: 5vh;
+  margin-bottom: 60rpx;
+  color: #666666;
+  font-size: 24rpx;
+  line-height: 42rpx;
+}
 
-<style lang="scss" scoped></style>
+.btn {
+  height: 80rpx;
+  margin-top: 40rpx;
+  color: #333333;
+  font-size: 30rpx;
+  line-height: 80rpx;
+  text-align: center;
+  background: #f1f1f1;
+}
+</style>

+ 0 - 0
src/services/modules/minePage/personalCard/index.ts → src/services/modules/mine/personalCard/index.ts


+ 0 - 0
src/services/modules/minePage/personalCard/type.d.ts → src/services/modules/mine/personalCard/type.d.ts


+ 12 - 0
src/services/modules/mine/points-rule/index.ts

@@ -0,0 +1,12 @@
+import http from '../../../index'
+import type { TaskTypeDeptListResponse, TaskTypeListByRuleIdResponse } from './type'
+
+export const getTaskTypeDeptListApi = () => {
+  return http.get<TaskTypeDeptListResponse>(`/admin/wmtasktype/getTaskTypeDeptList`)
+}
+
+export const getTaskTypeListByRuleIdApi = (ruleId: string) => {
+  return http.get<TaskTypeListByRuleIdResponse>(
+    `/admin/api/getTaskTypeListByRuleId?ruleId=${ruleId}`
+  )
+}

+ 34 - 0
src/services/modules/mine/points-rule/type.d.ts

@@ -0,0 +1,34 @@
+export type TaskTypeDeptListResponse = TaskTypeDeptItem[]
+
+interface TaskTypeDeptItem {
+  label: string
+  value: string
+}
+
+export type TaskTypeListByRuleIdResponse = Record<string, TaskRuleItem[]>
+
+interface TaskRuleItem {
+  id: string
+  baseId: string
+  taskEntId: string
+  taskTypeName: string
+  taskTypeLevel: string
+  parentId: string
+  levelId: string
+  ruleId: string
+  deptId: string
+  score: string
+  taskScoreStart: string
+  taskScoreEnd: string
+  status: string
+  delFlag: string
+  enableFlag: string
+  tenantId: number
+  createUser: number | null
+  updateUser: number | null
+  createTime: string
+  updateTime: string | null
+  durgentId: string | null
+  realFlag: string | null
+  taskRemark: string | null
+}