Kaynağa Gözat

添加我得费用流水

xukai 7 ay önce
ebeveyn
işleme
b36ab4efb7

+ 6 - 7
.prettierrc

@@ -1,17 +1,16 @@
 {
-  "printWidth": 160,
+  "printWidth": 100,
   "tabWidth": 2,
   "useTabs": false,
-  "semi": false,
+  "semi": true,
   "singleQuote": true,
   "quoteProps": "as-needed",
-  "jsxSingleQuote": false,
-  "trailingComma": "none",
+  "jsxSingleQuote": true,
+  "trailingComma": "es5",
   "bracketSpacing": true,
   "bracketSameLine": false,
-  "arrowParens": "always",
+  "arrowParens": "avoid",
   "htmlWhitespaceSensitivity": "css",
   "vueIndentScriptAndStyle": true,
-  "endOfLine": "lf",
-  "jsxBracketSameLine": true
+  "endOfLine": "lf"
 }

+ 11 - 0
src/api/finace/index.ts

@@ -0,0 +1,11 @@
+import request from '/@/utils/micro_request.js'
+const finacePath = import.meta.env.VITE_FINANCE
+
+export function useFinaceApi() {
+  return {
+    // 获取资金流水
+    getList: (query?: object) => {
+      return request.postRequest(finacePath, 'FinanceOrder', 'GetFinBillableOrderList', query);
+    }
+  }
+}

+ 32 - 1
src/constants/pageConstants.ts

@@ -67,4 +67,35 @@ export enum FeedingSpecial {
 export enum MyCageType {
   MINE_CAGE = 'mineCage',
   MY_CAGE_HISTORY = 'myCageHistory'
-}
+}
+
+export enum FeeType {
+  INSTRUMENTS_AND_EQUIPMENT = '10',
+  EXPERIMENTAL_PLATFORMS = '20',
+  TECHNICAL_SERVICES = '30',
+  LABORATORY_ANIMALS = '40'
+}
+
+export const FeeTypeList = [
+  {
+    name: '仪器设备',
+    id: FeeType.INSTRUMENTS_AND_EQUIPMENT
+  },
+  {
+    name: '实验平台',
+    id: FeeType.EXPERIMENTAL_PLATFORMS
+  },
+  {
+    name: '技术服务',
+    id: FeeType.TECHNICAL_SERVICES
+  },
+  {
+    name: '实验动物',
+    id: FeeType.LABORATORY_ANIMALS
+  }
+]
+
+export enum FeeStatus {
+  NOT_PAID = '10',
+  PAID = '20'
+}

+ 8 - 0
src/router.ts

@@ -223,6 +223,14 @@ const routes = [
           title: '个人中心'
         }
       },
+      {
+        name: 'myCashFlow',
+        path: '/my-cash-flow',
+        component: () => import('/@/view/myCashFlow/index.vue'),
+        meta: {
+          title: '我的资金流水'
+        }
+      },
       {
         name: 'userEdit',
         path: '/user/edit',

+ 323 - 0
src/view/myCashFlow/index.vue

@@ -0,0 +1,323 @@
+<template>
+  <div class="entry-container">
+    <div class="search-wrap" ref="searchWrapRef">
+      <el-form :model="state.queryParams" ref="queryRef">
+        <el-form-item prop="serialNo">
+          <el-select
+            v-model="state.queryParams.feeType"
+            style="width: 100%"
+            placeholder="费用类型"
+            clearable
+            @change="search"
+          >
+            <el-option
+              v-for="item in FeeTypeList"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+            ></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item prop="serialNo">
+          <el-select
+            v-model="state.queryParams.feeStatus"
+            style="width: 100%"
+            placeholder="费用状态"
+            clearable
+            @change="search"
+          >
+            <el-option label="已支付" value="20"></el-option>
+            <el-option label="未支付" value="10"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item prop="serialNo">
+          <el-date-picker
+            v-model="state.queryParams.feeTime"
+            type="date"
+            style="width: 100%"
+            placeholder="费用时间"
+            clearable
+            @change="search"
+          />
+        </el-form-item>
+      </el-form>
+      <div style="text-align: right">
+        <el-button @click="handleExport" style="height: 25px" type="primary">导出</el-button>
+      </div>
+    </div>
+
+    <div class="list-container">
+      <van-list
+        v-model:loading="state.loading"
+        :finished="state.finished"
+        finished-text="没有更多了"
+        @load="onLoad"
+      >
+        <van-cell v-for="item in state.pageList" :key="item" @click="handleCheckDetail(item)">
+          <template #default>
+            <div class="list">
+              <header class="flex justify-between">
+                <strong class="title">{{ `${item.userName}的费用流水` }}</strong>
+                <van-tag v-if="item.approveStatus == ApproveStatus.WAIT_SUBMIT" type="warning"
+                  >待提交</van-tag
+                >
+                <van-tag v-else-if="item.approveStatus == ApproveStatus.APPROVING" type="primary"
+                  >审核中</van-tag
+                >
+                <van-tag v-else-if="item.approveStatus == ApproveStatus.PASS" type="success"
+                  >通过</van-tag
+                >
+                <van-tag v-else-if="item.approveStatus == ApproveStatus.REVOKE" type="success"
+                  >撤销</van-tag
+                >
+                <van-tag v-else-if="item.approveStatus == ApproveStatus.REFUSE" type="danger"
+                  >拒绝</van-tag
+                >
+              </header>
+              <p class="inst-title">
+                <span>费用类型</span>
+                <span class="title ml8">{{ item.projectGroupName }}</span>
+              </p>
+              <p class="inst-title">
+                <span>费用时间</span>
+                <span class="title ml8">
+                  {{ item.userName }}
+                </span>
+              </p>
+              <p class="inst-title">
+                <span>确认时间</span>
+                <span class="title ml8">
+                  {{ formatToChineseDate(item.createdTime) }}
+                </span>
+              </p>
+              <p class="inst-title">
+                <span>总计金额</span>
+                <span class="title ml8">
+                  {{ formatToChineseDate(item.startDate) }}
+                </span>
+              </p>
+              <p class="inst-title">
+                <span>状态</span>
+                <span class="title ml8">
+                  {{ item.number }}
+                </span>
+              </p>
+              <footer class="flex justify-between mt16">
+                <span class="title">
+                  <el-button
+                    style="height: 25px"
+                    type="primary"
+                    @click.stop="handleRefundable(item)"
+                    >确认</el-button
+                  >
+                </span>
+                <span class="time">{{ formatDate(new Date(item.createdTime), 'YYYY-mm-dd') }}</span>
+              </footer>
+            </div>
+          </template>
+        </van-cell>
+      </van-list>
+    </div>
+
+    <DetailModal
+      :showDialog="showDetailDialog"
+      :isReturnCageList="false"
+      ref="detailModalRef"
+      @close="() => (showDetailDialog = false)"
+    />
+    <ReturnCageDialog
+      ref="returnCageDialogRef"
+      :currentRefundableItemNumber="currentRefundableItemNumber"
+      :getTableData="handleRefresh"
+    />
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { reactive, ref, onMounted, defineAsyncComponent, ComponentPublicInstance } from 'vue';
+  import to from 'await-to-js';
+
+  import { formatDate, formatToChineseDate } from '/@/utils/formatTime';
+  import {
+    ApproveStatus,
+    ReturnStatus,
+    LeavelList,
+    ApproveStatusList,
+    MyCageType,
+    FeeTypeList,
+  } from '/@/constants/pageConstants';
+  import { useUserInfos } from '/@/hooks/useUserInfos';
+  import { usePlatAnimalCageApplicationApi } from '/@/api/platform/animal';
+  import { useFinaceApi } from '/@/api/finace';
+
+  interface ReturnCageDialogInstance extends ComponentPublicInstance {
+    handleOpenRefundableDialog: (id: number) => void;
+  }
+
+  interface DetailModalInstance extends ComponentPublicInstance {
+    initForm: (id: number) => void;
+  }
+
+  const DetailModal = defineAsyncComponent(
+    () => import('/@/view/animal/application/components/Detail.vue')
+  );
+  const ReturnCageDialog = defineAsyncComponent(
+    () => import('/@/view/animal/application/components/ReturnCageDialog.vue')
+  );
+
+  const { userInfos } = useUserInfos();
+  const platAnimalCageApplicationApi = usePlatAnimalCageApplicationApi();
+  const finaceApi = useFinaceApi();
+
+  const returnCageDialogRef = ref<ReturnCageDialogInstance>();
+  const detailModalRef = ref<DetailModalInstance>();
+
+  const showDetailDialog = ref<boolean>(false);
+  const activeStatus = ref<MyCageType>(MyCageType.MINE_CAGE);
+  const currentRefundableItemNumber = ref<number>(0);
+
+  const state = reactive({
+    pageList: [],
+    loading: false,
+    finished: false,
+    queryParams: {
+      pageNum: 1,
+      pageSize: 25,
+      isMyself: 1,
+      feeType: '',
+      feeStatus: '',
+      feeTime: '',
+    },
+  });
+
+  const formatApproveStatus = (status: number) => {
+    return ApproveStatusList.find(item => item.id === status)?.name || '';
+  };
+
+  const handleRefundable = (row: any) => {
+    currentRefundableItemNumber.value = row.number;
+    returnCageDialogRef.value.handleOpenRefundableDialog(row.id);
+  };
+
+  const handleRefresh = () => {
+    resetQueryParams();
+    onLoad();
+  };
+
+  const resetQueryParams = () => {
+    (state.pageList = []),
+      (state.loading = false),
+      (state.finished = false),
+      (state.queryParams = {
+        pageNum: 1,
+        pageSize: 25,
+        isMyself: 1,
+        feeType: '',
+        feeStatus: '',
+        feeTime: '',
+      });
+  };
+
+  const onLoad = async (isSearch?: boolean) => {
+    state.loading = true;
+    const apiRequest =
+      activeStatus.value === MyCageType.MINE_CAGE
+        ? platAnimalCageApplicationApi.getList(state.queryParams)
+        : platAnimalCageApplicationApi.getMyCageHistoryList(state.queryParams);
+
+    const [err, res]: ToResponse = await to(apiRequest);
+
+    state.loading = false;
+
+    if (err) return;
+
+    const list = res?.data?.list || [];
+
+    if (!isSearch) {
+      for (const item of list) {
+        state.pageList.push(item);
+      }
+      state.queryParams.pageNum++;
+      if (list.length < state.queryParams.pageSize) {
+        state.finished = true;
+      }
+    } else {
+      state.pageList = list;
+    }
+  };
+
+  const handleCheckDetail = (row: any) => {
+    detailModalRef.value.initForm(row.id);
+    showDetailDialog.value = true;
+  };
+
+  const search = () => {
+    onLoad(true);
+  };
+
+  const getList = async () => {
+    const [err, res]: ToResponse = await to(finaceApi.getList(state.queryParams));
+    if (err) return;
+    console.log(res);
+  };
+
+  const handleExport = async () => {};
+
+  onMounted(() => {
+    getList();
+    onLoad();
+  });
+</script>
+
+<style lang="scss" scoped>
+  .entry-container {
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    .search-wrap {
+      background: #fff;
+      margin-bottom: 10px;
+      padding: 15px;
+    }
+    .list-container {
+      overflow-y: auto;
+      padding: 10px;
+      border-radius: 4px;
+      flex: 1;
+    }
+    .van-list {
+      .van-cell {
+        background-color: #fff;
+        + .van-cell {
+          margin-top: 10px;
+        }
+        header,
+        footer {
+          color: #333;
+        }
+        .title {
+          flex: 1;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          text-align: left;
+        }
+        .inst-title {
+          color: #333;
+          text-align: left;
+          flex: 1;
+          overflow: hidden;
+          white-space: nowrap;
+          text-overflow: ellipsis;
+          margin-top: 4px;
+          span:first-child {
+            color: rgb(120, 120, 120);
+          }
+        }
+        .time {
+          color: #f69a4d;
+        }
+      }
+    }
+  }
+</style>

+ 230 - 18
src/view/user/index.vue

@@ -34,7 +34,7 @@
             <span>123,000</span>
             <p>账户余额(¥)</p>
           </li>
-          <li>
+          <li @click="onRouterPush('/my-cash-flow')">
             <span>11</span>
             <p>流水</p>
           </li>
@@ -48,6 +48,9 @@
           </li>
         </ul>
       </div>
+      <div class="card" @click="showOperatingGuidelines">
+        <h4>点击查看操作指引</h4>
+      </div>
     </div>
     <footer>
       <!-- <wx-open-subscribe template="-pVpM6TH1oz1cZntEhGzQjmMMODF9tLLzh_1yjjWYSk" @success="handleSubscribe">
@@ -55,35 +58,116 @@
           <button class="w100 ">订阅</button>
         </component>
       </wx-open-subscribe> -->
-      <van-button class="w100 mt10" color="#1cb4fd" plain @click="onRouterPush('/user/password')">修改密码</van-button>
+      <van-button class="w100 mt10" color="#1cb4fd" plain @click="onRouterPush('/user/password')"
+        >修改密码</van-button
+      >
       <van-button class="w100 mt10" @click="signOut" type="primary">切换账号</van-button>
     </footer>
+
+    <van-popup position="bottom" v-model:show="showPopup" :style="{ padding: '20px' }">
+      <div class="operation-guide">
+        <div class="guide-header">
+          <h3>操作指引</h3>
+          <van-icon name="cross" @click="showPopup = false" />
+        </div>
+        
+        <div class="steps-container">
+          <div 
+            v-for="(item, index) in handleStepList" 
+            :key="item.step"
+            class="step-item"
+            :class="{ 'last-step': index === handleStepList.length - 1 }"
+          >
+            <div class="step-number-wrapper">
+              <div class="step-number">{{ item.step }}</div>
+              <div v-if="index < handleStepList.length - 1" class="step-line"></div>
+            </div>
+            
+            <div class="step-content">
+              <h4 class="step-title">{{ item.title }}</h4>
+              <p class="step-desc">{{ item.desc }}</p>
+              <!-- <van-button 
+                v-if="item.btn" 
+                size="small" 
+                type="primary" 
+                class="step-btn"
+                @click="handleStepAction(item)"
+              >
+                {{ item.btn }}
+              </van-button> -->
+            </div>
+          </div>
+        </div>
+        
+        <div class="guide-footer">
+          <van-icon name="info-o" />
+          <span>请按照步骤顺序完成操作</span>
+        </div>
+      </div>
+    </van-popup>
   </div>
 </template>
 
 <script lang="ts" setup>
-  import { storeToRefs } from 'pinia'
-  import { useUserInfo } from '/@/stores/userInfo'
-  import { Local } from '/@/utils/storage'
-  import { showConfirmDialog } from 'vant'
-  import { useRouter } from 'vue-router'
-  const router = useRouter()
-  const storesUseUserInfo = useUserInfo()
-  const { userInfos, sdkConfig, openId } = storeToRefs(storesUseUserInfo)
+  import { ref } from 'vue';
+  import { storeToRefs } from 'pinia';
+  import { useUserInfo } from '/@/stores/userInfo';
+  import { Local } from '/@/utils/storage';
+  import { showConfirmDialog } from 'vant';
+  import { useRouter } from 'vue-router';
+  const router = useRouter();
+  const storesUseUserInfo = useUserInfo();
+  const { userInfos, sdkConfig, openId } = storeToRefs(storesUseUserInfo);
+
+  const showPopup = ref<boolean>(false);
+
+  const handleStepList = ref([
+    {
+      title: '第一步:入室培训和考试',
+      desc: '请先完成入室必要培训,掌握安全操作知识,并通过考试',
+      btn: '培训报名',
+      step: 1,
+    },
+    {
+      title: '第二步:提交入室申请',
+      desc: '在线完成入室前的培训,掌握必要的安全操作知识,并通过考试',
+      btn: '前往提交',
+      step: 2,
+    },
+    {
+      title: '第三步:上传入室资料',
+      desc: '整理相关凭证并将入室资料上传到系统中',
+      step: 3,
+    },
+    {
+      title: '第四步:等待审核和房间分配',
+      desc: '由平台老师进行审核,通过后分配操作室',
+      step: 4,
+    },
+  ]);
+
   const onRouterPush = (val: string) => {
-    router.push(val)
-  }
+    router.push(val);
+  };
   const signOut = () => {
     showConfirmDialog({
-      message: '确认切换账号?'
+      message: '确认切换账号?',
     }).then(() => {
-      Local.remove('token')
-      window.location.reload()
-    })
-  }
+      Local.remove('token');
+      window.location.reload();
+    });
+  };
   const handleSubscribe = (errMsg: any, subscribeDetails: any) => {
     console.log(errMsg, subscribeDetails);
-  }
+  };
+
+  const showOperatingGuidelines = () => {
+    showPopup.value = true;
+  };
+
+  const handleStepAction = (item: any) => {
+    // Implementation of handleStepAction
+  };
 </script>
 
 <style lang="scss" scoped>
@@ -196,4 +280,132 @@
       margin-bottom: 20px;
     }
   }
+  :deep(.van-popup--bottom) {
+    padding: 20px !important;
+  }
+
+  .operation-guide {
+    max-height: 70vh;
+    overflow-y: auto;
+    
+    .guide-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 20px;
+      padding-bottom: 15px;
+      border-bottom: 1px solid #eee;
+      
+      h3 {
+        margin: 0;
+        font-size: 18px;
+        font-weight: 600;
+        color: #333;
+      }
+      
+      .van-icon {
+        font-size: 18px;
+        color: #999;
+        cursor: pointer;
+        
+        &:hover {
+          color: #666;
+        }
+      }
+    }
+    
+    .steps-container {
+      .step-item {
+        display: flex;
+        margin-bottom: 20px;
+        
+        &.last-step {
+          margin-bottom: 0;
+        }
+        
+        .step-number-wrapper {
+          position: relative;
+          margin-right: 15px;
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          
+          .step-number {
+            width: 32px;
+            height: 32px;
+            background: linear-gradient(135deg, #1c9bfd, #5eb7fc);
+            color: white;
+            border-radius: 50%;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            font-weight: 600;
+            font-size: 14px;
+            box-shadow: 0 2px 8px rgba(28, 155, 253, 0.3);
+          }
+          
+          .step-line {
+            width: 2px;
+            height: 40px;
+            background: linear-gradient(to bottom, #1c9bfd, #e0e0e0);
+            margin-top: 8px;
+          }
+        }
+        
+        .step-content {
+          flex: 1;
+          padding-top: 2px;
+          
+          .step-title {
+            margin: 0 0 8px 0;
+            font-size: 16px;
+            font-weight: 600;
+            color: #333;
+            line-height: 1.4;
+          }
+          
+          .step-desc {
+            margin: 0 0 12px 0;
+            font-size: 14px;
+            color: #666;
+            line-height: 1.5;
+          }
+          
+          .step-btn {
+            border-radius: 20px;
+            padding: 8px 16px;
+            font-size: 12px;
+            background: linear-gradient(135deg, #1c9bfd, #5eb7fc);
+            border: none;
+            box-shadow: 0 2px 8px rgba(28, 155, 253, 0.3);
+            
+            &:active {
+              transform: translateY(1px);
+            }
+          }
+        }
+      }
+    }
+    
+    .guide-footer {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      margin-top: 20px;
+      padding: 15px;
+      background: #f8f9fa;
+      border-radius: 8px;
+      
+      .van-icon {
+        margin-right: 6px;
+        color: #1c9bfd;
+        font-size: 16px;
+      }
+      
+      span {
+        font-size: 13px;
+        color: #666;
+      }
+    }
+  }
 </style>