zhuli před 4 týdny
rodič
revize
2a2c6d635f

+ 9 - 1
src/api/modules/homeEntry.ts

@@ -21,6 +21,10 @@ export const getMerchantByPhone = params => {
 export const getNoticeList = params => {
   return http.get<StoreUser.ResStoreUserList>(PORT_NONE + `/notice/getNoticeList`, params);
 };
+//通知查看详情标记已读
+export const markNoticeAsRead = params => {
+  return http.get<StoreUser.ResStoreUserList>(PORT_NONE + `/notice/markNoticeAsRead`, params);
+};
 //通知列表  一键已读
 export const markAllNoticesAsRead = params => {
   return http.post(PORT_NONE + `/notice/markAllNoticesAsRead`, params);
@@ -48,7 +52,7 @@ export const updateMerchantUserInfo = params => {
 
 //财务   可提现金额
 export const getAccountBalance = params => {
-  return http.post(PORT_NONE + `/incomeManage/getAccountBalance`, params);
+  return http.get<StoreUser.ResStoreUserList>(PORT_NONE + `/incomeManage/getAccountBalance`, params);
 };
 //已到账金额/未到账金额
 export const getPaymentCycle = params => {
@@ -58,6 +62,10 @@ export const getPaymentCycle = params => {
 export const checkPayPassword = params => {
   return http.get<StoreUser.ResStoreUserList>(PORT_NONE + `/merchantUser/checkPayPassword`, params);
 };
+//提现记录列表
+export const getCashOutRecordList = params => {
+  return http.get<StoreUser.ResStoreUserList>(PORT_NONE + `/incomeManage/getCashOutRecordList`, params);
+};
 export const getUserByPhone = params => {
   return http.get(PORT_NONE + `/merchantUser/getMerchantByPhone`, params);
 };

+ 28 - 0
src/assets/json/authMenuList.json

@@ -543,6 +543,34 @@
             "isAffix": false,
             "isKeepAlive": false
           }
+        },
+        {
+          "path": "/financialManagement/withdrawaRecord",
+          "name": "withdrawaRecord",
+          "component": "/financialManagement/withdrawaRecord",
+          "meta": {
+            "icon": "CreditCard",
+            "title": "提现记录",
+            "isLink": "",
+            "isHide": false,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
+        },
+        {
+          "path": "/financialManagement/withdrawaRequest",
+          "name": "withdrawaRequest",
+          "component": "/financialManagement/withdrawaRequest",
+          "meta": {
+            "icon": "CreditCard",
+            "title": "提现申请",
+            "isLink": "",
+            "isHide": true,
+            "isFull": false,
+            "isAffix": false,
+            "isKeepAlive": false
+          }
         }
       ]
     }

+ 86 - 5
src/layouts/components/Header/components/Avatar.vue

@@ -1,7 +1,7 @@
 <template>
   <el-dropdown trigger="click">
     <div class="avatar">
-      <img src="@/assets/images/avatar.gif" alt="avatar" />
+      <img :src="avatarUrl" alt="avatar" />
     </div>
     <template #dropdown>
       <el-dropdown-menu>
@@ -18,23 +18,95 @@
     </template>
   </el-dropdown>
   <!-- infoDialog -->
-  <InfoDialog ref="infoRef" />
+  <InfoDialog ref="infoRef" @avatar-updated="getUserInfo" />
   <!-- passwordDialog -->
   <PasswordDialog ref="passwordRef" />
 </template>
 
 <script setup lang="ts">
-import { ref } from "vue";
+import { ref, onMounted, computed } from "vue";
 import { LOGIN_URL } from "@/config";
 import { useRouter } from "vue-router";
 import { logoutApi } from "@/api/modules/login";
 import { useUserStore } from "@/stores/modules/user";
+import { useAuthStore } from "@/stores/modules/auth";
 import { ElMessageBox, ElMessage } from "element-plus";
 import InfoDialog from "./InfoDialog.vue";
 import PasswordDialog from "./PasswordDialog.vue";
+import { getMerchantByPhone } from "@/api/modules/homeEntry";
+import { localGet, localSet } from "@/utils/index";
+import { resetRouter } from "@/routers/index";
+import defaultAvatar from "@/assets/images/avatar.gif";
 
 const router = useRouter();
 const userStore = useUserStore();
+const authStore = useAuthStore();
+
+// 头像URL,先从本地存储读取,避免刷新后显示默认图片
+const getCachedAvatar = () => {
+  const userInfo = localGet("geeker-user")?.userInfo || {};
+  const phone = userInfo.phone;
+  if (phone) {
+    const cachedAvatar = localGet(`avatar_${phone}`);
+    return cachedAvatar || "";
+  }
+  return "";
+};
+
+const headImg = ref<string>(getCachedAvatar());
+
+// 计算头像URL,如果有上传的头像则使用上传的,否则使用默认头像
+const avatarUrl = computed(() => {
+  console.log(headImg.value);
+  return headImg.value && headImg.value !== null && headImg.value !== "" ? headImg.value : defaultAvatar;
+});
+
+// 获取用户信息
+const getUserInfo = async () => {
+  const userInfo = localGet("geeker-user")?.userInfo || {};
+  if (userInfo.phone) {
+    try {
+      const res: any = await getMerchantByPhone({ phone: userInfo.phone });
+      console.log("API返回数据:", res);
+      if (res.code == 200 && res.data) {
+        // 兼容 headImg 和 avatar 两种字段名
+        const avatarUrl = res.data.headImg || res.data.avatar || "";
+        if (avatarUrl && avatarUrl.trim() !== "") {
+          headImg.value = avatarUrl;
+          // 缓存头像URL到本地存储
+          localSet(`avatar_${userInfo.phone}`, avatarUrl);
+          console.log("设置头像URL:", headImg.value);
+        } else {
+          console.warn("API返回的头像URL为空,保持使用缓存或默认头像");
+          // 如果API返回空,但本地有缓存,保持使用缓存
+          if (!headImg.value) {
+            const cached = getCachedAvatar();
+            if (cached) {
+              headImg.value = cached;
+            }
+          }
+        }
+      } else {
+        console.warn("获取用户信息失败,响应码:", res.code);
+      }
+    } catch (error) {
+      // API调用失败时,如果本地有缓存,继续使用缓存的头像
+      if (!headImg.value) {
+        const cached = getCachedAvatar();
+        if (cached) {
+          headImg.value = cached;
+        }
+      }
+    }
+  } else {
+    console.warn("用户信息中没有phone字段");
+  }
+};
+
+// 组件挂载时获取用户信息
+onMounted(() => {
+  getUserInfo();
+});
 
 // 退出登录
 const logout = () => {
@@ -46,10 +118,19 @@ const logout = () => {
     // 1.执行退出登录接口
     // await logoutApi();
 
-    // 2.清除 Token
+    // 2.清除动态路由
+    resetRouter();
+
+    // 3.清除菜单列表和权限信息
+    authStore.authMenuList = [];
+    authStore.authButtonList = {};
+    authStore.routeName = "";
+
+    // 4.清除 Token 和用户信息
     userStore.setToken("");
+    userStore.setUserInfo({ name: "" });
 
-    // 3.重定向到登陆页
+    // 5.重定向到登陆页
     router.replace(LOGIN_URL);
     ElMessage.success("退出登录成功!");
   });

+ 6 - 0
src/layouts/components/Header/components/InfoDialog.vue

@@ -42,6 +42,10 @@ import { localGet } from "@/utils/index";
 import defaultAvatar from "@/assets/images/avatar.gif";
 import { head } from "lodash";
 
+const emit = defineEmits<{
+  avatarUpdated: [];
+}>();
+
 const userInfo = localGet("geeker-user")?.userInfo || {};
 const dialogVisible = ref(false);
 
@@ -121,6 +125,8 @@ const getSaveUserInfo = async () => {
       dialogVisible.value = false;
       // 可以刷新用户信息
       getUserInfo();
+      // 通知父组件头像已更新
+      emit("avatarUpdated");
     } else {
       ElMessage.error(res.msg || "保存失败,请重试");
     }

+ 23 - 4
src/layouts/components/Header/components/Message.vue

@@ -18,11 +18,18 @@ const unreadCount = ref<number>(0);
 // 获取未读消息数量
 const getUnreadCount = async () => {
   try {
-    const receiverId = localGet("iphone") || localGet("geeker-user")?.userInfo?.phone || "";
-    if (!receiverId) return;
+    const phone = localGet("iphone") || localGet("geeker-user")?.userInfo?.phone || "";
+    if (!phone) return;
 
-    const res: any = await getNoticeList({ pageNum: 1, pageSize: 100, receiverId });
-    if (res.code == "200") {
+    // 使用与 notice.vue 相同的 receiverId 格式
+    const receiverId = "store_" + phone;
+    const res: any = await getNoticeList({
+      pageNum: 1,
+      pageSize: 100,
+      receiverId,
+      noticeType: 1
+    });
+    if (res.code == 200 || res.code == "200") {
       const noticeList = res.data?.records || res.data?.list || [];
       // 计算未读消息数量
       unreadCount.value = noticeList.filter((item: any) => !item.isRead).length;
@@ -51,3 +58,15 @@ onBeforeUnmount(() => {
   mittBus.off("updateUnreadCount");
 });
 </script>
+
+<style scoped lang="scss">
+.message {
+  display: flex;
+  align-items: center;
+  .item {
+    :deep(.el-badge__content) {
+      border: none;
+    }
+  }
+}
+</style>

+ 6 - 2
src/views/financialManagement/index.vue

@@ -153,8 +153,12 @@ const handleAction = async (key: string) => {
     };
     const res: any = await checkPayPassword(param as any);
     if (res.code == 200) {
-      ElMessage.error(res.data.message);
-      router.push({ path: "/financialManagement/realName" });
+      if (res.data.data == "false") {
+        ElMessage.error(res.data.message);
+        router.push({ path: "/financialManagement/realName" });
+      } else {
+        router.push({ path: "/financialManagement/withdrawaRequest" });
+      }
     }
   }
   if (key == "settled") {

+ 106 - 59
src/views/financialManagement/realName.vue

@@ -8,7 +8,8 @@
           <span class="shop-name">每天汪汪的店铺</span>
         </div>
         <div class="auth-status">
-          <span class="status-badge">未认证</span>
+          <span class="status-badge" v-if="!userInfo.idCard">未认证</span>
+          <span class="status-badge status-badge-success" v-if="userInfo.idCard">已认证</span>
         </div>
       </div>
     </div>
@@ -33,19 +34,42 @@
         <div class="auth-div" v-if="!goAuth">
           <el-row class="auth-row">
             <el-col :span="2"> 姓名 </el-col>
-            <el-col :span="12"> 王* </el-col>
+            <el-col :span="12">
+              {{ userInfo.name }}
+            </el-col>
           </el-row>
           <el-row class="auth-row">
             <el-col :span="2"> 身份证号 </el-col>
-            <el-col :span="12"> 2315************9966 </el-col>
+            <el-col :span="12">
+              {{ userInfo.idCard }}
+            </el-col>
           </el-row>
           <el-row class="auth-row">
             <el-col :span="2"> 认证时间 </el-col>
-            <el-col :span="12"> 2025/11/19 </el-col>
+            <el-col :span="12">
+              {{ userInfo.createdTime }}
+            </el-col>
           </el-row>
         </div>
       </div>
     </div>
+    <!-- 实名认证对话框 -->
+    <el-dialog v-model="dialogVisible" title="实名认证" width="500px" :close-on-click-modal="false">
+      <el-form ref="authFormRef" :model="authForm" :rules="authRules" label-width="100px" label-position="left">
+        <el-form-item label="姓名" prop="name">
+          <el-input v-model="authForm.name" placeholder="请输入姓名" clearable />
+        </el-form-item>
+        <el-form-item label="身份证号码" prop="idCard">
+          <el-input v-model="authForm.idCard" placeholder="请输入身份证号码" clearable maxlength="18" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="handleCancel">取消</el-button>
+          <el-button type="primary" :loading="loading" @click="handleConfirm">确认</el-button>
+        </span>
+      </template>
+    </el-dialog>
 
     <!-- 提现密码 -->
     <div class="content-section" v-if="activeTab === 'withdrawPassword'">
@@ -163,10 +187,10 @@
       </div>
     </div>
     <!--收款账户-->
-    <div class="content-section" v-if="activeTab === 'receivingAccount'">
-      <div class="content-wrapper">
-        <!-- <el-button type="primary" class="auth-button" @click="handleGoZFB"> 添加支付宝账号 </el-button> -->
-        <div class="zfb-wrapper">
+    <!-- <div class="content-section" v-if="activeTab === 'receivingAccount'">
+      <div class="content-wrapper"> -->
+    <!-- <el-button type="primary" class="auth-button" @click="handleGoZFB"> 添加支付宝账号 </el-button> -->
+    <!-- <div class="zfb-wrapper">
           <div class="zfb-left">
             <el-image :src="zfbIcon" class="zfbIcon" />
             <div>
@@ -180,10 +204,10 @@
           </div>
         </div>
       </div>
-    </div>
+    </div> -->
 
     <!-- 忘记密码对话框 -->
-    <el-dialog v-model="forgotPasswordVisible" title="忘记密码" width="500px" draggable :close-on-click-modal="false">
+    <!-- <el-dialog v-model="forgotPasswordVisible" title="忘记密码" width="500px" draggable :close-on-click-modal="false">
       <el-form
         ref="forgotPasswordFormRef"
         :model="forgotPasswordForm"
@@ -214,29 +238,11 @@
           />
         </el-form-item>
       </el-form>
-    </el-dialog>
-
-    <!-- 实名认证对话框 -->
-    <el-dialog v-model="dialogVisible" title="实名认证" width="500px" :close-on-click-modal="false">
-      <el-form ref="authFormRef" :model="authForm" :rules="authRules" label-width="100px" label-position="left">
-        <el-form-item label="姓名" prop="name">
-          <el-input v-model="authForm.name" placeholder="请输入姓名" clearable />
-        </el-form-item>
-        <el-form-item label="身份证号码" prop="idCard">
-          <el-input v-model="authForm.idCard" placeholder="请输入身份证号码" clearable maxlength="18" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="handleCancel">取消</el-button>
-          <el-button type="primary" :loading="loading" @click="handleConfirm">确认</el-button>
-        </span>
-      </template>
-    </el-dialog>
+    </el-dialog> -->
 
     <!--添加支付宝账号--->
-    <el-dialog v-model="dialogVisibleZFB" title="添加支付宝账号" width="500px" :close-on-click-modal="false">
-      <el-form ref="authFormRef" :model="authForm" :rules="authRules" label-width="100px" label-position="left">
+    <!-- <el-dialog v-model="dialogVisibleZFB" title="添加支付宝账号" width="500px" :close-on-click-modal="false">
+      <el-form ref="authFormRef1" :model="authForm" :rules="authRules" label-width="100px" label-position="left">
         <el-form-item label="支付宝账号" prop="name">
           <el-input v-model="authForm.zfbName" placeholder="请输入支付宝账号" clearable />
         </el-form-item>
@@ -247,29 +253,29 @@
           <el-button type="primary" :loading="loading" @click="handleConfirmZFB">确认</el-button>
         </span>
       </template>
-    </el-dialog>
+    </el-dialog> -->
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, computed } from "vue";
+import { ref, reactive, computed, onMounted } from "vue";
 import { useRouter } from "vue-router";
 import { ElMessage, type FormInstance, type FormRules } from "element-plus";
+import { verifyIdInfo, checkPayPassword } from "@/api/modules/homeEntry";
 import { localGet } from "@/utils/index";
 import homeIcon from "../../assets/images/home-icon.png";
 import zfbIcon from "../../assets/financial/zfb-icon.png";
-
+const userInfo = localGet("geeker-user").userInfo;
 const router = useRouter();
 const activeTab = ref("realName");
 const dialogVisible = ref(false);
 const loading = ref(false);
 //实名认证
-const goAuth = ref(true);
+const goAuth = ref(!userInfo.idCard);
 const authFormRef = ref<FormInstance>();
 const authForm = reactive({
   name: "",
-  idCard: "",
-  zfbName: ""
+  idCard: ""
 });
 // 实名认证表单验证规则
 const authRules = reactive<FormRules>({
@@ -284,25 +290,47 @@ const authRules = reactive<FormRules>({
     }
   ]
 });
+//去实名认证
+const handleGoAuth = () => {
+  // 打开实名认证对话框
+  dialogVisible.value = true;
+  // 重置表单
+  if (authFormRef.value) {
+    authFormRef.value.resetFields();
+  }
+};
 //实名认证 确认
 const handleConfirm = async () => {
   if (!authFormRef.value) return;
 
   // 表单验证
-  await authFormRef.value.validate(valid => {
+  await authFormRef.value.validate(async valid => {
     if (valid) {
       loading.value = true;
-      // 这里可以调用API提交实名认证信息
-      // 模拟API调用
-      setTimeout(() => {
+      try {
+        const params = {
+          name: authForm.name,
+          idCard: authForm.idCard,
+          appType: 1
+        };
+        const res: any = await verifyIdInfo(params);
+        console.log(res);
+        if (res && res.code == 200) {
+          ElMessage.success(res.msg || "实名认证提交成功");
+          dialogVisible.value = false;
+          goAuth.value = false;
+          // 重置表单
+          authFormRef.value?.resetFields();
+          // 这里可以更新认证状态或刷新页面数据
+        } else {
+          ElMessage.error(res.msg || "实名认证失败,请重试");
+        }
+      } catch (error) {
+        console.error("实名认证失败:", error);
+        ElMessage.error("实名认证失败,请重试");
+      } finally {
         loading.value = false;
-        ElMessage.success("实名认证提交成功");
-        dialogVisible.value = false;
-        goAuth.value = false;
-        // 重置表单
-        authFormRef.value?.resetFields();
-        // 这里可以更新认证状态或刷新页面数据
-      }, 1000);
+      }
     }
   });
 };
@@ -396,6 +424,33 @@ const forgotPasswordForm = reactive({
 const forgotPasswordRules: FormRules = {
   verificationCode: [{ required: true, message: "请输入验证码", trigger: "blur" }]
 };
+
+// 检查提现密码并自动跳转
+const checkWithdrawPassword = async () => {
+  try {
+    // 如果已实名认证,检查是否设置了提现密码
+    if (userInfo.idCard) {
+      const param = {
+        storeUserId: userInfo.id
+      };
+      const res: any = await checkPayPassword(param as any);
+      if (res.code == 200) {
+        // 如果未设置提现密码,自动跳转到提现密码标签页
+        if (res.data.data == "false") {
+          activeTab.value = "withdrawPassword";
+        }
+      }
+    }
+  } catch (error) {
+    console.error("检查提现密码失败:", error);
+  }
+};
+
+// 页面加载时检查
+onMounted(() => {
+  checkWithdrawPassword();
+});
+
 // 获取用户手机号
 const userPhone = computed(() => {
   return localGet("iphone") || localGet("geeker-user")?.userInfo?.phone || "";
@@ -414,15 +469,6 @@ const handleTabClick = (tab: any) => {
   // 这里可以根据需要实现路由跳转或内容切换
 };
 
-const handleGoAuth = () => {
-  // 打开实名认证对话框
-  dialogVisible.value = true;
-  // 重置表单
-  if (authFormRef.value) {
-    authFormRef.value.resetFields();
-  }
-};
-
 const handleCancel = () => {
   dialogVisible.value = false;
   // 重置表单
@@ -488,6 +534,9 @@ const handleCancel = () => {
         background: #f81515;
         border-radius: 39px;
       }
+      .status-badge-success {
+        background: #6c8ff8;
+      }
     }
   }
 }
@@ -555,7 +604,6 @@ const handleCancel = () => {
   background-color: #ffffff;
   .content-wrapper-realName {
     width: 100%;
-    border: 1px solid red;
     .goAuthBtn {
       display: flex;
       justify-content: center;
@@ -564,7 +612,6 @@ const handleCancel = () => {
     .auth-div {
       padding-top: 10px;
       padding-left: 41px;
-      border: 1px solid red;
       .auth-row {
         padding: 10px 0;
       }

+ 36 - 22
src/views/financialManagement/unposted.vue

@@ -1,26 +1,32 @@
 <template>
   <div class="table-box">
-    <ProTable ref="proTable" :columns="columns" :request-api="getTableList" :init-param="initParam" :data-callback="dataCallback">
+    <ProTable
+      ref="proTable"
+      :columns="columns"
+      :request-api="getTableList"
+      :init-param="initParam"
+      :data-callback="dataCallback"
+      :pagination="true"
+    >
       <!-- 表格操作 -->
       <template #operation="scope">
         <el-button type="primary" link @click="toDetail(scope.row)"> 查看详情 </el-button>
       </template>
     </ProTable>
     <el-dialog v-model="dialogVisible" title="详情" width="500px">
-      <h3>火锅一人惨</h3>
-      <el-row>
+      <h2>{{ unposted.couponName }}</h2>
+      <el-row class="couponRow">
         <el-col :span="12"> 实际收益:+138.32 </el-col>
-        <el-col :span="12"> 技术服务费(3%):-138.32 </el-col>
+        <el-col :span="12"> {{ `技术服务费(${unposted.commissionRate || 3}%)` }}:{{ unposted.commissionStr }} </el-col>
       </el-row>
-      <el-row>
-        <el-col :span="12"> 售价:+138.32 </el-col>
+      <el-row class="couponRow">
+        <el-col :span="12"> 售价:{{ unposted.incomeMoney || ((unposted?.money || 0) / 100).toFixed(2) || "0.00" }} </el-col>
       </el-row>
       <h3>订单信息</h3>
       <div>
-        <div>券码:</div>
-        <div>下单时间:</div>
-        <div>验券时间:</div>
-        <div>入账时间:</div>
+        <div class="couponRow">下单时间:{{ unposted.orderTime }}</div>
+        <div class="couponRow">验券时间:{{ unposted.checkTime }}</div>
+        <div class="couponRow">入账时间:</div>
       </div>
     </el-dialog>
   </div>
@@ -36,28 +42,33 @@ import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
 import { getPaymentCycle } from "@/api/modules/homeEntry";
 import { ElMessageBox } from "element-plus/es";
 import { localGet } from "@/utils";
-const dialogVisible = ref(true);
+const dialogVisible = ref(false);
+const unposted = ref<any>({});
+const toDetail = (row: any) => {
+  dialogVisible.value = true;
+  unposted.value = row;
+};
 // ProTable 实例(需要在使用它的地方之前定义)
 const proTable = ref<ProTableInstance>();
 
 // 表格配置项
 const columns = reactive<ColumnProps<any>[]>([
   {
-    prop: "name",
+    prop: "couponName",
     label: "商品名称",
     search: {
       el: "input"
     }
   },
+  // {
+  //   prop: "price",
+  //   label: "券码",
+  //   render: (scope: any) => {
+  //     return scope.row.price ? `¥${scope.row.price}` : "--";
+  //   }
+  // },
   {
-    prop: "price",
-    label: "券码",
-    render: (scope: any) => {
-      return scope.row.price ? `¥${scope.row.price}` : "--";
-    }
-  },
-  {
-    prop: "saleNum",
+    prop: "createdTime",
     label: "入账时间",
     render: scope => {
       return scope.row.saleNum === null || !scope.row.saleNum ? "--" : scope.row.saleNum;
@@ -88,8 +99,8 @@ onActivated(() => {
 // 或者直接去 hooks/useTable.ts 文件中把字段改为你后端对应的就行
 const dataCallback = (data: any) => {
   return {
-    list: data.records,
-    total: data.total
+    list: data.data?.records || data.records || [],
+    total: data.data?.total || data.total || 0
   };
 };
 
@@ -119,6 +130,9 @@ const getTableList = (params: any) => {
     }
   }
 }
+.couponRow {
+  padding-bottom: 10px;
+}
 .reject-reason-content {
   padding: 20px 0;
   .reject-reason-item {

+ 407 - 0
src/views/financialManagement/withdrawaRecord.vue

@@ -0,0 +1,407 @@
+<template>
+  <div class="table-box">
+    <ProTable
+      ref="proTable"
+      :columns="columns"
+      :request-api="getTableList"
+      :init-param="initParam"
+      :data-callback="dataCallback"
+      :pagination="true"
+    >
+      <!-- 表格操作 -->
+      <template #operation="scope">
+        <el-button type="primary" link @click="toDetail(scope.row)"> 查看详情 </el-button>
+      </template>
+    </ProTable>
+    <el-dialog v-model="dialogVisible" title="提现详情" width="500px">
+      <div class="withdrawal-detail">
+        <!-- 提现进度 -->
+        <div class="progress-section">
+          <h4 class="section-title">提现进度</h4>
+          <el-steps :active="currentStep" direction="vertical" :process-status="processStatus" :finish-status="finishStatus">
+            <el-step>
+              <template #title>
+                <div class="step-title">发起提现</div>
+              </template>
+              <template #description>
+                <div class="step-time">
+                  {{ withdrawalDetail.createdTime || "2025/10/1 12:00:00" }}
+                </div>
+              </template>
+            </el-step>
+            <el-step>
+              <template #title>
+                <div class="step-title">等待审核</div>
+              </template>
+            </el-step>
+            <el-step>
+              <template #title>
+                <div class="step-title">
+                  {{ paymentName(withdrawalDetail.paymentStatus) }}
+                </div>
+              </template>
+            </el-step>
+          </el-steps>
+        </div>
+
+        <!-- 提现详情 -->
+        <div class="detail-section">
+          <div class="detail-list">
+            <div class="detail-item">
+              <span class="detail-label">收款账户</span>
+              <span class="detail-value">{{ withdrawalDetail.settlementAccount || "支付宝账户 (130****1234)" }}</span>
+            </div>
+            <div class="detail-item">
+              <span class="detail-label">提现金额</span>
+              <span class="detail-value amount">¥{{ withdrawalDetail.money || "6146.6" }}</span>
+            </div>
+            <div class="detail-item">
+              <span class="detail-label">发起提现时间</span>
+              <span class="detail-value">{{ withdrawalDetail.createdTime || "2025/10/1 12:00:00" }}</span>
+            </div>
+            <div class="detail-item">
+              <span class="detail-label">结算账户</span>
+              <span class="detail-value">{{ withdrawalDetail.settlementAccount || "重庆老火锅_钱包" }}</span>
+            </div>
+            <div class="detail-item">
+              <span class="detail-label">提现状态</span>
+              <span class="detail-value status">{{ paymentName(withdrawalDetail.paymentStatus) }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="tsx" name="voucherManagement">
+import { computed, onActivated, onMounted, reactive, ref, watch } from "vue";
+import { useRouter } from "vue-router";
+import type { FormInstance, FormRules } from "element-plus";
+import { ElMessage } from "element-plus";
+import ProTable from "@/components/ProTable/index.vue";
+import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
+import { getCashOutRecordList } from "@/api/modules/homeEntry";
+import { ElMessageBox } from "element-plus/es";
+import { localGet } from "@/utils";
+const dialogVisible = ref(false);
+const withdrawalDetail = ref<any>({});
+// ProTable 实例(需要在使用它的地方之前定义)
+const proTable = ref<ProTableInstance>();
+
+// 提现状态枚举
+const paymentStatusEnum = [
+  { label: "提现中", value: 0 },
+  { label: "提现成功", value: 1 },
+  { label: "提现失败", value: 2 },
+  { label: "提现待审核", value: 3 },
+  { label: "提现拒绝", value: 4 },
+  { label: "提现待审核通过", value: 5 }
+];
+
+// 表格配置项
+const columns = reactive<ColumnProps<any>[]>([
+  {
+    prop: "settlementAccount",
+    label: "收款账号",
+    render: (scope: any) => {
+      return "支付宝账号" + "(" + scope.row.settlementAccount + ")";
+    }
+  },
+  {
+    prop: "money",
+    label: "提现金额",
+    render: (scope: any) => {
+      return scope.row.money ? `¥${scope.row.money}` : "--";
+    }
+  },
+  {
+    prop: "cashOutType",
+    label: "提现方式",
+    render: scope => {
+      return scope.row.cashOutType ? "自动提现" : "手动提现";
+    }
+  },
+  {
+    prop: "createdTime",
+    label: "发起提现时间"
+  },
+  {
+    prop: "paymentStatus",
+    label: "提现状态",
+    render: scope => {
+      return scope.row.paymentStatus === 0
+        ? "提现中"
+        : scope.row.paymentStatus === 1
+          ? "提现成功"
+          : scope.row.paymentStatus === 2
+            ? "提现失败"
+            : scope.row.paymentStatus === 3
+              ? "提现待审核"
+              : scope.row.paymentStatus === 4
+                ? "提现拒绝"
+                : "提现待审核通过";
+    },
+    search: {
+      el: "select",
+      props: { placeholder: "请选择", clearable: true }
+    },
+    enum: paymentStatusEnum,
+    fieldNames: { label: "label", value: "value" }
+  },
+  { prop: "operation", label: "操作", fixed: "right", width: 330 }
+]);
+
+// 如果表格需要初始化请求参数,直接定义传给 ProTable (之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
+const initParam = reactive({
+  storeId: localGet("createdId"),
+  cashOutEndTime: null,
+  cashOutStartTime: null
+});
+// 页面加载时触发查询
+onMounted(async () => {
+  proTable.value?.getTableList();
+});
+
+// 从其他页面返回时触发查询
+onActivated(() => {
+  proTable.value?.getTableList();
+});
+
+// dataCallback 是对于返回的表格数据做处理,如果你后台返回的数据不是 list && total 这些字段,可以在这里进行处理成这些字段
+// 或者直接去 hooks/useTable.ts 文件中把字段改为你后端对应的就行
+const dataCallback = (data: any) => {
+  return {
+    list: data.cashOutRecordList || data.list || [],
+    total: data.total || 0
+  };
+};
+
+// 如果你想在请求之前对当前请求参数做一些操作,可以自定义如下函数:params 为当前所有的请求参数(包括分页),最后返回请求列表接口
+// 默认不做操作就直接在 ProTable 组件上绑定	:requestApi="getUserList"
+const getTableList = (params: any) => {
+  let newParams = JSON.parse(JSON.stringify(params));
+  return getCashOutRecordList(newParams);
+};
+
+// 查看详情
+const toDetail = (row: any) => {
+  withdrawalDetail.value = row;
+  dialogVisible.value = true;
+};
+const paymentName = computed(() => {
+  return status => {
+    switch (status) {
+      case 0:
+        return "提现中";
+      case 1:
+        return "提现成功";
+      case 2:
+        return "提现失败";
+      case 3:
+        return "提现待审核";
+      case 4:
+        return "提现拒绝";
+      case 5:
+        return "提现待审核通过";
+      default:
+        return "";
+    }
+  };
+});
+
+// 计算当前步骤
+// active 表示当前激活的步骤索引(从0开始)
+// 步骤0: 发起提现(总是已完成)
+// 步骤1: 等待审核(根据状态判断)
+// 步骤2: 提现成功/拒绝(根据状态判断)
+const currentStep = computed(() => {
+  const status = withdrawalDetail.value.paymentStatus;
+  // 0: 提现中 -> 步骤1(发起提现已完成,等待审核进行中)
+  // 1: 提现成功 -> 步骤2(所有步骤都已完成)
+  // 2: 提现失败 -> 步骤1(等待审核失败)
+  // 3: 提现待审核 -> 步骤1(等待审核进行中)
+  // 4: 提现拒绝 -> 步骤2(审核已完成,在第三步显示拒绝状态)
+  // 5: 提现待审核通过 -> 步骤2(所有步骤都已完成)
+  if (status === 1 || status === 5) {
+    return 2; // 提现成功,所有步骤都完成
+  } else if (status === 4) {
+    return 2; // 提现拒绝,在第三步显示错误状态
+  } else if (status === 0 || status === 3) {
+    return 1; // 等待审核进行中
+  } else if (status === 2) {
+    return 1; // 提现失败,在等待审核阶段失败
+  }
+  return 0; // 默认在第一步
+});
+
+// 处理状态(用于失败情况)
+const processStatus = computed<"error" | "process" | "finish" | "wait">(() => {
+  const status = withdrawalDetail.value.paymentStatus;
+  if (status === 4) {
+    return "error" as const; // 提现拒绝,在第三步显示错误状态
+  } else if (status === 2) {
+    return "error" as const; // 提现失败,在等待审核阶段显示错误状态
+  }
+  return "process" as const; // 进行中
+});
+
+// 完成状态
+const finishStatus = computed<"finish">(() => {
+  return "finish" as const; // 完成状态
+});
+// 获取状态文本
+const getStatusText = (status: string) => {
+  const statusMap: Record<string, string> = {
+    processing: "提现中",
+    pending: "等待审核",
+    success: "提现成功",
+    failed: "提现失败",
+    rejected: "审核拒绝"
+  };
+  return statusMap[status] || "提现中";
+};
+</script>
+
+<style lang="scss" scoped>
+// 在组件样式中添加
+.date-range {
+  display: block; // 确保换行生效
+  padding: 0 8px; // 可选:增加内边距
+  word-wrap: break-word; // 长单词内换行
+  white-space: normal; // 允许自然换行
+}
+.table-header-btn {
+  .button {
+    margin-bottom: 10px;
+  }
+  .tabs {
+    :deep(.el-tabs__nav-wrap::after) {
+      height: 0;
+    }
+  }
+}
+.reject-reason-content {
+  padding: 20px 0;
+  .reject-reason-item {
+    display: flex;
+    margin-bottom: 20px;
+    &:last-child {
+      margin-bottom: 0;
+    }
+    .reject-reason-label {
+      flex-shrink: 0;
+      min-width: 100px;
+      font-size: 14px;
+      font-weight: 500;
+      color: #606266;
+    }
+    .reject-reason-value {
+      flex: 1;
+      font-size: 14px;
+      color: #303133;
+      word-break: break-word;
+      &.reject-reason-text {
+        min-height: 80px;
+        padding: 12px;
+        line-height: 1.6;
+        white-space: pre-wrap;
+        background-color: #f5f7fa;
+        border-radius: 4px;
+      }
+    }
+  }
+}
+.withdrawal-detail {
+  .section-title {
+    margin: 0 0 20px;
+    font-size: 16px;
+    font-weight: 600;
+    color: #303133;
+  }
+  .progress-section {
+    margin-bottom: 30px;
+    :deep(.el-steps) {
+      .el-step {
+        margin-bottom: 24px;
+        &:last-child {
+          margin-bottom: 0;
+        }
+      }
+      .el-step__head {
+        .el-step__icon {
+          width: 20px;
+          height: 20px;
+          font-size: 14px;
+        }
+      }
+      .el-step__main {
+        padding-bottom: 8px;
+        .el-step__title {
+          margin-bottom: 8px;
+          font-size: 14px;
+          line-height: 1.5;
+          .step-title {
+            font-size: 14px;
+            font-weight: 500;
+            color: #303133;
+          }
+        }
+        .el-step__description {
+          .step-time {
+            margin-top: 4px;
+            font-size: 12px;
+            color: #909399;
+          }
+        }
+      }
+      .el-step__line {
+        top: 20px;
+        bottom: -24px;
+        left: 9px;
+        height: calc(100% + 4px);
+        border-left: 2px dashed #dcdfe6;
+      }
+      .el-step__line-inner {
+        height: 100%;
+        border: none;
+      }
+      .el-step:last-child .el-step__line {
+        display: none;
+      }
+    }
+  }
+  .detail-section {
+    .detail-list {
+      .detail-item {
+        display: flex;
+        align-items: flex-start;
+        margin-bottom: 16px;
+        &:last-child {
+          margin-bottom: 0;
+        }
+        .detail-label {
+          flex-shrink: 0;
+          width: 100px;
+          font-size: 14px;
+          color: #606266;
+        }
+        .detail-value {
+          flex: 1;
+          font-size: 14px;
+          color: #303133;
+          word-break: break-word;
+          &.amount {
+            font-size: 16px;
+            font-weight: 600;
+            color: #303133;
+          }
+          &.status {
+            color: #409eff;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 570 - 0
src/views/financialManagement/withdrawaRequest.vue

@@ -0,0 +1,570 @@
+<template>
+  <div class="withdrawal-request-page">
+    <!-- 头部 -->
+    <div class="header">
+      <el-button class="back-btn" @click="goBack"> 返回 </el-button>
+      <h2 class="title">提现申请</h2>
+    </div>
+
+    <!-- 内容区域 -->
+    <div class="content">
+      <!-- 可提现金额 -->
+      <div class="section">
+        <div class="section-title">可提现金额</div>
+        <div class="available-amount-box">
+          <div class="available-amount">¥ {{ formatCurrency(availableAmount) }}</div>
+        </div>
+      </div>
+
+      <!-- 提现金额输入 -->
+      <div class="section">
+        <div class="section-title">提现金额</div>
+        <div class="amount-input-wrapper">
+          <el-input
+            v-model="withdrawAmount"
+            placeholder="请输入提现金额"
+            class="amount-input"
+            type="number"
+            @input="handleAmountInput"
+          />
+          <el-button type="primary" link class="withdraw-all-btn" @click="handleWithdrawAll"> 全部提现 </el-button>
+        </div>
+        <div class="min-amount-tip">最低提现金额为0.01元</div>
+      </div>
+
+      <!-- 提现账户 -->
+      <div class="section">
+        <div class="section-title">提现账户</div>
+        <div class="account-list">
+          <div
+            v-for="account in accountList"
+            :key="account.id"
+            class="account-item"
+            :class="{ active: selectedAccountId === account.id }"
+            @click="selectAccount(account.id)"
+          >
+            <div class="account-left">
+              <img :src="account.icon" alt="" class="account-icon" />
+              <div class="account-info">
+                <div class="account-name">
+                  {{ account.name }}
+                </div>
+                <div class="account-number">
+                  {{ account.accountNumber }}
+                </div>
+              </div>
+            </div>
+            <div class="account-right">
+              <div v-if="selectedAccountId === account.id" class="selected-indicator" />
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 底部按钮 -->
+    <div class="footer">
+      <el-button class="cancel-btn" @click="goBack"> 取消 </el-button>
+      <el-button type="primary" class="submit-btn" @click="handleSubmit"> 提交申请 </el-button>
+    </div>
+
+    <!-- 确认对话框 -->
+    <el-dialog
+      v-model="confirmDialogVisible"
+      title="提现金额域账户确认"
+      width="500px"
+      :close-on-click-modal="false"
+      class="confirm-dialog"
+    >
+      <div class="confirm-content">
+        <div class="confirm-item">
+          <span class="confirm-label">提现金额:</span>
+          <span class="confirm-value">¥ {{ withdrawAmount || "0" }}</span>
+        </div>
+        <div class="confirm-item">
+          <span class="confirm-label">提现账户:</span>
+          <span class="confirm-value">{{ selectedAccount?.name }}账户 ({{ selectedAccount?.accountNumber }})</span>
+        </div>
+        <div class="password-section">
+          <div class="password-label">请输入提现密码</div>
+          <el-input
+            v-model="withdrawPassword"
+            type="password"
+            placeholder="请输入6位提现密码"
+            maxlength="6"
+            show-password
+            class="password-input"
+            @input="handlePasswordInput"
+          />
+        </div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button class="dialog-cancel-btn" @click="handleCancelConfirm"> 取消 </el-button>
+          <el-button type="primary" class="dialog-confirm-btn" @click="handleConfirmSubmit" :loading="submitLoading">
+            确认
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from "vue";
+import { useRouter } from "vue-router";
+import { ElMessage } from "element-plus";
+import { getAccountBalance } from "@/api/modules/homeEntry";
+import { localGet } from "@/utils";
+import zfbIcon from "@/assets/financial/zfb-icon.png";
+
+const router = useRouter();
+
+// 可提现金额
+const availableAmount = ref<number>(586);
+
+// 提现金额
+const withdrawAmount = ref<string>("");
+
+// 账户列表
+const accountList = ref([
+  {
+    id: 1,
+    name: "支付宝",
+    accountNumber: "130****1234",
+    icon: zfbIcon
+  }
+]);
+
+// 选中的账户ID
+const selectedAccountId = ref<number>(1);
+
+// 确认对话框
+const confirmDialogVisible = ref<boolean>(false);
+const withdrawPassword = ref<string>("");
+const submitLoading = ref<boolean>(false);
+
+// 获取选中的账户
+const selectedAccount = computed(() => {
+  return accountList.value.find(account => account.id === selectedAccountId.value);
+});
+
+// 返回
+const goBack = () => {
+  router.back();
+};
+
+// 格式化金额
+const formatCurrency = (value: number) => {
+  return value.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
+};
+
+// 全部提现
+const handleWithdrawAll = () => {
+  withdrawAmount.value = availableAmount.value.toString();
+};
+
+// 金额输入处理
+const handleAmountInput = (value: string) => {
+  const numValue = parseFloat(value);
+  if (isNaN(numValue) || numValue < 0) {
+    withdrawAmount.value = "";
+    return;
+  }
+  if (numValue > availableAmount.value) {
+    withdrawAmount.value = availableAmount.value.toString();
+    ElMessage.warning("提现金额不能超过可提现金额");
+  }
+};
+
+// 选择账户(已选中的不能取消)
+const selectAccount = (id: number) => {
+  // 如果点击的是已选中的账户,不执行任何操作(不可取消)
+  if (selectedAccountId.value === id) {
+    return;
+  }
+  selectedAccountId.value = id;
+};
+
+// 提交申请
+const handleSubmit = () => {
+  const amount = parseFloat(withdrawAmount.value);
+
+  // 验证提现金额
+  if (!withdrawAmount.value || isNaN(amount) || amount <= 0) {
+    ElMessage.warning("请输入提现金额");
+    return;
+  }
+
+  if (amount < 0.01) {
+    ElMessage.warning("最低提现金额为0.01元");
+    return;
+  }
+
+  if (amount > availableAmount.value) {
+    ElMessage.warning("提现金额不能超过可提现金额");
+    return;
+  }
+
+  // 验证账户
+  if (!selectedAccountId.value) {
+    ElMessage.warning("请选择提现账户");
+    return;
+  }
+
+  // 打开确认对话框
+  confirmDialogVisible.value = true;
+  withdrawPassword.value = "";
+};
+
+// 密码输入处理(只允许数字)
+const handlePasswordInput = (value: string) => {
+  withdrawPassword.value = value.replace(/\D/g, "");
+};
+
+// 取消确认
+const handleCancelConfirm = () => {
+  confirmDialogVisible.value = false;
+  withdrawPassword.value = "";
+};
+
+// 确认提交
+const handleConfirmSubmit = async () => {
+  // 验证密码
+  if (!withdrawPassword.value) {
+    ElMessage.warning("请输入提现密码");
+    return;
+  }
+
+  if (withdrawPassword.value.length !== 6) {
+    ElMessage.warning("请输入6位提现密码");
+    return;
+  }
+
+  if (!/^\d{6}$/.test(withdrawPassword.value)) {
+    ElMessage.warning("提现密码必须为6位数字");
+    return;
+  }
+
+  submitLoading.value = true;
+  try {
+    // TODO: 调用提现API
+    // const params = {
+    //   amount: parseFloat(withdrawAmount.value),
+    //   accountId: selectedAccountId.value,
+    //   password: withdrawPassword.value
+    // };
+    // const res = await submitWithdrawal(params);
+
+    // 模拟API调用
+    await new Promise(resolve => setTimeout(resolve, 1500));
+
+    ElMessage.success("提现申请提交成功");
+    confirmDialogVisible.value = false;
+    withdrawPassword.value = "";
+
+    // 提交成功后返回上一页
+    setTimeout(() => {
+      goBack();
+    }, 1500);
+  } catch (error: any) {
+    ElMessage.error(error.message || "提现申请提交失败,请重试");
+  } finally {
+    submitLoading.value = false;
+  }
+};
+
+// 获取可提现金额
+const fetchAccountBalance = async () => {
+  try {
+    const userInfo = localGet("geeker-user")?.userInfo || {};
+    let param = {
+      storeId: userInfo.storeId || localGet("createdId")
+    };
+    const res: any = await getAccountBalance(param as any);
+    if (res.code == 200 && res.data) {
+      availableAmount.value = res.data.balance || res.data.availableAmount || res.data.amount || 586;
+    }
+  } catch (error) {
+    console.error("获取可提现金额失败:", error);
+  }
+};
+
+onMounted(() => {
+  fetchAccountBalance();
+});
+</script>
+
+<style scoped lang="scss">
+.withdrawal-request-page {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  min-height: 100vh;
+  background-color: #ffffff;
+}
+.header {
+  position: relative;
+  display: flex;
+  align-items: center;
+  padding: 20px;
+  border-bottom: 1px solid #e4e7ed;
+  .back-btn {
+    padding: 8px 16px;
+    font-size: 14px;
+    color: #606266;
+    border: 1px solid #dcdfe6;
+    border-radius: 4px;
+    &:hover {
+      color: #409eff;
+      background-color: #ecf5ff;
+      border-color: #b3d8ff;
+    }
+  }
+  .title {
+    position: absolute;
+    left: 50%;
+    margin: 0;
+    font-size: 18px;
+    font-weight: bold;
+    color: #333333;
+    transform: translateX(-50%);
+  }
+}
+.content {
+  display: flex;
+  flex: 1;
+  flex-direction: column;
+  gap: 32px;
+  padding: 24px 20px;
+}
+.section {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  .section-title {
+    font-size: 16px;
+    font-weight: 500;
+    color: #333333;
+  }
+}
+.available-amount-box {
+  padding: 20px;
+  margin-top: 8px;
+  background-color: #ffffff;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  .available-amount {
+    font-size: 36px;
+    font-weight: 600;
+    color: #333333;
+  }
+}
+.amount-input-wrapper {
+  display: flex;
+  gap: 12px;
+  align-items: center;
+  margin-top: 8px;
+  .amount-input {
+    flex: 1;
+    :deep(.el-input__wrapper) {
+      padding: 12px 16px;
+      border-radius: 4px;
+    }
+  }
+  .withdraw-all-btn {
+    padding: 0;
+    font-size: 14px;
+    color: #409eff;
+    white-space: nowrap;
+    background: transparent;
+    border: none;
+    &:hover {
+      color: #66b1ff;
+    }
+  }
+}
+.min-amount-tip {
+  margin-top: 4px;
+  font-size: 12px;
+  color: #909399;
+}
+.account-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  margin-top: 8px;
+}
+.account-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 16px;
+  cursor: pointer;
+  background-color: #ffffff;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  transition: all 0.3s;
+  &:hover {
+    border-color: #409eff;
+  }
+  &.active {
+    background-color: #f5f7fa;
+    border-color: #409eff;
+  }
+  .account-left {
+    display: flex;
+    flex: 1;
+    gap: 12px;
+    align-items: center;
+    .account-icon {
+      width: 40px;
+      height: 40px;
+      object-fit: contain;
+    }
+    .account-info {
+      display: flex;
+      flex-direction: column;
+      gap: 4px;
+      .account-name {
+        font-size: 16px;
+        font-weight: 500;
+        color: #333333;
+      }
+      .account-number {
+        font-size: 14px;
+        color: #909399;
+      }
+    }
+  }
+  .account-right {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 24px;
+    height: 24px;
+    .selected-indicator {
+      width: 16px;
+      height: 16px;
+      background-color: #409eff;
+      border: 3px solid #ffffff;
+      border-radius: 50%;
+      box-shadow: 0 0 0 2px #409eff;
+    }
+  }
+}
+.footer {
+  display: flex;
+  gap: 12px;
+  justify-content: center;
+  padding: 20px;
+  background-color: #ffffff;
+  border-top: 1px solid #e4e7ed;
+  .cancel-btn {
+    width: 100px;
+    padding: 12px;
+    font-size: 16px;
+    color: #606266;
+    background-color: #f5f7fa;
+    border: 1px solid #dcdfe6;
+    border-radius: 4px;
+    &:hover {
+      color: #409eff;
+      background-color: #ecf5ff;
+      border-color: #b3d8ff;
+    }
+  }
+  .submit-btn {
+    width: 100px;
+    padding: 12px;
+    font-size: 16px;
+    color: #ffffff;
+    background-color: #409eff;
+    border: none;
+    border-radius: 4px;
+    &:hover {
+      background-color: #66b1ff;
+    }
+  }
+}
+:deep(.confirm-dialog) {
+  .el-dialog__header {
+    padding: 20px 24px;
+    border-bottom: 1px solid #e4e7ed;
+  }
+  .el-dialog__title {
+    font-size: 18px;
+    font-weight: 600;
+    color: #333333;
+  }
+  .el-dialog__body {
+    padding: 24px;
+  }
+}
+.confirm-content {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+  .confirm-item {
+    display: flex;
+    gap: 12px;
+    align-items: center;
+    font-size: 14px;
+    .confirm-label {
+      min-width: 80px;
+      font-weight: 500;
+      color: #606266;
+    }
+    .confirm-value {
+      font-weight: 500;
+      color: #333333;
+    }
+  }
+  .password-section {
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+    margin-top: 8px;
+    .password-label {
+      font-size: 14px;
+      font-weight: 500;
+      color: #606266;
+    }
+    .password-input {
+      :deep(.el-input__wrapper) {
+        padding: 12px 16px;
+        border-radius: 4px;
+      }
+    }
+  }
+}
+.dialog-footer {
+  display: flex;
+  gap: 12px;
+  justify-content: flex-end;
+  .dialog-cancel-btn {
+    padding: 10px 20px;
+    font-size: 14px;
+    color: #606266;
+    background-color: #f5f7fa;
+    border: 1px solid #dcdfe6;
+    border-radius: 4px;
+    &:hover {
+      color: #409eff;
+      background-color: #ecf5ff;
+      border-color: #b3d8ff;
+    }
+  }
+  .dialog-confirm-btn {
+    padding: 10px 20px;
+    font-size: 14px;
+    color: #ffffff;
+    background-color: #409eff;
+    border: none;
+    border-radius: 4px;
+    &:hover {
+      background-color: #66b1ff;
+    }
+  }
+}
+</style>

+ 21 - 7
src/views/home/notice.vue

@@ -60,7 +60,7 @@
 import { ref, onMounted } from "vue";
 import { ElMessage } from "element-plus";
 import { CircleCheck } from "@element-plus/icons-vue";
-import { getNoticeList, markAllNoticesAsRead } from "@/api/modules/homeEntry";
+import { getNoticeList, markAllNoticesAsRead, markNoticeAsRead } from "@/api/modules/homeEntry";
 import { useRouter } from "vue-router";
 const router = useRouter();
 import mittBus from "@/utils/mittBus";
@@ -70,7 +70,8 @@ import { get } from "lodash";
 interface NoticeItem {
   id: number;
   title: string;
-  time: string;
+  time?: string;
+  createdTime?: string;
   content: string;
   context?: string;
   isRead: boolean;
@@ -151,12 +152,25 @@ const parseContext = (context: string | undefined): string => {
 };
 
 // 查看详情
-const handleViewDetail = (item: NoticeItem) => {
-  // 标记为已读
+const handleViewDetail = async (item: NoticeItem) => {
+  // 如果未读,调用接口标记为已读
   if (!item.isRead) {
-    item.isRead = true;
-    // 通知 Message 组件更新未读数量
-    mittBus.emit("updateUnreadCount");
+    try {
+      const res: any = await markNoticeAsRead({
+        id: item.id
+      });
+      if (res.code == 200) {
+        // 标记为已读
+        item.isRead = true;
+        // 通知 Message 组件更新未读数量
+        mittBus.emit("updateUnreadCount");
+      } else {
+        ElMessage.error(res.message || "标记已读失败");
+      }
+    } catch (error) {
+      console.error("标记已读失败:", error);
+      ElMessage.error("标记已读失败,请重试");
+    }
   }
   // 显示详情对话框
   currentNotice.value = item;

+ 8 - 5
src/views/login/index.vue

@@ -579,7 +579,7 @@
 </template>
 
 <script setup lang="ts" name="login">
-import { ref, reactive, watch, onMounted, onBeforeUnmount } from "vue";
+import { ref, reactive, watch, onMounted, onBeforeUnmount, nextTick } from "vue";
 import { useRoute, useRouter } from "vue-router";
 import { HOME_URL } from "@/config";
 import { Login } from "@/api/interface/index";
@@ -867,10 +867,13 @@ const handleLogin = async () => {
         // 3.清空 tabs、keepAlive 数据
         await tabsStore.setTabs([]);
         await keepAliveStore.setKeepAliveName([]);
-        console.log("调整");
-        console.log(router.getRoutes(), route);
-        // 4.跳转到首页
-        router.push(HOME_URL);
+
+        // 4.等待路由完全初始化后再跳转
+        // 使用 nextTick 确保路由已完全添加
+        await nextTick();
+
+        // 5.跳转到首页,使用 replace 避免历史记录问题
+        await router.replace(HOME_URL);
 
         ElNotification({
           title: "登录成功",