浏览代码

知识库列表

CzRger 2 月之前
父节点
当前提交
34eeeef0a8

二进制
src/assets/images/knowledge-item-add.png


二进制
src/assets/images/knowledge-item-bg.png


二进制
src/assets/images/knowledge-item-icon.png


文件差异内容过多而无法显示
+ 6 - 0
src/assets/svg/czr_add1.svg


文件差异内容过多而无法显示
+ 10 - 0
src/assets/svg/star.svg


+ 48 - 38
src/components/SvgIcon/index.vue

@@ -1,47 +1,57 @@
 <template>
   <svg aria-hidden="true" class="svg-icon" :style="`width: ${size}px;height: ${size}px;transform: rotate(${rotate}deg);`">
-    <use :xlink:href="symbolId" :fill="color" />
+    <use :xlink:href="symbolId" :fill="colorCpt" />
   </svg>
 </template>
 
 
-<script lang="ts">
-  import {
-    defineComponent,
-    computed,
-  } from "vue";
-  export default defineComponent({
-    name: "SvgIcon",
-    components: {},
-    props: {
-      prefix: {
-        type: String,
-        default: 'icon'
-      },
-      name: {
-        type: String,
-        required: true
-      },
-      color: {
-        type: String,
-        default: ''
-      },
-      size: {
-        type: [Number, String],
-        default: '18'
-      },
-      rotate: {
-        type: [Number, String],
-        default: 0
-      }
-    },
-    setup(props, { emit }) {
-      const symbolId = computed(() => `#${props.prefix}-${props.name}`)
-      return {
-        symbolId
-      }
-    }
-  })
+<script setup lang="ts">
+defineOptions({
+  name: 'SvgIcon',
+});
+import {
+  computed, reactive,
+} from "vue";
+
+const props = defineProps({
+  prefix: {
+    type: String,
+    default: 'icon'
+  },
+  name: {
+    type: String,
+    required: true
+  },
+  color: {
+    type: String,
+    default: ''
+  },
+  size: {
+    type: [Number, String],
+    default: '18'
+  },
+  rotate: {
+    type: [Number, String],
+    default: 0
+  },
+  active: {
+    type: Boolean,
+    default: false
+  },
+})
+const state: any = reactive({
+  defaultColor: '#A7ADB9'
+})
+const symbolId = computed(() => `#${props.prefix}-${props.name}`)
+const colorCpt = computed(() => {
+  if (props.color) {
+    return props.color
+  }
+  if (props.active) {
+    return 'var(--czr-main-color)'
+  }
+  return state.defaultColor
+})
 </script>
 
 <style scoped>

+ 5 - 32
src/components/czr-ui/CzrFormColumn.vue

@@ -1,10 +1,5 @@
 <template>
   <el-col class="czr-form-column" :class="{
-    span1: filterSpan == 1,
-    span2: filterSpan == 2,
-    span3: filterSpan == 3,
-    span4: filterSpan == 4,
-    span5: filterSpan == 5,
     transparent: transparent
   }" :span="span" :offset="offset" ref="ref_czrFormColumn">
     <el-form-item :label="label" :label-width="labelWidth" :class="{
@@ -238,12 +233,11 @@ import UploadCom from './czr-form-link/upload.vue'
 import { v4 } from "uuid";
 const props = defineProps({
   span: {type: Number, default: 6}, // 栅格
-  filterSpan: { default: null },  // 列表筛选条件单独5列布局
   offset: {type: Number, default: 0}, // 栅格
   param: {},  // 绑定值
   label: {type: String, default: ''}, // 标题
   required: {default: false}, // 必填项
-  labelWidth: {type: String, default: 'auto'},  // 标题宽度
+  labelWidth: {type: [String, Number], default: 'auto'},  // 标题宽度
   link: {type: String, default: 'input', validator(val: string) {
       return ['cascader', 'checkbox', 'date', 'datetime', 'input', 'radio', 'select', 'switch', 'dept', 'time', 'upload', 'number', 'input-number', 'tree-select', 'rich', 'area'].includes(val)
     }
@@ -362,31 +356,8 @@ defineExpose({
 
 <style scoped lang="scss">
 .czr-form-column {
-  &.span1 {
-    width: 20%;
-    max-width: 20%;
-    flex: unset;
-  }
-  &.span2 {
-    width: 40%;
-    max-width: 40%;
-    flex: unset;
-  }
-  &.span3 {
-    width: 60%;
-    max-width: 60%;
-    flex: unset;
-  }
-  &.span4 {
-    width: 80%;
-    max-width: 80%;
-    flex: unset;
-  }
-  &.span5 {
-    width: 100%;
-    max-width: 100%;
-    flex: unset;
-  }
+  max-width: unset;
+  flex: unset;
   &.transparent {
     :deep(.el-form-item) {
       margin-bottom: 0;
@@ -397,6 +368,7 @@ defineExpose({
     }
   }
   :deep(.el-form-item) {
+    height: 2.25rem;
     &.link_time {
       margin-top: 1px;
     }
@@ -428,6 +400,7 @@ defineExpose({
       flex-wrap: unset;
       >div:first-child {
         flex: 1;
+        height: 100%;
       }
       .unit {
         margin-left: 6px;

+ 141 - 0
src/components/czr-ui/CzrTableCard.vue

@@ -0,0 +1,141 @@
+<template>
+  <div class="czr-table-card-main">
+    <div class="czr-table-card-content">
+      <template v-if="data?.length > 0">
+        <div class="czr-table-card-content-list" :style="`gap: ${rowMargin} ${colMargin};grid-template-columns: repeat(${col}, calc((100% - ${colMargin} * ${col - 1}) / ${col}));`">
+          <template v-for="(item, index) in data">
+            <div class="model">
+              <slot name="model" :info="item" :index="index"/>
+            </div>
+          </template>
+        </div>
+      </template>
+      <template v-else>
+        <div class="czr-table-card-content-no-data el-table__empty-text">
+          暂无数据
+        </div>
+      </template>
+    </div>
+    <div class="ctc-page">
+      <div class="total">
+        <template v-if="isValue(selectLength)">
+          已选中 {{ selectLength }} 条 /
+        </template>
+        共 {{ total }} 条
+      </div>
+      <el-pagination
+          v-if="noPage === false"
+          class="__cus-pagination"
+          :currentPage="page"
+          :page-size="pageSize"
+          background
+          :page-sizes="pageSizes"
+          :layout="pageLayoutCpt"
+          :total="Number(total)"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+      />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+defineOptions({
+  name: 'CzrTableCard',
+});
+import {
+  computed,
+  ref,
+  reactive,
+} from 'vue'
+import {isValue} from "@/utils/czr-util";
+
+const emit = defineEmits(['handlePage'])
+const props = defineProps({
+  total: {
+    required: true
+  },
+  page: {},
+  pageSize: {},
+  pageSizes: {
+    default: () => [20, 40, 60, 80, 100]
+  },
+  noPage: {
+    default: false
+  },
+  col: {
+    default: 2
+  },
+  colMargin: {
+    default: '1rem'
+  },
+  rowMargin: {
+    default: '1rem'
+  },
+  data: {
+    required: true,
+    default: () => []
+  },
+  noLayout: {
+    default: () => []
+  },
+  selectLength: {
+    default: null
+  },
+})
+const state = reactive({
+  pageLayout: ['sizes', 'prev', 'pager', 'next', 'jumper']
+});
+const pageLayoutCpt = computed(() => {
+  return state.pageLayout.filter((v: string) => props.noLayout.every((s: string) => v !== s)).join(',')
+})
+const handleSizeChange = (val: Number) => {
+  emit('handlePage', 1, val)
+}
+const handleCurrentChange = (val: Number) => {
+  emit('handlePage', val, props.pageSize)
+}
+</script>
+
+<style scoped lang="scss">
+  .czr-table-card-main {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    $cus-page-height: 32px;
+    .czr-table-card-content {
+      overflow-y: auto;
+      overflow-x: hidden;
+      flex: 1;
+      .czr-table-card-content-list {
+        display: grid;
+        .model {
+          overflow: hidden;
+        }
+      }
+      .czr-table-card-content-no-data {
+        width: 100%;
+        height: 100%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-size: 14px;
+      }
+    }
+    :deep(.ctc-page) {
+      height: $cus-page-height;
+      margin-top: 25px;
+      font-size: 14px;
+      font-family: Microsoft YaHei;
+      font-weight: 400;
+      color: #999999;
+      display: flex;
+      justify-content: space-between;
+      .total {
+        display: flex;
+        align-items: center;
+      }
+    }
+  }
+</style>

+ 6 - 0
src/components/czr-ui/czr-form-link/select.vue

@@ -87,4 +87,10 @@ onMounted(() => {
     flex-wrap: unset;
   }
 }
+:deep(.el-select) {
+  height: 100%;
+  .el-select__wrapper {
+    height: 100%;
+  }
+}
 </style>

+ 0 - 2
src/layout/top-left/head/index.vue

@@ -26,8 +26,6 @@ const {proxy} = getCurrentInstance()
 const state: any = reactive({})
 const titleCpt =  computed(() => import.meta.env.VITE_TITLE)
 const menusCpt: any = computed(() => router.options.routes.filter(v => v.name === 'root')[0])
-console.log(menusCpt)
-console.log(route)
 </script>
 
 <style lang="scss" scoped>

+ 2 - 0
src/layout/top-left/index.vue

@@ -42,6 +42,7 @@ const state: any = reactive({})
   .top-left-layout-center {
     flex: 1;
     display: flex;
+    overflow: hidden;
     .top-left-layout-center-sub-menu {
       width: 300px;
       height: 100%;
@@ -51,6 +52,7 @@ const state: any = reactive({})
       display: flex;
       flex-direction: column;
       .top-left-layout-center-content-views {
+        overflow: hidden;
         flex: 1;
         padding: 0 1rem 1rem;
       }

+ 1 - 72
src/style/czr.scss

@@ -1,6 +1,7 @@
 :root {
   --czr-main-color: rgba(47, 130, 255, 1);
   --czr-main-color-rgb: 47, 130, 255;
+  --czr-gap: 0.63rem;
 }
 
 .__disabled {
@@ -42,58 +43,6 @@
   }
 }
 
-.__normal-page {
-  width: 100%;
-  height: 100%;
-  display: flex;
-  overflow: hidden;
-  .__normal-tree {
-    border-radius: 8px;
-    width: 246px;
-    height: 100%;
-    display: flex;
-    flex-direction: column;
-    margin-right: 16px;
-    background-color: #FFFFFF;
-    .__normal-tree_filter {
-      width: 100%;
-      height: 60px;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      padding: 0 10px;
-    }
-    .__normal-tree_content {
-      flex: 1;
-      overflow: auto;
-      padding: 10px;
-      .el-tree {
-        .el-tree-node {
-          .el-tree-node__content {
-            height: auto;
-            .el-tree-node__label {
-              white-space: break-spaces;
-              padding: 4px 0;
-            }
-          }
-          &.is-current {
-            .el-tree-node__label {
-              color: var(--czr-main-color);
-            }
-          }
-        }
-      }
-    }
-  }
-  .__normal-content {
-    flex: 1;
-    position: relative;
-    overflow: hidden;
-  }
-}
-.__normal-form {
-  padding: 16px 24px 0 24px;
-}
 .__czr-pagination {
   display: flex;
   justify-content: flex-end;
@@ -169,7 +118,6 @@
     }
   }
 }
-
 .__czr-dialog {
   &.__czr-dialog-auto-height {
     .el-overlay-dialog {
@@ -309,25 +257,6 @@
     background-color: #0062E9;
   }
 }
-.__czr-title_table {
-  font-family: PingFang SC, PingFang SC;
-  font-weight: bold;
-  font-size: 18px;
-  color: #0B46C9;
-  display: flex;
-  align-items: center;
-  position: relative;
-  padding-left: 10px;
-  box-sizing: border-box;
-  &:before {
-    content: '';
-    position: absolute;
-    left: 0;
-    width: 3px;
-    height: 17px;
-    background-color: #0B46C9;
-  }
-}
 
 .__czr-popover {
   max-width: 60% !important;

+ 8 - 0
src/style/manage.scss

@@ -13,4 +13,12 @@
   font-size: 1.5rem;
   color: #303133;
   margin-left: 1.5rem;
+}
+.bm-filter {
+  .el-row {
+    gap: var(--czr-gap);
+    .el-form-item {
+      margin-bottom: 0;
+    }
+  }
 }

+ 258 - 2
src/views/manage/knowledge/index.vue

@@ -2,18 +2,274 @@
   <div class="bm-main-box">
     <div class="flex items-center">
       <div class="bm-main-box-title">{{$route.meta.title}}</div>
+      <div class="ml-auto flex items-center gap-[var(--czr-gap)]">
+        <div class="__hover text-[0.88rem] text-[#576275] flex items-center gap-[0.3rem]" @click="state.query.form.isCreate = !state.query.form.isCreate">
+          <SvgIcon name="czr_add1" size="14" :active="state.query.form.isCreate"/>我的创建
+        </div>
+        <div class="__hover text-[0.88rem] text-[#576275] flex items-center gap-[0.3rem]" @click="state.query.form.isStar = !state.query.form.isStar">
+          <SvgIcon name="star" size="15" class="mb-[2px]" :active="state.query.form.isStar"/>我的收藏
+        </div>
+        <CzrForm class="bm-filter" label-width="" @handleEnter="onSearch">
+          <CzrFormColumn
+            class="__czr-table-form-column w-[6.68rem]"
+            :span="24"
+            :label-width="0"
+            v-model:param="state.query.form.group"
+            link="select"
+            :options="[
+              {label: '分组一', value: 1},
+              {label: '分组三', value: 2},
+              {label: '分组二', value: 3},
+            ]"
+            placeholder="全部分組"
+          />
+          <CzrFormColumn
+            class="__czr-table-form-column w-[6.88rem]"
+            :span="24"
+            :label-width="0"
+            v-model:param="state.query.form.tag"
+            link="select"
+            :options="[
+                {label: '标签一', value: 1},
+                {label: '标签三', value: 2},
+                {label: '标签二', value: 3},
+              ]"
+            placeholder="全部标签"
+          />
+          <CzrFormColumn
+            class="__czr-table-form-column w-[15.63rem]"
+            :span="24"
+            :label-width="0"
+            v-model:param="state.text"
+            placeholder="按名称搜索"
+            :prefix-icon="Search"
+          />
+        </CzrForm>
+      </div>
+    </div>
+    <div class="overflow-hidden flex-1 mt-[1rem]" v-loading="state.query.loading">
+      <CzrTableCard
+        class="table-card"
+        :page="state.query.page.pageNum"
+        :pageSize="state.query.page.pageSize"
+        :total="state.query.result.total"
+        :data="state.query.result.data"
+        @handlePage="onPage"
+        col-margin="0px"
+        row-margin="0px"
+        :col="4"
+      >
+        <template #model="{ info }">
+          <template v-if="info.empty">
+            <div class="knowledge flex justify-center items-center __hover" @click="onAdd">
+              <img src="@/assets/images/knowledge-item-add.png"/>
+              <span class="text-[1.25rem] ml-[var(--czr-gap)] text-[var(--czr-main-color)]">创建知识库</span>
+            </div>
+          </template>
+          <template v-else>
+            <div class="knowledge flex flex-col">
+              <div class="flex">
+                <img src="@/assets/images/knowledge-item-icon.png" class="mr-[var(--czr-gap)]"/>
+                <div class="flex flex flex-col justify-around">
+                  <div class="text-[1.25rem] text-[#2E3238] font-bold">{{info.p1}}</div>
+                  <div class="text-[0.75rem] text-[#6F7889]">创建者:{{info.p2}}</div>
+                </div>
+              </div>
+              <div class="text-[0.88rem] text-[#606266] mb-auto mt-[var(--czr-gap)]">{{info.p3}}</div>
+              <div class="flex items-center text-[0.75rem] text-[#6F7889] gap-[var(--czr-gap)]">
+                <div>文档数:{{info.p4}}</div>
+                <div>|</div>
+                <div>文档数:{{info.p5}}</div>
+                <div>|</div>
+                <div>文档数:{{info.p6}}</div>
+                <el-tooltip content="编辑" effect="light" placement="top">
+                  <SvgIcon name="czr_edit" size="14" class="__hover ml-auto"/>
+                </el-tooltip>
+                <el-tooltip content="删除" effect="light" placement="top">
+                  <SvgIcon name="czr_del" size="16" class="__hover"/>
+                </el-tooltip>
+                <el-tooltip :content="info.p7 ? '取消收藏' : '收藏'" effect="light" placement="top">
+                  <SvgIcon name="star" size="15" class="__hover" :active="!!info.p7"/>
+                </el-tooltip>
+              </div>
+            </div>
+          </template>
+        </template>
+      </CzrTableCard>
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import {getCurrentInstance, reactive, ref} from "vue";
+import {getCurrentInstance, onMounted, reactive, ref, watch} from "vue";
+import { Search } from '@element-plus/icons-vue'
+import {debounce} from "lodash";
 
 const emits = defineEmits([])
 const props = defineProps({})
 const {proxy}: any = getCurrentInstance()
-const state: any = reactive({})
+const state: any = reactive({
+  text: '',
+  query: {
+    loading: false,
+    page: {
+      pageNum: 1,
+      pageSize: 20
+    },
+    form: {},
+    formReal: {},
+    result: {
+      total: 0,
+      data: []
+    },
+  },
+  detail: {
+    show: false,
+    transfer: {}
+  }
+})
+const setText = debounce((v) => {
+  state.query.form.name = v
+}, 1000)
+watch(() => state.text, (n) => {
+  setText(n)
+})
+watch(() => state.query.form, (n) => {
+  onSearch()
+}, {deep: true})
+const onPage = (pageNum, pageSize) => {
+  state.query.page = {
+    pageNum: pageNum,
+    pageSize: pageSize,
+  }
+  const params = {
+    pageNum: state.query.page.pageNum,
+    pageSize: state.query.page.pageSize,
+  }
+  //  添加表单参数
+  for (const [k, v] of Object.entries(state.query.formReal)) {
+    if (proxy.$czrUtil.isValue(v)) {
+      params[k] = v
+    }
+  }
+  state.query.loading = true
+  setTimeout(() => {
+    state.query.result.total = 100
+    const arr: any = [{empty: true}]
+    for (let i = 1; i <= params.pageSize; i++) {
+      const n = (params.pageNum - 1) * params.pageSize + i
+      arr.push({
+        p1: '部门知识库-' + n,
+        p2: '王一鸣',
+        p3: '只是一个政务服务事项办事指南',
+        p4: n,
+        p5: '2980k',
+        p6: n % 4,
+        p7: n % 2
+      })
+    }
+    state.query.result.data = arr
+    state.query.loading = false
+  }, 1000)
+  // state.query.loading = true
+  // listRuleLogisticsDataSubscribeInfo(params).then(res => {
+  //   if (res.code == 200) {
+  //     state.query.result.total = res.total
+  //     state.query.result.data = res.rows
+  //     state.query.loading = false
+  //   } else {
+  //     ElMessage.error(res.msg)
+  //   }
+  // }).catch(() => {
+  //   state.query.loading = false
+  // })
+}
+const onSearch = () => {
+  state.query.formReal = JSON.parse(JSON.stringify(state.query.form))
+  onPage(1, state.query.page.pageSize)
+}
+const onReset = () => {
+  state.query.page = {
+    pageNum: 1,
+    pageSize: 20
+  }
+  state.query.form = {}
+  onSearch()
+}
+const onAdd = () => {
+  state.detail.transfer = {
+    mode: 'add'
+  }
+  state.detail.show = true
+}
+const onEdit = (row) => {
+  state.detail.transfer = {
+    mode: 'edit',
+    id: row.id
+  }
+  state.detail.show = true
+}
+const onDel = (row = null) => {
+  // if (row) {
+  //   ElMessageBox.confirm(`<span class="__cus-del-sub-text">请确认是否删除${row.manifestNumber}?<br/><span>删除之后无法恢复该数据!</span></span>`, "提示", {
+  //     confirmButtonText: "是",
+  //     cancelButtonText: "否",
+  //     type: "warning",
+  //     dangerouslyUseHTMLString: true,
+  //   } as any).then(() => {
+  //     state.query.loading = true
+  //     delRuleLogisticsDataSubscribeInfo(row.id).then(res => {
+  //       if (res.code == 200) {
+  //         ElMessage.success('删除成功!')
+  //         onSearch()
+  //       } else {
+  //         ElMessage.error(res.msg)
+  //       }
+  //     })
+  //   }).catch(() => {
+  //     state.query.loading = false
+  //   })
+  // } else {
+  //   if (state.query.selected.length == 0) {
+  //     ElMessage.warning('请至少选择一条记录!')
+  //     return
+  //   }
+  //   ElMessageBox.confirm(`<span class="__cus-del-sub-text">请确认是否删除${state.query.selected.length}条记录?<br/><span>删除之后无法恢复该数据!</span></span>`, "提示", {
+  //     confirmButtonText: "是",
+  //     cancelButtonText: "否",
+  //     type: "warning",
+  //     dangerouslyUseHTMLString: true,
+  //   } as any).then(() => {
+  //     state.query.loading = true
+  //     delRuleLogisticsDataSubscribeInfo(state.query.selected.map(v => v.id).join(',')).then(res => {
+  //       if (res.code == 200) {
+  //         ElMessage.success('删除成功!')
+  //         onSearch()
+  //       } else {
+  //         ElMessage.error(res.msg)
+  //       }
+  //     })
+  //   }).catch(() => {
+  //     state.query.loading = false
+  //   })
+  // }
+}
+
+const initDictionary = () => {
+}
+onMounted(() => {
+  initDictionary()
+  onReset()
+})
 </script>
 
 <style lang="scss" scoped>
+.knowledge {
+  width: 100%;
+  height: 11.81rem;
+  background-image: url("@/assets/images/knowledge-item-bg.png");
+  background-repeat: no-repeat;
+  background-size: 100% 100%;
+  padding: 1rem 1.5rem;
+}
 </style>