From 737179a0ce34147269cccf288fecd0e7bb4c309b Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期三, 11 二月 2026 10:42:19 +0800
Subject: [PATCH] 成绩查询+个人中心

---
 src/router/main/index.js                                        |   10 
 src/views/main/score/index.vue                                  |  182 ++++++
 src/views/main/appraisalPlan/index.vue                          |  143 ++++
 src/router/main/score/index.js                                  |    9 
 src/views/main/components/UploadIdCard.vue                      |  239 ++++++++
 src/router/main/examTicket/index.js                             |    9 
 src/views/main/home/index.vue                                   |   13 
 src/views/main/notice/list.vue                                  |    4 
 src/utils/tool.js                                               |   51 +
 src/views/main/appraisalPlan/components/signupDialog.vue        |  190 +++++++
 src/views/main/certificate/index.vue                            |  202 +++++++
 src/views/main/components/MyHeader.vue                          |    5 
 src/router/main/certificate/index.js                            |    9 
 src/views/main/center/index.vue                                 |  176 ++++++
 src/views/main/components/UploadBtn.vue                         |   16 
 public/favicon.ico                                              |    0 
 src/router/main/center/index.js                                 |    9 
 src/views/main/examTicket/index.vue                             |  170 ++++++
 src/assets/images/uploadBox/face.png                            |    0 
 src/views/main/appraisalPlan/components/signupSuccessDialog.vue |   63 ++
 src/assets/images/uploadBox/emblem.png                          |    0 
 src/views/main/center/mySignup/index.vue                        |   71 ++
 22 files changed, 1,542 insertions(+), 29 deletions(-)

diff --git a/public/favicon.ico b/public/favicon.ico
index df36fcf..b0d8840 100644
--- a/public/favicon.ico
+++ b/public/favicon.ico
Binary files differ
diff --git a/src/assets/images/uploadBox/emblem.png b/src/assets/images/uploadBox/emblem.png
new file mode 100644
index 0000000..6d2b696
--- /dev/null
+++ b/src/assets/images/uploadBox/emblem.png
Binary files differ
diff --git a/src/assets/images/uploadBox/face.png b/src/assets/images/uploadBox/face.png
new file mode 100644
index 0000000..52c2958
--- /dev/null
+++ b/src/assets/images/uploadBox/face.png
Binary files differ
diff --git a/src/router/main/center/index.js b/src/router/main/center/index.js
new file mode 100644
index 0000000..1a3fdc6
--- /dev/null
+++ b/src/router/main/center/index.js
@@ -0,0 +1,9 @@
+const score = [
+  {
+    path: 'center',
+    name: '涓汉涓績',
+    component: () => import('@/views/main/center/index.vue'),
+  },
+]
+
+export default score
\ No newline at end of file
diff --git a/src/router/main/certificate/index.js b/src/router/main/certificate/index.js
new file mode 100644
index 0000000..4983dfe
--- /dev/null
+++ b/src/router/main/certificate/index.js
@@ -0,0 +1,9 @@
+const certificate = [
+  {
+    path: 'certificate',
+    name: '璇佷功鏌ヨ',
+    component: () => import('@/views/main/certificate/index.vue'),
+  },
+]
+
+export default certificate
\ No newline at end of file
diff --git a/src/router/main/examTicket/index.js b/src/router/main/examTicket/index.js
new file mode 100644
index 0000000..24ccd43
--- /dev/null
+++ b/src/router/main/examTicket/index.js
@@ -0,0 +1,9 @@
+const examTicket = [
+  {
+    path: 'examTicket',
+    name: '鍑嗚�冭瘉鏌ヨ',
+    component: () => import('@/views/main/examTicket/index.vue'),
+  },
+]
+
+export default examTicket
\ No newline at end of file
diff --git a/src/router/main/index.js b/src/router/main/index.js
index 448bc1a..be598fb 100644
--- a/src/router/main/index.js
+++ b/src/router/main/index.js
@@ -1,11 +1,19 @@
 import home from '@/router/main/home/index.js'
 import notice from '@/router/main/notice/index.js'
 import appraisalPlan from '@/router/main/appraisalPlan'
+import examTicket from '@/router/main/examTicket/index.js'
+import score from '@/router/main/score/index.js'
+import certificate from '@/router/main/certificate/index.js'
+import center from '@/router/main/center/index.js'
 
 const mainRouter = [
   ...home,
   ...notice,
-  ...appraisalPlan
+  ...appraisalPlan,
+  ...examTicket,
+  ...score,
+  ...certificate,
+  ...center
 ]
 const router = [
   {
diff --git a/src/router/main/score/index.js b/src/router/main/score/index.js
new file mode 100644
index 0000000..f932815
--- /dev/null
+++ b/src/router/main/score/index.js
@@ -0,0 +1,9 @@
+const score = [
+  {
+    path: 'score',
+    name: '鎴愮哗鏌ヨ',
+    component: () => import('@/views/main/score/index.vue'),
+  },
+]
+
+export default score
\ No newline at end of file
diff --git a/src/utils/tool.js b/src/utils/tool.js
index 9826791..411e105 100644
--- a/src/utils/tool.js
+++ b/src/utils/tool.js
@@ -5,12 +5,12 @@
  * @returns {string} 鍥剧墖鐨勫畬鏁碪RL
  */
 export const getImageUrl = (imageName) => {
-  try {
-    return new URL('../assets/images/' + imageName, import.meta.url).href;
-  } catch (error) {
-    console.warn(`Failed to load image: ${imageName}`, error);
-    return "";
-  }
+  // 浣跨敤 Vite 鐨� glob 鍔熻兘鍔ㄦ�佸鍏ュ浘鐗囪祫婧�
+  const modules = import.meta.glob('../assets/images/**/*.{png,jpg,jpeg,gif,svg,webp}', { eager: true });
+  // 绉婚櫎寮�澶寸殑鏂滄潬
+  const cleanName = imageName.startsWith('/') ? imageName.slice(1) : imageName;
+  const path = `../assets/images/${cleanName}`;
+  return modules[path]?.default || '';
 };
 export const getUUID = () => {
   let s = []
@@ -66,11 +66,39 @@
   }
 }
 
-export const getOccupationName = (code) => {
+export const getOccupationName = (id) => {
   const { occupationItems } = useOptionItemsStore()
-  const obj = occupationItems.find(ele => ele.code == code)
+  const obj = occupationItems.find(ele => ele.id == id)
   
-  return obj?.name || ''
+  return obj?.occupationJob || ''
+}
+
+const levelKey = {
+  '5': '浜旂骇',
+  '4': '鍥涚骇',
+  '3': '涓夌骇',
+  '2': '浜岀骇',
+  '1': '涓�绾�'
+}
+
+export const getLevelItems = (occupationId) => {
+  const { occupationItems } = useOptionItemsStore()
+  const obj = occupationItems.find(ele => ele.id == occupationId)
+  if (!obj) return []
+  let levelItems = []
+  obj.levelStr.split(',').forEach(ele => {
+    levelItems.push({ name: levelKey[ele], value: ele },)
+  })
+  return levelItems || []
+}
+
+export const getLevelName = (levelStr) => {
+  let levelList = levelStr?.split(',') || []
+  let tempList = []
+  levelList.forEach(ele => {
+    tempList.push(levelKey[ele])
+  })
+  return tempList.join(',')
 }
 
 export const getJobName = (occupationCode, jobCode) => {
@@ -82,4 +110,9 @@
   if (!job) return ''
 
   return job.jobName || ''
+}
+
+export const getFileUrlName = (url = '') => {
+  const fullFilename = url.substring(url.lastIndexOf('/') + 1);
+  return fullFilename || ''
 }
\ No newline at end of file
diff --git a/src/views/main/appraisalPlan/components/signupDialog.vue b/src/views/main/appraisalPlan/components/signupDialog.vue
new file mode 100644
index 0000000..ea3f84e
--- /dev/null
+++ b/src/views/main/appraisalPlan/components/signupDialog.vue
@@ -0,0 +1,190 @@
+<template>
+  <el-dialog
+    v-model="dialogFlag"
+    width="700"
+    title="濉啓鎶ュ悕淇℃伅"
+    align-center
+    :show-close="false"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+  >
+    <el-divider class="m-0"></el-divider>
+    <div class="py-7">
+      <el-row align="middle">
+        <el-col :span="3" style="display: flex; flex-direction: column; align-items: center;">
+          <el-row>
+            <el-tag round :color="active==1?'#0069ff':'#999999'" style="width: 36px;height: 36px;">
+              <el-text class="text-white text-lg">1</el-text>
+            </el-tag>
+          </el-row>
+          <el-row class="mt-1"><el-text :style="{color: active==1?'#0069ff':'#666666'}">濉啓璧勬枡</el-text></el-row>
+        </el-col>
+        <el-col :span="18">
+          <div style="width: 100%;border-top: 1px solid #c3c3c3; margin-top: -10px;"></div>
+        </el-col>
+        <el-col :span="3" style="display: flex; flex-direction: column; align-items: center;">
+          <el-row>
+            <el-tag round :color="active==2?'#0069ff':'#fff'" style="width: 36px;height: 36px;border-color: #999999;">
+              <el-text class="text-lg" :style="{color: active==2?'#fff':'#999999' }">2</el-text>
+            </el-tag>
+          </el-row>
+          <el-row class="mt-1"><el-text :style="{color: active==2?'#0069ff':'#666666'}">纭淇℃伅</el-text></el-row>
+        </el-col>
+      </el-row>
+      <el-form ref="form" :model="signupInfo" :rules="rules" class="pb-6">
+        <template v-if="active==1">
+          <contentTitle title="鑰冭瘯淇℃伅" />
+          <div class="px-3">
+            <el-form-item>
+              <el-text>璇勪环鑱屼笟锛�</el-text>
+              <el-text class="text-black">{{ signupInfo.occupationJob }}</el-text>
+            </el-form-item>
+            <el-form-item>
+              <el-text>璇勪环绛夌骇锛�</el-text>
+              <el-text class="text-black">{{ signupInfo.levelText }}</el-text>
+            </el-form-item>
+          </div>
+          <contentTitle title="鑰冭瘯绉戠洰" />
+          <div class="px-3">
+            <el-form-item label="绉戠洰" prop="subject">
+              <el-checkbox-group v-model="signupInfo.subject">
+                <el-checkbox 
+                  v-for="(subject,index) in subjectItems" 
+                  :key="`subject${index}`"
+                  :label="subject.label" 
+                  :value="subject.value" 
+                />  
+              </el-checkbox-group>
+            </el-form-item>
+          </div>
+          <div class="px-3">
+            <el-form-item label="濮撳悕" prop="name" class="flex-1 mr-6">
+              <el-input v-model="signupInfo.name" />
+            </el-form-item>
+            <el-form-item label="鎬у埆" prop="gender" class="flex-1 mr-6">
+              <el-radio-group v-model="signupInfo.gender">
+                <el-radio :value="1">鐢�</el-radio>
+                <el-radio :value="2">濂�</el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-form-item label="鎵嬫満鍙�" prop="mobilePhone" class="flex-1 mr-6">
+              <el-input v-model="signupInfo.mobilePhone" />
+            </el-form-item>
+            <el-form-item label="韬唤璇佸彿" prop="idNumber" class="flex-1 mr-6">
+              <el-input v-model="signupInfo.idNumber" />
+            </el-form-item>
+          </div>
+        </template>
+        <template v-else-if="active == 2">
+          <contentTitle title="鑰冭瘯淇℃伅" />
+          <div class="px-3">
+            <el-form-item>
+              <el-text>璇勪环鑱屼笟锛�</el-text>
+              <el-text class="text-black">{{ signupInfo.occupationJob }}</el-text>
+            </el-form-item>
+            <el-form-item>
+              <el-text>璇勪环绛夌骇锛�</el-text>
+              <el-text class="text-black">{{ signupInfo.levelText }}</el-text>
+            </el-form-item>
+          </div>
+          <contentTitle title="鑰冭瘯绉戠洰" />
+        </template>
+      </el-form>
+    </div>
+    <el-divider class="m-0"></el-divider>
+    <template #footer>
+      <el-row justify="end">
+        <el-button @click="close">鍙栨秷</el-button>
+        <el-button type="primary" @click="lastStep" v-if="active==2">涓婁竴姝�</el-button>
+        <el-button type="primary" @click="nextStep" v-if="active==1">涓嬩竴姝�</el-button>
+        <el-button type="primary" @click="confirm" :loading="confirmLoading" v-if="active==2">鎻愪氦鎶ュ悕</el-button>
+      </el-row>
+    </template>
+  </el-dialog>
+</template>
+<script>
+import contentTitle from '@/views/main/components/contentTitle.vue'
+export default {
+  components: {
+    contentTitle
+  },
+  data() {
+    return {
+      dialogFlag: false,
+      active: 1,
+      subjectItems: [
+        { label: '鐞嗚鐭ヨ瘑', value: 'theory' },
+        { label: '瀹炴搷鎶�鑳�', value: 'operation' }
+      ],
+      signupInfo: {
+        occupationJob: '浜掕仈缃戣惀閿�甯�',
+        levelText: '涓夌骇',
+        subject: [],
+        name: '榛勫┓濠�',
+        gender: 2,
+        mobilePhone: '13537710000',
+        idNumber: '445224200000000'
+      },
+      rules: {
+        subject: [ this.$rules.required() ],
+        name: [ this.$rules.required() ],
+        gender: [ this.$rules.required() ],
+        mobilePhone: [ this.$rules.required() ],
+        idNumber: [ this.$rules.required() ],
+      },
+      confirmLoading: false
+    }
+  },
+  props: {
+    modelValue: {
+      type: Boolean,
+      default: false
+    },
+    currentPlan: {
+      type: Object,
+      default: () => {
+        return {}
+      }
+    }
+  },
+  watch: {
+    modelValue(val) {
+      this.dialogFlag = val
+    },
+    dialogFlag(val) {
+      this.$emit('update:modelValue', val)
+    },
+  },
+  mounted() {
+    
+  },
+  methods: {
+    lastStep() {
+      if (this.active == 2) {
+        this.active--
+      }
+    },
+    async nextStep() {
+      if (this.active == 1) {
+        // try {
+        //   await this.$refs.form.validateField('code')
+        // } catch(error) {
+        //   return
+        // }
+      }
+      this.active++
+    },
+    close() {
+      this.dialogFlag = false
+    },
+    confirm() {
+      this.dialogFlag = false
+      this.active = 1
+      this.$emit('signupSuccess')
+    }
+  }
+}
+</script>
+
+<style scoped>
+</style>
\ No newline at end of file
diff --git a/src/views/main/appraisalPlan/components/signupSuccessDialog.vue b/src/views/main/appraisalPlan/components/signupSuccessDialog.vue
new file mode 100644
index 0000000..50f7139
--- /dev/null
+++ b/src/views/main/appraisalPlan/components/signupSuccessDialog.vue
@@ -0,0 +1,63 @@
+<template>
+  <el-dialog
+    v-model="dialogFlag"
+    width="700"
+    title="鎶ュ悕鎴愬姛"
+    align-center
+    :show-close="false"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+  >
+    <el-divider class="m-0"></el-divider>
+    <div class="py-7">
+      <el-row justify="center">
+        <Icon icon="iconoir:check-circle-solid" width="70" height="70"  style="color: #1ba53a" />
+      </el-row>
+      <el-row justify="center" class="mt-2">
+        <el-text class="text-black text-lg font-medium">鎻愪氦鎶ュ悕鎴愬姛锛岃鑰愬績绛夊緟瀹℃牳缁撴灉銆�</el-text>
+      </el-row>
+      <el-row justify="center" class="mt-2">
+        <el-text style="color: #666666;">瀹℃牳缁撴灉浼氫互鐭俊褰㈠紡鍙戦�佺粰鎮紝鎮ㄤ篃鍙互鍒般�愮敤鎴蜂腑蹇冦��-銆愭垜鐨勬姤鍚嶃�戞煡鐪嬪鏍哥粨鏋溿��</el-text>
+      </el-row>
+    </div>
+    <el-divider class="m-0"></el-divider>
+    <template #footer>
+      <el-row justify="end">
+        <el-button type="primary" @click="dialogFlag=false">濂界殑</el-button>
+      </el-row>
+    </template>
+  </el-dialog>
+</template>
+<script>
+export default {
+  components: {
+  },
+  data() {
+    return {
+      dialogFlag: false,
+    }
+  },
+  props: {
+    modelValue: {
+      type: Boolean,
+      default: false
+    },
+  },
+  watch: {
+    modelValue(val) {
+      this.dialogFlag = val
+    },
+    dialogFlag(val) {
+      this.$emit('update:modelValue', val)
+    },
+  },
+  mounted() {
+    
+  },
+  methods: {
+  }
+}
+</script>
+
+<style scoped>
+</style>
\ No newline at end of file
diff --git a/src/views/main/appraisalPlan/index.vue b/src/views/main/appraisalPlan/index.vue
index 6624bc0..b7f0a51 100644
--- a/src/views/main/appraisalPlan/index.vue
+++ b/src/views/main/appraisalPlan/index.vue
@@ -8,7 +8,7 @@
           <el-text class="text-title">璇勪环璁″垝</el-text>
         </el-row>
 
-        <el-row class="mt-5">
+        <el-row class="mt-5 custom-input">
           <el-input 
             v-model="filter.keyword" 
             style="width: 739px;height: 70px;"
@@ -24,7 +24,7 @@
       </div>
     </div>
     <div class="main-content py-4">
-      <el-form-item label="绛夌骇锛�" class="mb-6">
+      <el-form-item label="绛夌骇锛�" class="mb-4">
         <el-radio-group  v-model="filter.level">
           <el-radio
             v-for="(item,index) in levelItems"
@@ -35,7 +35,7 @@
           </el-radio>
         </el-radio-group>
       </el-form-item>
-      <el-form-item label="鑰冪偣锛�" class="mb-6">
+      <el-form-item label="鑰冪偣锛�" class="mb-4">
         <el-select v-model="filter.area" placeholder="绛涢�夊湴甯�" style="width: 240px">
           <el-option
             v-for="item in areaItems"
@@ -46,6 +46,38 @@
         </el-select>
       </el-form-item>
 
+      <div v-if="calendarFlag">
+        <el-row>
+          <el-text class="text-lg ">璇勪环璁″垝鏈堝巻</el-text>
+        </el-row>
+        <el-form-item class="my-2">
+          <el-select v-model="year" placeholder="绛涢�夊勾浠�" style="width: 240px">
+            <el-option
+              v-for="item in yearItems"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-table :data="monthList" border style="width: 100%" class="mb-6">
+          <el-table-column 
+            v-for="(item,index) in monthHeaders" 
+            :prop="item.value" 
+            :label="item.label" 
+            :key="`month${index}`" 
+            align="center"
+          >
+            <template #default="{ row }">
+              <div v-if="row[item.value]">
+                <Icon icon="iconamoon:check-duotone" width="22" height="22"  style="color: var(--el-color-primary)" />
+              </div>
+              <span v-else></span>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      
       <el-table 
         :data="filterList" 
         table-layout="auto" 
@@ -61,8 +93,24 @@
           :align="item.align || 'start'"
           :sortable="item.sortable"
         >
-          <template #signupTime="{ row }">
-            {{ row }}
+          <template #default="{ row }">
+            <div v-if="item.value=='signupTime'">
+              <el-row>鎶ュ悕寮�濮嬫椂闂达細{{ row.startTime }}</el-row>
+              <el-row>鎶ュ悕缁撴潫鏃堕棿锛歿{ row.endTime }}</el-row>
+            </div>
+            <div v-if="item.value=='orgInfo'">
+              <el-row>鏈烘瀯鍚嶇О锛歿{ row.orgInfo.name }}</el-row>
+              <el-row>鏈烘瀯鑱旂郴浜猴細{{ row.orgInfo.concatName }}</el-row>
+              <el-row>鏈烘瀯鑱旂郴鏂瑰紡锛歿{ row.orgInfo.mobile }}</el-row>
+            </div>
+            <div v-if="item.value=='operation'">
+              <el-row @click="signup(row)">
+                <el-text v-if="row.signupFlag && userInfo.id">宸叉姤鍚�</el-text>
+                <el-text v-else tag="ins" class="text-primary cursor-p">
+                  鎶ュ悕鑰冭瘯
+                </el-text>
+              </el-row>
+            </div>
           </template>
         </el-table-column>
       </el-table>
@@ -78,12 +126,38 @@
         />
       </el-row>
     </div>
+
+    <signupDialog 
+      v-model="signupDialogVisible" 
+      :currentPlan="currentPlan" 
+      @signupSuccess="signupSuccessDialogVisible=true"
+    >
+    </signupDialog>
+
+    <signupSuccessDialog 
+      v-model="signupSuccessDialogVisible"
+    ></signupSuccessDialog>
+
   </div>
   
 </template>
 
 <script>
+import { useLoginStore } from '@/stores/login.js'
+import { useSessionStore } from '@/stores/session.js'
+import { storeToRefs } from 'pinia';
+import signupDialog from '@/views/main/appraisalPlan/components/signupDialog.vue'
+import signupSuccessDialog from '@/views/main/appraisalPlan/components/signupSuccessDialog.vue'
 export default {
+  components: {
+    signupDialog,
+    signupSuccessDialog
+  },
+  setup() {
+    const { userInfo } = storeToRefs(useSessionStore())
+    const { loginDialogVisible } = storeToRefs(useLoginStore())
+    return { userInfo, loginDialogVisible }
+  },
   data() {
     return {
       page: 1,
@@ -110,7 +184,7 @@
       ],
       noticeList: [],
       headers: [
-        { label: '鑱屼笟锛堝伐绉嶏級鍚嶇О', value: 'occupationJob' },
+        { label: '鑱屼笟锛堝伐绉嶏級鍚嶇О', value: 'occupationJob'},
         { label: '绛夌骇', value: 'level' },
         { label: '鑰冪偣', value: 'examAddress' },
         { label: '璇勪环璁″垝鍚嶇О', value: 'planName', width: 150 },
@@ -118,7 +192,36 @@
         { label: '璇勪环鏈烘瀯淇℃伅', value: 'orgInfo' },
         { label: '鎿嶄綔', value: 'operation', align: 'center' },
       ],
-      planList: []
+      planList: [],
+      calendarFlag: true,
+      year: '2025',
+      yearItems: [
+        { label: '2022骞�', value: '2022' },
+        { label: '2023骞�', value: '2023' },
+        { label: '2024骞�', value: '2024' },
+        { label: '2025骞�', value: '2025' },
+        { label: '2026骞�', value: '2026' },
+      ],
+      monthHeaders: [
+        { label: '1鏈�', value: '1' },
+        { label: '2鏈�', value: '2' },
+        { label: '3鏈�', value: '3' },
+        { label: '4鏈�', value: '4' },
+        { label: '5鏈�', value: '5' },
+        { label: '6鏈�', value: '6' },
+        { label: '7鏈�', value: '7' },
+        { label: '8鏈�', value: '8' },
+        { label: '9鏈�', value: '9' },
+        { label: '10鏈�', value: '10' },
+        { label: '11鏈�', value: '11' },
+        { label: '12鏈�', value: '12' },
+      ],
+      monthList: [
+        { 1: false, 2: false, 3: true, 4: true, 5: false, 6: false, 7: true, 8: false, 9: false, 10: true, 11: true, 12: false, }
+      ],
+      signupDialogVisible: false,
+      currentPlan: {},
+      signupSuccessDialogVisible: false
     }
   },
   computed: {
@@ -146,7 +249,8 @@
               name: '涓北甯傛妧甯堝闄�',
               concatName: '鍚�*瀹�',
               mobile: '13424571164'
-            }
+            },
+            signupFlag: false
           },
           {
             id: '2',
@@ -160,7 +264,8 @@
               name: '涓北甯傛妧甯堝闄�',
               concatName: '鍚�*瀹�',
               mobile: '13424571164'
-            }
+            },
+            signupFlag: true
           },
           {
             id: '3',
@@ -188,7 +293,8 @@
               name: '涓北甯傛妧甯堝闄�',
               concatName: '鍚�*瀹�',
               mobile: '13424571164'
-            }
+            },
+            signupFlag: false
           },
           {
             id: '5',
@@ -202,7 +308,8 @@
               name: '涓北甯傛妧甯堝闄�',
               concatName: '鍚�*瀹�',
               mobile: '13424571164'
-            }
+            },
+            signupFlag: false
           },
           {
             id: '6',
@@ -216,12 +323,22 @@
               name: '涓北甯傛妧甯堝闄�',
               concatName: '鍚�*瀹�',
               mobile: '13424571164'
-            }
+            },
+            signupFlag: false
           },
         ]
         this.totalCount = 6
       }, 400)
     },
+    signup(item) {
+      if (this.userInfo.id) {
+        this.signupDialogVisible = true
+        this.currentPlan = item
+      } else {
+        this.loginDialogVisible = true
+        this.$message.primary('璇峰厛鐧诲綍')
+      }
+    }
   }
 }
 
@@ -233,7 +350,7 @@
   background-repeat: no-repeat, no-repeat, repeat; /* 鍒嗗埆璁剧疆 */
   background-size: cover;
 }
-:deep(.el-input__inner) {
+.custom-input :deep(.el-input__inner) {
   padding-left: 10px;
   font-size: 18px;
 }
diff --git a/src/views/main/center/index.vue b/src/views/main/center/index.vue
new file mode 100644
index 0000000..c2c52c9
--- /dev/null
+++ b/src/views/main/center/index.vue
@@ -0,0 +1,176 @@
+<template>
+  <div>
+    <div class="content-bg">
+      <div class="main-content" style="z-index: 10;">
+        <ReturnBtn></ReturnBtn>
+
+        <el-row>
+          <el-text class="text-title">涓汉涓績</el-text>
+        </el-row>
+
+        <el-card shadow="never" class="mt-4" :style="{ height: contentHeight + 'px'}">
+          <el-row style="height: 100%;">
+            <el-col :span="2">
+              <el-row 
+                v-for="(tab,index) in tabList" 
+                :key="`tab${index}`" 
+                style="height: 50%;width: 100%;border-right: 1px solid #e4e7ed;"
+                justify="center"
+                align="middle"
+                class="cursor-p"
+                @click="activeTab=tab.value"
+                :class="{ 'active-tab': tab.value==activeTab }"
+              >
+                <div>
+                  <el-row justify="center">
+                    <Icon :icon="tab.iconName" width="24" height="24" :style="{color: tab.value==activeTab?'#0069FF':'#333333'}" />
+                    </el-row>
+                  <el-row class="mt-1 font-medium">{{ tab.label }}</el-row>
+                </div>
+              </el-row>
+            </el-col>
+            <el-col :span="22">
+              <div v-if="activeTab=='toExam'" class="p-4 px-6">
+                <el-row align="middle">
+                  <el-text class="font-medium">濮撳悕锛歿{ form.name }}</el-text>
+                  <el-button text class="ml-2">
+                    <Icon icon="ix:pen-filled" width="18" height="18"  style="color: black" />
+                  </el-button>
+                </el-row>
+                <el-row align="middle">
+                  <el-text class="font-medium">鎬у埆锛歿{ form.gender }}</el-text>
+                  <el-button text class="ml-2">
+                    <Icon icon="ix:pen-filled" width="18" height="18"  style="color: black" />
+                  </el-button>
+                </el-row>
+                <el-row align="middle">
+                  <el-text class="font-medium">鎵嬫満鍙凤細{{ form.mobilePhone }}</el-text>
+                  <el-button text class="ml-2">
+                    <Icon icon="ix:pen-filled" width="18" height="18"  style="color: black" />
+                  </el-button>
+                </el-row>
+                <el-row align="middle">
+                  <el-text class="font-medium">韬唤璇佸彿锛歿{ form.idCard }}</el-text>
+                  <el-button text class="ml-2">
+                    <Icon icon="ix:pen-filled" width="18" height="18"  style="color: black" />
+                  </el-button>
+                </el-row>
+                <el-row align="middle" class="mt-2">
+                  <el-text class="font-medium">韬唤璇佹鍙嶉潰锛�</el-text>
+                </el-row>
+                <el-row>
+                  <UploadIdCard v-model="form.idCardFace" :accept="['jpg', 'png']" type="face"></UploadIdCard>
+                  <UploadIdCard v-model="form.idCardEmblem" :accept="['jpg', 'png']" type="emblem"></UploadIdCard>
+                </el-row>
+                <el-row align="middle" class="mt-2">
+                  <el-text class="font-medium">璇佷功/鍑嗚�冭瘉澶村儚锛�</el-text>
+                </el-row>
+                <UploadBtn v-model="form.certificateAvatar" listType="picture-card" :accept="['jpg', 'png']" ></UploadBtn>
+
+                <el-row class="mt-5">
+                  <el-text>缁戝畾寰俊锛氭殏鏈粦瀹氬井淇�</el-text>
+                  <el-text tag="ins" class="ml-6 font-medium text-primary">鎵爜缁戝畾</el-text>
+                </el-row>
+              </div>
+              <div v-else-if="activeTab=='alreadyExam'">
+                <mySignup></mySignup>
+              </div>
+            </el-col>
+          </el-row>
+        </el-card>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import UploadIdCard from '@/views/main/components/UploadIdCard.vue'
+import { useLoginStore } from '@/stores/login.js'
+import { useSessionStore } from '@/stores/session.js'
+import { storeToRefs } from 'pinia';
+import mySignup from '@/views/main/center/mySignup/index.vue'
+export default {
+  components: {
+    mySignup,
+    UploadIdCard,
+  },
+  setup() {
+    const { userInfo } = storeToRefs(useSessionStore())
+    const { loginDialogVisible } = storeToRefs(useLoginStore())
+    return { userInfo, loginDialogVisible }
+  },
+  data() {
+    return {
+      form: {
+        name: '榛勫┓濠�',
+        gender: '濂�',
+        mobilePhone: '13000000000',
+        idCard: '441481155511116622',
+        idCardFace: [],
+        idCardEmblem: [],
+        certificateAvatar: [],
+      },
+      tabList: [
+        { label: '涓汉淇℃伅', iconName: 'mdi:layers-triple', value: 'toExam' },
+        { label: '鎴戠殑鎶ュ悕', iconName: 'ant-design:file-search-outlined', value: 'alreadyExam' },
+      ],
+      activeTab: 'toExam',
+      contentHeight: 580,
+      examList: [],
+      searchKeyword: '',
+    }
+  },
+  computed: {
+    
+  },
+  watch: {
+    activeTab: {
+      handler: function() {
+        this.getExamList()
+      },
+      immediate: true
+    }
+  },
+  created() {
+
+  },
+  methods: {
+    getExamList() {
+      if (this.activeTab == 'toExam') {
+        this.examList = [
+          
+        ]
+      } else if (this.activeTab == 'alreadyExam') {
+        this.examList = [
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+        ]
+      }
+    }
+  }
+}
+
+</script>
+<style scoped>
+.content-bg {
+  height: 240px;
+  background-image: url('@/assets/images/contentBg.png');
+  background-repeat: no-repeat, no-repeat, repeat; /* 鍒嗗埆璁剧疆 */
+  background-size: cover;
+}
+.active-tab {
+  background-color: #EEF5FF;
+  border: 1px solid #0069FF !important; 
+  color: #0069FF !important;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/main/center/mySignup/index.vue b/src/views/main/center/mySignup/index.vue
new file mode 100644
index 0000000..8b7d2f9
--- /dev/null
+++ b/src/views/main/center/mySignup/index.vue
@@ -0,0 +1,71 @@
+<template>
+  <div class="p-4 px-2">
+    <el-row class="">
+      <Filtrate :filter="filter" :refresh="getList" :loadingFlag="ListLoadingFlag">
+        <el-row style="width: 100%;">
+          <el-select style="flex: 1;" class="mr-2" v-model="filter.status" placeholder="鍏ㄩ儴鐘舵��">
+            <el-option
+              v-for="item in statusItems"
+              :key="item.status"
+              :label="item.label"
+              :value="item.status"
+            />
+          </el-select>
+          <el-input
+            style="flex: 3;"
+            class="mr-2"
+            v-model="filter.search"
+            placeholder="鎼滅储璇勪环鏈烘瀯鍚嶇О/缁熶竴绀句細淇$敤浠g爜"
+          >
+            <template #prefix>
+              <el-icon class="el-input__icon"><search /></el-icon>
+            </template>
+          </el-input>
+        </el-row>
+      </Filtrate>
+    </el-row>
+    <DataTable
+      v-model:filter="filter"
+      :headers="headers"
+      :list="list"
+      :totalCount="totalCount"
+    ></DataTable>
+
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      filter: {
+        status: ''
+      },
+      statusItems: [
+        { label: '鍏ㄩ儴鐘舵��', status: '' },
+        { label: '瀹℃牳涓�', status: 'auditing' },
+        { label: '瀹℃牳涓嶉�氳繃', status: 'reject' },
+        { label: '瀹℃牳閫氳繃', status: 'pass' },
+      ],
+      headers: [
+        { label: '璇勪环璁″垝鍚嶇О', value: 'planName' },
+        { label: '鑱屼笟鍚嶇О', value: 'occupationJob' },
+        { label: '绛夌骇', value: 'level' },
+        { label: '鑰冪偣', value: 'address' },
+        { label: '鐘舵��', value: 'status' },
+        { label: '鎿嶄綔', value: 'operation' }
+      ],
+      ListLoadingFlag: false,
+    }
+  },
+  methods: {
+    getList() {
+
+    },
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>
\ No newline at end of file
diff --git a/src/views/main/certificate/index.vue b/src/views/main/certificate/index.vue
new file mode 100644
index 0000000..cf45d45
--- /dev/null
+++ b/src/views/main/certificate/index.vue
@@ -0,0 +1,202 @@
+<template>
+  <div>
+    <div class="content-bg">
+      <div class="main-content">
+        <ReturnBtn></ReturnBtn>
+
+        <el-row class="mt-6">
+          <el-text class="text-title">璇佷功鏌ヨ</el-text>
+        </el-row>
+      </div>
+    </div>
+    <div class="main-content py-4" style="margin-top: -130px;">
+      <el-card shadow="never" class="p-4" style="min-height: 500px;">
+        <el-form-item>
+          <el-input v-model="keyword" placeholder="鎼滅储鍏抽敭瀛�">
+            <template #prefix>
+              <el-icon class="el-input__icon"><search /></el-icon>
+            </template>
+          </el-input>
+        </el-form-item>
+        <el-card 
+          v-for="(certificate,index) in certificateList"
+          :key="`certificate${index}`"
+          class="p-4 mb-4"
+          shadow="never"
+        >
+          <el-row justify="space-between" align="middle">
+            <div>
+              <el-row><el-text class="text-lg text-black font-medium">{{ certificate.occupationJob }}</el-text></el-row>
+              <el-row class="mt-2">
+                <el-text>{{ certificate.batch }}</el-text>
+              </el-row>
+            </div>
+            <div>
+              <el-button text style="background-color: #F0F7FF;">
+                <el-row class="mr-1">
+                  <Icon icon="flowbite:eye-solid" width="20" height="20"  style="color: #007AFF" />
+                </el-row>
+                <el-row class="text-primary">鏌ョ湅鍑嗚�冭瘉</el-row>
+              </el-button>
+              <el-button text style="background-color: #F0F7FF;">
+                <el-row class="mr-1">
+                  <Icon icon="material-symbols:download" width="20" height="20"  style="color: #007AFF" />
+                </el-row>
+                <el-row class="text-primary">涓嬭浇鍑嗚�冭瘉</el-row>
+              </el-button>
+            </div>
+          </el-row>
+        </el-card>
+        <el-row justify="center" v-if="certificateList.length==0">
+          <el-text>鏆傛棤鏁版嵁~</el-text>
+        </el-row>
+      </el-card>
+    </div>
+  </div>
+</template>
+
+<script>
+import { useLoginStore } from '@/stores/login.js'
+import { useSessionStore } from '@/stores/session.js'
+import { storeToRefs } from 'pinia';
+export default {
+  components: {
+
+  },
+  setup() {
+    const { userInfo } = storeToRefs(useSessionStore())
+    const { loginDialogVisible } = storeToRefs(useLoginStore())
+    return { userInfo, loginDialogVisible }
+  },
+  data() {
+    return {
+      certificateList: [],
+      keyword: '',
+      fieldList: [
+        { label: '濮撳悕', value: 'name' },
+        { label: '韬唤璇佸彿', value: 'idCard' },
+        { label: '鍑嗚�冭瘉鍙�', value: 'examCard' },
+        { label: '鐞嗚鐭ヨ瘑鎴愮哗', value: 'theoryScore' },
+        { label: '鎿嶄綔鎶�鑳芥垚缁�', value: 'operationScore' },
+        { label: '鎴愮哗鐘舵��', value: 'statusText' },
+      ]
+    }
+  },
+  computed: {},
+  watch: {
+  },
+  created() {
+    this.getCertificateList()
+  },
+  methods: {
+    getCertificateList() {
+      this.certificateList = [
+        { 
+          id: 1,
+          occupationJob: '鐢靛伐锛堢淮淇數宸ワ級-鍥涚骇', 
+          batch: '2025骞�12鏈堝叕鍏辫惀鍏诲笀绗�1鎵�',
+          name: '榛勫┓濠�',
+          idCard: '440203198906302518',
+          examCard: '250610110746092',
+          theoryScore: '--',
+          operationScore: '--',
+          status: 'auditing',
+          statusText: '鎴愮哗瀹℃牳涓�',
+          foldFlag: false
+        },
+        { 
+          id: 2,
+          occupationJob: '鐢靛伐锛堢淮淇數宸ワ級-鍥涚骇', 
+          batch: '2025骞�12鏈堝叕鍏辫惀鍏诲笀绗�1鎵�',
+          name: '榛勫┓濠�',
+          idCard: '440203198906302518',
+          examCard: '250610110746092',
+          theoryScore: '78鍒嗭紙鍚堟牸锛�',
+          operationScore: '85鍒嗭紙鍚堟牸锛�',
+          status: 'public',
+          statusText: '鎴愮哗鍏ず涓�',
+          foldFlag: true
+        },
+        { 
+          id: 3,
+          occupationJob: '鐢靛伐锛堢淮淇數宸ワ級-鍥涚骇', 
+          batch: '2025骞�12鏈堝叕鍏辫惀鍏诲笀绗�1鎵�',
+          name: '榛勫┓濠�',
+          idCard: '440203198906302518',
+          examCard: '250610110746092',
+          theoryScore: '78鍒嗭紙鍚堟牸锛�',
+          operationScore: '85鍒嗭紙鍚堟牸锛�',
+          status: 'effectiveness',
+          statusText: '鎴愮哗宸茬敓鏁�',
+          foldFlag: true
+        },
+        { 
+          id: 4,
+          occupationJob: '鐢靛伐锛堢淮淇數宸ワ級-鍥涚骇', 
+          batch: '2025骞�12鏈堝叕鍏辫惀鍏诲笀绗�1鎵�',
+          name: '榛勫┓濠�',
+          idCard: '440203198906302518',
+          examCard: '250610110746092',
+          theoryScore: '85鍒嗭紙鍚堟牸锛�',
+          operationScore: '78鍒嗭紙鍚堟牸锛�',
+          status: 'cancellation',
+          statusText: '鎴愮哗宸蹭綔搴�',
+          failReason: '杩濊浣滃紛',
+          foldFlag: true
+        },
+        { 
+          id: 5,
+          occupationJob: '鐢靛伐锛堢淮淇數宸ワ級-鍥涚骇', 
+          batch: '2025骞�12鏈堝叕鍏辫惀鍏诲笀绗�1鎵�',
+          name: '榛勫┓濠�',
+          idCard: '440203198906302518',
+          examCard: '250610110746092',
+          theoryScore: '85鍒嗭紙鍚堟牸锛�',
+          operationScore: '78鍒嗭紙鍚堟牸锛�',
+          status: 'cancellation',
+          statusText: '鎴愮哗宸蹭綔搴�',
+          failReason: '杩濊浣滃紛',
+          foldFlag: true
+        },
+        { 
+          id: 6,
+          occupationJob: '鐢靛伐锛堢淮淇數宸ワ級-鍥涚骇', 
+          batch: '2025骞�12鏈堝叕鍏辫惀鍏诲笀绗�1鎵�',
+          name: '榛勫┓濠�',
+          idCard: '440203198906302518',
+          examCard: '250610110746092',
+          theoryScore: '85鍒嗭紙鍚堟牸锛�',
+          operationScore: '78鍒嗭紙鍚堟牸锛�',
+          status: 'cancellation',
+          statusText: '鎴愮哗宸蹭綔搴�',
+          failReason: '杩濊浣滃紛',
+          foldFlag: true
+        },
+      ]
+    },
+    getStyle(status) {
+      let obj = {
+        auditing: '#007AFF',
+        public: '#FF9009',
+        effectiveness: '#1BA53A',
+        cancellation: '#FF4040',
+      }
+      return { color: obj[status] }
+    }
+  }
+}
+
+</script>
+<style scoped>
+.content-bg {
+  height: 240px;
+  background-image: url('@/assets/images/contentBg.png');
+  background-repeat: no-repeat, no-repeat, repeat; /* 鍒嗗埆璁剧疆 */
+  background-size: cover;
+}
+.active-tab {
+  background-color: #EEF5FF;
+  border: 1px solid #0069FF !important; 
+  color: #0069FF !important;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/main/components/MyHeader.vue b/src/views/main/components/MyHeader.vue
index d3c54d0..4d65e5f 100644
--- a/src/views/main/components/MyHeader.vue
+++ b/src/views/main/components/MyHeader.vue
@@ -10,7 +10,7 @@
         </el-row>
         <template #dropdown>
           <el-dropdown-menu>
-            <el-dropdown-item>涓汉涓績</el-dropdown-item>
+            <el-dropdown-item @click="gotoCenter()">涓汉涓績</el-dropdown-item>
             <el-dropdown-item @click="logout()">閫�鍑虹櫥褰�</el-dropdown-item>
           </el-dropdown-menu>
         </template>
@@ -57,6 +57,9 @@
         })
       }, 1000)
     },
+    gotoCenter() {
+      this.$router.push('/main/center')
+    },
     logout() {
       this.$messageBox.confirm('纭畾瑕侀��鍑虹櫥褰曞悧', '鎻愮ず', 
       { confirmButtonText: '纭畾', cancelButtonText: '鍙栨秷', type: 'error' }).then(res => {
diff --git a/src/views/main/components/UploadBtn.vue b/src/views/main/components/UploadBtn.vue
index 42bf5fa..0df0c52 100644
--- a/src/views/main/components/UploadBtn.vue
+++ b/src/views/main/components/UploadBtn.vue
@@ -77,12 +77,14 @@
 </template>
 
 <script>
+import { getFileUrlName } from '@/utils/tool.js'
 const pdf = 'application/pdf'
 const xls = 'application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
 const doc = 'application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document'
 const image = 'image/*'
 const jpg = 'image/jpeg'
 const png = 'image/png'
+const zip = 'application/zip,application/x-zip-compressed'
 import { genFileId } from 'element-plus'
 export default {
   data() {
@@ -101,7 +103,7 @@
     accept: {
       type: Array,
       default: () => {
-        return ['pdf', 'xls', 'doc', 'image', 'jpg', 'png']
+        return ['pdf', 'xls', 'doc', 'image', 'jpg', 'png', 'zip']
       }
     },
     limitFileCount: {
@@ -123,19 +125,26 @@
     modelValue: {
       handler: function() {
         this.list = this.modelValue
+        this.list.forEach(ele => {
+          if (!ele.name) {
+            ele.name = getFileUrlName(ele.url)
+          }
+        })
       },
+      immediate: true,
       deep: true
     },
     list: {
       handler: function(val) {
         this.$emit('update:modelValue', val)
       },
+      immediate: true,
       deep: true
     }
   },
   computed: {
     acceptType() {
-      let obj = { pdf, xls, doc, image, jpg, png }
+      let obj = { pdf, xls, doc, image, jpg, png, zip }
       return this.accept.map(ele => obj[ele]).join(',')
     },
     tip() {
@@ -145,7 +154,8 @@
         doc: 'WORD',
         image: '鍥剧墖',
         jpg: 'JPEG/JPG',
-        png: 'PNG'
+        png: 'PNG',
+        zip: 'ZIP'
       }
       let tip = this.accept.map(ele => obj[ele]).join('銆�')
       return `鏀寔${tip}绫诲瀷鏂囦欢` 
diff --git a/src/views/main/components/UploadIdCard.vue b/src/views/main/components/UploadIdCard.vue
new file mode 100644
index 0000000..924684c
--- /dev/null
+++ b/src/views/main/components/UploadIdCard.vue
@@ -0,0 +1,239 @@
+<template>
+  <el-upload
+    ref="upload"
+    class="my-3 mr-4" action="#"
+    v-model:file-list="list"
+    :accept="acceptType"
+    list-type="picture-card"
+    :http-request="uploadRequest"
+    :multiple="true"
+    :limit="limitFileCount"
+    :on-preview="handlePreview"
+    :on-remove="handleRemove"
+    :before-remove="beforeRemove"
+    :on-exceed="handleExceed"
+    :disabled="disabled"
+    :class="{ hideUpload: hideUpload }"
+  >
+    <template #default>
+      <el-image
+        ref="backgroundImg"
+        :src="$getImageUrl(`/uploadBox/${type}.png`)"
+      >
+      </el-image>
+    </template>
+    <template #file="{ file, index }">
+      <el-image
+        ref="previewImg"
+        :src="file.url"
+        :initial-index="initialPreviewImgIndex"
+        :preview-src-list="filterPreviewImgList"
+      >
+      </el-image>
+      <span class="el-upload-list__item-actions">
+        <span @click="previewImage(index)">
+          <el-icon><zoom-in /></el-icon>
+        </span>
+        <!-- <span @click="replaceUpload(index)">
+          <el-icon><upload /></el-icon>
+        </span> -->
+        <span @click="deleteFileItem(index)">
+          <el-icon><delete /></el-icon>
+        </span>
+      </span>
+    </template>
+  </el-upload>
+  <el-dialog v-model="imgPreviewDialogFlag">
+    <img w-full :src="previewUrl" alt="Preview Image" />
+  </el-dialog>
+</template>
+
+<script>
+import { getFileUrlName } from '@/utils/tool.js'
+const pdf = 'application/pdf'
+const xls = 'application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+const doc = 'application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document'
+const image = 'image/*'
+const jpg = 'image/jpeg'
+const png = 'image/png'
+import { genFileId } from 'element-plus'
+export default {
+  data() {
+    return {
+      list: [],
+      imgPreviewDialogFlag: false,
+      previewUrl: '',
+      initialPreviewImgIndex: 0
+    }
+  },
+  props: {
+    listType: {
+      type: String,
+      default: 'text'
+    },
+    accept: {
+      type: Array,
+      default: () => {
+        return ['image', 'jpg', 'png']
+      }
+    },
+    limitFileCount: {
+      type: Number,
+      default: 1
+    },
+    modelValue: {
+      type: Array,
+      default: () => {
+        return []
+      }
+    },
+    disabled: {
+      type: Boolean,
+      default: false
+    },
+    type: {
+      type: String,
+      default: 'face'
+    }
+  },
+  watch: {
+    modelValue: {
+      handler: function() {
+        this.list = this.modelValue
+        this.list.forEach(ele => {
+          if (!ele.name) {
+            ele.name = getFileUrlName(ele.url)
+          }
+        })
+      },
+      immediate: true,
+      deep: true
+    },
+    list: {
+      handler: function(val) {
+        this.$emit('update:modelValue', val)
+      },
+      immediate: true,
+      deep: true
+    }
+  },
+  computed: {
+    acceptType() {
+      let obj = { pdf, xls, doc, image, jpg, png }
+      return this.accept.map(ele => obj[ele]).join(',')
+    },
+    tip() {
+      let obj = {
+        pdf: 'PDF',
+        xls: 'EXCEL',
+        doc: 'WORD',
+        image: '鍥剧墖',
+        jpg: 'JPEG/JPG',
+        png: 'PNG'
+      }
+      let tip = this.accept.map(ele => obj[ele]).join('銆�')
+      return `鏀寔${tip}绫诲瀷鏂囦欢` 
+    },
+    againUploadFlag() {
+      return this.limitFileCount == 1  && this.list.length == 1
+    },
+    filterPreviewImgList() {
+      let list = this.list.map(ele => ele.url )
+      return list
+    },
+    hideUpload() {
+      return this.list.length >= this.limitFileCount
+    }
+  },
+  methods: {
+    uploadRequest(UploadRequestOptions) {
+      const data = {
+        file: UploadRequestOptions.file,
+        directory: ''
+      }
+      this.$axios.post('/infra/file/upload', data, { 
+        headers: { 'Content-Type': "multipart/form-data" }
+      }).then(res => {
+        let index = this.list.findIndex(ele => ele.uid == data.file.uid)
+        if (res.data.code == 0) {
+          let index = this.list.findIndex(ele => ele.uid == data.file.uid)
+          if (index != -1) {
+            this.list[index].uploadStatus = 'success'
+            this.list[index].url = res.data.data
+          }
+          this.$message.success('涓婁紶鎴愬姛')
+        } else {
+          if (index != -1) {
+            this.list[index].uploadStatus = 'fail'
+          }
+          this.$message.error(res.data.msg || '涓婁紶澶辫触')
+        }
+      })
+    },
+    deleteFileItem(index) {
+      this.list.splice(index, 1)
+    },
+    previewImage(index) {
+      this.initialPreviewImgIndex = index
+      this.$refs.previewImg?.showPreview()
+    },
+    replaceUpload() {
+
+    },
+    handleRemove() {},
+    beforeRemove() {},
+    handlePreview() {},
+    handleExceed(file) {
+      if (this.againUploadFlag) {
+        this.$refs.upload?.clearFiles()
+        let newFile = file[0]
+        newFile.uid = genFileId()
+        this.$refs.upload?.handleStart(newFile)
+        this.$refs.upload?.submit()
+      } else {
+        this.$message.error(`鏈�澶氭敮鎸佷笂浼� ${this.limitFileCount} 涓枃浠禶)
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.hideUpload :deep(.el-upload--picture-card) {
+  display: none !important;
+}
+.file-box {
+  display: flex;
+  color: #007AFF;
+  align-content: center;
+  background-color: #ECF5FF;
+  padding: 8px;
+  font-size: 15px;
+  line-height: 16px;
+}
+
+:deep(.el-upload--picture-card), :deep(.el-upload-list__item) {
+  width: 160px !important;
+  height: 90px !important;
+  padding: 1px 0;
+}
+
+.el-upload-list__item-actions {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.5); /* 鍗婇�忔槑榛戣壊钂欑増 */
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  opacity: 0; /* 榛樿閫忔槑 */
+  transition: opacity .3s; /* 娣诲姞杩囨浮鍔ㄧ敾鏁堟灉 */
+}
+
+/* 榧犳爣鎮仠鏃舵樉绀鸿挋鐗� */
+.el-upload-list__item:hover .el-upload-list__item-actions {
+  opacity: 1;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/main/examTicket/index.vue b/src/views/main/examTicket/index.vue
new file mode 100644
index 0000000..c90f0da
--- /dev/null
+++ b/src/views/main/examTicket/index.vue
@@ -0,0 +1,170 @@
+<template>
+  <div>
+    <div class="content-bg">
+      <div class="main-content" style="z-index: 10;">
+        <ReturnBtn></ReturnBtn>
+
+        <el-row class="mt-6">
+          <el-text class="text-title">鍑嗚�冭瘉鏌ヨ</el-text>
+        </el-row>
+
+        <el-card shadow="never" class="mt-6" :style="{ height: contentHeight + 'px'}">
+          <el-row style="height: 100%;">
+            <el-col :span="2">
+              <el-row 
+                v-for="(tab,index) in tabList" 
+                :key="`tab${index}`" 
+                style="height: 50%;width: 100%;border-right: 1px solid #e4e7ed;"
+                justify="center"
+                align="middle"
+                class="cursor-p"
+                @click="activeTab=tab.value"
+                :class="{ 'active-tab': tab.value==activeTab }"
+              >
+                <div>
+                  <el-row justify="center">
+                    <Icon :icon="tab.iconName" width="24" height="24"  :style="{color: tab.value==activeTab?'#0069FF':'#333333'}" />
+                    </el-row>
+                  <el-row class="mt-1 font-medium">{{ tab.label }}</el-row>
+                </div>
+              </el-row>
+            </el-col>
+            <el-col :span="22">
+              <el-row class="p-4 text-primary font-medium" justify="space-between">
+                <span>鍏� {{ examList.length }} 涓噯鑰冭瘉</span>
+                <el-form-item class="mb-0">
+                  <el-input v-model="searchKeyword" placeholder="鎼滅储鍏抽敭瀛�">
+                    <template #append>
+                      <el-button>鏌ヨ</el-button>
+                    </template>
+                  </el-input>
+                </el-form-item>
+              </el-row>
+              <el-divider class="m-0"></el-divider>
+              <el-scrollbar ref="scrollbarRef" class="p-4" :style="{ height: (contentHeight - 70)  + 'px'}" always>
+                <el-card 
+                  v-for="(exam,index) in examList"
+                  :key="`exam${index}`"
+                  class="p-4 mb-4"
+                  shadow="never"
+                >
+                  <el-row justify="space-between" align="middle">
+                    <div>
+                      <el-row><el-text class="text-lg text-black font-medium">{{ exam.occupationJob }}</el-text></el-row>
+                      <el-row class="mt-2">
+                        <el-text>{{ exam.batch }}</el-text>
+                      </el-row>
+                    </div>
+                    <div>
+                      <el-button text style="background-color: #F0F7FF;">
+                        <el-row class="mr-1">
+                          <Icon icon="flowbite:eye-solid" width="20" height="20"  style="color: #007AFF" />
+                        </el-row>
+                        <el-row class="text-primary">鏌ョ湅鍑嗚�冭瘉</el-row>
+                      </el-button>
+                      <el-button text style="background-color: #F0F7FF;">
+                        <el-row class="mr-1">
+                          <Icon icon="material-symbols:download" width="20" height="20"  style="color: #007AFF" />
+                        </el-row>
+                        <el-row class="text-primary">涓嬭浇鍑嗚�冭瘉</el-row>
+                      </el-button>
+                    </div>
+                  </el-row>
+                </el-card>
+                <el-row justify="center" v-if="examList.length==0">
+                  <el-text>鏆傛棤鏁版嵁~</el-text>
+                </el-row>
+              </el-scrollbar>
+            </el-col>
+          </el-row>
+
+        </el-card>
+      </div>
+    </div>
+    <div class="main-content py-4">
+
+    </div>
+
+  </div>
+  
+</template>
+
+<script>
+import { useLoginStore } from '@/stores/login.js'
+import { useSessionStore } from '@/stores/session.js'
+import { storeToRefs } from 'pinia';
+export default {
+  components: {
+
+  },
+  setup() {
+    const { userInfo } = storeToRefs(useSessionStore())
+    const { loginDialogVisible } = storeToRefs(useLoginStore())
+    return { userInfo, loginDialogVisible }
+  },
+  data() {
+    return {
+      tabList: [
+        { label: '寰呰�冭瘯', iconName: 'gravity-ui:pencil-to-line', value: 'toExam' },
+        { label: '宸茶�冭瘯', iconName: 'ic:round-history', value: 'alreadyExam' },
+      ],
+      activeTab: 'toExam',
+      contentHeight: 530,
+      examList: [],
+      searchKeyword: '',
+    }
+  },
+  computed: {
+    
+  },
+  watch: {
+    activeTab: {
+      handler: function() {
+        this.getExamList()
+      },
+      immediate: true
+    }
+  },
+  created() {
+
+  },
+  methods: {
+    getExamList() {
+      if (this.activeTab == 'toExam') {
+        this.examList = [
+          
+        ]
+      } else if (this.activeTab == 'alreadyExam') {
+        this.examList = [
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+          { occupationJob: '鑱屼笟鍩硅甯�-浜岀骇', batch: '2025骞�12鏈堣亴涓氬煿璁笀绗竴鎵�' },
+        ]
+      }
+    }
+  }
+}
+
+</script>
+<style scoped>
+.content-bg {
+  height: 240px;
+  background-image: url('@/assets/images/contentBg.png');
+  background-repeat: no-repeat, no-repeat, repeat; /* 鍒嗗埆璁剧疆 */
+  background-size: cover;
+}
+.active-tab {
+  background-color: #EEF5FF;
+  border: 1px solid #0069FF !important; 
+  color: #0069FF !important;
+}
+</style>
\ No newline at end of file
diff --git a/src/views/main/home/index.vue b/src/views/main/home/index.vue
index 850d37d..2cae64e 100644
--- a/src/views/main/home/index.vue
+++ b/src/views/main/home/index.vue
@@ -54,7 +54,15 @@
 </template>
 
 <script>
+import { useLoginStore } from '@/stores/login.js'
+import { useSessionStore } from '@/stores/session.js'
+import { storeToRefs } from 'pinia';
 export default {
+  setup() {
+    const { userInfo } = storeToRefs(useSessionStore())
+    const { loginDialogVisible } = storeToRefs(useLoginStore())
+    return { userInfo, loginDialogVisible }
+  },
   data() {
     return {
       operationList: [
@@ -114,6 +122,11 @@
       this.$router.push('/main/noticeList')
     },
     goOperationPage(item) {
+      if (item.value != 'appraisalPlan' && !this.userInfo.id) {
+        this.loginDialogVisible = true
+        this.$message.primary('璇峰厛鐧诲綍')
+        return
+      }
       this.$router.push(`/main/${item.value}`)
     }
   }
diff --git a/src/views/main/notice/list.vue b/src/views/main/notice/list.vue
index 2c1f9d9..dfa8219 100644
--- a/src/views/main/notice/list.vue
+++ b/src/views/main/notice/list.vue
@@ -8,7 +8,7 @@
           <el-text class="text-title">閫氱煡鍏憡</el-text>
         </el-row>
 
-        <el-row class="mt-5">
+        <el-row class="mt-5 custom-input">
           <el-input 
             v-model="filter.keyword" 
             style="width: 739px;height: 70px;"
@@ -185,7 +185,7 @@
   background-repeat: no-repeat, no-repeat, repeat; /* 鍒嗗埆璁剧疆 */
   background-size: cover;
 }
-:deep(.el-input__inner) {
+.custom-input :deep(.el-input__inner) {
   padding-left: 10px;
   font-size: 18px;
 }
diff --git a/src/views/main/score/index.vue b/src/views/main/score/index.vue
new file mode 100644
index 0000000..e9d4ebf
--- /dev/null
+++ b/src/views/main/score/index.vue
@@ -0,0 +1,182 @@
+<template>
+  <div>
+    <div class="content-bg">
+      <div class="main-content">
+        <ReturnBtn></ReturnBtn>
+
+        <el-row class="mt-6">
+          <el-text class="text-title">鎴愮哗鏌ヨ</el-text>
+        </el-row>
+      </div>
+    </div>
+    <div class="main-content py-4" style="margin-top: -130px;">
+      <el-card shadow="never" class="p-4" style="min-height: 500px;">
+        <el-form-item>
+          <el-input v-model="keyword" placeholder="鎼滅储鍏抽敭瀛�">
+            <template #prefix>
+              <el-icon class="el-input__icon"><search /></el-icon>
+            </template>
+          </el-input>
+        </el-form-item>
+        <div v-for="(score,index) in scoreList" 
+          class="mt-3" 
+          :key="`scoreCard${index}`"
+          style="border-radius: 5px;"
+          :style="{ border: `1px solid ${score.foldFlag?'#dcdcdc':'#007aff'}` }"
+        >
+          <el-card shadow="never" class="p-3  cursor-p" style="background: #F7F7F7;border: none;" @click="score.foldFlag=!score.foldFlag">
+            <el-row justify="space-between" align="middle">
+              <div>
+                <el-row><el-text class="text-lg text-black font-medium">{{ score.occupationJob }}</el-text></el-row>
+                <el-row class="mt-1">
+                  <el-text>{{ score.batch }}</el-text>
+                </el-row>
+              </div>
+              <Icon v-if="score.foldFlag" icon="mingcute:down-line" width="22" height="22"  style="color: #333333" />
+              <Icon v-else icon="mingcute:up-line" width="22" height="22"  style="color: #007AFF" />
+            </el-row>
+          </el-card>
+          <div v-if="!score.foldFlag" class="px-4">
+            <el-row v-for="(field,index) in fieldList"
+              style="border-bottom: 1px solid #DCDCDC;" 
+              class="py-4"
+              :key="`field${index}`"
+            >
+              <el-text style="width: 170px;color: #666666;" class="font-medium">{{ field.label }}</el-text>
+              <el-text v-if="field.value == 'statusText'" class="font-medium">
+                <div style="display: flex;flex-direction: column;">
+                  <el-text :style="getStyle(score.status)" style="align-self: start;">{{ score.statusText }}</el-text>
+                  <el-text v-if="score.status=='cancellation'">浣滃簾鍘熷洜锛歿{ score.failReason }}</el-text>
+                </div>
+              </el-text>
+              <el-text v-else class=" text-black font-medium">{{score[field.value]}}</el-text>
+            </el-row>
+          </div>
+        </div>
+        <el-row justify="center" v-if="scoreList.length==0">
+          <el-text>鏆傛棤鏁版嵁~</el-text>
+        </el-row>
+      </el-card>
+    </div>
+  </div>
+</template>
+
+<script>
+import { useLoginStore } from '@/stores/login.js'
+import { useSessionStore } from '@/stores/session.js'
+import { storeToRefs } from 'pinia';
+export default {
+  components: {
+
+  },
+  setup() {
+    const { userInfo } = storeToRefs(useSessionStore())
+    const { loginDialogVisible } = storeToRefs(useLoginStore())
+    return { userInfo, loginDialogVisible }
+  },
+  data() {
+    return {
+      scoreList: [],
+      keyword: '',
+      fieldList: [
+        { label: '濮撳悕', value: 'name' },
+        { label: '韬唤璇佸彿', value: 'idCard' },
+        { label: '鍑嗚�冭瘉鍙�', value: 'examCard' },
+        { label: '鐞嗚鐭ヨ瘑鎴愮哗', value: 'theoryScore' },
+        { label: '鎿嶄綔鎶�鑳芥垚缁�', value: 'operationScore' },
+        { label: '鎴愮哗鐘舵��', value: 'statusText' },
+      ]
+    }
+  },
+  computed: {
+    
+  },
+  watch: {
+  },
+  created() {
+    this.getScoreList()
+  },
+  methods: {
+    getScoreList() {
+      this.scoreList = [
+        { 
+          id: 1,
+          occupationJob: '鐢靛伐锛堢淮淇數宸ワ級-鍥涚骇', 
+          batch: '2025骞�12鏈堝叕鍏辫惀鍏诲笀绗�1鎵�',
+          name: '榛勫┓濠�',
+          idCard: '440203198906302518',
+          examCard: '250610110746092',
+          theoryScore: '--',
+          operationScore: '--',
+          status: 'auditing',
+          statusText: '鎴愮哗瀹℃牳涓�',
+          foldFlag: false
+        },
+        { 
+          id: 2,
+          occupationJob: '鐢靛伐锛堢淮淇數宸ワ級-鍥涚骇', 
+          batch: '2025骞�12鏈堝叕鍏辫惀鍏诲笀绗�1鎵�',
+          name: '榛勫┓濠�',
+          idCard: '440203198906302518',
+          examCard: '250610110746092',
+          theoryScore: '78鍒嗭紙鍚堟牸锛�',
+          operationScore: '85鍒嗭紙鍚堟牸锛�',
+          status: 'public',
+          statusText: '鎴愮哗鍏ず涓�',
+          foldFlag: true
+        },
+        { 
+          id: 3,
+          occupationJob: '鐢靛伐锛堢淮淇數宸ワ級-鍥涚骇', 
+          batch: '2025骞�12鏈堝叕鍏辫惀鍏诲笀绗�1鎵�',
+          name: '榛勫┓濠�',
+          idCard: '440203198906302518',
+          examCard: '250610110746092',
+          theoryScore: '78鍒嗭紙鍚堟牸锛�',
+          operationScore: '85鍒嗭紙鍚堟牸锛�',
+          status: 'effectiveness',
+          statusText: '鎴愮哗宸茬敓鏁�',
+          foldFlag: true
+        },
+        { 
+          id: 4,
+          occupationJob: '鐢靛伐锛堢淮淇數宸ワ級-鍥涚骇', 
+          batch: '2025骞�12鏈堝叕鍏辫惀鍏诲笀绗�1鎵�',
+          name: '榛勫┓濠�',
+          idCard: '440203198906302518',
+          examCard: '250610110746092',
+          theoryScore: '85鍒嗭紙鍚堟牸锛�',
+          operationScore: '78鍒嗭紙鍚堟牸锛�',
+          status: 'cancellation',
+          statusText: '鎴愮哗宸蹭綔搴�',
+          failReason: '杩濊浣滃紛',
+          foldFlag: true
+        },
+      ]
+    },
+    getStyle(status) {
+      let obj = {
+        auditing: '#007AFF',
+        public: '#FF9009',
+        effectiveness: '#1BA53A',
+        cancellation: '#FF4040',
+      }
+      return { color: obj[status] }
+    }
+  }
+}
+
+</script>
+<style scoped>
+.content-bg {
+  height: 240px;
+  background-image: url('@/assets/images/contentBg.png');
+  background-repeat: no-repeat, no-repeat, repeat; /* 鍒嗗埆璁剧疆 */
+  background-size: cover;
+}
+.active-tab {
+  background-color: #EEF5FF;
+  border: 1px solid #0069FF !important; 
+  color: #0069FF !important;
+}
+</style>
\ No newline at end of file

--
Gitblit v1.8.0