Bladeren bron

feat: 周日报填写静态页面

qiny 1 jaar geleden
bovenliggende
commit
caf939da26

+ 1 - 1
client/src/utils/tool.ts

@@ -36,7 +36,7 @@ export const sortByKey = (
 ): any[] => {
   const newList = cloneDeep(dataSource)
   if (order === 'asc') return newList.sort((x, y) => x[key] - y[key])
-  return cloneDeep(dataSource).sort((x, y) => y[key] - x[key])
+  return newList.sort((x, y) => y[key] - x[key])
 }
 
 /**

+ 1 - 1
client/src/views/OaSystem/personnelManagement/dailyCenter/editorDetail.vue

@@ -162,7 +162,7 @@ const sendReportHandle = async (isTemp) => {
     const reportId = writeData.value?.isLog?.id ?? updateLogId.value ?? ''
     params.reportId = reportId
   }
-  // console.log('提交(暂存)~~~~~~~~~~~~~~~~~~~', params, writeData.value)
+  // console.log('提交(暂存)~~~~~~~~~~~~~~~~~~~', params)
   // return
   const result: any = await request.postOriginal({
     url: '/adm/report/add-report-info',

+ 1 - 0
client_h5/package.json

@@ -17,6 +17,7 @@
   "dependencies": {
     "axios": "^1.6.8",
     "dingtalk-jsapi": "^3.0.33",
+    "moment": "^2.30.1",
     "pinia": "^2.1.7",
     "vant": "^4.8.11",
     "vue": "^3.4.19",

+ 438 - 0
client_h5/src/components/UserSelect/index.vue

@@ -0,0 +1,438 @@
+<template>
+  <van-field
+    v-model="data.names"
+    is-link
+    readonly
+    v-bind="$attrs"
+    @click="sendWordOpen"
+  />
+  <van-popup
+    v-model:show="sendWordShow"
+    round
+    position="bottom"
+    class="pop-model"
+  >
+    <div class="tree-box">
+      <van-search
+        label-align="left"
+        v-model="data.treeParam"
+        show-action
+        placeholder="请输入接收人"
+        @search="searchTreeByParam"
+      >
+        <template #action>
+          <div @click="searchTreeByParam">搜索</div>
+        </template>
+      </van-search>
+      <div class="tree-container">
+        <div class="tree-btns" v-if="props.selectAll">
+          <van-checkbox
+            style="margin-right: 24px"
+            v-if="props.multiple"
+            shape="square"
+            @click.stop="allSelectChange"
+            v-model="data.allSelect"
+            >全选</van-checkbox
+          >
+        </div>
+        <div class="tree-data">
+          <TreeSelect
+            ref="treeSelectRef"
+            @change="checkChange"
+            :labelKey="props.labelKey"
+            :idKey="props.idKey"
+            :pidKey="props.pidKey"
+            :isLink="props.isLink"
+            :list="data.list"
+            :listObj="data.listObj"
+            :treeParamAlready="data.treeParamAlready"
+            :multiple="props.multiple"
+            @confirm="onConfirm"
+            :defaultId="props.modelValue[0]"
+          ></TreeSelect>
+        </div>
+      </div>
+    </div>
+
+    <div class="tree-confirm">
+      <van-button
+        v-if="multiple"
+        type="primary"
+        block
+        @click="handleConfirm"
+        class="submit"
+        >确定</van-button
+      >
+    </div>
+  </van-popup>
+</template>
+
+<script setup>
+/**
+ * @description 用户选择组件
+ * @author chinyan
+ */
+import { reactive, watch, ref, nextTick, onMounted } from "vue";
+import TreeSelect from "./tree.vue";
+import request from "@/utils/request";
+
+const emits = defineEmits(["update:modelValue", "change", "confirm"]);
+const props = defineProps({
+  // 绑定值
+  modelValue: {
+    type: Array,
+    default: [],
+  },
+  // 传入数据,数组或树形数组
+  listData: {
+    type: Array,
+    default() {
+      return [];
+    },
+  },
+  // label key
+  labelKey: {
+    type: String,
+    default() {
+      return "name";
+    },
+  },
+  // id key
+  idKey: {
+    type: String,
+    default() {
+      return "id";
+    },
+  },
+  // pid key
+  pidKey: {
+    type: String,
+    default() {
+      return "pid";
+    },
+  },
+  // 是否联动勾选
+  isLink: {
+    type: Boolean,
+    default() {
+      return true;
+    },
+  },
+  // 是否多选
+  multiple: {
+    type: Boolean,
+    default: true,
+  },
+  // 是否支持全选
+  selectAll: {
+    type: Boolean,
+    default: false,
+  },
+});
+
+const data = reactive({
+  treeParam: "",
+  treeParamAlready: "", // 已经进行了搜索的参数
+  list: props.listData, // 树数组
+  listObj: {}, // 数组对象
+  selectList: [], // 选中的数据
+  searchSomeDataList: [], // 搜索相同对象数组
+  canCheckList: [], // 能够选择的数据集合
+  canCheckListFixed: [], // 固定的能够选择的数据集合
+  allSelect: false, // 是否全选
+  names: "",
+});
+
+const treeSelectRef = ref(null);
+
+const init = (type) => {
+  if (type) {
+    data.names = "";
+  }
+
+  data.treeParam = "";
+  data.treeParamAlready = "";
+  data.canCheckList = [];
+  data.canCheckListFixed = [];
+};
+const initData = (options) => {
+  if (options && options.length) {
+    data.list = options;
+    init();
+    data.listObj = setListObj(options);
+  }
+};
+// 将树形数据转为扁平对象
+const setListObj = (list, pid) => {
+  let listObj = {};
+  list.forEach((itm) => {
+    if (pid) {
+      itm[props.pidKey] = pid;
+    }
+    data.canCheckList.push(itm);
+    data.canCheckListFixed.push(itm);
+    listObj[itm[props.idKey]] = itm;
+    if (itm.children && itm.children.length) {
+      listObj = {
+        ...listObj,
+        ...setListObj(itm.children, itm[props.idKey]),
+      };
+    }
+  });
+  return listObj;
+};
+
+// 确认
+const handleConfirm = () => {
+  const showSelectList = filterData(data.selectList);
+  // data.names = showSelectList.map((itm) => itm[props.labelKey]).join(',')
+  emits(
+    "update:modelValue",
+    showSelectList.map((itm) => itm[props.idKey])
+  );
+  emits("confirm", showSelectList);
+  sendWordShow.value = false;
+};
+const onConfirm = (e) => {
+  // data.names = e.map((itm) => itm[props.labelKey]).join(',')
+  emits(
+    "update:modelValue",
+    e.map((itm) => itm[props.idKey])
+  );
+  emits("confirm", e);
+  sendWordShow.value = false;
+};
+
+// 过滤数据
+const filterData = (selectList) => {
+  if (
+    data.canCheckList.length ===
+    selectList.filter(
+      (itm) => itm[props.labelKey].indexOf(data.treeParamAlready) !== -1
+    ).length
+  ) {
+    data.allSelect = true;
+  } else {
+    data.allSelect = false;
+  }
+  // 过滤出展示中,且打勾的数据
+  const showSelectList = selectList.filter((itm) => {
+    return !itm.isHide && !itm.isShowChildren;
+  });
+  return showSelectList;
+};
+// 该方法在 树形数据变化 和 全选变化 时会执行
+const checkChange = (selectList) => {
+  data.selectList = selectList;
+  const showSelectList = filterData(selectList);
+  emits("change", showSelectList);
+};
+// 根据参数搜索
+const searchTreeByParam = () => {
+  const someDataList = []; // 搜索数据
+  const someDataCanCheckList = []; // 搜索且能够check的数据
+  data.treeParamAlready = data.treeParam;
+  treeSelectRef.value?.outDataBuffer();
+  for (const id in data.listObj) {
+    if (data.treeParam) {
+      data.listObj[id].isHide = true;
+      data.listObj[id].isShowChildren = false;
+      if (
+        data.listObj[id][props.labelKey].indexOf(data.treeParam) !== -1 ||
+        data.listObj[id].checked
+      ) {
+        data.listObj[id].isHide = false;
+        someDataList.push(data.listObj[id]);
+        someDataCanCheckList.push(data.listObj[id]);
+      }
+    } else {
+      data.listObj[id].isHide = false;
+    }
+  }
+
+  data.searchSomeDataList = someDataList;
+  data.canCheckList = someDataCanCheckList.length
+    ? someDataCanCheckList
+    : deepClone(data.canCheckListFixed);
+  setShowData(someDataList);
+  checkChange(data.selectList);
+};
+// 设置展示和展开数据
+const setShowData = (datas, bool) => {
+  const d = [];
+  datas.forEach((itm) => {
+    if (itm[props.pidKey]) {
+      if (
+        !d.find(
+          (item) =>
+            item[props.idKey] === data.listObj[itm[props.pidKey]][props.idKey]
+        )
+      ) {
+        d.push(data.listObj[itm[props.pidKey]]);
+      }
+      if (bool === false || bool === true) {
+        data.listObj[itm[props.pidKey]].checked = bool;
+      }
+      data.listObj[itm[props.pidKey]].isHide = false;
+      data.listObj[itm[props.pidKey]].isShowChildren = true;
+    }
+  });
+  if (d.length) {
+    setShowData(d, bool);
+  }
+};
+// 获取全部可选择数据,进行全选/取消
+const toggleAllSelectData = (bool) => {
+  let selectData = [];
+  for (const id in data.listObj) {
+    data.listObj[id].isShowChildren = false;
+    if (data.listObj[id][props.labelKey].indexOf(data.treeParam) !== -1) {
+      data.listObj[id].checked = bool;
+      data.listObj[id].isHide = false;
+      selectData.push(data.listObj[id]);
+    }
+  }
+  setShowData(selectData, bool);
+};
+// 全选状态改变
+const allSelectChange = () => {
+  toggleAllSelectData(data.allSelect);
+};
+const deepClone = (obj) => {
+  const type = Object.prototype.toString.call(obj); // 通过原型对象获取对象类型
+  let newObj;
+  if (type === "[object Array]") {
+    // 数组
+    newObj = [];
+    if (obj.length > 0) {
+      for (let i = 0; i < obj.length; i++) {
+        newObj.push(deepClone(obj[i]));
+      }
+    }
+  } else if (type === "[object Object]") {
+    // 对象
+    newObj = {};
+    for (const i in obj) {
+      newObj[i] = deepClone(obj[i]);
+    }
+  } else {
+    // 基本类型和方法可以直接赋值
+    newObj = obj;
+  }
+  return newObj;
+};
+
+// 设置默认值
+const setDefault = () => {
+  const someDataList = []; // 默认数据
+  const someDataCanCheckList = []; // 搜索且能够check的数据
+  treeSelectRef.value?.outDataBuffer();
+  for (const id in data.listObj) {
+    data.listObj[id].checked = false;
+    data.listObj[id].isShowChildren = false;
+    props.modelValue.forEach((mid) => {
+      if (data.listObj[id][props.idKey] === mid) {
+        data.listObj[id].checked = true;
+        someDataList.push(data.listObj[id]);
+        someDataCanCheckList.push(data.listObj[id]);
+      }
+    });
+  }
+  data.names = someDataList.map((item) => item.name).join(",");
+
+  data.searchSomeDataList = someDataList;
+  data.canCheckList = someDataCanCheckList.length
+    ? someDataCanCheckList
+    : deepClone(data.canCheckListFixed);
+  setShowData(someDataList);
+  // filterData();
+  // checkChange(data.selectList);
+};
+
+watch(
+  () => props.listData,
+  () => {
+    initData(props.listData);
+  },
+  { deep: true, immediate: true }
+);
+
+watch(
+  () => props.modelValue,
+  () => {
+    setDefault();
+  },
+  { deep: true, immediate: true }
+);
+
+onMounted(() => {
+  nextTick(() => {
+    setDefaultTime();
+  });
+});
+
+const setDefaultTime = () => {
+  if (props.listData.length === 0) {
+    setTimeout(() => {
+      setDefaultTime();
+    }, 100);
+  } else {
+    setDefault();
+  }
+};
+
+const sendWordShow = ref(false);
+const sendWordOpen = () => {
+  sendWordShow.value = true;
+  if (props.multiple) {
+    setDefault();
+  }
+  // searchTreeByParam();
+};
+
+defineExpose({
+  init,
+  setListObj,
+  checkChange,
+  searchTreeByParam,
+  setShowData,
+  toggleAllSelectData,
+  allSelectChange,
+  deepClone,
+  setDefault,
+});
+</script>
+
+<style lang="scss" scoped>
+.pop-model {
+  width: 100%;
+}
+.tree-box {
+  --van-search-content-background-color: #eeeeee;
+  --van-search-content-background: #eeeeee;
+  .tree-container {
+    padding: 0;
+  }
+  .tree-data {
+    height: 60vh;
+    padding: 0 12px;
+    overflow-y: auto;
+  }
+  .tree-btns {
+    padding-left: 12px;
+    margin-bottom: 24px;
+    display: flex;
+    align-items: center;
+  }
+}
+
+.tree-confirm {
+  width: 100%;
+  padding: 12px 0;
+  display: flex;
+  justify-content: center;
+  .submit {
+    width: 90%;
+  }
+}
+</style>

+ 319 - 0
client_h5/src/components/UserSelect/tree.vue

@@ -0,0 +1,319 @@
+<template>
+  <div class="list">
+    <div
+      class="item"
+      v-for="item in props.list"
+      :key="item[idKey]"
+      v-show="!item.isHide"
+    >
+      <div class="title">
+        <div class="checkbox-box">
+          <van-checkbox
+            v-if="props.multiple"
+            icon-size="16px"
+            shape="square"
+            @click.stop="checkChange(item)"
+            v-model="item.checked"
+            ><span style="font-size: 15px">{{
+              item[labelKey]
+            }}</span></van-checkbox
+          >
+          <p
+            v-else
+            :style="{
+              fontSize: '15px',
+              color:
+                defaultId === item[idKey] ? 'var(--van-primary-color)' : '',
+            }"
+            @click.stop="checkChange(item)"
+          >
+            {{ item[labelKey] }}
+          </p>
+        </div>
+        <div @click.stop="itemClick(item)" class="arrow">
+          <van-icon
+            v-if="item.children && item.children.length"
+            :name="item.isShowChildren ? 'arrow-up' : 'arrow-down'"
+          />
+        </div>
+      </div>
+      <div class="tree" v-show="item.isShowChildren">
+        <tree
+          :labelKey="props.labelKey"
+          :idKey="props.idKey"
+          :pidKey="props.pidKey"
+          :isLink="props.isLink"
+          v-if="item.children && item.children.length"
+          :list="item.children"
+          :listObj="props.listObj"
+          :isFirstFloor="false"
+          :multiple="props.multiple"
+          @confirm="onConfirm"
+          :defaultId="defaultId"
+        >
+        </tree>
+      </div>
+    </div>
+  </div>
+</template>
+<script setup>
+import { reactive, watch } from "vue";
+import tree from "./tree.vue";
+const emits = defineEmits(["change", "confirm"]);
+const props = defineProps({
+  // label key
+  labelKey: {
+    type: String,
+    default() {
+      return "name";
+    },
+  },
+  // id key
+  idKey: {
+    type: String,
+    default() {
+      return "id";
+    },
+  },
+  // pid key
+  pidKey: {
+    type: String,
+    default() {
+      return "pid";
+    },
+  },
+  // 是否联动勾选
+  isLink: {
+    type: Boolean,
+    default() {
+      return true;
+    },
+  },
+  // 是否是第一层
+  isFirstFloor: {
+    type: Boolean,
+    default() {
+      return true;
+    },
+  },
+  // 树形结构
+  list: {
+    type: Array,
+    default() {
+      return [];
+    },
+  },
+  // 树形扁平化数据
+  listObj: {
+    type: Object,
+    default() {
+      return {};
+    },
+  },
+  // 树形搜索参数
+  treeParamAlready: {
+    type: String,
+    default() {
+      return "";
+    },
+  },
+  // 是否全选
+  allSelect: {
+    type: Boolean,
+    default() {
+      return false;
+    },
+  },
+  // 是否多选
+  multiple: {
+    type: Boolean,
+    default: true,
+  },
+  // 单选默认值
+  defaultId: String,
+});
+
+const data = reactive({
+  firstLoad: true,
+  checkboxValue1: [],
+  showList: [],
+  isOutData: true, // 需要将数据抛出
+});
+
+watch(
+  () => props.list,
+  () => {
+    if (data.firstLoad) {
+      outDataBuffer();
+      data.firstLoad = false;
+    }
+    // 判断 是第一层树 且 不是进行显示隐藏操作时,进行数据的抛出
+    if (props.isFirstFloor && data.isOutData) {
+      if (props.multiple) {
+        outCheckedData();
+      }
+    }
+  },
+  { deep: true }
+);
+
+// 展开
+const itemClick = (item) => {
+  outDataBuffer();
+  item.isShowChildren = !item.isShowChildren;
+};
+// 数据抛出缓冲(在list数据变化时,不想抛出选择的数据时,调用该方法)
+const outDataBuffer = () => {
+  data.isOutData = false;
+  setTimeout(() => {
+    data.isOutData = true;
+  }, 500);
+};
+// 获取选中对象
+const getCheckData = (list) => {
+  let deptList = [];
+  list.forEach((itm) => {
+    // && itm.label.indexOf(this.treeParamAlready) !== -1
+    if (itm.checked) {
+      deptList.push(itm);
+    }
+    if (itm.children && itm.children.length) {
+      deptList = deptList.concat(getCheckData(itm.children));
+    }
+  });
+  return deptList;
+};
+// 单项checked改变
+const checkChange = (item) => {
+  // 多选
+  if (props.multiple) {
+    // item.checked = !item.checked
+    if (props.isLink) {
+      // 展开所有可以展开的节点
+      if (item.checked) {
+        expandAll(item);
+      }
+      // 判断父级是否需要勾选
+      checkParent(item);
+      // 勾选子级
+      if (item.children && item.children.length) {
+        checkChidren(item.children, item.checked);
+      }
+    }
+    return;
+  }
+
+  // 单选
+  if (item.children && item.children.length) return;
+  toggleAllSelectData(props.list);
+  item.checked = true;
+  outCheckedData();
+};
+
+// 获取全部可选择数据,进行全选/取消
+const toggleAllSelectData = (list) => {
+  list.forEach((itm) => {
+    itm.checked = false;
+    if (itm.children && itm.children.length) {
+      toggleAllSelectData(itm.children);
+    }
+  });
+};
+
+// 展开所有可以展开的节点
+const expandAll = (item) => {
+  if (item.children?.length) {
+    item.isShowChildren = true;
+    item.children.forEach((itm) => {
+      expandAll(itm);
+    });
+  }
+};
+// 判断父级是否需要勾选
+const checkParent = (item) => {
+  // 父级不存在不再往下走
+  if (!props.listObj[item[props.pidKey]]) {
+    return;
+  }
+  let someDataCount = 0; // 同级的相同父级数据量
+  let checkedDataCount = 0; // 同级已勾选的数据量
+  for (const id in props.listObj) {
+    const itm = props.listObj[id];
+    if (itm[props.pidKey] === item[props.pidKey] && !itm.isHide) {
+      someDataCount++;
+      if (itm.checked) {
+        checkedDataCount++;
+      }
+    }
+  }
+  const isEqual = someDataCount === checkedDataCount;
+  if (props.listObj[item[props.pidKey]].checked != isEqual) {
+    props.listObj[item[props.pidKey]].checked = isEqual;
+    checkParent(props.listObj[item[props.pidKey]]);
+  }
+};
+// 根据父级统一取消勾选或勾选
+const checkChidren = (list, isChecked) => {
+  list.forEach((itm) => {
+    itm.checked = isChecked;
+    if (itm.children && itm.children.length) {
+      checkChidren(itm.children, isChecked);
+    }
+  });
+};
+// 抛出选中的数据
+const outCheckedData = () => {
+  const checkedList = getCheckData(props.list);
+  emits("change", checkedList);
+  onConfirm(checkedList);
+};
+
+const onConfirm = (e) => {
+  if (props.multiple) return;
+  if (e[0].children && e[0].children.length) return;
+  emits("confirm", e);
+};
+
+defineExpose({
+  itemClick,
+  outDataBuffer,
+  getCheckData,
+  checkChange,
+  expandAll,
+  checkParent,
+  checkChidren,
+  outCheckedData,
+});
+</script>
+
+<style lang="scss" scoped>
+.list {
+  .item {
+    margin-bottom: 10px;
+
+    .title {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      margin-bottom: 10px;
+
+      .checkbox-box {
+        display: flex;
+        align-items: center;
+        cursor: pointer;
+        padding: 10px 0;
+      }
+
+      .arrow {
+        width: 80px;
+        display: flex;
+        justify-content: flex-end;
+      }
+    }
+
+    .tree {
+      margin-left: 50px;
+    }
+  }
+}
+</style>

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

@@ -21,6 +21,22 @@ const routes: RouteRecordRaw[] = [
         },
         component: () => import("@/pages/leave/index.vue"),
       },
+      {
+        path: "daily",
+        name: "Daily",
+        meta: {
+          title: "日志",
+        },
+        component: () => import("@/pages/logs/Daily/index.vue"),
+      },
+      {
+        path: "weekly",
+        name: "Weekly",
+        meta: {
+          title: "周报",
+        },
+        component: () => import("@/pages/logs/Weekly/index.vue"),
+      },
     ] as RouteRecordRaw[],
   },
 ] as RouteRecordRaw[];

+ 17 - 0
client_h5/src/utils/tools.ts

@@ -0,0 +1,17 @@
+/**
+ * 获取用户信息的方法
+ */
+export const getUserInfo = () => {
+  // const { wsCache } = useCache()
+  // const userInfo = wsCache.get(CACHE_KEY.USER)
+  // 先模拟数据
+  const userInfo = {
+    id: "ea81aaf2-74fe-429f-acaa-25a9a898abcf",
+    nickname: "秦源",
+    avatar:
+      "https://static-legacy.dingtalk.com/media/lQLPM44eP2eGrFfNAxjNAxiwthCI7LPuiz0FRw-0RwC5AA_792_792.png",
+    deptId: "264",
+    deptName: "数智产业研发部",
+  };
+  return userInfo;
+};