Преглед изворни кода

feat: ✨ 扫码核销以及核销明细

zhangtao пре 3 недеља
родитељ
комит
be7e982a8c

+ 11 - 11
alova.config.ts

@@ -1,6 +1,6 @@
 /* eslint-disable no-irregular-whitespace */
 
-import type { Config } from '@alova/wormhole'
+import type { Config } from "@alova/wormhole";
 
 // For more config detailed visit:
 // https://alova.js.org/tutorial/getting-started/extension-integration
@@ -13,28 +13,28 @@ export default <Config>{
        * 1. openapi json file url
        * 2. local file
        */
-      input: 'https://petstore3.swagger.io/api/v3/openapi.json',
+      // input: 'https://petstore3.swagger.io/api/v3/openapi.json',
       /**
        * input file platform. Currently only swagger is supported.
        * When this parameter is specified, the input field only needs to specify the document address without specifying the openapi file
        */
-      platform: 'swagger',
+      platform: "swagger",
 
       /**
        * output path of interface file and type file.
        * Multiple generators cannot have the same address, otherwise the generated code will overwrite each other.
        */
-      output: 'src/api',
+      output: "src/api",
 
       /**
        * the mediaType of the generated response data. default is `application/json`
        */
-      responseMediaType: 'application/json',
+      responseMediaType: "application/json",
 
       /**
        * the bodyMediaType of the generated request body data. default is `application/json`
        */
-      bodyMediaType: 'application/json',
+      bodyMediaType: "application/json",
 
       /**
        * the generated api version. options are `2` or `3`, default is `auto`.
@@ -44,13 +44,13 @@ export default <Config>{
       /**
        * type of generated code. The options ​​are `auto/ts/typescript/module/commonjs`.
        */
-      type: 'typescript',
+      type: "typescript",
 
       /**
        * exported global api name, you can access the generated api globally through this name, default is `Apis`.
        * it is required when multiple generators are configured, and it cannot be repeated
        */
-      global: 'Apis',
+      global: "Apis",
 
       /**
        * filter or convert the generated api information, return an apiDescriptor, if this function is not specified, the apiDescripor object is not converted
@@ -61,12 +61,12 @@ export default <Config>{
 
         // Filter out any deprecated APIs if needed
         if (apiDescriptor.deprecated) {
-          return undefined // Skip this API
+          return undefined; // Skip this API
         }
         // You can transform the API descriptor here if needed
         // For example, add custom headers, modify parameters, etc.
 
-        return apiDescriptor
+        return apiDescriptor;
       },
     },
   ],
@@ -81,4 +81,4 @@ export default <Config>{
     // Check for updates every 5 minutes
     interval: 5 * 60 * 1000,
   },
-}
+};

+ 1 - 0
src/api/apiDefinitions.ts

@@ -24,6 +24,7 @@ Some useful links:
  * **Do not edit the file manually.**
  */
 export default {
+  "app.repealVerifyRecord": ["PUT", "/app/course/repealVerifyRecord"],
   "app.pageAppIsinVerifyRecords": [
     "GET",
     "/app/course/pageAppIsinVerifyRecords",

+ 8 - 7
src/api/core/handlers.ts

@@ -64,11 +64,13 @@ export async function handleAlovaResponse(
       "code" in data &&
       data.code >= 400
     ) {
-      uni.showToast({
-        title: (data as any).message,
-        duration: 2000,
-        icon: "none",
-      });
+      setTimeout(() => {
+        uni.showToast({
+          title: (data as any).message,
+          duration: 2000,
+          icon: "none",
+        });
+      }, 100);
       reject(`${(data as any).message}`);
     }
 
@@ -78,10 +80,9 @@ export async function handleAlovaResponse(
     if (import.meta.env.MODE === "development") {
       console.log("[Alova Response]", json);
     }
-    console.log(json, "请求数据");
-
     // Return data for successful responses
     resolve(json.result);
+    uni.hideLoading();
   });
 }
 

+ 0 - 4
src/api/core/instance.ts

@@ -43,10 +43,6 @@ export const alovaInstance = createAlova({
     onError: handleAlovaError,
 
     // Complete handler - runs after success or error
-    onComplete: async () => {
-      // Any cleanup or logging can be done here
-      uni.hideLoading();
-    },
   },
   // We'll use the middleware in the hooks
   // middleware is not directly supported in createAlova options

+ 12 - 3
src/api/globals.d.ts

@@ -205,6 +205,15 @@ declare global {
       ): Alova2Method<any, "sys.upload", Config>;
     };
     app: {
+      repealVerifyRecord<
+        Config extends Alova2MethodConfig<any> & {
+          params: {
+            appIsinId: string;
+          };
+        },
+      >(
+        config: Config,
+      ): Alova2Method<any, "app.repealVerifyRecord", Config>;
       pageAppIsinVerifyRecords<
         Config extends Alova2MethodConfig<
           PaginationResponse<AppIsinVerifyVO[]>
@@ -432,7 +441,7 @@ export interface AppIsinVerifyVO {
   /**
    * ID
    */
-  id?: string;
+  id: string;
   /**
    * 价格/元
    */
@@ -973,7 +982,7 @@ export interface AppOrderProInfoVerifyVO {
    */
   isinId?: string;
   /**
-   * 券状态 1、待使用 2、已使用 3、已失效
+   * 券状态  0-待使用 1-已使用 2-已失效
    */
   isinStatus?: number;
   [property: string]: any;
@@ -1031,7 +1040,7 @@ export interface AppOrderProInfo {
    */
   orderId?: string;
   /**
-   * 订单状态
+   * 订单状态0-待付款 1-待使用 2-已使用 3-已到期 4-已取消 5-退款中 6已退款
    */
   orderStatus?: number;
   /**

+ 1 - 0
src/components.d.ts

@@ -42,6 +42,7 @@ declare module 'vue' {
     WdTabbar: typeof import('wot-design-uni/components/wd-tabbar/wd-tabbar.vue')['default']
     WdTabbarItem: typeof import('wot-design-uni/components/wd-tabbar-item/wd-tabbar-item.vue')['default']
     WdTabs: typeof import('wot-design-uni/components/wd-tabs/wd-tabs.vue')['default']
+    WdTag: typeof import('wot-design-uni/components/wd-tag/wd-tag.vue')['default']
     WdText: typeof import('wot-design-uni/components/wd-text/wd-text.vue')['default']
     WdTextarea: typeof import('wot-design-uni/components/wd-textarea/wd-textarea.vue')['default']
     WdToast: typeof import('wot-design-uni/components/wd-toast/wd-toast.vue')['default']

+ 9 - 5
src/components/classItem/index.vue

@@ -25,11 +25,7 @@
         >预约这节</commonbtn
       ><commonbtn
         bg-color="#0074FF"
-        @click="
-          handleGoPath(
-            `/subPack/ExtensionClass/index?coursesId=${item.coursesId}&id=${item.id}`,
-          )
-        "
+        @click="handlePost"
         v-if="type == 2 && showBtn"
         >延期这节</commonbtn
       >
@@ -93,6 +89,14 @@ function handlePic(id: string) {
     return uni.showToast({ title: "全部学生已核销" });
   handleGoPath(`/subPack/selectClass/index?id=${id}`);
 }
+function handlePost() {
+  if (props.item?.postponeNum == 0)
+    return uni.showToast({ title: "暂无可延期人员" });
+  const { item } = props;
+  handleGoPath(
+    `/subPack/ExtensionClass/index?coursesId=${item?.coursesId}&id=${item?.id}`,
+  );
+}
 </script>
 
 <style scoped></style>

+ 3 - 11
src/subPack/EmployeeListAdd/index.vue

@@ -55,7 +55,7 @@
             :rules="[{ required: true, message: '请输入登录账号' }]"
           />
         </customFormItem>
-        <customFormItem label="登录密码" required>
+        <customFormItem label="登录密码" v-if="type == 0">
           <wd-input
             show-password
             placeholder="请输入登录密码"
@@ -400,10 +400,10 @@ async function getDetaile() {
     }
     formData = editData.value;
     formData.deptName = findNamesByKeys(
-      formData.selecteddeparts,
+      formData.selecteddeparts.split(","),
       deptList.value,
     ).join(",");
-    console.log(formData, "formData");
+    console.log(deptList.value, "formData");
   }
 }
 function handleShow() {
@@ -454,14 +454,6 @@ function handleChangeRole(e: { value: string }) {
   });
 }
 
-watch(
-  () => formData,
-  () => {
-    console.log(formData, "formDatas");
-  },
-  { immediate: true },
-);
-
 /**
  * 通过key数组在树形结构中查找对应的name数组
  * @param keys 要查找的节点id数组

+ 64 - 18
src/subPack/writeOff/index.vue

@@ -37,28 +37,71 @@
             <wd-checkbox
               :model-value="item.isinId"
               checked-color="#fdd143"
-              v-if="item.isinStatus == 1"
+              v-if="item.isinStatus == 0"
             >
             </wd-checkbox>
+            <wd-tag
+              v-if="item.appOrderProInfo.orderStatus == 6"
+              bg-color="rgba(0,0,0,0.1)"
+              color="rgba(0,0,0,0.3)"
+              >已退款</wd-tag
+            >
+            <wd-tag
+              v-if="item.appOrderProInfo.orderStatus == 2"
+              bg-color="rgba(0,0,0,0.1)"
+              color="rgba(0,0,0,0.3)"
+              >已使用</wd-tag
+            >
+            <wd-tag
+              v-if="item.appOrderProInfo.orderStatus == 3"
+              bg-color="rgba(0,0,0,0.1)"
+              color="rgba(0,0,0,0.3)"
+              >已到期</wd-tag
+            >
           </view>
         </template>
         <!-- 包场 -->
-        <view class="flex justify-between w-full" v-else>
-          <view>场次</view>
-          <view class="flex-1">
-            <view
-              class="flex items-center justify-between ml20rpx mb20rpx"
-              v-for="item in data.appOrderProInfoVerifyVOS"
-              :key="item.isinId"
-            >
-              <view class=""> 06-11(今天)16:17 | 羽毛球1 ¥59 </view>
-              <wd-checkbox
-                :model-value="item.isinId"
-                checked-color="#fdd143"
-              ></wd-checkbox>
+        <template v-else>
+          <view
+            class="flex justify-between items-center w-full mb20rpx"
+            v-for="item in data.appOrderProInfoVerifyVOS"
+            :key="item.id"
+          >
+            <view>场次</view>
+            <view class="flex-1">
+              <view class="flex items-center justify-between ml20rpx">
+                <view class="">
+                  {{ item.appOrderProInfo.productName }} ¥{{
+                    item.appOrderProInfo.price
+                  }}
+                </view>
+                <wd-tag
+                  v-if="item.appOrderProInfo.orderStatus == 6"
+                  bg-color="rgba(0,0,0,0.1)"
+                  color="rgba(0,0,0,0.3)"
+                  >已退款</wd-tag
+                >
+                <wd-tag
+                  v-if="item.appOrderProInfo.orderStatus == 2"
+                  bg-color="rgba(0,0,0,0.1)"
+                  color="rgba(0,0,0,0.3)"
+                  >已使用</wd-tag
+                >
+                <wd-tag
+                  v-if="item.appOrderProInfo.orderStatus == 3"
+                  bg-color="rgba(0,0,0,0.1)"
+                  color="rgba(0,0,0,0.3)"
+                  >已到期</wd-tag
+                >
+                <wd-checkbox
+                  :model-value="item.isinId"
+                  checked-color="#fdd143"
+                  v-if="item.isinStatus == 0"
+                ></wd-checkbox>
+              </view>
             </view>
           </view>
-        </view>
+        </template>
       </wd-checkbox-group>
 
       <wd-divider color="#F0F0F0"></wd-divider>
@@ -101,7 +144,6 @@ const { data, send: getOrder } = useRequest(
     }),
   {
     immediate: false,
-    middleware: createGlobalLoadingMiddleware({ loadingText: "加载中..." }),
   },
 );
 const { send: submit } = useRequest(
@@ -117,7 +159,7 @@ const { send: submit } = useRequest(
 const AvailableNumber = computed(
   //消费张数,几张可用
   () =>
-    data.value.appOrderProInfoVerifyVOS?.filter((it) => it.isinStatus == 1)
+    data.value.appOrderProInfoVerifyVOS?.filter((it) => it.isinStatus == 0)
       .length,
 );
 onLoad(async (query: any) => {
@@ -147,7 +189,11 @@ async function handleSubmit() {
 }
 </script>
 
-<style scoped></style>
+<style scoped>
+:deep(.wd-checkbox) {
+  margin-bottom: 0px !important;
+}
+</style>
 <route lang="json">
 {
   "name": "writeOff",

+ 55 - 21
src/subPack/writeOffDetaile/index.vue

@@ -6,55 +6,70 @@
       :key="item.id"
     >
       <!-- 固定场 -->
-      <template v-if="item.orderType >= GoodsType.noFixed">
+      <template v-if="item.orderType < GoodsType.noFixed">
         <view class="flex items-center justify-between">
           <view class="flex items-center">
             <view class="text-[rgb(0,0,0,0.3)] text-28rpx mr20rpx">场次</view>
-            <view>03-07(今天)16:00-17:00|羽毛球</view>
+            <view>{{ item.productName }} </view>
           </view>
-          <view class="text-#FB5B5B font-semibold">¥25.9</view>
+          <view class="text-#FB5B5B font-semibold"
+            >¥{{ item.originalPrice || 0 }}</view
+          >
         </view>
         <view class="mt24rpx w152rpx">
-          <commonbtn bg-color="rgba(0,0,0,0.1)">
-            <text class="text-[rgb(0,0,0,0.3)]">已核销</text>
-          </commonbtn>
+          <wd-tag
+            bg-color="rgba(0,0,0,0.1)"
+            custom-class="space"
+            color="rgba(0,0,0,0.3)"
+            >已核销</wd-tag
+          >
         </view>
       </template>
 
       <!-- 无固定场 -->
-      <view class="flex items-center justify-between">
+      <view class="flex items-center justify-between" v-else>
         <image
           :src="item.productImage"
           class="w160rpx h-160rpx rounded-32rpx min-w-160rpx mr20rpx"
+          mode="aspectFill"
         />
         <view class="flex-1">
           <view class="flex items-center justify-between">
-            <wd-text text="足球五人制闲时1..." :lines="1"></wd-text>
+            <wd-text :text="item.productName" :lines="1"></wd-text>
             <view class="w152rpx">
-              <commonbtn bg-color="rgba(0,0,0,0.1)">
-                <text class="text-[rgb(0,0,0,0.3)]">已核销</text>
-              </commonbtn>
+              <wd-tag
+                custom-class="space"
+                bg-color="rgba(0,0,0,0.1)"
+                color="rgba(0,0,0,0.3)"
+                >已核销</wd-tag
+              >
             </view>
           </view>
           <view class="text-32rpx mt20rpx font-semibold text-#FB5B5B"
-            >¥25.9</view
+            >¥{{ item.originalPrice || 0 }}</view
           >
         </view>
       </view>
       <wd-divider color="#F0F0F0"></wd-divider>
       <view class="flex items-center justify-between">
         <view class="text-[rgb(0,0,0,0.3)]">核验时间</view>
-        <view>2025-05-02 16:14:56</view>
+        <view>{{ item.useTime }}</view>
       </view>
       <view class="flex items-center justify-between mt20rpx">
         <view class="text-[rgb(0,0,0,0.3)]">使用人/券号</view>
-        <view>0017 1712 5994</view>
+        <view>{{ item.ticketNo }}</view>
       </view>
-      <view class="mt24rpx w152rpx">
-        <commonbtn bg-color="#FDD143" @click="handleRevoke">
+      <view
+        class="mt24rpx w152rpx"
+        v-if="item.isinStatus && dayjs().diff(dayjs(item.useTime), 'hour') < 1"
+      >
+        <commonbtn bg-color="#FDD143" @click="handleRevoke(item.id)">
           <text class="text-#000000">撤销</text>
         </commonbtn>
       </view>
+      <view class="mt24rpx w152rpx text-[rgb(0,0,0,0.3)]" v-else>
+        已过时效
+      </view>
     </view>
   </view>
   <wd-status-tip image="content" tip="暂无内容" v-else />
@@ -63,28 +78,47 @@
 <script setup lang="ts">
 import img1 from "@/subPack/static/hxmx.png";
 import { GoodsType } from "@/config";
-const { data } = usePagination(
+import dayjs from "dayjs";
+const { data, refresh } = usePagination(
   (pageNo, pageSize) =>
     Apis.app.pageAppIsinVerifyRecords({ params: { pageNo, pageSize } }),
   { initialData: [], data: (res) => res.records },
 );
+const { send } = useRequest(
+  (appIsinId) => Apis.app.repealVerifyRecord({ params: { appIsinId } }),
+  { immediate: false },
+);
 function handleSuccess() {
   uni.showToast({
     image: img1,
-    title: "核销成功",
+    title: "销成功",
   });
 }
-handleSuccess();
-function handleRevoke() {
+
+function handleRevoke(id: string) {
   uni.showModal({
     title: "确认操作",
     content: "确认要撤销吗?",
     cancelText: "不撤销",
+    success: async function (res) {
+      if (res.confirm) {
+        await send(id);
+        refresh();
+        setTimeout(() => {
+          handleSuccess();
+        }, 500);
+      }
+    },
   });
 }
 </script>
 
-<style scoped></style>
+<style scoped>
+:deep(.space) {
+  padding: 10rpx !important;
+  font-size: 24rpx !important;
+}
+</style>
 <route lang="json">
 {
   "name": "writeOffDetaile",