瀏覽代碼

feat: ✨ 扫码核销等等

zhangtao 1 天之前
父節點
當前提交
66bb76174b

+ 28 - 22
manifest.config.ts

@@ -1,16 +1,16 @@
-import { defineManifestConfig } from '@uni-helper/vite-plugin-uni-manifest'
+import { defineManifestConfig } from "@uni-helper/vite-plugin-uni-manifest";
 
 export default defineManifestConfig({
-  'name': 'wot-demo',
-  'appid': '',
-  'description': '',
-  'versionName': '1.0.0',
-  'versionCode': '100',
-  'transformPx': false,
+  name: "sports-merchant-minapp",
+  appid: "",
+  description: "",
+  versionName: "1.0.0",
+  versionCode: "100",
+  transformPx: false,
   /* 5+App特有相关 */
-  'app-plus': {
+  "app-plus": {
     usingComponents: true,
-    nvueStyleCompiler: 'uni-app',
+    nvueStyleCompiler: "uni-app",
     compilerVersion: 3,
     splashscreen: {
       alwaysShowBeforeRender: true,
@@ -49,36 +49,42 @@ export default defineManifestConfig({
     },
   },
   /* 快应用特有相关 */
-  'quickapp': {},
+  quickapp: {},
   /* 小程序特有相关 */
-  'mp-weixin': {
-    appid: '',
+  "mp-weixin": {
+    appid: "wxe70e6c132e5b4c28",
     setting: {
       urlCheck: false,
     },
     usingComponents: true,
     darkmode: true,
-    themeLocation: 'theme.json',
+    themeLocation: "theme.json",
+    requiredPrivateInfos: ["getLocation"],
+    permission: {
+      "scope.userLocation": {
+        desc: "将获取你的位置,以添加到水印相机",
+      },
+    },
   },
-  'mp-alipay': {
+  "mp-alipay": {
     usingComponents: true,
     compileOptions: {
-      globalObjectMode: 'enable',
+      globalObjectMode: "enable",
       treeShaking: true,
     },
   },
-  'mp-baidu': {
+  "mp-baidu": {
     usingComponents: true,
   },
-  'mp-toutiao': {
+  "mp-toutiao": {
     usingComponents: true,
   },
-  'h5': {
+  h5: {
     darkmode: true,
-    themeLocation: 'theme.json',
+    themeLocation: "theme.json",
   },
-  'uniStatistics': {
+  uniStatistics: {
     enable: false,
   },
-  'vueVersion': '3',
-})
+  vueVersion: "3",
+});

+ 6 - 0
src/api/apiDefinitions.ts

@@ -24,6 +24,12 @@ Some useful links:
  * **Do not edit the file manually.**
  */
 export default {
+  "app.pageAppIsinVerifyRecords": [
+    "GET",
+    "/app/course/pageAppIsinVerifyRecords",
+  ],
+  "app.classPostpone": ["POST", "/app/course/classPostpone"],
+  "app.getClassPostponeUsers": ["GET", "/app/course/getClassPostponeUsers"],
   "app.temporaryCourse": ["POST", "/app/course/temporaryCourse"],
   "app.getFamilyMembersByName": ["GET", "/app/course/getFamilyMembersByName"],
   "app.courseUploadImage": ["POST", "/app/course/courseUploadImage"],

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

@@ -32,7 +32,6 @@ export async function handleAlovaResponse(
     | UniApp.DownloadSuccessData,
 ) {
   return new Promise((resolve, reject) => {
-    const globalToast = useGlobalToast();
     // Extract status code and data from UniApp response
     const { statusCode, data } =
       response as UniNamespace.RequestSuccessCallbackResult;
@@ -79,6 +78,8 @@ 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);
   });

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

@@ -205,6 +205,36 @@ declare global {
       ): Alova2Method<any, "sys.upload", Config>;
     };
     app: {
+      pageAppIsinVerifyRecords<
+        Config extends Alova2MethodConfig<
+          PaginationResponse<AppIsinVerifyVO[]>
+        > & {
+          params: {
+            pageNo: number;
+            pageSize: number;
+          };
+        },
+      >(
+        config: Config,
+      ): Alova2Method<
+        PaginationResponse<AppIsinVerifyVO[]>,
+        "app.pageAppIsinVerifyRecords",
+        Config
+      >;
+      classPostpone<
+        Config extends Alova2MethodConfig<any> & {
+          data: classPostpone;
+        },
+      >(
+        config: Config,
+      ): Alova2Method<any, "app.classPostpone", Config>;
+      getClassPostponeUsers<
+        Config extends Alova2MethodConfig<FamilyUserVO[]> & {
+          params: { coursePriceRulesId: string };
+        },
+      >(
+        config: Config,
+      ): Alova2Method<FamilyUserVO[], "app.getClassPostponeUsers", Config>;
       appCategory<Config extends Alova2MethodConfig<any>>(): Alova2Method<
         any,
         "app.appCategory",
@@ -389,6 +419,103 @@ declare global {
 
   var Apis: Apis;
 }
+/**
+ * org.jeecg.modules.app.vo.AppIsinVerifyVO
+ *
+ * AppIsinVerifyVO
+ */
+export interface AppIsinVerifyVO {
+  /**
+   * 时间段
+   */
+  frameTimeStr?: string;
+  /**
+   * ID
+   */
+  id?: string;
+  /**
+   * 价格/元
+   */
+  OriginalPrice?: number;
+  originalPrice?: number;
+  /**
+   * 商品图片
+   */
+  productImage?: string;
+  /**
+   * 商品名称
+   */
+  productName?: string;
+  /**
+   * 备注
+   */
+  remark?: string;
+  /**
+   * 券号
+   */
+  ticketNo?: string;
+  /**
+   * 日期
+   */
+  useDateStr?: string;
+  /**
+   * 使用时间
+   */
+  useTime?: string;
+  [property: string]: any;
+  /**
+   * 0-学校 1-包场 2-无固定场 3-个人赛 4-团队赛 5-课程
+   */
+  orderType: number;
+}
+
+/**
+ * ClassPostponeForm
+ */
+export interface postponeRequest {
+  /**
+   * 所选补课课时ID
+   */
+  coursePriceRulesId?: string;
+  /**
+   * 用户列表
+   */
+  familyUserVOList?: FamilyUserVO[];
+  /**
+   * 订单ID
+   */
+  orderId?: string;
+  /**
+   * 延课原因
+   */
+  postponeReason?: string;
+  /**
+   * 原课时id
+   */
+  priceRulesId?: string;
+  [property: string]: any;
+}
+
+/**
+ * org.jeecg.modules.app.vo.FamilyUserVO
+ *
+ * FamilyUserVO
+ */
+export interface FamilyUserVO {
+  /**
+   * 用户ID
+   */
+  familyUserId: string;
+  /**
+   * 用户人脸图片
+   */
+  familyUserImage?: string;
+  /**
+   * 用户名称
+   */
+  familyUserName?: string;
+  [property: string]: any;
+}
 
 /**
  * FamilyMembers

+ 0 - 26
src/api/index.ts

@@ -1,5 +1,4 @@
 // Import the core alova instance
-import { BASE_URL } from "@/config";
 import alovaInstance from "./core/instance";
 
 // Export the global Apis object from the generated code
@@ -22,28 +21,3 @@ type Data<T> = {
   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" });
-      },
-    });
-  });
-}

+ 2 - 2
src/auto-imports.d.ts

@@ -24,6 +24,7 @@ declare global {
   const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
   const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
   const controlledRef: typeof import('@vueuse/core')['controlledRef']
+  const convertEmptyToNull: typeof import('./utils/index')['convertEmptyToNull']
   const createApis: typeof import('./api/createApis')['createApis']
   const createApp: typeof import('vue')['createApp']
   const createEventHook: typeof import('@vueuse/core')['createEventHook']
@@ -377,6 +378,7 @@ declare module 'vue' {
     readonly computedWithControl: UnwrapRef<typeof import('@vueuse/core')['computedWithControl']>
     readonly controlledComputed: UnwrapRef<typeof import('@vueuse/core')['controlledComputed']>
     readonly controlledRef: UnwrapRef<typeof import('@vueuse/core')['controlledRef']>
+    readonly convertEmptyToNull: UnwrapRef<typeof import('./utils/index')['convertEmptyToNull']>
     readonly createApis: UnwrapRef<typeof import('./api/createApis')['createApis']>
     readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
     readonly createEventHook: UnwrapRef<typeof import('@vueuse/core')['createEventHook']>
@@ -507,7 +509,6 @@ 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']>
@@ -682,7 +683,6 @@ declare module 'vue' {
     readonly useWindowFocus: UnwrapRef<typeof import('@vueuse/core')['useWindowFocus']>
     readonly useWindowScroll: UnwrapRef<typeof import('@vueuse/core')['useWindowScroll']>
     readonly useWindowSize: UnwrapRef<typeof import('@vueuse/core')['useWindowSize']>
-    readonly utils: UnwrapRef<typeof import('./utils/index')['default']>
     readonly watch: UnwrapRef<typeof import('vue')['watch']>
     readonly watchArray: UnwrapRef<typeof import('@vueuse/core')['watchArray']>
     readonly watchAtMost: UnwrapRef<typeof import('@vueuse/core')['watchAtMost']>

+ 1 - 0
src/components.d.ts

@@ -43,6 +43,7 @@ declare module 'vue' {
     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']
     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']
     WdUpload: typeof import('wot-design-uni/components/wd-upload/wd-upload.vue')['default']
   }

+ 10 - 2
src/components/classItem/index.vue

@@ -23,7 +23,11 @@
         >预约这节</commonbtn
       ><commonbtn
         bg-color="#0074FF"
-        @click="handleGoPath('/subPack/ExtensionClass/index')"
+        @click="
+          handleGoPath(
+            `/subPack/ExtensionClass/index?coursesId=${item.coursesId}&id=${item.id}`,
+          )
+        "
         v-if="type == 2 && showBtn"
         >延期这节</commonbtn
       >
@@ -38,7 +42,11 @@
     </view>
     <view
       class="mt20rpx pl20rpx flex items-center text-24rpx"
-      @click="handleGoPath(`/subPack/PersonnelView/index?id=${item.id}`)"
+      @click="
+        handleGoPath(
+          `/subPack/PersonnelView/index?id=${item.id}&postponeNum=${item.postponeNum}&writtenOffNum=${item.writtenOffNum}&unwrittenOffNum=${item.unwrittenOffNum}`,
+        )
+      "
     >
       <view class="text-[rgb(0,0,0,0.3)] mr20rpx"
         >共{{ item?.totalNum }}人</view

+ 2 - 2
src/manifest.json

@@ -1,5 +1,5 @@
 {
-  "name": "wot-demo",
+  "name": "sports-merchant-minapp",
   "appid": "",
   "description": "",
   "versionName": "1.0.0",
@@ -42,7 +42,7 @@
   },
   "quickapp": {},
   "mp-weixin": {
-    "appid": "",
+    "appid": "wxe70e6c132e5b4c28",
     "setting": {
       "urlCheck": false
     },

+ 2 - 1
src/pages.json

@@ -126,7 +126,8 @@
           "type": "page",
           "name": "ExtensionClass",
           "style": {
-            "navigationBarTitleText": "填写延期信息"
+            "navigationBarTitleText": "填写延期信息",
+            "disableScroll": true
           }
         },
         {

+ 32 - 50
src/subPack/Camera/index.vue

@@ -55,12 +55,11 @@
     </view>
   </view>
   <page-container
-    :show="uploading"
+    :show="isShow"
     :duration="false"
     :overlay="false"
     @beforeleave="beforeleave"
   >
-    <wd-message-box></wd-message-box>
   </page-container>
 </template>
 
@@ -75,19 +74,7 @@ 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("相机初始化完成!!!");
 }
@@ -110,38 +97,48 @@ function takePhoto() {
       if (res.tempImagePath) {
         const img = await addWatermarkToImage(res.tempImagePath);
         FilePath.value = img;
-        await send({ filePath: img, name: "file" });
-        console.log(data.value);
+        const resData = await uploadFile(img);
+        isShow.value = false;
+        let resDataPath = JSON.parse(resData);
+        useCameraStore().setImg(resDataPath.message);
 
-        // const path = `${BASE_URL}/sys/common/static/${resData.message}`;
-        // console.log(path, "上传文件");
-        // useCameraStore().setImg(path);
-        // isShow.value = false;
-        // router.back();
-        // uni.hideLoading();
+        router.back();
+        uni.hideLoading();
       }
     },
-    fail(err) {
+    complete: () => {
       isShow.value = false;
+    },
+    fail(err) {
       uni.hideLoading();
     },
   });
 }
 
 function beforeleave() {
-  messageBox
-    .confirm({
-      title: "正在上传图片,确认退出吗?",
-      cancelButtonText: "退出",
-      confirmButtonText: "继续上传",
-      closeOnClickModal: false,
-    })
-    .then(async () => {
-      console.log("点击了确定按钮");
-    })
-    .catch(() => {
-      abort();
+  if (isShow.value) {
+    return;
+  }
+}
+
+function uploadFile(path: string) {
+  return new Promise<string>((resolve, reject) => {
+    const uploadTask = uni.uploadFile({
+      url: `${BASE_URL}/sys/common/upload`,
+      filePath: path,
+      name: "file",
+      header: {
+        "X-Access-Token": useUserStore().token,
+      },
+      success: (res) => {
+        console.log(res, "上传成功======================");
+        resolve(res.data);
+      },
+      fail: (err) => {
+        console.log(err, "上传失败======================");
+      },
     });
+  });
 }
 function addWatermarkToImage(imagePath: string): Promise<string> {
   return new Promise((resolve, reject) => {
@@ -396,21 +393,6 @@ function addWatermarkToImage(imagePath: string): Promise<string> {
     });
   });
 }
-
-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>

+ 61 - 19
src/subPack/EmployeeListAdd/components/upload/index.vue

@@ -9,7 +9,9 @@
       },
     }"
     @oversize="handleOversize"
-    @success="handleSuccess"
+    :file-list="fileListModel"
+    @remove="handleRemove"
+    @change="handleChange"
   >
     <upload :disabled="true"></upload>
   </wd-upload>
@@ -20,35 +22,75 @@
 import { BASE_UPLOADURL, BASE_URL } from "@/config";
 import upload from "../CustomUpload/index.vue";
 import {
-  uploadProps,
+  type UploadChangeEvent,
+  type UploadFile,
   type UploadOversizeEvent,
-  type UploadSuccessEvent,
+  type UploadRemoveEvent,
 } from "wot-design-uni/components/wd-upload/types";
-const props = defineProps({
-  ...uploadProps,
-  tip: {
-    type: String,
-    default: "",
-  },
-});
-
+import dayjs from "dayjs";
+import { isArray, isString } from "wot-design-uni/components/common/util";
+const props = defineProps<{ fileListCustom: string | string[]; tip: string }>();
+const emit = defineEmits(["update:fileListCustom"]);
 const { token } = useUserStore();
+const fileListModel = computed<UploadFile[]>({
+  get() {
+    if (!props.fileListCustom) return [];
+    console.log(props.fileListCustom, "fileList");
+
+    if (isArray(props.fileListCustom)) {
+      return props.fileListCustom.map((it) => {
+        return {
+          url: it,
+          time: dayjs().valueOf(),
+          status: "success",
+        };
+      });
+    }
+    if (isString(props.fileListCustom)) {
+      return props.fileListCustom.split(",").map((it) => {
+        return {
+          url: it,
+          time: dayjs().valueOf(),
+          status: "success",
+        };
+      });
+    }
+    return [];
+  },
+  set(val) {
+    let newList = "";
+    if (val.length) {
+      newList = val.map((it) => it.url).join(",");
+    }
+    console.log(val, "修改值");
 
+    emit("update:fileListCustom", newList);
+  },
+});
 function handleOversize(e: UploadOversizeEvent) {
-  console.log(e.file, "微博读书2222");
   uni.showToast({
     title: "文件过大",
     icon: "none",
   });
 }
-function handleSuccess(e: UploadSuccessEvent) {
-  const { file, fileList, formData } = e;
-  const fileRes = JSON.parse(String(file.response));
-  props.fileList.push({
-    url: `${BASE_URL}/sys/common/static/${fileRes.message}`,
-    status: "success",
+
+function handleRemove({ file }: UploadRemoveEvent) {
+  const findIndex = fileListModel.value.findIndex((it) => it.time == file.time);
+  fileListModel.value = fileListModel.value.splice(findIndex, 1);
+  const newList = fileListModel.value.map((it) => it.url).join(",");
+  emit("update:fileListCustom", newList);
+}
+function handleChange({ fileList }: UploadChangeEvent) {
+  let newfile: UploadFile[] = [];
+  fileList.map((it) => {
+    const fileRes = JSON.parse(String(it.response));
+    newfile.push({
+      url: fileRes.message,
+      status: "success",
+      time: dayjs().valueOf(),
+    });
   });
-  console.log(file);
+  fileListModel.value = [...fileListModel.value, ...newfile];
 }
 </script>
 

+ 22 - 45
src/subPack/EmployeeListAdd/index.vue

@@ -163,7 +163,7 @@
               tip="单张照片,不超过5MB"
               :max-size="5242880"
               :sourceType="['camera']"
-              :file-list="formData.certificateInnocence"
+              v-model:file-list-custom="formData.certificateInnocence"
               accept="image"
               :limit="1"
             ></upload>
@@ -190,7 +190,7 @@
             <upload
               tip="单张照片,不超过5MB"
               :max-size="5242880"
-              :file-list="formData.backgroundImg"
+              v-model:file-list-custom="formData.backgroundImg"
               :sourceType="['camera']"
               accept="image"
               :limit="1"
@@ -215,7 +215,7 @@
               tip="单张照片,不超过5MB"
               :max-size="5242880"
               :sourceType="['camera']"
-              :file-list="formData.healthy"
+              v-model:file-list-custom="formData.healthy"
               accept="image"
               :limit="1"
             ></upload>
@@ -249,7 +249,7 @@
             :max-size="5242880"
             :sourceType="['camera']"
             accept="image"
-            :file-list="formData.honor"
+            v-model:file-list-custom="formData.honor"
             :limit="9"
           ></upload>
         </customFormItem>
@@ -305,17 +305,16 @@
 </template>
 
 <script setup lang="ts">
-import { BASE_UPLOADURL } from "@/config";
 import tree from "./components/tree/index.vue";
 import upload from "./components/upload/index.vue";
 import { type FormInstance } from "wot-design-uni/components/wd-form/types";
 import { createGlobalLoadingMiddleware } from "@/api/core/middleware";
-import assignFormValues from "@/utils";
+import { convertEmptyToNull } from "@/utils";
 const titleArr = ["新增员工", "编辑员工"];
 const treePicker = ref();
 const form = ref<FormInstance>();
 const isShowInstructor = ref(false);
-const formData = reactive({
+let formData = reactive<any>({
   status: 1,
   password: "",
   trainingPrograms: [],
@@ -351,7 +350,7 @@ const { send: handleuserEdit, loading: loading } = useRequest(
     middleware: createGlobalLoadingMiddleware(),
   },
 );
-const { send: queryById } = useRequest(
+const { send: queryById, data: editData } = useRequest(
   (id) => Apis.sys.queryById({ data: { id } }),
   {
     immediate: false,
@@ -370,36 +369,16 @@ watch(
 );
 async function getDetaile() {
   if (type.value == 1) {
-    const res = await queryById(id.value);
-    if (res.instructorId) {
+    await queryById(id.value);
+    if (editData.value.instructorId) {
       isShowInstructor.value = true;
     }
-    assignFormValues(formData, res, {
-      ignoreEmpty: true,
-      transform: {
-        backgroundImg: (value) => {
-          return value ? value.split(",").map((url: string) => ({ url })) : [];
-        },
-        healthy: (value) => {
-          return value ? value.split(",").map((url: string) => ({ url })) : [];
-        },
-        certificateInnocence: (value) =>
-          value ? value.split(",").map((url: string) => ({ url })) : [],
-        honor: (value) => {
-          if (typeof value == "string") {
-            return value.split(",").map((url: string) => ({ url }));
-          }
-          return value;
-        },
-        selectedroles: (value) => {
-          return value ? value.join(",") : "";
-        },
-      },
-    });
+    formData = editData.value;
     formData.deptName = findNamesByKeys(
       formData.selecteddeparts,
       deptList.value,
     ).join(",");
+    console.log(formData, "formData");
   }
 }
 function handleShow() {
@@ -412,15 +391,11 @@ function handleClickLeft() {
 async function handleSubmit() {
   const val = await form.value?.validate();
   if (val?.valid) {
-    const obj = {
+    const obj = convertEmptyToNull({
       ...formData,
-      honor: transformImg("honor") || null,
-      backgroundImg: transformImg("backgroundImg") || null,
-      healthy: transformImg("healthy") || null,
-      trainingPrograms: formData.trainingPrograms.join(",") || null,
-      certificateInnocence: transformImg("certificateInnocence") || null,
       selecteddeparts: formData.selecteddeparts.join(","),
-    };
+      trainingPrograms: formData.trainingPrograms.join(",") || null,
+    });
     console.log(obj, "提交表单");
     type.value == 0
       ? await handleAdd(obj)
@@ -453,12 +428,14 @@ function handleChangeRole(e: { value: string }) {
     }
   });
 }
-function transformImg(key: keyof typeof formData) {
-  const list = formData[key]
-    ? (formData[key] as any[]).map((it) => it.url).join(",")
-    : null;
-  return list;
-}
+
+watch(
+  () => formData,
+  () => {
+    console.log(formData, "formDatas");
+  },
+  { immediate: true },
+);
 
 /**
  * 通过key数组在树形结构中查找对应的name数组

+ 94 - 34
src/subPack/ExtensionClass/index.vue

@@ -3,23 +3,24 @@
     <view class="bg-white rounded-16rpx p-24rpx">
       <wd-select-picker
         label="补课课时"
-        v-model="value"
-        :columns="columns"
+        v-model="MakeUpClassId"
+        :columns="data"
         placeholder="请选择补课课时"
         align-right
         type="radio"
+        value-key="id"
         safe-area-inset-bottom
+        label-key="name"
       ></wd-select-picker>
       <wd-divider color="#F0F0F0"></wd-divider>
-      <view class="flex items-center justify-end mt24rpx">
-        <view class="text-24rpx text-[rgb(0,0,0,0.3)]"
-          >未找到合适的补课课时,<text class="text-#0074FF">去新增</text>
-        </view>
-      </view>
     </view>
     <view class="mt20rpx bg-white rounded-16rpx p24rpx mb20rpx">
       <view class="mb20rpx">
-        <wd-checkbox modelValue="shop" size="large" shape="square"
+        <wd-checkbox
+          v-model="isCheckAll"
+          size="large"
+          shape="square"
+          @change="handleCheckAllChange"
           ><text class="font-semibold text-32rpx">全选</text>
         </wd-checkbox>
       </view>
@@ -28,42 +29,100 @@
         inline
         size="large"
         shape="square"
+        @change="handleChange"
       >
-        <wd-checkbox modelValue="jingmai">张三</wd-checkbox>
-        <wd-checkbox modelValue="shop">李四</wd-checkbox>
-        <wd-checkbox modelValue="shop">李四</wd-checkbox>
-        <wd-checkbox modelValue="shop">李四</wd-checkbox>
-        <wd-checkbox modelValue="shop">李四</wd-checkbox>
-        <wd-checkbox modelValue="shop">李四</wd-checkbox>
-        <wd-checkbox modelValue="shop">李四</wd-checkbox>
-        <wd-checkbox modelValue="shop">李四</wd-checkbox>
-        <wd-checkbox modelValue="shop">李四</wd-checkbox>
-        <wd-checkbox modelValue="shop">李四</wd-checkbox>
-        <wd-checkbox modelValue="shop">李四</wd-checkbox>
-        <wd-checkbox modelValue="shop">李四</wd-checkbox>
+        <wd-checkbox
+          :modelValue="item.familyUserId"
+          v-for="item in userList"
+          :key="item.familyUserId"
+          >{{ item.familyUserName }}</wd-checkbox
+        >
       </wd-checkbox-group>
+      <view
+        class="mt24rpx border border-gray border-solid rounded-xl overflow-hidden"
+      >
+        <wd-textarea
+          v-model="postponeReason"
+          placeholder="请填写延课原因"
+          border
+        />
+      </view>
     </view>
-    <WdButton block size="large">提交</WdButton>
+    <WdButton block size="large" @click="handleSubmit">提交</WdButton>
   </view>
 </template>
 
 <script setup lang="ts">
-const columns = ref<Record<string, any>[]>([
-  {
-    value: "101",
-    label: "男装",
-  },
+import router from "@/router";
+
+const isCheckAll = ref(false);
+const MakeUpClassId = ref<string>("");
+const checkedAll = ref<string[]>([]);
+const postponeReason = ref("");
+const priceRulesId = ref();
+const { send: getUserList, data: userList } = useRequest(
+  (coursePriceRulesId) =>
+    Apis.app.getClassPostponeUsers({ params: { coursePriceRulesId } }),
+  { immediate: false },
+);
+const { data, send: getData } = useRequest(
+  (id: string, coursesType: number) =>
+    Apis.app.queryListByCoursesId({
+      params: { coursesType, id },
+    }),
   {
-    value: "102",
-    label: "奢侈品",
+    immediate: false,
   },
+);
+const { send: submit } = useRequest(
+  (data) =>
+    Apis.app.classPostpone({
+      data,
+    }),
   {
-    value: "103",
-    label: "女装",
+    immediate: false,
   },
-]);
-const value = ref<string>("101");
-const checkedAll = ref(["1"]);
+);
+onLoad(async (query: any) => {
+  priceRulesId.value = query.id;
+  await getUserList(query.id);
+  await getData(query.coursesId, 1);
+});
+function handleChange({ value }: any) {
+  if (value.length == userList.value.length) {
+    isCheckAll.value = true;
+  } else {
+    isCheckAll.value = false;
+  }
+}
+function handleCheckAllChange() {
+  if (isCheckAll.value) {
+    checkedAll.value = userList.value.map((it) => it.familyUserId) as string[];
+  } else {
+    checkedAll.value = [];
+  }
+}
+async function handleSubmit() {
+  if (!MakeUpClassId.value)
+    return uni.showToast({ title: "请选择补课课时", icon: "none" });
+  if (checkedAll.value.length == 0)
+    return uni.showToast({ title: "请选择补课学生", icon: "none" });
+  if (!postponeReason.value)
+    return uni.showToast({ title: "请填写延课原因", icon: "none" });
+  await submit({
+    coursePriceRulesId: MakeUpClassId.value,
+    postponeReason: postponeReason.value,
+    priceRulesId: priceRulesId.value,
+    familyUserVOList: checkedAll.value.map((item) => {
+      return {
+        familyUserId: item,
+      };
+    }),
+  });
+  router.back();
+
+  console.log(MakeUpClassId.value, "补课课时");
+}
 </script>
 
 <style scoped>
@@ -75,7 +134,8 @@ const checkedAll = ref(["1"]);
 {
   "name": "ExtensionClass",
   "style": {
-    "navigationBarTitleText": "填写延期信息"
+    "navigationBarTitleText": "填写延期信息",
+    "disableScroll": true
   }
 }
 </route>

+ 30 - 10
src/subPack/PersonnelView/index.vue

@@ -1,35 +1,54 @@
 <template>
   <view>
     <wd-tabs v-model="tab" swipeable sticky @change="handleChange">
-      <block v-for="item in tabList" :key="item.id">
+      <block v-for="(item, idx) in tabList" :key="idx">
         <wd-tab :title="item.title">
-          <view class="bg-#F6F6F6 px32rpx pt-24rpx bottom-safe-area">
+          <view
+            class="bg-#F6F6F6 px32rpx pt-24rpx bottom-safe-area"
+            v-if="data"
+          >
             <view
               class="p24rpx box-border bg-white mb24rpx rounded-32rpx"
               v-for="it in data"
+              :key="it.id"
             >
               <view class="flex items-center justify-between">
-                <view class="font-semibold text-22rpx text-#222222">{{
+                <view class="font-semibold text-32rpx text-#222222">{{
                   it.useUserName
                 }}</view>
-                <commonbtn bg-color="#0074FF">未核销</commonbtn>
+                <commonbtn
+                  bg-color="#0074FF"
+                  v-if="it.verifyStatus == 0 && it.orPostpone != 1"
+                  >未核销</commonbtn
+                >
+                <commonbtn
+                  bg-color="rgba(0,0,0,0.3)"
+                  v-if="it.verifyStatus == 1"
+                  >已核销</commonbtn
+                >
+                <commonbtn bg-color="#FB5B5B" v-if="it.orPostpone == 1"
+                  >延课</commonbtn
+                >
               </view>
               <view
                 class="flex items-center justify-between text-28rpx mt24rpx"
               >
                 <view class="text-[rgb(0,0,0,0.3)]">手机号码</view>
-                <view>185659254423</view>
+                <view>{{ it.useUserPhone }}</view>
               </view>
               <view class="text-[rgb(0,0,0,0.3)] mt24rpx"> 人脸照片 </view>
               <view class="mt24rpx">
                 <image
-                  src="https://pic1.arkoo.com/56D0B40F99F841DF8A2425762AE2565D/picture/o_1i4qop009177v1tgf14db15he1iaj1is.jpg"
+                  :src="it.useUserImage"
                   class="w-160rpx h-160rpx rounded-32rpx"
                 />
               </view>
-              <view class="mt24rpx flex items-center justify-between">
+              <view
+                class="mt24rpx flex items-center justify-between"
+                v-if="it.orPostpone == 1"
+              >
                 <view class="text-28rpx text-[rgb(0,0,0,0.3)]">延课原因</view>
-                <view>生病了无法参与剧烈运动</view>
+                <view>{{ it.postponeReason }}</view>
               </view>
             </view>
           </view>
@@ -50,6 +69,9 @@ const tabList = ref([
 const coursePriceRulesId = ref();
 onLoad(async (query: any) => {
   coursePriceRulesId.value = query.id;
+  tabList.value[1].title = `延课(${query.postponeNum})`;
+  tabList.value[2].title = `已核销(${query.writtenOffNum})`;
+  tabList.value[3].title = `未核销(${query.unwrittenOffNum})`;
   await getList({ coursePriceRulesId: query.id });
 });
 const { data, send: getList } = useRequest(
@@ -60,9 +82,7 @@ const { data, send: getList } = useRequest(
   { 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,

+ 5 - 4
src/subPack/writeOff/index.vue

@@ -11,10 +11,10 @@
     >
       <wd-checkbox-group v-model="checkedAll">
         <!-- 无固定场 -->
-        <template v-if="type > GoodsType.noFixed">
+        <template v-if="type >= GoodsType.noFixed">
           <view class="flex items-center">
             <image
-              src="https://pic1.arkoo.com/56D0B40F99F841DF8A2425762AE2565D/picture/o_1i4qop009177v1tgf14db15he1iaj1is.jpg"
+              :src="data.productImage"
               class="w200rpx h-200rpx rounded-32rpx min-w-200rpx"
             />
             <view class="ml20rpx flex-1">
@@ -48,11 +48,12 @@
           <view class="flex-1">
             <view
               class="flex items-center justify-between ml20rpx mb20rpx"
-              v-for="item in 5"
+              v-for="item in data.appOrderProInfoVerifyVOS"
+              :key="item.isinId"
             >
               <view class=""> 06-11(今天)16:17 | 羽毛球1 ¥59 </view>
               <wd-checkbox
-                :model-value="item"
+                :model-value="item.isinId"
                 checked-color="#fdd143"
               ></wd-checkbox>
             </view>

+ 22 - 13
src/subPack/writeOffDetaile/index.vue

@@ -2,26 +2,29 @@
   <view class="px32rpx py20rpx">
     <view
       class="bg-white mb20rpx rounded-32rpx p24rpx box-border"
-      v-for="item in 10"
+      v-for="item in data"
+      :key="item.id"
     >
       <!-- 固定场 -->
-      <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>
+      <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>
+          <view class="text-#FB5B5B font-semibold">¥25.9</view>
         </view>
-        <view class="text-#FB5B5B font-semibold">¥25.9</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>
-      </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>
+        </view>
+      </template>
 
       <!-- 无固定场 -->
       <view class="flex items-center justify-between">
         <image
-          src="https://pic1.arkoo.com/56D0B40F99F841DF8A2425762AE2565D/picture/o_1i4qop009177v1tgf14db15he1iaj1is.jpg"
+          :src="item.productImage"
           class="w160rpx h-160rpx rounded-32rpx min-w-160rpx mr20rpx"
         />
         <view class="flex-1">
@@ -58,6 +61,12 @@
 
 <script setup lang="ts">
 import img1 from "@/subPack/static/hxmx.png";
+import { GoodsType } from "@/config";
+const { data } = usePagination(
+  (pageNo, pageSize) =>
+    Apis.app.pageAppIsinVerifyRecords({ params: { pageNo, pageSize } }),
+  { initialData: [], data: (res) => res.records },
+);
 function handleSuccess() {
   uni.showToast({
     image: img1,

+ 40 - 94
src/utils/index.ts

@@ -9,100 +9,6 @@ export function getCurrentPath() {
   const currentPage = pages[pages.length - 1];
   return currentPage.route || "";
 }
-/**
- * 通用表单赋值方法
- * @param form 表单数据对象
- * @param data 需要赋值的数据
- * @param options 配置选项
- */
-function assignFormValues<T extends Record<string, any>>(
-  form: T,
-  data: Partial<T>,
-  options?: {
-    /**
-     * 是否忽略空值(null, undefined, '')
-     * @default false
-     */
-    ignoreEmpty?: boolean;
-    /**
-     * 自定义字段映射
-     * @example { 'oldKey': 'newKey' }
-     */
-    fieldMap?: Record<string, string>;
-    /**
-     * 需要特殊处理的字段
-     */
-    transform?: Partial<Record<string, (value: any) => any>>;
-  },
-): void {
-  const { ignoreEmpty = false, fieldMap = {}, transform = {} } = options || {};
-
-  for (const key in data) {
-    if (Object.prototype.hasOwnProperty.call(data, key)) {
-      const targetKey = fieldMap[key] || key;
-
-      // 检查目标表单是否有这个字段
-      if (!(targetKey in form)) {
-        continue;
-      }
-
-      let value = data[key];
-
-      // 处理空值
-      if (
-        ignoreEmpty &&
-        (value === null || value === undefined || value === "")
-      ) {
-        continue;
-      }
-
-      // 处理特殊转换
-      if (targetKey in transform) {
-        value = transform[targetKey]!(value);
-      } else if (typeof value === "string") {
-        // 字符串特殊处理
-        if (
-          key.includes("Img") ||
-          key.includes("image") ||
-          key.includes("photo") ||
-          key.includes("certificate")
-        ) {
-          // 图片字段处理
-          if (value) {
-            value = value.split(",").map((url: string) => ({ url }));
-          } else {
-            value = [] as unknown as T[keyof T];
-          }
-        } else if (key === "selecteddeparts" || key.includes("selected")) {
-          // 选择字段处理
-          value = value ? value.split(",") : [];
-        }
-      }
-
-      // 类数组对象特殊处理
-      if (
-        Array.isArray(form[targetKey]) &&
-        !Array.isArray(value) &&
-        typeof value === "string"
-      ) {
-        try {
-          const parsed = JSON.parse(value);
-          if (Array.isArray(parsed)) {
-            form[targetKey as keyof T] = parsed as T[keyof T];
-            continue;
-          }
-        } catch (e) {
-          // 解析失败,继续使用原值
-        }
-      }
-
-      form[targetKey as keyof T] = value as T[keyof T];
-    }
-  }
-}
-
-export default assignFormValues;
-
 export function getImageURL(url?: string) {
   return BASE_URL + "/sys/common/static/" + url;
 }
@@ -135,3 +41,43 @@ export function areAllItemsAllFieldsFilled(
     return areAllFieldsFilled(rest);
   });
 }
+
+/**
+ * 将对象中的空字符串和空数组字段设置为 null
+ * @param obj - 需要处理的对象
+ * @returns 处理后的新对象
+ */
+export function convertEmptyToNull(obj: any): any {
+  // 处理基本类型
+  if (obj === null || obj === undefined) {
+    return null;
+  }
+
+  // 处理字符串类型
+  if (typeof obj === "string") {
+    return obj === "" ? null : obj;
+  }
+
+  // 处理数组类型
+  if (Array.isArray(obj)) {
+    if (obj.length === 0) {
+      return null;
+    }
+    // 递归处理数组中的每个元素
+    return obj.map((item) => convertEmptyToNull(item));
+  }
+
+  // 处理对象类型
+  if (typeof obj === "object") {
+    const newObj: any = {};
+    for (const key in obj) {
+      if (Object.prototype.hasOwnProperty.call(obj, key)) {
+        newObj[key] = convertEmptyToNull(obj[key]);
+      }
+    }
+    return newObj;
+  }
+
+  // 其他类型直接返回
+  return obj;
+}