Bläddra i källkod

feat: 完善手机端周日报

qiny 1 år sedan
förälder
incheckning
81494fe51c

+ 2 - 2
client_h5/.env.dev

@@ -1,7 +1,7 @@
 # VITE_BASE_URL='http://10.10.10.7:18080/'
-VITE_BASE_URL='http://localhost:6090/'
+VITE_BASE_URL='http://10.10.10.7:48080/'
 
 # File上传路径
 VITE_FILE_BASE_URI='/admin-api/infra/file'
 
-VITE_AUTHORIZATION='test83e06d0d-af60-4419-9437-c9a68bf1b669'
+VITE_AUTHORIZATION='7066084402244d5facfec42a9c07ea48'

+ 2 - 2
client_h5/src/components/UserSelect/index.vue → client_h5/src/components/SelectTree/index.vue

@@ -17,7 +17,7 @@
         label-align="left"
         v-model="data.treeParam"
         show-action
-        placeholder="请输入接收人"
+        placeholder="请输入关键字进行搜索"
         @search="searchTreeByParam"
       >
         <template #action>
@@ -69,7 +69,7 @@
 
 <script setup>
 /**
- * @description 用户选择组件
+ * @description 移动端树选择组件
  * @author chinyan
  */
 import { reactive, watch, ref, nextTick, onMounted } from "vue";

+ 0 - 0
client_h5/src/components/UserSelect/tree.vue → client_h5/src/components/SelectTree/tree.vue


+ 61 - 0
client_h5/src/components/UserSelect.vue

@@ -0,0 +1,61 @@
+<template>
+  <SelectTree
+    ref="userTreeSelectRef"
+    v-model="receiveUserIds"
+    :listData="listData"
+    placeholder="请选择接收人"
+  />
+</template>
+<script lang="ts" setup>
+/**
+ * @description 用户选择组件
+ */
+import { ref } from "vue";
+import SelectTree from "./SelectTree/index.vue";
+import { getUserInfo } from "@/utils/tools";
+import request from "@/utils/request";
+// 用户树的ref
+const userTreeSelectRef = ref(null);
+const receiveUserIds = ref([]);
+const listData = ref<any[]>([]);
+onMounted(async () => {
+  const dataSource = await getAllUser();
+  listData.value = transformUserListToTree(dataSource);
+});
+const userInfo = getUserInfo();
+const getAllUser = async () => {
+  const params = { userId: userInfo.id };
+  const result: any = await request.get("/business/common/user-tree", {
+    params,
+  });
+  return result;
+};
+// 写一个方法,把人员及部门的列表数据转换为树状数据
+const transformUserListToTree = (arr: any) => {
+  const map: any = {};
+  const roots: any = [];
+  // 将数组转换为以id为key的对象
+  arr.forEach((item: any) => {
+    map[item.id] = { ...item, label: item.name, children: [] };
+  });
+  // 将子节点挂载到父节点的children字段下
+  arr.forEach((item: any) => {
+    const node = {
+      id: map[item.id].id,
+      name: map[item.id].name,
+      value: map[item.id].id,
+      children: map[item.id].children ?? [],
+    };
+    if (item.pid && map[item.pid]) {
+      map[item.pid].children.push(node);
+    } else if (item.pid) {
+      // console.log(`找不到对应id的父节点,删除数据: ${item.name}`)
+      // delete map[item.id]
+    } else {
+      roots.push(node);
+    }
+  });
+  return roots;
+};
+</script>
+<style scoped lang="scss"></style>

+ 2 - 0
client_h5/src/pages/home/index.vue

@@ -32,10 +32,12 @@ const navMenus: MenuItem[] = [
   {
     title: "我的日志",
     icon: "../assets/images/wdrz_icon.png",
+    path: "myDailyLogs",
   },
   {
     title: "我的周报",
     icon: "../assets/images/wdrz_icon.png",
+    path: "myWeeklyLogs",
   },
 ];
 interface ProcessMenuItem extends MenuItem {

+ 57 - 0
client_h5/src/pages/myLogs/Daily/MyLogs.vue

@@ -0,0 +1,57 @@
+<template>
+  <div class="log-calendar">
+    <van-calendar
+      title="手机端仅能查看近两个月填报情况"
+      :poppable="false"
+      :show-confirm="false"
+      :style="{ height: '500px' }"
+      :min-date="minDate"
+      :max-date="maxDate"
+      :formatter="formatter"
+      @confirm="jumpDetail"
+    />
+  </div>
+</template>
+<script lang="ts" setup>
+import moment from "moment";
+import { http } from "../http";
+/**
+ * @description 日报详情
+ */
+// 限制日期范围
+const minDate = moment().subtract(2, "months").startOf("month").toDate();
+const maxDate = moment().toDate();
+
+const logObj = ref<any>({});
+onMounted(async () => {
+  const logList = await http.getLogList("daily");
+  logList.map((item: any) => {
+    const date = item.reportStartDate;
+    logObj.value[date] = item;
+  });
+});
+// 格式化日历
+const formatter = (day: any) => {
+  const date = moment(day.date).format("YYYY-MM-DD");
+  if (logObj.value[date]) {
+    day.topInfo = "已填";
+  }
+  return day;
+};
+// 跳转到日志填写或详情页面
+const { push } = useRouter();
+const jumpDetail = (day: any) => {
+  const date = moment(day).format("YYYY-MM-DD");
+  if (logObj.value[date]) {
+    const detail = logObj.value[date];
+    push(`/logsDetail?id=${detail.id}`);
+  } else {
+    push(`/daily?date=${day}`);
+  }
+};
+</script>
+<style scoped lang="scss">
+.log-calendar {
+  padding: 20px 10px;
+}
+</style>

+ 73 - 61
client_h5/src/pages/myLogs/Daily/index.vue

@@ -1,17 +1,13 @@
 <template>
   <div class="container">
     <div class="timer">
-      <van-cell
-        title="日志日期"
-        :value="formData.date"
-        @click="calendarShow = true"
-      />
-      <van-calendar
+      <van-cell title="日志日期" :value="today" @click="calendarShow = true" />
+      <!-- <van-calendar
         v-model:show="calendarShow"
         @confirm="onConfirm"
         :min-date="minDate"
         :max-date="maxDate"
-      />
+      /> -->
     </div>
     <div class="title">今日工作</div>
     <van-field
@@ -20,17 +16,11 @@
       autosize
       type="textarea"
       placeholder=""
-      show-word-limit
     />
     <div class="title">工作量分配</div>
     <ProjectList type="daily" :onChange="projectChange" />
     <div class="title">接收人</div>
-    <UserTree
-      ref="treeSelectRef"
-      v-model="formData.receiveUserIds"
-      :listData="transformUserListToTree(userList)"
-      placeholder="请选择接收人"
-    />
+    <SelectUser v-model="formData.receiveUserIds" />
     <van-button round type="primary" block class="send-btn" @click="onSubmit"
       >发送</van-button
     >
@@ -42,87 +32,109 @@
  */
 import moment from "moment";
 import { reactive } from "vue";
-import { userList } from "../mock";
-import UserTree from "@/components/UserSelect/index.vue";
+import { showSuccessToast, showToast } from "vant";
+import SelectUser from "@/components/UserSelect.vue";
 import ProjectList from "../components/ProjectList.vue";
 import { getUserInfo } from "@/utils/tools";
-
-const treeSelectRef = ref(null);
-
-// 写一个方法,把人员及部门的列表数据转换为树状数据
-const transformUserListToTree = (arr: any) => {
-  const map: any = {};
-  const roots: any = [];
-  // 将数组转换为以id为key的对象
-  arr.forEach((item: any) => {
-    map[item.id] = { ...item, label: item.name, children: [] };
-  });
-  // 将子节点挂载到父节点的children字段下
-  arr.forEach((item: any) => {
-    const node = {
-      id: map[item.id].id,
-      name: map[item.id].name,
-      value: map[item.id].id,
-      children: map[item.id].children ?? [],
-    };
-    if (item.pid && map[item.pid]) {
-      map[item.pid].children.push(node);
-    } else if (item.pid) {
-      // console.log(`找不到对应id的父节点,删除数据: ${item.name}`)
-      // delete map[item.id]
-    } else {
-      roots.push(node);
-    }
-  });
-  return roots;
-};
-
+import { http } from "../http";
+import { IReport } from "../interface";
+import { onSubmitCheck } from "../service";
 interface FormData {
-  date: string | Date; // 日期
   reportContent: string; // 内容
   weeklyWorkloadList: {
     projectId: string;
     workTime: number;
   }[]; // 工作量分配
   receiveUserIds: string[]; // 接收人
+  reportYear: string | number; // 年
+  reportMonth: string | number; // 月
+  reportWeek: string | number; // 周
+  reportDay: string | number; // 本周第几天
 }
 
-const currentDate = moment().format("YYYY-MM-DD");
+// 页面数据
+const today = ref(moment().format("YYYY-MM-DD"));
 const formData = reactive<FormData>({
-  date: currentDate,
   reportContent: "",
   weeklyWorkloadList: [],
   receiveUserIds: [],
+  reportYear: moment().format("YYYY"),
+  reportMonth: moment().format("MM"),
+  reportWeek: 0,
+  reportDay: 0,
+});
+
+const route = useRoute();
+onMounted(async () => {
+  // 查看有没有传日期进来
+  const query: any = route.query;
+  if (query.date) {
+    today.value = moment(query.date).format("YYYY-MM-DD");
+  }
+  // 回填历史接收人
+  const receiveUser = await http.getReceiveUser();
+  formData.receiveUserIds = receiveUser;
+  // 获取当日详情
+  await getIsWorkDays();
 });
 
 // 日历
 const calendarShow = ref(false);
-const onConfirm = (value: any) => {
-  formData.date = moment(value).format("YYYY-MM-DD");
-  calendarShow.value = false;
-};
+// const onConfirm = async (value: any) => {
+//   today.value = moment(value).format("YYYY-MM-DD");
+//   calendarShow.value = false;
+//   // 获取当日详情
+//   await getIsWorkDays();
+// };
 
 // 可选择的最小日期,一个月之前,最大日期:今天
-const minDate = moment().subtract(1, "months").toDate();
-const maxDate = moment().toDate();
+// const minDate = moment().subtract(1, "months").toDate();
+// const maxDate = moment().toDate();
 
 // 工作量改变
 const projectChange = (data: any) => {
   formData.weeklyWorkloadList = data;
 };
+
 // 提交
-const onSubmit = () => {
+const { push } = useRouter();
+const onSubmit = async () => {
   const userInfo = getUserInfo();
-  const params = {
+  const params: IReport = {
     ...formData,
     reportType: "daily",
     userId: userInfo.id ?? "",
     deptId: userInfo.deptId ?? "",
-    reportStartDate: moment(formData.date).valueOf(),
-    reportEndDate: moment(formData.date).valueOf(),
+    reportStartDate: moment(today.value).valueOf(),
+    reportEndDate: moment(today.value).valueOf(),
     isTemp: false,
   };
-  console.log("submit-params", params);
+  const submitCheck = onSubmitCheck(params);
+  if (!submitCheck.success) {
+    showToast({
+      message: submitCheck.msg,
+      position: "top",
+    });
+  }
+  const result: any = await http.submitReport(params);
+  if (result.msg == "success") {
+    showSuccessToast("发送成功");
+    push(`/logsDetail?id=${result.data}`);
+  }
+};
+
+// 获取是否工作日
+const getIsWorkDays = async (date?: string) => {
+  const searchDate = date ?? today.value;
+  const startDate = moment(searchDate).format("YYYY-MM-DD HH:mm:ss");
+  const endDate = moment(searchDate).format("YYYY-MM-DD HH:mm:ss");
+  const workDays = await http.getWorkDayList(startDate, endDate);
+  if (workDays.length > 0) {
+    formData.reportYear = workDays[0].year;
+    formData.reportMonth = workDays[0].month;
+    formData.reportWeek = workDays[0].week;
+    formData.reportDay = workDays[0].dayOfWeek;
+  }
 };
 </script>
 <style lang="scss" scoped>

+ 235 - 0
client_h5/src/pages/myLogs/Weekly/MyLogs.vue

@@ -0,0 +1,235 @@
+<template>
+  <div class="log-list">
+    <div class="title">
+      <div class="check-btn" @click="checkMonth('prev')">
+        <van-icon name="arrow-left" />
+      </div>
+      <div class="show-month">{{ moment(thisMonth).format("YYYY年M月") }}</div>
+      <div class="check-btn" @click="checkMonth('next')">
+        <van-icon name="arrow" />
+      </div>
+    </div>
+    <div class="content">
+      <div
+        v-for="(item, index) in thisMonthLogs"
+        :key="item.week"
+        class="week-item"
+        :style="{ backgroundColor: item.bgColor }"
+        @click="goToWeeklyPage(item)"
+      >
+        <div class="left">
+          <span class="week-title">{{ weekTitleList[index] }}</span>
+          <span class="week-date">{{
+            `${moment(item.startDate).format("M月D日")} ~ ${moment(
+              item.endDate
+            ).format("M月D日")}`
+          }}</span>
+        </div>
+        <div class="right">
+          <div class="icon-box" :style="{ backgroundColor: item.color }">
+            <van-icon :name="item.icon" />
+          </div>
+          <span>{{ item.type }}</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+/**
+ * @description 周报列表详情
+ * 先获取到最近三个月的工作日列表详情,对工作日进行周数划分
+ * 再获取近三个月的周报列表,对周报填报情况进行判断
+ */
+import moment from "moment";
+import { showToast } from "vant";
+import { http } from "../http";
+import {
+  getMonthRange,
+  setWorkDayListToWeek,
+  mergeWorkDayAndLogs,
+} from "../service";
+
+// 所有的周日报和周划分
+const allWorkDayListAndLogs = ref<any>([]);
+// 本月日期
+const thisMonth = ref<any>(moment());
+// 本月的周日报和周划分
+const thisMonthLogs = ref<any>([]);
+
+// 获取工作日列表
+onMounted(async () => {
+  const workObj = await workDayList();
+  const logList = await http.getLogList("weekly");
+  allWorkDayListAndLogs.value = mergeWorkDayAndLogs(workObj, logList);
+  initPageList();
+});
+
+// 仅能查看近三个月的
+const checkMonth = (type: "prev" | "next") => {
+  if (
+    type == "prev" &&
+    moment(thisMonth.value).format("YYYY-MM") ==
+      moment().subtract(2, "month").format("YYYY-MM")
+  ) {
+    showToast({
+      message: "仅能查看近三个月的周报,更多请登录OA进行查看!",
+      position: "top",
+    });
+    return;
+  }
+  if (
+    type == "next" &&
+    moment(thisMonth.value).format("YYYY-MM") == moment().format("YYYY-MM")
+  ) {
+    showToast({
+      message: "还未到下个月!",
+      position: "top",
+    });
+    return;
+  }
+  if (type == "prev") {
+    thisMonth.value = moment(thisMonth.value).subtract(1, "month");
+  } else {
+    thisMonth.value = moment(thisMonth.value).add(1, "month");
+  }
+  initPageList();
+};
+// 获取最近三个月的工作日列表详情
+const workDayList = async () => {
+  const monthRange = getMonthRange();
+  const workDays = await http.getWorkDayList(monthRange[0], monthRange[1]);
+  const workDayObj = setWorkDayListToWeek(workDays);
+  return workDayObj;
+};
+
+// 合并工作日和周报列表并初始化页面
+const initPageList = () => {
+  const pointer = moment(thisMonth.value).format("YYYY-M");
+  thisMonthLogs.value = allWorkDayListAndLogs.value[pointer].map(
+    (item: any) => {
+      // 默认未填
+      item.type = statusObj[1].type;
+      item.icon = statusObj[1].icon;
+      item.color = statusObj[1].color;
+      item.bgColor = statusObj[1].bgColor;
+      // 如果本周
+      const startOfWeek = moment().startOf("week");
+      const endOfWeek = moment().endOf("week");
+      if (moment(item.startDate).isBetween(startOfWeek, endOfWeek)) {
+        item.icon = statusObj[2].icon;
+        item.color = statusObj[2].color;
+        item.bgColor = statusObj[2].bgColor;
+        item.type = statusObj[2].type;
+      }
+      // 如果已填
+      if (item.isLog.length > 0) {
+        const isLog = item.isLog[0];
+        item.id = isLog.id;
+        item.icon = statusObj[0].icon;
+        item.color = statusObj[0].color;
+        item.bgColor = statusObj[0].bgColor;
+        item.type = statusObj[0].type;
+      }
+      // 如果未到
+      if (moment(item.startDate).isAfter(moment())) {
+        item.icon = statusObj[3].icon;
+        item.color = statusObj[3].color;
+        item.bgColor = statusObj[3].bgColor;
+        item.type = statusObj[3].type;
+      }
+      return item;
+    }
+  );
+};
+// 颜色列表
+const weekTitleList = ["第一周", "第二周", "第三周", "第四周", "第五周"];
+const statusObj = [
+  { type: "已填", icon: "success", color: "#0ACE9D", bgColor: "#EAF8F4" },
+  { type: "未填", icon: "cross", color: "#F85638", bgColor: "#F8EAEA" },
+  { type: "待填", icon: "edit", color: "#1B80EB", bgColor: "#DDEDFD" },
+  { type: "未到", icon: "ellipsis", color: "#BDC7CE", bgColor: "#F2F6FA" },
+];
+// 跳转到周报填写或详情页面
+const { push } = useRouter();
+const goToWeeklyPage = (item: any) => {
+  if (item.id) {
+    push(`/logsDetail?id=${item.id}`);
+  } else if (item.type == "未到") {
+    showToast({
+      message: "还未到这周!",
+      position: "top",
+    });
+  } else {
+    push(`/weekly?startDate=${item.startDate}&endDate=${item.endDate}`);
+  }
+};
+</script>
+<style scoped lang="scss">
+.log-list {
+  padding: 20px 10px;
+  .title {
+    height: 48px;
+    line-height: 48px;
+    background: #f3f6fa;
+    border-radius: 40px 40px 40px 40px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    .show-month {
+    }
+    .check-btn {
+      width: 30px;
+      height: 30px;
+      background-color: #fff;
+      border-radius: 50%;
+      line-height: 30px;
+      text-align: center;
+      font-size: 18px;
+      margin: 0 10px;
+      color: #5b656f;
+    }
+  }
+  .content {
+    margin-top: 30px;
+    .week-item {
+      height: 50px;
+      line-height: 50px;
+      margin-bottom: 15px;
+      border-radius: 4px 4px 4px 4px;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 0 10px;
+      .week-title {
+        font-weight: bold;
+        font-size: 16px;
+        color: #2b333c;
+      }
+      .week-date {
+        font-weight: 400;
+        font-size: 15px;
+        color: #2b333c;
+        margin-left: 12px;
+      }
+      .right {
+        display: flex;
+        height: 50px;
+        justify-content: space-between;
+        align-items: center;
+        padding-right: 5px;
+      }
+      .icon-box {
+        width: 20px;
+        height: 20px;
+        border-radius: 50%;
+        line-height: 20px;
+        text-align: center;
+        font-size: 12px;
+        margin: 0 10px;
+        color: #fff;
+      }
+    }
+  }
+}
+</style>

+ 79 - 60
client_h5/src/pages/myLogs/Weekly/index.vue

@@ -3,16 +3,16 @@
     <div class="timer">
       <van-cell
         title="周报时间"
-        :value="formData.date"
+        :value="thisWeek"
         @click="calendarShow = true"
       />
-      <van-calendar
+      <!-- <van-calendar
         v-model:show="calendarShow"
         @confirm="onConfirm"
         type="range"
         :min-date="minDate"
         :max-date="maxDate"
-      />
+      /> -->
     </div>
 
     <div class="title">本周工作</div>
@@ -22,17 +22,11 @@
       autosize
       type="textarea"
       placeholder=""
-      show-word-limit
     />
     <div class="title">工作量分配</div>
     <ProjectList type="weekly" :onChange="projectChange" />
     <div class="title">接收人</div>
-    <UserTree
-      ref="treeSelectRef"
-      v-model="formData.receiveUserIds"
-      :listData="transformUserListToTree(userList)"
-      placeholder="请选择接收人"
-    />
+    <SelectUser v-model="formData.receiveUserIds" />
     <van-button round type="primary" block class="send-btn" @click="onSubmit"
       >发送</van-button
     >
@@ -44,85 +38,88 @@
  */
 import moment from "moment";
 import { reactive } from "vue";
-import { userList } from "../mock";
-import UserTree from "@/components/UserSelect/index.vue";
+import { showSuccessToast, showToast } from "vant";
+import SelectUser from "@/components/UserSelect.vue";
 import ProjectList from "../components/ProjectList.vue";
 import { getUserInfo } from "@/utils/tools";
-const treeSelectRef = ref(null);
-
-// 写一个方法,把人员及部门的列表数据转换为树状数据
-const transformUserListToTree = (arr: any) => {
-  const map: any = {};
-  const roots: any = [];
-  // 将数组转换为以id为key的对象
-  arr.forEach((item: any) => {
-    map[item.id] = { ...item, label: item.name, children: [] };
-  });
-  // 将子节点挂载到父节点的children字段下
-  arr.forEach((item: any) => {
-    const node = {
-      id: map[item.id].id,
-      name: map[item.id].name,
-      value: map[item.id].id,
-      children: map[item.id].children ?? [],
-    };
-    if (item.pid && map[item.pid]) {
-      map[item.pid].children.push(node);
-    } else if (item.pid) {
-      // console.log(`找不到对应id的父节点,删除数据: ${item.name}`)
-      // delete map[item.id]
-    } else {
-      roots.push(node);
-    }
-  });
-  return roots;
-};
+import { http } from "../http";
+import { IReport } from "../interface";
+import { onSubmitCheck } from "../service";
 
 interface FormData {
-  date: string; // 日期
   reportContent: string; // 内容
   weeklyWorkloadList: {
     projectId: string;
     workTime: number;
   }[]; // 工作量分配
   receiveUserIds: string[]; // 接收人
+  reportYear: string | number; // 年
+  reportMonth: string | number; // 月
+  reportWeek: string | number; // 周
 }
 
-const currentDate = `${moment().format("YYYY/MM/DD")} ~ ${moment().format(
-  "YYYY/MM/DD"
-)}`;
+// 页面数据
+const thisWeek = ref(
+  `${moment()
+    .startOf("week")
+    .subtract(1, "day")
+    .format("YYYY/MM/DD")} ~ ${moment()
+    .endOf("week")
+    .add(1, "day")
+    .format("YYYY/MM/DD")}`
+);
 const formData = reactive<FormData>({
-  date: currentDate,
   reportContent: "",
   weeklyWorkloadList: [],
   receiveUserIds: [],
+  reportYear: moment().format("YYYY"),
+  reportMonth: moment().format("MM"),
+  reportWeek: 0,
+});
+
+const route = useRoute();
+onMounted(async () => {
+  // 查看有没有传日期进来
+  const query: any = route.query;
+  if (query.startDate && query.endDate) {
+    thisWeek.value = `${moment(query.startDate).format(
+      "YYYY/MM/DD"
+    )} ~ ${moment(query.endDate).format("YYYY/MM/DD")}`;
+  }
+  const receiveUser = await http.getReceiveUser();
+  formData.receiveUserIds = receiveUser;
+  // 获取当日详情
+  await getIsWorkDays();
 });
 
 // 日历
 const calendarShow = ref(false);
-const onConfirm = (values: any) => {
-  const [start, end] = values;
-  formData.date = `${moment(start).format("YYYY/MM/DD")} ~ ${moment(end).format(
-    "YYYY/MM/DD"
-  )}`;
-  calendarShow.value = false;
-};
+// const onConfirm = async (values: any) => {
+//   const [start, end] = values;
+//   thisWeek.value = `${moment(start).format("YYYY/MM/DD")} ~ ${moment(
+//     end
+//   ).format("YYYY/MM/DD")}`;
+//   calendarShow.value = false;
+//   // 获取当日详情
+//   await getIsWorkDays();
+// };
 
 // 可选择的最小日期,一个月之前,最大日期:今天
-const minDate = moment().subtract(2, "months").toDate();
-const maxDate = moment().toDate();
+// const minDate = moment().subtract(2, "months").toDate();
+// const maxDate = moment().toDate();
 
 // 工作量改变
 const projectChange = (data: any) => {
   formData.weeklyWorkloadList = data;
 };
+
 // 提交
-const onSubmit = () => {
-  const date = formData.date.split(" ~ ");
+const { push } = useRouter();
+const onSubmit = async () => {
+  const date = thisWeek.value.split(" ~ ");
   const userInfo = getUserInfo();
-  const params = {
+  const params: IReport = {
     ...formData,
-    // date,
     reportType: "weekly",
     userId: userInfo.id ?? "",
     deptId: userInfo.deptId ?? "",
@@ -130,7 +127,29 @@ const onSubmit = () => {
     reportEndDate: moment(date[1]).valueOf(),
     isTemp: false,
   };
-  console.log("submit-params", params);
+  const submitCheck = onSubmitCheck(params);
+  if (!submitCheck.success) {
+    showToast({
+      message: submitCheck.msg,
+      position: "top",
+    });
+  }
+  const result: any = await http.submitReport(params);
+  if (result.msg == "success") {
+    showSuccessToast("发送成功");
+    push(`/logsDetail?id=${result.data}`);
+  }
+};
+// 获取是否工作日
+const getIsWorkDays = async (date?: string) => {
+  const searchDate = date?.split(" ~ ") ?? thisWeek.value.split(" ~ ");
+  const startDate = moment(searchDate[1]).format("YYYY-MM-DD HH:mm:ss");
+  const endDate = moment(searchDate[1]).format("YYYY-MM-DD HH:mm:ss");
+  const workDays = await http.getWorkDayList(startDate, endDate);
+  formData.reportYear = workDays[0].year;
+  formData.reportMonth = workDays[0].month;
+  formData.reportWeek = workDays[0].week;
+  // return workDays[0];
 };
 </script>
 <style lang="scss" scoped>

+ 255 - 0
client_h5/src/pages/myLogs/components/LogDetail.vue

@@ -0,0 +1,255 @@
+<template>
+  <div class="container">
+    <div class="timer">
+      <van-cell :title="pageTitle.dateTitle" :value="today" />
+    </div>
+    <div class="title">{{ pageTitle.contentTitle }}</div>
+    <van-field
+      v-model="formData.reportContent"
+      rows="5"
+      autosize
+      type="textarea"
+      readonly
+    />
+    <div class="title">工作量分配</div>
+    <div class="workload-list">
+      <van-highlight
+        v-for="item in formData.workloadList"
+        :key="item.id"
+        :keywords="`(${item.workTime}小时)`"
+        :source-string="`(${item.workTime}小时) ${item.projectName}`"
+      />
+    </div>
+    <div class="title">接收人</div>
+    <div class="receive-names">
+      <van-tag
+        plain
+        class="names-tag"
+        type="primary"
+        v-for="item in formData.receiveNames"
+        :key="item"
+        >{{ item }}</van-tag
+      >
+    </div>
+    <div class="title">评论</div>
+    <div class="comment-title">
+      {{
+        commentList.length === 0
+          ? "暂无评论"
+          : `共有${commentList.length}条评论`
+      }}
+    </div>
+    <div class="comment-content" v-if="commentList.length > 0">
+      <div
+        v-for="(item, index) in commentList"
+        :key="index"
+        class="comment-item"
+      >
+        <div class="left">
+          <span>{{ item.commentUserName.substr(-2) }}</span>
+        </div>
+        <div class="right">
+          <div class="title">
+            {{
+              `${item.commentUserName} ${moment(item.commentDate).format(
+                "YYYY-MM-DD HH:mm:ss"
+              )}`
+            }}
+            <span v-if="isMyComment(item)" @click="deleteComment(item)"
+              >删除</span
+            >
+            <span v-if="!item.isReply" @click="replyComment(item)">回复</span>
+            <span v-else @click="replyComment(item, true)">取消回复</span>
+          </div>
+          <div class="item-content">{{ item.commentContent }}</div>
+        </div>
+      </div>
+    </div>
+    <div class="comment-input" v-if="route.query?.id">
+      <div class="comment-emoji">
+        <van-tag
+          round
+          plain
+          color="#626b70"
+          type="primary"
+          size="large"
+          class="emoji-tag"
+          v-for="(item, index) in emojiList"
+          :key="index"
+          @click="submitComment(item)"
+          >{{ item }}</van-tag
+        >
+      </div>
+      <van-field
+        v-model="commentInput"
+        center
+        clearable
+        placeholder="请输入评论内容"
+      >
+        <template #button>
+          <van-button size="small" type="primary" @click="submitComment()"
+            >发送</van-button
+          >
+        </template>
+      </van-field>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+/**
+ * @description 周日报详情
+ */
+import moment from "moment";
+import { reactive } from "vue";
+import { showToast } from "vant";
+import { http } from "../http";
+import { isMyComment } from "../service";
+interface FormData {
+  reportContent: string; // 内容
+  workloadList: any[]; // 工作量分配
+  receiveNames: string[]; // 接收人
+}
+const route = useRoute();
+onMounted(() => {
+  initData();
+  initCommentList();
+});
+
+// 页面标题
+const pageTitle = ref({
+  type: "daily", // daily | weekly
+  dateTitle: "日志日期", // 日志日期 | 周报时间
+  contentTitle: "今日工作", // 今日工作 | 本周工作
+});
+
+// 页面数据
+const today = ref<string | Date>(moment().format("YYYY-MM-DD"));
+const formData = reactive<FormData>({
+  reportContent: "",
+  workloadList: [],
+  receiveNames: [],
+});
+
+// 初始化日志数据
+const initData = async () => {
+  const reportId: any = route.query?.id ?? "";
+  const result = await http.getLogDetail(reportId);
+
+  formData.reportContent = result.reportContent ?? "";
+  formData.workloadList = result.workload ?? [];
+  formData.receiveNames = result.receiveNames ?? [];
+  if (result.reportType == "weekly") {
+    // 重置标题
+    pageTitle.value = {
+      type: "weekly",
+      dateTitle: "周报时间",
+      contentTitle: "本周工作",
+    };
+    today.value = `${moment(result.reportStartDate).format(
+      "YYYY/MM/DD"
+    )} ~ ${moment(result.reportEndDate).format("YYYY/MM/DD")}`;
+  } else {
+    today.value = result.reportStartDate ?? today.value;
+  }
+};
+
+// 初始化评论
+const commentList = ref<any[]>([]);
+const initCommentList = async () => {
+  const reportId: any = route.query?.id ?? "";
+  const result = await http.getReportCommentList(reportId);
+  commentList.value =
+    result?.map((item: any) => ({
+      ...item,
+      isReply: false,
+    })) ?? [];
+};
+
+// 回复评论
+const replyComment = (data: any, isCancel?: boolean) => {
+  if (isCancel) {
+    commentInput.value = "";
+    commentList.value = commentList.value.map((item) => {
+      return {
+        ...item,
+        isReply: false,
+      };
+    });
+    return;
+  }
+  commentInput.value = `回复“${data.commentUserName}”:`;
+  commentList.value = commentList.value.map((item) => {
+    return {
+      ...item,
+      isReply: item.id == data.id,
+    };
+  });
+};
+
+// 删除评论
+const deleteComment = async (data: any) => {
+  const result: any = await http.deleteReportComment(data.id);
+  if (result.msg == "success") {
+    showToast({
+      message: "删除成功",
+      position: "top",
+    });
+    await initCommentList();
+  } else {
+    showToast({
+      message: "删除失败,请稍后重试!",
+      position: "top",
+    });
+  }
+};
+// 评论
+const commentInput = ref("");
+const emojiList = [
+  "赞!👍",
+  "写的太棒了!👏",
+  "学习了🌹",
+  "感谢你的分享🙏",
+  "加油💪",
+  "早点休息😴",
+];
+// 提交评论
+const submitComment = async (content?: string) => {
+  if (!content && commentInput.value == "") {
+    showToast({
+      message: "评论内容不能为空!",
+      position: "top",
+    });
+    return;
+  }
+  const params: any = {
+    reportId: route.query?.id ?? "",
+    commentContent: content ?? commentInput.value,
+  };
+  // 判断是否是回复评论
+  const isReply = commentList.value.find((item) => item.isReply === true);
+  if (isReply) {
+    // 回复评论,被回复人用户ID =
+    params.replyToUserid = isReply.commentUserId;
+  }
+  const result: any = await http.addReportComment(params);
+  if (result.msg == "success") {
+    showToast({
+      message: "评论成功!",
+      position: "top",
+    });
+    await initCommentList();
+    // 清空输入框
+    if (!content) {
+      commentInput.value = "";
+    }
+  } else {
+    showToast({
+      message: "评论失败,请稍后重试!",
+      position: "top",
+    });
+  }
+};
+</script>
+<style lang="scss" scoped>
+@import "../page.scss";
+</style>

+ 18 - 15
client_h5/src/pages/myLogs/components/ProjectList.vue

@@ -25,8 +25,7 @@
  * @description
  */
 import { ref } from "vue";
-import { projectList } from "../mock";
-
+import { http } from "../http";
 interface IProp {
   type: "weekly" | "daily";
   onChange: (data: any) => any;
@@ -34,17 +33,21 @@ interface IProp {
 
 const { type, onChange } = defineProps<IProp>();
 
-// 模拟项目数据
-const projectDataSource = projectList.map((item) => {
-  return {
-    projectId: item.id,
-    projectName: item.xmmc,
-    workTime: 0,
-    workTimeName: "",
-  };
+const dataSource = ref<any[]>([]);
+
+onMounted(async () => {
+  // 获取项目列表
+  const projectData: any = await http.getProjectList();
+  dataSource.value =
+    projectData?.map((item: any) => {
+      return {
+        projectId: item.id,
+        projectName: item.xmmc,
+        workTime: 0,
+        workTimeName: "",
+      };
+    }) ?? [];
 });
-console.log("projectDataSource", projectDataSource);
-const dataSource = ref(projectDataSource);
 
 // 生成工作时长数据
 const workTimeList = (type: string) => {
@@ -72,7 +75,7 @@ const projectClick = (project: any) => {
 };
 const timerClick = (timer: any) => {
   dataSource.value = dataSource.value
-    .map((item) => {
+    .map((item: any) => {
       if (item.projectId == activeProject.value) {
         item.workTime = timer.key;
         item.workTimeName = timer.title;
@@ -82,8 +85,8 @@ const timerClick = (timer: any) => {
     .sort((a, b) => b.workTime - a.workTime);
   active.value = 0;
   const weeklyWorkloadList = dataSource.value
-    .filter((item) => item.workTime > 0)
-    .map((item) => ({
+    .filter((item: any) => item.workTime > 0)
+    .map((item: any) => ({
       projectId: item.projectId,
       workTime: item.workTime,
     }));

+ 97 - 10
client_h5/src/pages/myLogs/http.ts

@@ -1,30 +1,117 @@
 import request from "@/utils/request";
 import { getUserInfo } from "@/utils/tools";
 import { IReport, IReportList } from "./interface";
+import { getNearThreeMonth } from "./service";
 // 周日报所有的请求接口
 const api = {
-  submit: "/adm/report/add-report-info", // 周日报提交、暂存
   list: "/adm/report/list", // 周日报列表
-  project: "/project/page", // 项目列表 '/business'
-  workDayList: "/adm/workday/list", // 获取工作日列表
+  submit: "/admin-api/adm/report/add-report-info", // 周日报提交、暂存
+  project: "/business/project/page", // 项目列表
+  workDayList: "/admin-api/adm/workday/list", // 获取工作日列表
+  receiveUser: "/admin-api/adm/report/recent-receive-user", // 获取最近接收人
+  logDetail: "/admin-api/adm/report/query-detail", // 获取周日报详情
+  reportCommentList: "/admin-api/adm/reportComment/getList", // 获取周日报评论列表
+  deleteComment: "/admin-api/adm/reportComment/delete", // 删除周日报评论
+  addComment: "/admin-api/adm/reportComment/send", // 添加周日报评论
+  logList: "/admin-api/adm/report/list", // 获取周报日志列表
 };
 
 const userInfo = getUserInfo();
-
 export const http = {
   // 请求周日报列表
-  getReportList: (params: IReportList) => {
-    return request.get(api.list, { params });
+  getReportList: async (params: IReportList) => {
+    return await request.get(api.list, { params });
   },
   // 提交、暂存周日报
-  submitReport: (params: IReport) => {
-    return request.post(api.submit, params);
+  submitReport: async (params: IReport) => {
+    return await request.post(api.submit, params);
   },
   // 项目列表
-  getProjectList: () => {
-    return request.get(api.project, {
+  getProjectList: async () => {
+    const params = {
       pageSize: -1,
       userId: userInfo.id ?? "",
+    };
+    const result: any = await request.get(api.project, { params });
+    return result.msg == "success" ? result.data.records : [];
+  },
+  // 获取工作日列表 YYYY-MM-DD HH:mm:ss
+  getWorkDayList: async (startDate: string, endDate: string) => {
+    // const result: any = await request.get(api.workDayList, {
+    //   data: { dateDay: [startDate, endDate] },
+    // });
+    // 用一种不太优雅的方式实现请求效果
+    const encodedStartDate = encodeURIComponent(startDate);
+    const encodedEndDate = encodeURIComponent(endDate);
+    const url = `${api.workDayList}?dateDay%5B0%5D=${encodedStartDate}&dateDay%5B1%5D=${encodedEndDate}`;
+    const result: any = await request.get(url);
+    return result.msg == "success" ? result.data : [];
+  },
+  // 获取接收人历史
+  getReceiveUser: async () => {
+    const params = { userId: userInfo.id };
+    const result: any = await request.get(api.receiveUser, { params });
+    return result.msg == "success" ? result.data : [];
+  },
+  // 获取周日报详情
+  getLogDetail: async (reportId: string) => {
+    const params = { reportId };
+    const result: any = await request.get(api.logDetail, { params });
+    return result.msg == "success" ? result.data : {};
+  },
+  // 获取周日报评论列表
+  getReportCommentList: async (reportId: string) => {
+    const uId = userInfo.id ?? "";
+    const params = { reportId, uId };
+    const result: any = await request.get(api.reportCommentList, { params });
+    return result.msg == "success" ? result.data : [];
+  },
+  // 删除周日报评论
+  deleteReportComment: async (reportCommentId: string) => {
+    return await request.delete(`${api.deleteComment}`, {
+      data: [reportCommentId],
     });
   },
+  // 添加周日报评论
+  addReportComment: async (params: any) => {
+    const commentUserId = userInfo.id ?? "";
+    return await request.post(api.addComment, { ...params, commentUserId });
+  },
+  // 获取近三个月周日报日志列表
+  getLogList: async (type: "weekly" | "daily") => {
+    const nearThreeMonth = getNearThreeMonth();
+    const requests = [
+      request.get(api.logList, {
+        params: {
+          reportType: type,
+          reportYear: nearThreeMonth[0].reportYear,
+          reportMonth: nearThreeMonth[0].reportMonth,
+          userId: userInfo.id ?? "",
+        },
+      }),
+      request.get(api.logList, {
+        params: {
+          reportType: type,
+          reportYear: nearThreeMonth[1].reportYear,
+          reportMonth: nearThreeMonth[1].reportMonth,
+          userId: userInfo.id ?? "",
+        },
+      }),
+      request.get(api.logList, {
+        params: {
+          reportType: type,
+          reportYear: nearThreeMonth[1].reportYear,
+          reportMonth: nearThreeMonth[1].reportMonth,
+          userId: userInfo.id ?? "",
+        },
+      }),
+    ];
+    const result: any = await Promise.all(requests);
+    const allLog = [
+      ...(result?.[0].data ?? []),
+      ...(result?.[1].data ?? []),
+      ...(result?.[2].data ?? []),
+    ];
+    return allLog;
+  },
 };

+ 12 - 9
client_h5/src/pages/myLogs/interface.ts

@@ -2,19 +2,22 @@
 export interface IReport {
   deptId?: string; // 部门id
   fillingDate?: string; // 填写日期
-  isTemp?: string; // 是否暂存
-  receiveUserIds?: string; // 接收人用户ID
+  isTemp?: boolean; // 是否暂存
+  receiveUserIds?: string[]; // 接收人用户ID
   reportContent?: string; // 内容
-  reportDay?: string; // 报告天
-  reportEndDate?: string; // 日报周报结束日期
+  reportDay?: string | number; // 报告天
+  reportEndDate?: string | number; // 日报周报结束日期
   reportId?: string; // 报告ID
-  reportMonth?: string; // 报告月份
-  reportStartDate?: string; // 日报周报开始日期
+  reportMonth?: string | number; // 报告月份
+  reportStartDate?: string | number; // 日报周报开始日期
   reportType?: string; // daily:日报;weekly:周报
-  reportWeek?: string; // 报告周
-  reportYear?: string; // 报告年份
+  reportWeek?: string | number; // 报告周
+  reportYear?: string | number; // 报告年份
   userId?: string; // 用户ID
-  weeklyWorkloadList?: any[]; // 工作量
+  weeklyWorkloadList?: {
+    projectId: string;
+    workTime: number;
+  }[]; // 工作量
 }
 // 周日报列表
 export interface IReportList {

+ 86 - 0
client_h5/src/pages/myLogs/page.scss

@@ -34,4 +34,90 @@
     transform: translateX(-50%);
     width: 90%;
   }
+
+  // 详情样式们
+  .workload-list,
+  .receive-names {
+    padding: 5px 15px;
+  }
+  .names-tag {
+    margin-right: 4px;
+  }
+
+  .comment-title {
+    font-size: 14px;
+    padding-left: 15px;
+  }
+
+  .comment-content {
+    font-size: 14px;
+    padding-left: 15px;
+    max-height: 32vh;
+    overflow: scroll;
+  }
+
+  .comment-item {
+    display: flex;
+    justify-content: flex-start;
+    align-items: center;
+    border-bottom: 1px solid #e3eaf5;
+    height: 85px;
+    .left {
+      width: 40px;
+      text-align: center;
+      span {
+        display: inline-block;
+        width: 35px;
+        height: 35px;
+        line-height: 35px;
+        color: #ffffff;
+        background-color: #5a99e3;
+        font-size: 12px;
+        font-weight: 400;
+        border-radius: 50%;
+      }
+    }
+    .right {
+      font-weight: 400;
+      font-size: 14px;
+      color: #121518;
+      line-height: 16px;
+      .title {
+        padding: 5px;
+        font-weight: 400;
+        font-size: 14px;
+        color: #8a94a4;
+        margin-bottom: 8px;
+        span {
+          color: #1b80eb;
+          margin-left: 10px;
+          cursor: pointer;
+        }
+      }
+      .item-content {
+        padding-left: 5px;
+      }
+    }
+  }
+
+  .comment-input {
+    position: fixed;
+    left: 0;
+    bottom: 10px;
+    width: 100%;
+    .comment-emoji {
+      padding: 5px 15px;
+      overflow-x: auto;
+      font-size: 14px;
+      white-space: nowrap; /* 禁止换行 */
+      .emoji-tag {
+        margin-right: 8px;
+      }
+      // 隐藏滚动条未生效
+      ::-webkit-scrollbar {
+        width: 0 !important;
+        height: 0 !important;
+      }
+    }
+  }
 }

+ 160 - 2
client_h5/src/pages/myLogs/service.ts

@@ -1,2 +1,160 @@
-// 获取项目列表
-const getProjectList = () => {};
+/**
+ * @description 所有周日报通用的方法
+ */
+import { getUserInfo } from "@/utils/tools";
+import moment from "moment";
+const userInfo = getUserInfo();
+
+// 写一个方法,把人员及部门的列表数据转换为树状数据
+export const transformUserListToTree = (arr: any) => {
+  const map: any = {};
+  const roots: any = [];
+  // 将数组转换为以id为key的对象
+  arr.forEach((item: any) => {
+    map[item.id] = { ...item, label: item.name, children: [] };
+  });
+  // 将子节点挂载到父节点的children字段下
+  arr.forEach((item: any) => {
+    const node = {
+      id: map[item.id].id,
+      name: map[item.id].name,
+      value: map[item.id].id,
+      children: map[item.id].children ?? [],
+    };
+    if (item.pid && map[item.pid]) {
+      map[item.pid].children.push(node);
+    } else if (item.pid) {
+      // console.log(`找不到对应id的父节点,删除数据: ${item.name}`)
+      // delete map[item.id]
+    } else {
+      roots.push(node);
+    }
+  });
+  return roots;
+};
+// 提交校验
+export const onSubmitCheck = (formData: any) => {
+  if (formData.reportContent == "") {
+    return {
+      msg: "请填写今日工作",
+      success: false,
+    };
+  }
+  if (formData.weeklyWorkloadList.length == 0) {
+    return {
+      msg: "请分配工作量",
+      success: false,
+    };
+  }
+  if (formData.receiveUserIds.length == 0) {
+    return {
+      msg: "请选择接收人",
+      success: false,
+    };
+  }
+  return {
+    msg: "",
+    success: true,
+  };
+};
+// 判断是否是本人的评论,由于只能删除自己发出的评论,所以做一个判断
+export const isMyComment = (data: any) => {
+  return data.commentUserId == userInfo.id;
+};
+// 返回从当前日期计算起的三个月
+export const getNearThreeMonth = () => {
+  return [
+    {
+      reportYear: moment().format("YYYY"),
+      reportMonth: moment().format("M"),
+    },
+    {
+      reportYear: moment().subtract(1, "month").format("YYYY"),
+      reportMonth: moment().subtract(1, "month").format("M"),
+    },
+    {
+      reportYear: moment().subtract(2, "month").format("YYYY"),
+      reportMonth: moment().subtract(2, "month").format("M"),
+    },
+  ];
+};
+// 返回当前月尾那周周日 到 上上个月初那周的周一前一天的日期
+export const getMonthRange = () => {
+  const endTime = moment()
+    .endOf("month")
+    .endOf("week")
+    .add(1, "day")
+    .format("YYYY-MM-DD HH:mm:ss");
+  const startTime = moment()
+    .subtract(2, "month")
+    .startOf("month")
+    .startOf("week")
+    .format("YYYY-MM-DD HH:mm:ss");
+  return [startTime, endTime];
+};
+// 将工作日数据进行整理,转成按日期划分的对象
+export const setWorkDayListToObj = (data: any) => {
+  const obj: any = {};
+  data.forEach((item: any) => {
+    const date = moment(item.dateDay).format("YYYY-MM-DD");
+    obj[date] = item;
+  });
+  return obj;
+};
+// 将工作日数据进行整理,按周划分(传回来的周的划分是错的,但是时间紧迫,所以先这样)
+export const setWorkDayListToWeek = (data: any) => {
+  const weekObj: any = {};
+  data.forEach((item: any) => {
+    const { dateDay, dayOfWeek, week, month, year } = item;
+    const date = moment(dateDay).format("YYYY-MM-DD");
+    const desc = `这天是${year}-${month}的第${week}周的第${dayOfWeek}天`;
+    // week 表示第几周, dayOfWeek 表示本周的第几天
+    const title = `${year}-${month}`;
+    if (weekObj[title]) {
+      if (weekObj[title][week]) {
+        weekObj[title][week].push({ ...item, date, desc });
+      } else {
+        weekObj[title][week] = [{ ...item, date, desc }];
+      }
+    } else {
+      weekObj[title] = {
+        [week]: [{ ...item, date, desc }],
+      };
+    }
+  });
+  return weekObj;
+};
+// 处理数据,将工作周划分和周日报填报情况进行合并
+export const mergeWorkDayAndLogs = (workObj: any, logList: any) => {
+  const workList: any = [];
+  // 把复杂的对象拆为数组
+  for (const key in workObj) {
+    for (const week in workObj[key]) {
+      const data = workObj[key][week];
+      const logs = logList.filter((item: any) => {
+        const { reportStartDate, reportEndDate } = item;
+        const checkData = moment(data[data.length - 1].date);
+        return checkData.isBetween(reportStartDate, reportEndDate, null, "[]");
+      });
+      workList.push({
+        month: key,
+        week,
+        data,
+        startDate: data[0].date,
+        endDate: data[data.length - 1].date,
+        isLog: logs,
+      });
+    }
+  }
+  // 再把数组重组为以月划分的对象
+  const monthObj: any = {};
+  workList.map((item: any) => {
+    const { month } = item;
+    if (monthObj[month]) {
+      monthObj[month].push(item);
+    } else {
+      monthObj[month] = [item];
+    }
+  });
+  return monthObj;
+};

+ 24 - 0
client_h5/src/router/routes.ts

@@ -141,6 +141,30 @@ const routes: RouteRecordRaw[] = [
         },
         component: () => import("@/pages/myLogs/Weekly/index.vue"),
       },
+      {
+        path: "logsDetail",
+        name: "LogsDetail",
+        meta: {
+          title: "日志详情",
+        },
+        component: () => import("@/pages/myLogs/components/LogDetail.vue"),
+      },
+      {
+        path: "myDailyLogs",
+        name: "MyDailyLogs",
+        meta: {
+          title: "我的日志",
+        },
+        component: () => import("@/pages/myLogs/Daily/MyLogs.vue"),
+      },
+      {
+        path: "myWeeklyLogs",
+        name: "myWeeklyLogs",
+        meta: {
+          title: "我的周报",
+        },
+        component: () => import("@/pages/myLogs/Weekly/MyLogs.vue"),
+      },
     ] as RouteRecordRaw[],
   },
 ] as RouteRecordRaw[];