瀏覽代碼

feat: ✨ 拍照验课,相机水印

zhangtao 2 天之前
父節點
當前提交
94a30eefe8

+ 6 - 1
src/api/apiDefinitions.ts

@@ -24,6 +24,10 @@ Some useful links:
  * **Do not edit the file manually.**
  */
 export default {
+  "app.temporaryCourse": ["POST", "/app/course/temporaryCourse"],
+  "app.getFamilyMembersByName": ["GET", "/app/course/getFamilyMembersByName"],
+  "app.courseUploadImage": ["POST", "/app/course/courseUploadImage"],
+  "app.courseQueryUsers": ["POST", "/app/course/courseQueryUsers"],
   "app.queryKongfuZone": ["GET", "/app/user/queryKongfuZone"],
   "app.getMsgInfo": ["GET", "/app/home/getMsgInfo"],
   "app.getMsg": ["GET", "/app/home/getMsg"],
@@ -37,7 +41,7 @@ export default {
   "app.scanCodeVerification": ["PUT", "/app/course/scanCodeVerification"],
   "app.scanCodeQueryOrder": ["GET", "/app/course/scanCodeQueryOrder"],
   "app.queryMakeUpClassTable": ["GET", "/app/course/queryMakeUpClassTable"],
-  "app.getCourseInfo": ["GET", "//app/course/getCourseInfo/{courseId}"],
+  "app.getCourseInfo": ["GET", "/app/course/getCourseInfo/{courseId}"],
   "app.getPageCourse": ["GET", "/app/course/getPageCourse"],
   "app.appCategory": ["GET", "/app/appCategory/list"],
   "user.loginUser": ["POST", "/sys/loginApp"],
@@ -52,4 +56,5 @@ export default {
   "sys.userDel": ["delete", "/staff/staff/delete"],
   "sys.queryById": ["GET", "/staff/staff/queryById"],
   "sys.getCurrentUserDeparts": ["GET", "/sys/user/getCurrentUserDeparts"],
+  "sys.upload": ["POST", "/sys/common/upload"],
 };

+ 1 - 0
src/api/core/handlers.ts

@@ -95,6 +95,7 @@ export function handleAlovaError(error: any, method: Method) {
     uni.showToast({
       title: "服务器异常,请稍后再试",
       icon: "none",
+      duration: 2000,
     });
   }
 

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

@@ -31,6 +31,7 @@ export const alovaInstance = createAlova({
       // console.log(`[API Base URL] ${import.meta.env.VITE_API_BASE_URL}`);
       // console.log(`[Environment] ${import.meta.env.VITE_ENV_NAME}`);
     }
+    uni.showLoading({ mask: true });
   },
 
   // Response handlers
@@ -44,6 +45,7 @@ export const alovaInstance = createAlova({
     // 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

+ 231 - 0
src/api/globals.d.ts

@@ -30,6 +30,8 @@ import type {
 } from "alova";
 import type { $$userConfigMap, alovaInstance } from ".";
 import type apiDefinitions from "./apiDefinitions";
+import type { UniappUploadConfig } from "@alova/adapter-uniapp";
+import type { UploadingFileData } from "alova/client";
 
 type CollapsedAlova = typeof alovaInstance;
 type UserMethodConfigMap = typeof $$userConfigMap;
@@ -191,6 +193,16 @@ declare global {
       >(
         config: Config,
       ): Alova2Method<listData<deptart>, "sys.getCurrentUserDeparts", Config>;
+      upload<
+        Config extends Alova2MethodConfig<any> & {
+          data: {
+            name: string;
+            filePath: string;
+          };
+        },
+      >(
+        config: Config,
+      ): Alova2Method<any, "sys.upload", Config>;
     };
     app: {
       appCategory<Config extends Alova2MethodConfig<any>>(): Alova2Method<
@@ -328,11 +340,230 @@ declare global {
       queryKongfuZone<Config extends Alova2MethodConfig<sysRole[]> & {}>(
         config: Config,
       ): Alova2Method<sysRole[], "app.queryKongfuZone", Config>;
+      courseQueryUsers<
+        Config extends Alova2MethodConfig<AppCoursesVerificationRecord[]> & {
+          data: {
+            coursePriceRulesId: string;
+            verifyStatus: number;
+            orPostpone: number;
+          };
+        },
+      >(
+        config: Config,
+      ): Alova2Method<
+        AppCoursesVerificationRecord[],
+        "app.courseQueryUsers",
+        Config
+      >;
+      courseUploadImage<
+        Config extends Alova2MethodConfig<any> & {
+          data: {
+            id: string;
+            verifyImage: string;
+          }[];
+        },
+      >(
+        config: Config,
+      ): Alova2Method<any, "app.courseUploadImage", Config>;
+      getFamilyMembersByName<
+        Config extends Alova2MethodConfig<FamilyMembers[]> & {
+          params: {
+            name: string;
+          };
+        },
+      >(
+        config: Config,
+      ): Alova2Method<FamilyMembers[], "app.getFamilyMembersByName", Config>;
+      temporaryCourse<
+        Config extends Alova2MethodConfig<any> & {
+          data: {
+            coursePriceRulesId: string;
+            userIds: string[];
+          };
+        },
+      >(
+        config: Config,
+      ): Alova2Method<any, "app.temporaryCourse", Config>;
     };
   }
 
   var Apis: Apis;
 }
+
+/**
+ * FamilyMembers
+ */
+export interface FamilyMembers {
+  /**
+   * 创建时间
+   */
+  createTime?: string;
+  /**
+   * 删除标志;删除状态(0-正常,1-已删除)
+   */
+  delFlag?: number;
+  /**
+   * 姓名
+   */
+  fullName?: string;
+  /**
+   * 主键
+   */
+  id: string;
+  /**
+   * 身份证反面照
+   */
+  idCardBackImg?: string;
+  /**
+   * 身份证正面照
+   */
+  idCardFrontImg?: string;
+  /**
+   * 身份证号
+   */
+  identityCard?: string;
+  /**
+   * 手机号
+   */
+  phone?: string;
+  psnId?: string;
+  /**
+   * 身份证反面照
+   * 实名认证照片
+   */
+  realNameImg?: string;
+  /**
+   * 实名状态 0-未实名 ;1-已实名
+   */
+  realNameStatus?: number;
+  /**
+   * 修改时间
+   */
+  updateTime?: string;
+  /**
+   * 用户编号
+   */
+  userId?: string;
+  /**
+   * 实名状态 0-未实名 ;1-已实名
+   * 用户类型0-本人;1-家人
+   */
+  userType?: number;
+  [property: string]: any;
+}
+
+/**
+ * AppCoursesVerificationRecord
+ */
+export interface AppCoursesVerificationRecord {
+  /**
+   * 上课时间
+   */
+  coursesEndTime?: string;
+  /**
+   * 课程ID
+   */
+  coursesId?: string;
+  /**
+   * 课时名称
+   */
+  coursesName?: string;
+  /**
+   * 课程小节ID
+   */
+  coursesPriceRuleId?: string;
+  /**
+   * 上课时间
+   */
+  coursesStartTime?: string;
+  /**
+   * 课程类型(0-正常课 1-补课)
+   */
+  coursesType?: number;
+  /**
+   * 创建人;创建人
+   */
+  createBy?: string;
+  /**
+   * 创建时间;创建时间
+   */
+  createTime?: string;
+  /**
+   * 删除标志;删除状态(0-正常,1-已删除)
+   */
+  delFlag?: number;
+  /**
+   * id
+   */
+  id?: string;
+  /**
+   * 订单编号
+   */
+  orderCode?: string;
+  /**
+   * 订单id
+   */
+  orderId?: string;
+  /**
+   * id
+   * 部门编号
+   */
+  orgCode?: string;
+  /**
+   * 是否延课(0-未延课 1-已延课)
+   */
+  orPostpone?: number;
+  /**
+   * 延课原因
+   */
+  postponeReason?: string;
+  /**
+   * 系统状态;状态(0-正常,1-冻结)
+   */
+  status?: number;
+  /**
+   * 更新人;更新人
+   */
+  updateBy?: string;
+  /**
+   * 更新时间;更新时间
+   */
+  updateTime?: string;
+  /**
+   * 使用人ID
+   */
+  useUserId?: string;
+  /**
+   * 使用人人脸照片
+   */
+  useUserImage?: string;
+  /**
+   * 使用人名称
+   */
+  useUserName?: string;
+  /**
+   * 核验照片
+   */
+  verifyImage?: string;
+  /**
+   * 核销状态(0-未核销 1-已核销)
+   */
+  verifyStatus?: number;
+  /**
+   * 核验时间
+   */
+  verifyTime?: string;
+  /**
+   * 核验人ID
+   */
+  verifyUserId?: string;
+  /**
+   * 核验人名称
+   */
+  verifyUserName?: string;
+  [property: string]: any;
+}
+
 export interface sysMsg {
   id: string;
   titile: string;

+ 31 - 0
src/api/index.ts

@@ -1,4 +1,5 @@
 // Import the core alova instance
+import { BASE_URL } from "@/config";
 import alovaInstance from "./core/instance";
 
 // Export the global Apis object from the generated code
@@ -16,3 +17,33 @@ const Apis = createApis(alovaInstance, $$userConfigMap);
 // Export both default and named export for AutoImport
 export default Apis;
 export { Apis };
+type Data<T> = {
+  code: number;
+  message: string;
+  result: T;
+};
+export function uploadFile<T>(options: UniApp.UploadFileOption) {
+  uni.showLoading({ title: "上传中...", mask: true });
+  return new Promise<Data<T>>((resolve, reject) => {
+    uni.uploadFile({
+      url: BASE_URL + options.url,
+      filePath: options.filePath,
+      name: "file",
+      method: "POST",
+      header: {
+        "Content-Type": "multipart/form-data",
+        "X-Access-Token": useUserStore().token,
+      },
+      success(res) {
+        uni.hideLoading();
+        resolve(JSON.parse(res.data));
+      },
+
+      fail(err) {
+        uni.hideLoading();
+        reject(err);
+        uni.showToast({ title: err.errMsg, icon: "none" });
+      },
+    });
+  });
+}

+ 7 - 1
src/auto-imports.d.ts

@@ -9,7 +9,7 @@ declare global {
   const Apis: typeof import('./api/index')['Apis']
   const CommonUtil: typeof import('wot-design-uni')['CommonUtil']
   const EffectScope: typeof import('vue')['EffectScope']
-  const UserCourseStore: typeof import('./store/course')['UserCourseStore']
+  const UserCourseStore: (typeof import("./store/course"))["UserCourseStore"]
   const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
   const alovaInstance: typeof import('./api/index')['alovaInstance']
   const api: typeof import('./api/index')['default']
@@ -154,6 +154,7 @@ declare global {
   const unref: typeof import('vue')['unref']
   const unrefElement: typeof import('@vueuse/core')['unrefElement']
   const until: typeof import('@vueuse/core')['until']
+  const uploadFile: typeof import('./api/index')['uploadFile']
   const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
   const useAnimate: typeof import('@vueuse/core')['useAnimate']
   const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
@@ -178,6 +179,7 @@ declare global {
   const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
   const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
   const useCached: typeof import('@vueuse/core')['useCached']
+  const useCameraStore: typeof import('./subPack/store/camera')['useCameraStore']
   const useClipboard: typeof import('@vueuse/core')['useClipboard']
   const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems']
   const useCloned: typeof import('@vueuse/core')['useCloned']
@@ -311,6 +313,7 @@ declare global {
   const useToast: typeof import('wot-design-uni')['useToast']
   const useToggle: typeof import('@vueuse/core')['useToggle']
   const useTransition: typeof import('@vueuse/core')['useTransition']
+  const useUploader: typeof import('alova/client')['useUploader']
   const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
   const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
   const useUserStore: typeof import('./store/user')['useUserStore']
@@ -504,6 +507,7 @@ declare module 'vue' {
     readonly unref: UnwrapRef<typeof import('vue')['unref']>
     readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
     readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
+    readonly uploadFile: UnwrapRef<typeof import('./api/index')['uploadFile']>
     readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
     readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
     readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
@@ -528,6 +532,7 @@ declare module 'vue' {
     readonly useBroadcastChannel: UnwrapRef<typeof import('@vueuse/core')['useBroadcastChannel']>
     readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
     readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
+    readonly useCameraStore: UnwrapRef<typeof import('./subPack/store/camera')['useCameraStore']>
     readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
     readonly useClipboardItems: UnwrapRef<typeof import('@vueuse/core')['useClipboardItems']>
     readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
@@ -661,6 +666,7 @@ declare module 'vue' {
     readonly useToast: UnwrapRef<typeof import('wot-design-uni')['useToast']>
     readonly useToggle: UnwrapRef<typeof import('@vueuse/core')['useToggle']>
     readonly useTransition: UnwrapRef<typeof import('@vueuse/core')['useTransition']>
+    readonly useUploader: UnwrapRef<typeof import('alova/client')['useUploader']>
     readonly useUrlSearchParams: UnwrapRef<typeof import('@vueuse/core')['useUrlSearchParams']>
     readonly useUserMedia: UnwrapRef<typeof import('@vueuse/core')['useUserMedia']>
     readonly useUserStore: UnwrapRef<typeof import('./store/user')['useUserStore']>

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

@@ -3,8 +3,8 @@
     <view class="flex items-center">
       <view class="w-8rpx h-28rpx bg-#0074FF"></view>
       <view class="ml20rpx text-28rpx">
-        {{ dayjs(item?.startTime).format("MM-DD hh:ss") }}
-        {{ dayjs(item?.endTime).format("hh:ss") }}
+        {{ dayjs(item?.startTime).format("MM-DD HH:mm") }}
+        {{ dayjs(item?.endTime).format("HH:mm") }}
       </view>
     </view>
     <view class="mt20rpx flex items-center justify-between pl20rpx">
@@ -12,13 +12,13 @@
       <wd-text :text="item.name" size="28rpx" :lines="1"></wd-text>
       <commonbtn
         bg-color="#0074FF"
-        @click="handleGoPath('/subPack/selectClass/index')"
+        @click="handleGoPath(`/subPack/selectClass/index?id=${item.id}`)"
         v-if="type == 0 && showBtn"
         >拍照核销</commonbtn
       >
       <commonbtn
         bg-color="#0074FF"
-        @click="handleGoPath('/subPack/ReservationClass/index')"
+        @click="handleGoPath(`/subPack/ReservationClass/index?id=${item.id}`)"
         v-if="type == 1 && showBtn"
         >预约这节</commonbtn
       ><commonbtn
@@ -38,7 +38,7 @@
     </view>
     <view
       class="mt20rpx pl20rpx flex items-center text-24rpx"
-      @click="handleGoPath('/subPack/PersonnelView/index')"
+      @click="handleGoPath(`/subPack/PersonnelView/index?id=${item.id}`)"
     >
       <view class="text-[rgb(0,0,0,0.3)] mr20rpx"
         >共{{ item?.totalNum }}人</view

+ 5 - 5
src/config/index.ts

@@ -3,8 +3,8 @@ const mapEnvVersion = {
    * 	开发版
    */
   // develop: "http://192.168.1.166:8080/jeecg-boot",
-  develop: "http://192.168.1.34:8080/jeecg-boot",
-  // develop: "http://192.168.0.11:8080/jeecg-boot",
+  // develop: "http://192.168.1.34:8080/jeecg-boot",
+  develop: "http://192.168.0.11:8080/jeecg-boot",
   /**
    * 	体验版
    */
@@ -22,10 +22,10 @@ export const BASE_URL =
   mapEnvVersion[uni.getAccountInfoSync().miniProgram.envVersion];
 
 // export const BASE_UPLOADURL = "http://192.168.0.11:8080/jeecg-boot/upload";
-export const BASE_UPLOADURL =
-  "http://192.168.1.166:8080/jeecg-boot/sys/common/upload";
 // export const BASE_UPLOADURL =
-//   "http://192.168.0.11:8080/jeecg-boot/sys/common/upload";
+// "http://192.168.1.166:8080/jeecg-boot/sys/common/upload";
+export const BASE_UPLOADURL =
+  "http://192.168.0.11:8080/jeecg-boot/sys/common/upload";
 
 //1-学校 2-包场 3-无固定场 4-个人赛 5-团队赛 6-课程 7-保险
 export enum GoodsType {

+ 11 - 1
src/pages.json

@@ -65,6 +65,16 @@
     {
       "root": "subPack",
       "pages": [
+        {
+          "path": "Camera/index",
+          "type": "page",
+          "name": "Camera",
+          "style": {
+            "navigationBarTitleText": "",
+            "navigationStyle": "custom",
+            "disableScroll": true
+          }
+        },
         {
           "path": "classInspection/index",
           "type": "page",
@@ -164,7 +174,7 @@
           "type": "page",
           "name": "selectClass",
           "style": {
-            "navigationBarTitleText": "选择课程"
+            "navigationBarTitleText": ""
           }
         },
         {

+ 434 - 0
src/subPack/Camera/index.vue

@@ -0,0 +1,434 @@
+<template>
+  <view class="w-full h-screen relative">
+    <camera
+      :device-position="position"
+      resolution="high"
+      :flash="flashMode"
+      frame-size="large"
+      @initdone="initdone"
+      @ready="ready"
+      class="w-full h-85%"
+    ></camera>
+    <view class="absolute left-20rpx bottom-25% text-white">
+      <view class="mb24rpx max-w-180rpx">
+        <view
+          class="font-semibold bg-white h45rpx rounded-8rpx p8rpx flex items-center"
+          ><view class="bg-amber p-4rpx text-black rounded-8rpx">打卡</view>
+          <view class="time-text ml8rpx">{{ dayjs().format("HH:mm") }}</view>
+        </view>
+      </view>
+      <view class="px8rpx border-l border-l-solid border-l-amber">
+        <view class=""
+          >贵州省贵阳市观山湖区世纪城街道北京西路8号观山湖区办公大楼13层观山湖区文体广电旅游局</view
+        >
+        <view class="mt2">
+          {{ dayjs().format("YYYY.MM.DD") }} {{ dayjs().format("dddd") }}
+        </view>
+      </view>
+    </view>
+    <canvas
+      id="watermarkCanvas"
+      canvas-id="watermarkCanvas"
+      style="position: fixed; top: -10000px"
+      :style="{
+        width: cavansInfo?.width + 'px',
+        height: cavansInfo?.height + 'px',
+      }"
+    ></canvas>
+    <view
+      class="bg-black h15% bottom-safe-area px120rpx box-border pb20rpx flex items-center w-full justify-between w-full"
+    >
+      <view @click="changeFlashMode">
+        <image
+          src="@/subPack/static/close.png"
+          class="w64rpx h64rpx"
+          v-if="flashMode == 'off'"
+        />
+        <image src="@/subPack/static/open.png" class="w64rpx h64rpx" v-else />
+      </view>
+      <view @click="takePhoto">
+        <image src="@/subPack/static/pic.png" class="w128rpx h128rpx" />
+      </view>
+      <view @click="changePosition">
+        <image src="@/subPack/static/back.png" class="w64rpx h64rpx" />
+      </view>
+    </view>
+  </view>
+  <page-container
+    :show="uploading"
+    :duration="false"
+    :overlay="false"
+    @beforeleave="beforeleave"
+  >
+    <wd-message-box></wd-message-box>
+  </page-container>
+</template>
+
+<script setup lang="ts">
+import { BASE_URL } from "@/config";
+import router from "@/router";
+import type { CameraDevicePosition, CameraFlash } from "@uni-helper/uni-types";
+import "dayjs/locale/zh-cn";
+import dayjs from "dayjs";
+const flashMode = ref<CameraFlash>("off");
+const position = ref<CameraDevicePosition>("back");
+dayjs.locale("zh-cn");
+const cavansInfo = ref<UniApp.GetImageInfoSuccessData>();
+const isShow = ref(false);
+const messageBox = useMessage();
+const FilePath = ref();
+const { send, abort, uploading, data } = useRequest(
+  (FormData) =>
+    Apis.sys.upload({
+      data: FormData,
+      requestType: "upload",
+      fileType: "image",
+    }),
+  {
+    immediate: false,
+  },
+);
+async function initdone() {
+  console.log("相机初始化完成!!!");
+}
+function ready() {
+  console.log("相机初始化成功!!!");
+}
+function changeFlashMode() {
+  flashMode.value = flashMode.value == "off" ? "on" : "off";
+}
+function changePosition() {
+  position.value = position.value == "back" ? "front" : "back";
+}
+function takePhoto() {
+  const ctx = wx.createCameraContext();
+  isShow.value = true;
+  uni.showLoading({ mask: true });
+  ctx.takePhoto({
+    quality: "high",
+    success: async (res) => {
+      if (res.tempImagePath) {
+        const img = await addWatermarkToImage(res.tempImagePath);
+        FilePath.value = img;
+        await send({ filePath: img, name: "file" });
+        console.log(data.value);
+
+        // const path = `${BASE_URL}/sys/common/static/${resData.message}`;
+        // console.log(path, "上传文件");
+        // useCameraStore().setImg(path);
+        // isShow.value = false;
+        // router.back();
+        // uni.hideLoading();
+      }
+    },
+    fail(err) {
+      isShow.value = false;
+      uni.hideLoading();
+    },
+  });
+}
+
+function beforeleave() {
+  messageBox
+    .confirm({
+      title: "正在上传图片,确认退出吗?",
+      cancelButtonText: "退出",
+      confirmButtonText: "继续上传",
+      closeOnClickModal: false,
+    })
+    .then(async () => {
+      console.log("点击了确定按钮");
+    })
+    .catch(() => {
+      abort();
+    });
+}
+function addWatermarkToImage(imagePath: string): Promise<string> {
+  return new Promise((resolve, reject) => {
+    const ctx = uni.createCanvasContext("watermarkCanvas");
+    console.log("addWatermarkToImage", imagePath);
+
+    uni.getImageInfo({
+      src: imagePath,
+      success: (imageInfo) => {
+        const canvasWidth = imageInfo.width;
+        const canvasHeight = imageInfo.height;
+        cavansInfo.value = imageInfo;
+        ctx.drawImage(imagePath, 0, 0, canvasWidth, canvasHeight);
+        // 字体大小统一为45px
+        const fontSize = 45;
+        const lineHeight = fontSize * 1.2;
+        const padding = 15;
+        const cornerRadius = 10;
+
+        // 计算水印总高度
+        const watermarkHeight = lineHeight * 6 + padding * 3;
+
+        // 计算文本宽度
+        ctx.setFontSize(fontSize);
+        const timeText = dayjs().format("HH:mm");
+        const checkInText = "打卡";
+        const addressText =
+          "贵州省贵阳市观山湖区世纪城街道北京西路8号观山湖区办公大楼13层观山湖区文体广电旅游局";
+        const dateText = `${dayjs().format("YYYY.MM.DD")} ${dayjs().format("dddd")}`;
+        const timeWidth = ctx.measureText(timeText).width;
+        const checkInWidth = ctx.measureText(checkInText).width + padding * 4; // 增加padding确保文字不溢出
+        const firstLineWidth = checkInWidth + timeWidth + padding * 2;
+
+        // 水印位置:左下角,留出边距
+        const margin = 30;
+        const startX = margin;
+        const startY = canvasHeight - watermarkHeight - margin;
+
+        // 1. 绘制第一行白色背景圆角矩形
+        ctx.setFillStyle("#ffffff");
+        ctx.beginPath();
+        ctx.moveTo(startX + cornerRadius, startY);
+        ctx.arcTo(
+          startX + firstLineWidth,
+          startY,
+          startX + firstLineWidth,
+          startY + lineHeight + padding * 2,
+          cornerRadius,
+        );
+        ctx.arcTo(
+          startX + firstLineWidth,
+          startY + lineHeight + padding * 2,
+          startX,
+          startY + lineHeight + padding * 2,
+          cornerRadius,
+        );
+        ctx.arcTo(
+          startX,
+          startY + lineHeight + padding * 2,
+          startX,
+          startY,
+          cornerRadius,
+        );
+        ctx.arcTo(
+          startX,
+          startY,
+          startX + firstLineWidth,
+          startY,
+          cornerRadius,
+        );
+        ctx.closePath();
+        ctx.fill();
+
+        // 2. 绘制"打卡"黄色背景圆角矩形
+        const checkInY = startY + padding;
+        ctx.setFillStyle("#ffcc00");
+        ctx.beginPath();
+        ctx.moveTo(startX + padding + cornerRadius, checkInY);
+        ctx.arcTo(
+          startX + checkInWidth - padding,
+          checkInY,
+          startX + checkInWidth - padding,
+          checkInY + lineHeight,
+          cornerRadius,
+        );
+        ctx.arcTo(
+          startX + checkInWidth - padding,
+          checkInY + lineHeight,
+          startX + padding,
+          checkInY + lineHeight,
+          cornerRadius,
+        );
+        ctx.arcTo(
+          startX + padding,
+          checkInY + lineHeight,
+          startX + padding,
+          checkInY,
+          cornerRadius,
+        );
+        ctx.arcTo(
+          startX + padding,
+          checkInY,
+          startX + checkInWidth - padding,
+          checkInY,
+          cornerRadius,
+        );
+        ctx.closePath();
+        ctx.fill();
+
+        // 3. 绘制"打卡"文字
+        ctx.setFillStyle("#000000");
+        ctx.setFontSize(fontSize);
+        ctx.font = `bold ${fontSize}px sans-serif`; // 设置字体加粗
+        ctx.fillText(
+          checkInText,
+          startX + padding * 2,
+          checkInY + lineHeight - 5,
+        );
+
+        // 4. 绘制渐变时间文字
+        const timeX = startX + checkInWidth + padding;
+        const timeY = checkInY + lineHeight - 5;
+
+        // 创建渐变
+        const gradient = ctx.createLinearGradient(
+          0,
+          timeY,
+          0,
+          timeY + lineHeight,
+        );
+        gradient.addColorStop(0, "#ff6b00"); // 顶部颜色
+        gradient.addColorStop(1, "#ff3c00"); // 底部颜色
+
+        ctx.setFillStyle(gradient);
+        ctx.font = `bold ${fontSize}px sans-serif`; // 设置字体加粗
+        ctx.fillText(timeText, timeX, timeY);
+        // 5. 绘制地址信息(带左边框,支持自动换行)
+        const maxWidth = canvasWidth - startX * 2 - padding * 6; // 最大宽度
+        const addressStartY = startY + lineHeight + padding * 3 + 60;
+
+        // 绘制地址文本(支持换行)
+        function drawTextWithWrap(
+          text: string,
+          x: number,
+          y: number,
+          maxWidth: number,
+          lineHeight: number,
+        ) {
+          let line = "";
+          let currentY = y;
+          for (let i = 0; i < text.length; i++) {
+            const testLine = line + text[i];
+            const metrics = ctx.measureText(testLine);
+            const testWidth = metrics.width;
+
+            if (testWidth > maxWidth && i > 0) {
+              // 绘制当前行并换行
+              ctx.fillText(line, x, currentY);
+              line = text[i];
+              currentY += lineHeight;
+            } else {
+              line = testLine;
+            }
+          }
+          // 绘制最后一行
+          ctx.fillText(line, x, currentY);
+          return currentY; // 返回最后一行的Y坐标
+        }
+
+        // 绘制地址左边框(支持多行)
+        ctx.setStrokeStyle("#ffcc00");
+        ctx.setLineWidth(6);
+
+        // 先测量地址文本需要多少行
+        const tempCtx = uni.createCanvasContext("watermarkCanvas");
+        tempCtx.setFontSize(fontSize);
+        let addressLines = [];
+        let addressLine = "";
+        for (let i = 0; i < addressText.length; i++) {
+          const testLine = addressLine + addressText[i];
+          const metrics = tempCtx.measureText(testLine);
+          const testWidth = metrics.width;
+
+          if (testWidth > maxWidth && i > 0) {
+            addressLines.push(addressLine);
+            addressLine = addressText[i];
+          } else {
+            addressLine = testLine;
+          }
+        }
+        addressLines.push(addressLine);
+
+        // 绘制地址左边框(每行一个)
+        for (let i = 0; i < addressLines.length; i++) {
+          const lineY = addressStartY + i * lineHeight;
+          ctx.beginPath();
+          ctx.moveTo(startX + padding, lineY - lineHeight + 5);
+          ctx.lineTo(startX + padding, lineY + 5);
+          ctx.stroke();
+        }
+
+        ctx.setFillStyle("#ffffff");
+        // 绘制地址文本(自动换行)
+        const lastAddressLineY = drawTextWithWrap(
+          addressText,
+          startX + padding * 3,
+          addressStartY,
+          maxWidth,
+          lineHeight,
+        );
+
+        // 6. 绘制日期信息(带左边框)
+        const dateY = lastAddressLineY + lineHeight;
+        // 绘制黄色左边框
+        ctx.setStrokeStyle("#ffcc00");
+        ctx.setLineWidth(6);
+        ctx.beginPath();
+        ctx.moveTo(startX + padding, dateY - lineHeight + 5);
+        ctx.lineTo(startX + padding, dateY + 5);
+        ctx.stroke();
+
+        ctx.setFillStyle("#ffffff");
+        // 增加与左边框的距离
+        ctx.fillText(dateText, startX + padding * 3, dateY);
+        ctx.draw(false, () => {
+          setTimeout(() => {
+            uni.canvasToTempFilePath({
+              canvasId: "watermarkCanvas",
+              x: 0,
+              y: 0,
+              width: canvasWidth,
+              height: canvasHeight,
+              destWidth: canvasWidth,
+              destHeight: canvasHeight,
+              quality: 1,
+              success: (res) => {
+                console.log("水印图片生成成功:", res.tempFilePath);
+                resolve(res.tempFilePath);
+              },
+              fail: (err) => {
+                console.error("导出水印图片失败:", err);
+                reject(err);
+              },
+            });
+          }, 100);
+        });
+      },
+      fail: (err) => {
+        console.error("获取图片信息失败:", err);
+        reject(err);
+      },
+    });
+  });
+}
+
+function getArryBuffer(filePath: string) {
+  return new Promise<ArrayBuffer>((resolve, reject) => {
+    uni.getFileSystemManager().readFile({
+      filePath: filePath,
+      encoding: "binary",
+      success(res) {
+        resolve(res.data as ArrayBuffer);
+      },
+      fail(err) {
+        reject(err);
+      },
+    });
+  });
+}
+</script>
+
+<style scoped>
+.time-text {
+  background: linear-gradient(to bottom, #02459f, #000);
+  -webkit-background-clip: text;
+  -webkit-text-fill-color: transparent;
+  background-clip: text;
+  text-fill-color: transparent;
+}
+</style>
+<route lang="json">
+{
+  "name": "Camera",
+  "style": {
+    "navigationBarTitleText": "",
+    "navigationStyle": "custom",
+    "disableScroll": true
+  }
+}
+</route>

+ 45 - 0
src/subPack/EmployeeListAdd/components/CustomUpload/index.vue

@@ -0,0 +1,45 @@
+<template>
+  <view
+    @click="handleClick"
+    class="bg-#F6F6F6 w120rpx h120rpx flex items-center justify-center rounded-32rpx"
+    v-if="!imgUrl"
+  >
+    <image src="@/subPack/static/upload.png" class="w40rpx h40rpx" />
+  </view>
+  <view class="w120rpx h120rpx relative" v-else>
+    <view class="absolute -left-20rpx -top-10rpx z10" @click="deleteImg">
+      <wd-icon name="error-fill" size="22px"></wd-icon>
+    </view>
+    <image
+      :src="imgUrl"
+      class="w120rpx h120rpx rounded-32rpx"
+      @click="handleImgClick"
+    />
+  </view>
+</template>
+
+<script setup lang="ts">
+const props = defineProps<{
+  disabled: boolean;
+  imgUrl?: string;
+  id?: string;
+}>();
+const emit = defineEmits(["click"]);
+const { dataId, img } = storeToRefs(useCameraStore());
+function handleClick() {
+  if (props.disabled) return;
+  console.log("click");
+  emit("click");
+}
+function deleteImg() {
+  dataId.value = String(props.id);
+  img.value = "";
+}
+function handleImgClick() {
+  uni.previewImage({
+    urls: [img.value],
+  });
+}
+</script>
+
+<style scoped></style>

+ 4 - 1
src/subPack/EmployeeListAdd/components/upload/index.vue

@@ -10,12 +10,15 @@
     }"
     @oversize="handleOversize"
     @success="handleSuccess"
-  ></wd-upload>
+  >
+    <upload :disabled="true"></upload>
+  </wd-upload>
   <view v-if="tip" class="text-[rgba(0,0,0,0.30)] text-24rpx">{{ tip }}</view>
 </template>
 
 <script setup lang="ts">
 import { BASE_UPLOADURL, BASE_URL } from "@/config";
+import upload from "../CustomUpload/index.vue";
 import {
   uploadProps,
   type UploadOversizeEvent,

+ 9 - 7
src/subPack/Makeupclass/index.vue

@@ -148,11 +148,11 @@ const isAll = ref(true);
 const isActiveItem = ref([0]);
 const makeupClassList = ref<
   {
-    startTime: string | number | null;
-    endTime: string | number | null;
-    name: string | null;
+    startTime: string | number;
+    endTime: string | number;
+    name: string;
   }[]
->([{ startTime: null, endTime: null, name: null }]);
+>([{ startTime: "", endTime: "", name: "" }]);
 const { courseItem } = storeToRefs(useCourseStore());
 
 const { data, send: getData } = useRequest(
@@ -189,7 +189,11 @@ watch(
   },
 );
 function handleAddClass() {
-  makeupClassList.value.push({ startTime: null, endTime: null, name: null });
+  makeupClassList.value.push({
+    startTime: "",
+    endTime: "",
+    name: "",
+  });
   isActiveItem.value.push(makeupClassList.value.length - 1);
 }
 function handleItemDown(idx: number) {
@@ -223,8 +227,6 @@ async function save() {
   const form: webMakeClass[] = JSON.parse(
     JSON.stringify(makeupClassList.value),
   );
-  console.log(form);
-
   if (!areAllItemsAllFieldsFilled(form))
     return uni.showToast({
       title: "请填写完整",

+ 31 - 9
src/subPack/PersonnelView/index.vue

@@ -1,17 +1,17 @@
 <template>
   <view>
-    <wd-tabs v-model="tab" swipeable sticky>
+    <wd-tabs v-model="tab" swipeable sticky @change="handleChange">
       <block v-for="item in tabList" :key="item.id">
         <wd-tab :title="item.title">
           <view class="bg-#F6F6F6 px32rpx pt-24rpx bottom-safe-area">
             <view
               class="p24rpx box-border bg-white mb24rpx rounded-32rpx"
-              v-for="it in 20"
+              v-for="it in data"
             >
               <view class="flex items-center justify-between">
-                <view class="font-semibold text-22rpx text-#222222"
-                  >杨锦新</view
-                >
+                <view class="font-semibold text-22rpx text-#222222">{{
+                  it.useUserName
+                }}</view>
                 <commonbtn bg-color="#0074FF">未核销</commonbtn>
               </view>
               <view
@@ -42,11 +42,33 @@
 <script setup lang="ts">
 const tab = ref(0);
 const tabList = ref([
-  { title: "全部", id: 0 },
-  { title: "延课(2)", id: 1 },
-  { title: "已核销(12)", id: 2 },
-  { title: "未核销(12)", id: 3 },
+  { title: "全部" },
+  { title: "延课(2)" },
+  { title: "已核销(12)" },
+  { title: "未核销(12)" },
 ]);
+const coursePriceRulesId = ref();
+onLoad(async (query: any) => {
+  coursePriceRulesId.value = query.id;
+  await getList({ coursePriceRulesId: query.id });
+});
+const { data, send: getList } = useRequest(
+  (data) =>
+    Apis.app.courseQueryUsers({
+      data,
+    }),
+  { immediate: false },
+);
+function handleChange(e: { index: number }) {
+  console.log(e, "sadasdad");
+  const { index } = e;
+
+  getList({
+    coursePriceRulesId: coursePriceRulesId.value,
+    verifyStatus: index == 2 ? 1 : index == 3 ? 0 : null,
+    orPostpone: index != 1 ? null : index,
+  });
+}
 onReachBottom(() => {
   console.log("页面触底");
 });

+ 82 - 66
src/subPack/ReservationClass/index.vue

@@ -1,92 +1,108 @@
 <template>
   <view class="px32rpx py-20rpx">
-    <view class="bg-white px24rpx py28rpx rounded-32rpx box-border">
-      <view class="text-32rpx font-semibold">选择约课学生</view>
-      <view class="mt28rpx">
-        <view
-          v-if="selectedTags.length"
-          class="flex flex-wrap gap-10rpx mb20rpx"
-        >
+    <view class="bg-white rounded-32rpx py28rpx">
+      <view class="px24rpx box-border">
+        <view class="text-32rpx font-semibold">选择约课学生</view>
+        <view class="mt28rpx">
           <view
-            v-for="name in selectedTags"
-            :key="name"
-            class="bg-[rgba(0,0,0,0.1)] text-[rgb(0,0,0,0.9)] text-24rpx px20rpx py10rpx rounded-full flex items-center cursor-pointer"
-            @click="removeTag(name)"
+            v-if="selectedTags.length"
+            class="flex flex-wrap gap-10rpx mb20rpx"
           >
-            {{ name }}
-            <text class="ml10rpx font-bold">×</text>
+            <view
+              v-for="item in selectedTags"
+              :key="item.id"
+              class="bg-[rgba(0,0,0,0.1)] text-[rgb(0,0,0,0.9)] text-24rpx px20rpx py10rpx rounded-full flex items-center cursor-pointer"
+              @click="removeTag(item)"
+            >
+              {{ item.fullName }}
+              <text class="ml10rpx font-bold">×</text>
+            </view>
+          </view>
+          <view class="flex items-center mt28rpx">
+            <view class="text-28rpx">学生姓名:</view>
+            <wd-input
+              v-model="inputValue"
+              type="text"
+              placeholder="请输入姓名"
+              no-border
+              @focus="inputFocus"
+            />
           </view>
         </view>
-        <view class="flex items-center mt28rpx">
-          <view class="text-28rpx">学生姓名:</view>
-          <wd-input
-            v-model="inputValue"
-            type="text"
-            placeholder="请输入姓名"
-            no-border
-            @focus="handleFocus"
-            @blur="handleBlur"
-          />
+      </view>
+      <view
+        v-if="data.length"
+        class="mt10rpx py10rpx rounded-xl px24rpx"
+        v-for="item in data"
+        @click="selectStudent(item)"
+        :key="item.id"
+        hover-class="hover:bg-gray-100"
+      >
+        <view class="text-28rpx">
+          {{ item.fullName }}
         </view>
-        <view
-          v-if="isFocused && filteredList.length"
-          class="mt10rpx hover:bg-gray-100 py10rpx rounded-xl"
-          v-for="name in filteredList"
-          @click="selectStudent(name)"
-        >
-          <view :key="name" class="text-28rpx">
-            {{ name }}
-          </view>
-          <view class="mt12rpx text-[rgb(0,0,0,0.3)] text-24rpx"
-            >159****6549</view
-          >
+        <view class="mt12rpx text-[rgb(0,0,0,0.3)] text-24rpx"
+          >{{ item.phone }}
         </view>
       </view>
     </view>
   </view>
-  <fixdbtn block size="large">提交</fixdbtn>
+  <fixdbtn block size="large" @click="handleSubmit">提交</fixdbtn>
 </template>
 
 <script setup lang="ts">
-import { ref, computed } from "vue";
-
+import type { FamilyMembers } from "@/api/globals";
+import router from "@/router";
+import { useWatcher } from "alova/client";
 const inputValue = ref("");
-const selectedTags = ref<string[]>([]);
-const isFocused = ref(false);
-
-// 模拟学生列表
-const studentList = ["张三", "李四", "王五", "赵六", "孙七", "李"];
-
-// 过滤出匹配的学生
-const filteredList = computed(() => {
-  if (!inputValue.value) return [];
-  return studentList.filter((name) => name.includes(inputValue.value));
+const selectedTags = ref<FamilyMembers[]>([]);
+const { data } = useWatcher(
+  () => Apis.app.getFamilyMembersByName({ params: { name: inputValue.value } }),
+  [inputValue],
+  { debounce: 500, immediate: false, initialData: [] },
+);
+const { send: setFormData } = useRequest(
+  (data) => Apis.app.temporaryCourse({ data }),
+  {
+    immediate: false,
+  },
+);
+const isFirst = ref(true);
+const coursePriceRulesId = ref();
+onLoad((query: any) => {
+  coursePriceRulesId.value = query.id;
 });
-
 // 选择学生
-const selectStudent = (name: string) => {
-  if (!selectedTags.value.includes(name)) {
-    selectedTags.value.push(name);
+const selectStudent = (item: FamilyMembers) => {
+  if (!selectedTags.value.some((it) => it.id == item.id)) {
+    selectedTags.value.push(item);
   }
   inputValue.value = "";
-  isFocused.value = false;
 };
-
+function inputFocus() {
+  if (!isFirst.value) return;
+  inputValue.value = "1";
+  inputValue.value = "";
+  isFirst.value = false;
+}
 // 删除 tag
-const removeTag = (name: string) => {
-  selectedTags.value = selectedTags.value.filter((tag) => tag !== name);
+const removeTag = (item: FamilyMembers) => {
+  selectedTags.value = selectedTags.value.filter((tag) => tag.id !== item.id);
 };
-
-// 输入框聚焦/失焦处理
-const handleFocus = () => {
-  isFocused.value = true;
-};
-const handleBlur = () => {
-  // 延迟隐藏,防止点击列表时触发隐藏
+async function handleSubmit() {
+  if (selectedTags.value.length == 0)
+    return uni.showToast({ title: "请选择约课的学生" });
+  console.log(selectedTags.value, "提交核销数据");
+  const formData = {
+    coursePriceRulesId: coursePriceRulesId.value,
+    userIds: selectedTags.value.map((it) => it.id),
+  };
+  await setFormData(formData);
+  uni.showToast({ title: "预约成功", icon: "success" });
   setTimeout(() => {
-    isFocused.value = false;
-  }, 200);
-};
+    router.back();
+  }, 1500);
+}
 </script>
 
 <style scoped>

+ 24 - 32
src/subPack/classInspectionDetaile/index.vue

@@ -6,11 +6,11 @@
     safeAreaInsetTop
     :bordered="false"
     leftArrow
-    @click-left="handleClickLeft"
+    @click-left="router.back()"
   ></wd-navbar>
   <view class="px32rpx py-24rpx" v-if="data">
     <course disabled :item="courseItem"></course>
-    <view class="mt24rpx flex items-center">
+    <view class="mt24rpx flex items-center" v-if="todayClass.length">
       <image src="@/subPack/static/nz.png" class="w-30rpx h30rpx"></image>
       <view class="text-32rpx ml-3 font-semibold">今日上课</view>
     </view>
@@ -54,25 +54,28 @@
 </template>
 
 <script setup lang="ts">
-import img0 from "@/subPack/static/yy.png";
-import img1 from "@/subPack/static/yq.png";
+import router from "@/router";
 const tab = ref(0);
 const type = ref(0);
 const titleArr = ["拍照验课", "选择课程", "选择延期课时"];
 const { courseItem } = storeToRefs(useCourseStore());
+const queryId = ref();
 onLoad((query: any) => {
   type.value = query.type;
-  getDataDetaile(query.id);
+  queryId.value = query.id;
 });
 const NormalClass = computed(() => {
-  return data.value.filter((it) => it.coursesType == 0);
+  return data.value.filter((it) => it.coursesType == 0 && !it.orToday);
 });
 const MakeUpCasses = computed(() => {
-  return data.value.filter((it) => it.coursesType == 1);
+  return data.value.filter((it) => it.coursesType == 1 && !it.orToday);
 });
 const todayClass = computed(() => {
   return data.value.filter((it) => it.orToday);
 });
+onShow(() => {
+  getData(queryId.value);
+});
 const { send: getData, data } = useRequest(
   (courseId: string) =>
     Apis.app.getCourseInfo({
@@ -84,35 +87,24 @@ const { send: getData, data } = useRequest(
   {
     immediate: false,
   },
-);
-async function getDataDetaile(id: string) {
-  const res = await getData(id);
-  // console.log(res, "asdsad");
-  console.log(todayClass.value);
-  console.log(NormalClass.value);
-  console.log(MakeUpCasses.value);
-}
+).onSuccess(() => {
+  setTimeout(() => {
+    uni
+      .createIntersectionObserver(getCurrentInstance())
+      .relativeToViewport(".title")
+      .observe(".view", (res) => {
+        console.log(res, "监听");
+
+        if (res.intersectionRatio == 0) {
+          tab.value = 0;
+        }
+      });
+  }, 10);
+});
 function handleGoView() {
   tab.value = 1;
   uni.pageScrollTo({ selector: ".view" });
 }
-uni
-  .createIntersectionObserver(getCurrentInstance())
-  .relativeToViewport(".title")
-  .observe(".view", (res) => {
-    if (res.intersectionRatio == 0) {
-      tab.value = 0;
-    }
-  });
-function handleClickLeft() {
-  uni.navigateBack();
-}
-function getToast(type: number) {
-  uni.showToast({
-    image: type == 1 ? img0 : img1,
-    title: type == 1 ? "预约成功" : "延期成功",
-  });
-}
 </script>
 
 <style scoped></style>

+ 84 - 11
src/subPack/selectClass/index.vue

@@ -1,30 +1,103 @@
 <template>
-  <view class="px32rpx py20rpx">
-    <view class="text-28rpx">未核销<text class="text-#0074FF">18</text>人</view>
+  <view class="px32rpx py20rpx" v-if="data">
+    <view class="text-28rpx"
+      >未核销<text class="text-#0074FF">{{ data.length }}</text
+      >人</view
+    >
 
     <view
       class="bg-white rounded-32rpx flex items-center justify-between p24rpx box-border mt20rpx"
+      v-for="(item, idx) in data"
+      :key="item.id"
     >
-      <view class="font-semibold text-32rpx">杨锦新</view>
-      <wd-upload image-mode="aspectFill"></wd-upload>
+      <view class="font-semibold text-32rpx">{{ item.useUserName }}</view>
+      <upload
+        :disabled="false"
+        @click="handleGoCamera(String(item.id))"
+        :imgUrl="item.verifyImage"
+        :key="item.id"
+        :id="item.id"
+      ></upload>
     </view>
+    <view class="h-180rpx"></view>
   </view>
-  <!-- <view
-    class="bg-white w-full fixed bottom-0 bottom-safe-area px32rpx pt-16rpx box-border rounded-t-32rpx box"
+  <fixdbtn block size="large" @click="handleSubmit" :loading="loading"
+    >提交</fixdbtn
   >
-    <wd-button block size="large">提交</wd-button>
-  </view> -->
-  <fixdbtn block size="large">提交</fixdbtn>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+import router from "@/router";
+import upload from "@/subPack/EmployeeListAdd/components/CustomUpload/index.vue";
+const { dataId, img } = storeToRefs(useCameraStore());
+const { data, send: getList } = useRequest(
+  (data) =>
+    Apis.app.courseQueryUsers({
+      data,
+    }),
+  { immediate: false },
+);
+const { send: setFormData, loading } = useRequest(
+  (data) =>
+    Apis.app.courseUploadImage({
+      data,
+    }),
+  { immediate: false },
+);
+const coursePriceRulesId = ref();
+onLoad(async (query: any) => {
+  coursePriceRulesId.value = query.id;
+  await getList({ coursePriceRulesId: query.id, verifyStatus: 0 });
+});
+async function handleSubmit() {
+  const form = data.value
+    .map((it) => {
+      return {
+        id: it.useUserId,
+        verifyImage: it.verifyImage,
+      };
+    })
+    .filter((it) => it.verifyImage);
+  if (!form.length)
+    return uni.showToast({ title: "最少核销一个人", icon: "none" });
+  const formData = {
+    coursePriceRulesId: coursePriceRulesId.value,
+    formList: form,
+  };
+  await setFormData(formData);
+  uni.showToast({
+    title: "拍照核销成功",
+    icon: "success",
+    duration: 2000,
+    mask: true,
+  });
+  setTimeout(() => {
+    router.back();
+  }, 2000);
+}
+function handleGoCamera(id: string) {
+  dataId.value = id;
+  router.push({ name: "Camera" });
+}
+watch(
+  () => img.value,
+  () => {
+    data.value = data.value.map((its) => {
+      return {
+        ...its,
+        verifyImage: dataId.value == its.id ? img.value : its.verifyImage,
+      };
+    });
+  },
+);
+</script>
 
 <style scoped></style>
 <route lang="json">
 {
   "name": "selectClass",
   "style": {
-    "navigationBarTitleText": "选择课程"
+    "navigationBarTitleText": ""
   }
 }
 </route>

二進制
src/subPack/static/back.png


二進制
src/subPack/static/close.png


二進制
src/subPack/static/open.png


二進制
src/subPack/static/pic.png


二進制
src/subPack/static/upload.png


+ 19 - 0
src/subPack/store/camera.ts

@@ -0,0 +1,19 @@
+import { defineStore } from "pinia";
+
+interface useCameraStore {
+  img: string;
+  dataId: string;
+}
+export const useCameraStore = defineStore({
+  id: "app-useCameraStore",
+  state: (): useCameraStore => ({
+    img: "",
+    dataId: "",
+  }),
+  getters: {},
+  actions: {
+    setImg(img: string) {
+      this.img = img;
+    },
+  },
+});

+ 8 - 2
src/subPack/writeOff/index.vue

@@ -5,7 +5,6 @@
       </image>
       <view class="font-semibold text-32rpx">{{ data.siteName }}</view>
     </view>
-    {{ type }}
     <view
       class="mt20rpx bg-white px-24rpx py-28rpx rounded-32rpx"
       v-if="data.appOrderProInfoVerifyVOS"
@@ -68,7 +67,7 @@
           >本次核销{{ checkedAll.length }}张</view
         >
       </view>
-      <view class="mt12rpx text-#FB5B5B"> 共{{ data.amount }}张可用</view>
+      <view class="mt12rpx text-#FB5B5B"> 共{{ AvailableNumber }}张可用</view>
     </view>
     <view class="mt20rpx flex items-center">
       <image src="@/subPack/static/i.png" class="w30rpx h30rpx mr6rpx" />
@@ -91,6 +90,7 @@ import { createGlobalLoadingMiddleware } from "@/api/core/middleware";
 import router from "@/router";
 const { isShowLoging } = storeToRefs(useUserStore());
 const checkedAll = ref([]);
+
 const { data, send: getOrder } = useRequest(
   (id: string) =>
     Apis.app.scanCodeQueryOrder({
@@ -113,6 +113,12 @@ const { send: submit } = useRequest(
     middleware: createGlobalLoadingMiddleware({ loadingText: "核销中..." }),
   },
 );
+const AvailableNumber = computed(
+  //消费张数,几张可用
+  () =>
+    data.value.appOrderProInfoVerifyVOS?.filter((it) => it.isinStatus == 1)
+      .length,
+);
 onLoad(async (query: any) => {
   getOrder(query.id);
 });

+ 1 - 0
src/uni-pages.d.ts

@@ -7,6 +7,7 @@ interface NavigateToOptions {
   url: "/pages/index/index" |
        "/pages/about/index" |
        "/pages/login/index" |
+       "/subPack/Camera/index" |
        "/subPack/classInspection/index" |
        "/subPack/classInspectionDetaile/index" |
        "/subPack/EmployeeList/index" |

+ 4 - 1
src/utils/index.ts

@@ -113,7 +113,10 @@ export function getImageURL(url?: string) {
  */
 function areAllFieldsFilled(obj: Record<string, any>): boolean {
   return Object.values(obj).every(
-    (value) => unref(value) !== null && unref(value) !== undefined,
+    (value) =>
+      unref(value) !== null &&
+      unref(value) !== undefined &&
+      unref(value) !== "",
   );
 }
 

+ 8 - 2
vite.config.ts

@@ -48,11 +48,17 @@ export default async () => {
           },
           {
             from: "alova/client",
-            imports: ["usePagination", "useRequest"],
+            imports: ["usePagination", "useRequest", "useUploader"],
           },
         ],
         dts: "src/auto-imports.d.ts",
-        dirs: ["src/composables", "src/store", "src/utils", "src/api"],
+        dirs: [
+          "src/composables",
+          "src/store",
+          "src/utils",
+          "src/api",
+          "src/subPack/store",
+        ],
         vueTemplate: true,
       }),
       // https://github.com/antfu/unocss