فهرست منبع

feat: ✨ 店铺余额

zhangtao 1 هفته پیش
والد
کامیت
1fface67ea

+ 2 - 0
src/api/apiDefinitions.ts

@@ -64,4 +64,6 @@ export default {
   "sys.queryById": ["GET", "/staff/staff/queryById"],
   "sys.getCurrentUserDeparts": ["GET", "/sys/user/getCurrentUserDeparts"],
   "sys.upload": ["POST", "/sys/common/upload"],
+  "sys.statisticsInfo": ["GET", "/statisticsInfo/findShopAccountOverviewDept"],
+  "sys.selectFundChanges": ["GET", "/statisticsInfo/selectFundChanges"],
 };

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

@@ -140,6 +140,19 @@ declare global {
       ): Alova2Method<any, "user.loginUser", Config>;
     };
     sys: {
+      selectFundChanges<
+        Config extends Alova2MethodConfig<moneyList[]> & {
+          data: {
+            pageNo: number;
+            pageSize: number;
+          };
+        },
+      >(
+        config: Config,
+      ): Alova2Method<moneyList[], "sys.selectFundChanges", Config>;
+      statisticsInfo<Config extends Alova2MethodConfig<money[]> & {}>(
+        config: Config,
+      ): Alova2Method<money[], "sys.statisticsInfo", Config>;
       staff<
         Config extends Alova2MethodConfig<{ records: sysStaff[] }> & {
           data: {
@@ -429,6 +442,33 @@ declare global {
 
   var Apis: Apis;
 }
+
+export interface moneyList {
+  amount_after: number;
+  amount_before: number;
+  amount_type: string;
+  change_amount: number;
+  change_reason: string;
+  create_time: string;
+  id: string;
+  income_expense_type: string;
+}
+
+export interface money {
+  /**
+   * 可用余额(元)
+   */
+  available: number;
+  /**
+   *已到账(元)
+   */
+  received: number;
+  /**
+   * 待结算(元)
+   */
+  toBeSettled: number;
+}
+
 /**
  * org.jeecg.modules.app.vo.AppIsinVerifyVO
  *

+ 49 - 18
src/components/classItem/index.vue

@@ -12,27 +12,58 @@
       <view class="max-w-500rpx">
         <wd-text :text="item.name" size="28rpx" :lines="1"></wd-text>
       </view>
-      <commonbtn bg-color="#0074FF" @click="handlePic(String(item.id))" v-if="type == 0 && showBtn">拍照核销</commonbtn>
-      <commonbtn bg-color="#0074FF" @click="handleGoPath(`/subPack/ReservationClass/index?id=${item.id}`)"
-        v-if="type == 1 && showBtn">预约这节</commonbtn>
-      <commonbtn bg-color="#0074FF" @click="handlePost" v-if="type == 2 && showBtn">延期这节</commonbtn>
-      <view v-if="!showBtn">
-        <view class="text-gray font-semibold text-28rpx" v-if="item.orDone">已完成</view>
-        <view v-else class="text-[#0074FF] font-semibold text-28rpx">未开始</view>
+      <view v-if="item.unwrittenOffNum">
+        <commonbtn
+          bg-color="#0074FF"
+          @click="handlePic(String(item.id))"
+          v-if="(type == 0 && showBtn) || item.unwrittenOffNum"
+          >拍照核销</commonbtn
+        >
+        <commonbtn
+          bg-color="#0074FF"
+          @click="handleGoPath(`/subPack/ReservationClass/index?id=${item.id}`)"
+          v-if="type == 1 && showBtn"
+          >预约这节</commonbtn
+        >
+        <commonbtn
+          bg-color="#0074FF"
+          @click="handlePost"
+          v-if="type == 2 && showBtn"
+          >延期这节</commonbtn
+        >
       </view>
+      <template v-else>
+        <view v-if="!showBtn">
+          <view class="text-gray font-semibold text-28rpx" v-if="item.orDone"
+            >已完成</view
+          >
+          <view v-else class="text-[#0074FF] font-semibold text-28rpx"
+            >未开始</view
+          >
+        </view>
+      </template>
     </view>
-    <view class="mt20rpx pl20rpx flex items-center text-24rpx" @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>
-      <view class="text-[rgb(0,0,0,0.3)] mr20rpx">延课
-        <text class="text-#0074FF"> {{ item.postponeNum }} </text> 人
+    <view
+      class="mt20rpx pl20rpx flex items-center text-24rpx"
+      @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
+      >
+      <view class="text-[rgb(0,0,0,0.3)] mr20rpx"
+        >延课 <text class="text-#0074FF"> {{ item.postponeNum }} </text> 人
       </view>
-      <view class="text-[rgb(0,0,0,0.3)] mr20rpx">已核销<text class="text-#0074FF"> {{ item.writtenOffNum }} </text>人
+      <view class="text-[rgb(0,0,0,0.3)] mr20rpx"
+        >已核销<text class="text-#0074FF"> {{ item.writtenOffNum }} </text>人
       </view>
-      <view class="text-[rgb(0,0,0,0.3)]">未核销<text class="text-#0074FF"> {{ item.unwrittenOffNum }} </text>人</view>
+      <view class="text-[rgb(0,0,0,0.3)]"
+        >未核销<text class="text-#0074FF"> {{ item.unwrittenOffNum }} </text
+        >人</view
+      >
     </view>
   </view>
 </template>
@@ -58,7 +89,7 @@ function handleGoPath(url: string) {
 }
 function handlePic(id: string) {
   if (props.item?.unwrittenOffNum == 0)
-    return uni.showToast({ title: "全部学生已核销" });
+    return uni.showToast({ title: "全部学生已核销", icon: "none" });
   handleGoPath(`/subPack/selectClass/index?id=${id}`);
 }
 function handlePost() {

+ 2 - 2
src/config/index.ts

@@ -4,8 +4,8 @@ const mapEnvVersion = {
    */
   // develop: "http://192.168.1.166:8080/jeecg-boot",
   // develop: "http://192.168.0.217:8080/jeecg-boot",
-  develop: "https://api.qlapp.cn/jeecgboot",
-  // develop: "http://192.168.0.11:8080/jeecg-boot",
+  // develop: "https://api.qlapp.cn/jeecgboot",
+  develop: "http://192.168.0.11:8080/jeecg-boot",
   /**
    * 	体验版
    */

+ 1 - 1
src/pages.json

@@ -175,7 +175,7 @@
           "type": "page",
           "name": "selectClass",
           "style": {
-            "navigationBarTitleText": ""
+            "navigationBarTitleText": "点名"
           }
         },
         {

+ 2 - 2
src/subPack/EmployeeListAdd/components/CustomUpload/index.vue

@@ -25,15 +25,15 @@ const props = defineProps<{
   id?: string;
 }>();
 const emit = defineEmits(["click"]);
-const { dataId, img } = storeToRefs(useCameraStore());
+const { img } = storeToRefs(useCameraStore());
 function handleClick() {
   if (props.disabled) return;
   console.log("click");
   emit("click");
 }
 function deleteImg() {
-  dataId.value = String(props.id);
   img.value = "";
+  console.log("删除图片");
 }
 function handleImgClick() {
   uni.previewImage({

+ 2 - 1
src/subPack/ExtensionClass/index.vue

@@ -20,6 +20,7 @@
           v-model="isCheckAll"
           size="large"
           shape="square"
+          v-if="userList.length"
           @change="handleCheckAllChange"
           ><text class="font-semibold text-32rpx">全选</text>
         </wd-checkbox>
@@ -64,7 +65,7 @@ const priceRulesId = ref();
 const { send: getUserList, data: userList } = useRequest(
   (coursePriceRulesId) =>
     Apis.app.getClassPostponeUsers({ params: { coursePriceRulesId } }),
-  { immediate: false },
+  { immediate: false, initialData: [] },
 );
 const { data, send: getData } = useRequest(
   (id: string, coursesType: number) =>

+ 121 - 4
src/subPack/Storebalance/index.vue

@@ -10,7 +10,9 @@
       <view class="text-[rgb(0,0,0,0.3)] mt20rpx"
         >用户购买商品后,增加待结算,退款后,减少待结算。</view
       >
-      <view class="mt20rpx text-48rpx font-semibold">32,124.19</view>
+      <view class="mt20rpx text-48rpx font-semibold">{{
+        statisticsInfo?.toBeSettled
+      }}</view>
     </view>
     <view class="px24rpx py28rpx bg-#F6F6F6 rounded-32rpx mt20rpx">
       <view class="flex items-center">
@@ -19,7 +21,9 @@
       <view class="text-[rgb(0,0,0,0.3)] mt20rpx"
         >商品进行分账成功后,增加可用余额。</view
       >
-      <view class="mt20rpx text-48rpx font-semibold">32,124.19</view>
+      <view class="mt20rpx text-48rpx font-semibold">{{
+        statisticsInfo?.available
+      }}</view>
     </view>
     <view class="px24rpx py28rpx bg-#F6F6F6 rounded-32rpx mt20rpx">
       <view class="flex items-center">
@@ -28,12 +32,125 @@
       <view class="text-[rgb(0,0,0,0.3)] mt20rpx"
         >分账成功后,T+1到账后,增加已到账。</view
       >
-      <view class="mt20rpx text-48rpx font-semibold">32,124.19</view>
+      <view class="mt20rpx text-48rpx font-semibold">{{
+        statisticsInfo?.received
+      }}</view>
     </view>
+    <template v-for="item in listData" :key="item.date">
+      <view
+        class="px24rpx py28rpx bg-#F6F6F6 rounded-32rpx mt20rpx flex items-center justify-between"
+      >
+        <view>
+          {{ dayjs(item.date).format("MM-DD") }}
+        </view>
+        <view class="flex items-center">
+          <view class="mr40rpx flex items-center">
+            <image :src="chu" class="w-40rpx h40rpx" />
+            <view class="ml16rpx">
+              {{ handleChu(item.child, "支出").toFixed(2) }}
+            </view>
+          </view>
+          <view class="flex items-center">
+            <image :src="ru" class="w-40rpx h40rpx" />
+            <view class="ml16rpx">
+              {{ handleChu(item.child, "收入").toFixed(2) }}
+            </view>
+          </view>
+        </view>
+      </view>
+      <view
+        class="flex items-center mt32rpx"
+        v-for="its in item.child"
+        :key="its.id"
+      >
+        <view class="w80rpx h80rpx min-w-80rpx">
+          <image
+            :src="its.income_expense_type == '支出' ? chu : ru"
+            class="w-full h-full"
+          />
+        </view>
+        <view class="ml28rpx flex items-center justify-between flex-1">
+          <view>
+            <view class="font-semibold text-32rpx">
+              {{ its.amount_type }}
+            </view>
+            <view class="flex items-center text-24rpx text-gray">
+              <view class="">
+                {{ dayjs(its.create_time).format("HH:mm") }}
+              </view>
+              <view class="mx-10rpx">|</view>
+              <view>{{ its.change_reason }} </view>
+            </view>
+          </view>
+          <view class="text-32rpx">
+            {{ its.income_expense_type == "支出" ? "-" : "+" }}¥{{
+              its.change_amount.toFixed(2)
+            }}
+          </view>
+        </view>
+      </view>
+    </template>
   </view>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+import type { money, moneyList } from "@/api/globals";
+import chu from "@/subPack/static/chu.png";
+import ru from "@/subPack/static/ru.png";
+import dayjs from "dayjs";
+
+const statisticsInfo = ref<money>();
+async function getData() {
+  const res = await Apis.sys.statisticsInfo({});
+  statisticsInfo.value = res[0];
+}
+
+const {
+  data: list,
+  page,
+  isLastPage,
+} = usePagination(
+  (pageNo, pageSize) =>
+    Apis.sys.selectFundChanges({ data: { pageNo, pageSize } }),
+  { append: true },
+);
+getData();
+const listData = computed(() => {
+  return groupDataByDate(list.value as moneyList[]);
+});
+function groupDataByDate(
+  dataArray: moneyList[],
+): { date: string; child: moneyList[] }[] {
+  const grouped: Record<string, { date: string; child: moneyList[] }> = {};
+  dataArray.forEach((item) => {
+    const dateKey = dayjs(item.create_time).format("YYYY-MM-DD");
+
+    // 如果该日期还不存在,初始化分组
+    if (!grouped[dateKey]) {
+      grouped[dateKey] = {
+        date: dateKey,
+        child: [],
+      };
+    }
+
+    grouped[dateKey].child.push(item);
+  });
+
+  return Object.values(grouped).sort(
+    (a: any, b: any) => dayjs(b.date).valueOf() - dayjs(a.date).valueOf(),
+  );
+}
+function handleChu(item: moneyList[], type: string) {
+  return item
+    .filter((it) => it.income_expense_type == type)
+    .reduce((sum, it) => sum + (it.change_amount || 0), 0);
+}
+onReachBottom(() => {
+  if (!isLastPage.value) {
+    page.value++;
+  }
+});
+</script>
 
 <style lang="scss">
 .page-wraper {

+ 62 - 37
src/subPack/selectClass/index.vue

@@ -1,29 +1,54 @@
 <template>
-  <view class="px32rpx py20rpx" v-if="data">
-    <view class="text-28rpx">未核销<text class="text-#0074FF">{{ data.length }}</text>人</view>
+  <view class="py20rpx" v-if="data">
+    <wd-card title="选择到场的学生">
+      <view class="mb20rpx">
+        <wd-checkbox
+          v-model="isCheckAll"
+          size="large"
+          shape="square"
+          v-if="data.length"
+          @change="handleCheckAllChange"
+          ><text class="font-semibold text-32rpx">全选</text>
+        </wd-checkbox>
+      </view>
+      <wd-checkbox-group
+        v-model="checkedAll"
+        inline
+        size="large"
+        shape="square"
+      >
+        <wd-checkbox
+          :modelValue="item.useUserId"
+          v-for="item in data"
+          :key="item.id"
+          >{{ item.useUserName }}</wd-checkbox
+        >
+      </wd-checkbox-group>
 
-    <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">{{ item.useUserName }}</view>
-      <upload :disabled="false" @click="handleGoCamera(item)" :imgUrl="item.verifyImage" :key="item.id" :id="item.id">
-      </upload>
-    </view>
-    <view class="h-180rpx"></view>
+      <view class="mt40rpx">
+        <upload :disabled="false" :imgUrl="img" @click="handleGoCamera">
+        </upload>
+      </view>
+      <template #footer></template>
+    </wd-card>
   </view>
-  <fixdbtn block size="large" @click="handleSubmit" :loading="loading">提交</fixdbtn>
+  <fixdbtn block size="large" @click="handleSubmit" :loading="loading"
+    >提交</fixdbtn
+  >
 </template>
 
 <script setup lang="ts">
-import type { AppCoursesVerificationRecord } from "@/api/globals";
 import router from "@/router";
 import upload from "@/subPack/EmployeeListAdd/components/CustomUpload/index.vue";
-const { dataId, img } = storeToRefs(useCameraStore());
+const { img } = storeToRefs(useCameraStore());
+const checkedAll = ref<string[]>([]);
+const isCheckAll = ref(false);
 const { data, send: getList } = useRequest(
   (data) =>
     Apis.app.courseQueryUsers({
       data,
     }),
-  { immediate: false },
+  { immediate: false, initialData: [] },
 );
 const { send: setFormData, loading } = useRequest(
   (data) =>
@@ -32,20 +57,29 @@ const { send: setFormData, loading } = useRequest(
     }),
   { immediate: false },
 );
+function handleCheckAllChange() {
+  if (isCheckAll.value) {
+    checkedAll.value = data.value.map((it) => it.useUserId) as string[];
+  } else {
+    checkedAll.value = [];
+  }
+}
 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 (!checkedAll.value.length)
+    return uni.showToast({ title: "请勾选到场学生", icon: "none" });
+  if (!img.value) return uni.showToast({ title: "请上传图片", icon: "none" });
+  const form = checkedAll.value.map((it) => {
+    return {
+      id: it,
+      verifyImage: img.value,
+    };
+  });
+
   if (!form.length)
     return uni.showToast({ title: "最少核销一个人", icon: "none" });
   const formData = {
@@ -61,29 +95,20 @@ async function handleSubmit() {
   });
   setTimeout(() => {
     router.back();
+    img.value = "";
   }, 2000);
 }
-function handleGoCamera(item: AppCoursesVerificationRecord) {
-  dataId.value = String(item.id);
+function handleGoCamera() {
   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">{
+<route lang="json">
+{
   "name": "selectClass",
   "style": {
-    "navigationBarTitleText": ""
+    "navigationBarTitleText": "点名"
   }
-}</route>
+}
+</route>

+ 0 - 0
src/subPack/static/组 8746@3x.png → src/subPack/static/chu.png


+ 0 - 0
src/subPack/static/组 8747@3x.png → src/subPack/static/ru.png


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

@@ -2,13 +2,11 @@ import { defineStore } from "pinia";
 
 interface useCameraStore {
   img: string;
-  dataId: string;
 }
 export const useCameraStore = defineStore({
   id: "app-useCameraStore",
   state: (): useCameraStore => ({
     img: "",
-    dataId: "",
   }),
   getters: {},
   actions: {