فهرست منبع

Merge remote-tracking branch 'origin/master'

liuc 7 ماه پیش
والد
کامیت
9c548dacb5

BIN
src/assets/images/page-detail-layout/selectedIcon.png


BIN
src/assets/images/page-detail-layout/unSelectedIcon.png


+ 13 - 0
src/components/BsUi/ContentsWrapper/index.vue

@@ -0,0 +1,13 @@
+<template >
+  <div class="content-wrap">
+    <slot></slot>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.content-wrap {
+  display: flex;
+  flex-direction: column;
+  gap: 15px;
+}
+</style>

+ 113 - 0
src/components/BsUi/Descriptions/index.vue

@@ -0,0 +1,113 @@
+<template>
+  <div class="description">
+    <div class="d-title" @click="handleClkHeader">
+      <span>{{ title }}</span>
+      <div>
+        <DownOutlined style="font-size:12px; color: #979797" v-if="foldState" />
+        <UpOutlined style="font-size:12px; color: #979797" size="10px" v-if="!foldState" />
+      </div>
+    </div>
+
+    <div v-show="foldState">
+      <div class="default_slot" v-if="slots.default">
+        <slot></slot>
+      </div>
+      <a-descriptions v-if="!isEmpty(items)" :bordered="false">
+        <a-descriptions-item v-for="(item, index) in items" :key="index" v-bind="item.extraProps">
+          <template #label>
+            <slot v-if="item.labelSlot" :name="item.labelSlot"></slot>
+            <span v-else class="dsc-label">{{ item.label }}</span>
+          </template>
+
+          <template v-if="item.valueSlot">
+            <slot :name="item.valueSlot"></slot>
+          </template>
+          <template v-if="!item.valueSlot">
+            <div class="dsc-value">
+              {{ item.value }}
+            </div>
+          </template>
+        </a-descriptions-item>
+      </a-descriptions>
+    </div>
+  </div>
+</template>
+<script setup>
+  import { isEmpty } from 'lodash';
+  import {ref, useSlots} from 'vue';
+
+  const props = defineProps({
+    title: {
+      required: true,
+      default: '',
+    },
+    items: {
+      required: false,
+      default: [],
+    },
+    isFolded: {
+      required: false,
+      default: false,
+    },
+  });
+
+  const foldState = ref(true);
+
+  const emits = defineEmits(['update:isFolded']);
+
+  const slots = useSlots();
+
+  const handleClkHeader = () => {
+    foldState.value = !foldState.value;
+  };
+</script>
+<style lang="scss" scoped>
+  .description {
+    width: 100%;
+    .d-title {
+      font-size: 16px;
+      font-weight: 600;
+      padding: 10px 0 10px 10px;
+      border-bottom: 1px solid #e4e7ed;
+      cursor: pointer;
+      position: relative;
+      display: flex;
+      align-items: center;
+      gap: 10px;
+      &:hover {
+        background: rgba(#000, .1);
+        border-radius: 8px;
+      }
+      &::before {
+        width: 4px;
+        height: 18px;
+        background: var(--webchat-toolbar-background-color);
+        position: absolute;
+        left: 0;
+        top: 50%;
+        transform: translateY(-50%);
+        content: '';
+        border-radius: 4px;
+      }
+    }
+    .dsc-label {
+      font-size: 14px;
+      color: #6c6c6c;
+    }
+    .dsc-value {
+      font-size: 14px;
+      color: #000;
+      font-weight: 500;
+    }
+    :deep(.ant-descriptions-header) {
+      margin: 0 0 10px 0;
+    }
+    :deep(.ant-descriptions-item) {
+      margin: 0;
+      padding: 10px 0 0 0;
+    }
+    .default_slot {
+      padding: 10px 0;
+    }
+  }
+</style>

+ 13 - 0
src/components/BsUi/Descriptions/item/index.vue

@@ -0,0 +1,13 @@
+<template>
+  <div class="item">
+
+  </div>
+</template>
+
+<script setup>
+
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 42 - 29
src/components/BsUi/SubTableInput/index.vue

@@ -6,7 +6,7 @@
           <div class="custom-table-btn">
             <div class="ct-btn">
               <slot v-if="slots.tableLeftBtn" name="tableLeftBtn"></slot>
-              <a-button type="text" style="width: 100%">
+              <a-button type="text" style="width: 100%" @click="handleAddBtn">
                 <template #icon> <PlusOutlined /></template>
                 <span>新增</span>
               </a-button>
@@ -26,10 +26,11 @@
 </template>
 
 <script setup lang="jsx">
-import {useSlots, h, ref, toRefs, onMounted, nextTick} from 'vue';
+  import { useSlots, h, ref, toRefs, onMounted, nextTick, watch } from 'vue';
   import BsTable, { useBsTable } from '/@/components/BsUi/Table';
   import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue';
   import { cloneDeep, isEmpty } from 'lodash';
+  import { DISPLAY_STATE } from '/@/components/BsUi/constant.js';
 
   const props = defineProps({
     customColumns: {
@@ -44,14 +45,9 @@ import {useSlots, h, ref, toRefs, onMounted, nextTick} from 'vue';
       required: false,
       default: 'id',
     },
-
-    getBsTableAdapter: {
-      required: true,
-      default: null,
-    },
   });
 
-  const emits = defineEmits('change');
+  const emits = defineEmits('change', 'update:value', 'add');
   const slots = useSlots();
 
   const selectedData = ref([]);
@@ -59,7 +55,7 @@ import {useSlots, h, ref, toRefs, onMounted, nextTick} from 'vue';
 
   const bsTableAdapter = {
     toolbarConfig: {
-      enable: true,
+      enable: false,
     },
     searchConfig: {
       enable: false,
@@ -67,7 +63,6 @@ import {useSlots, h, ref, toRefs, onMounted, nextTick} from 'vue';
     gridOptions: {
       border: true,
       minHeight: '0',
-      maxHeight: '300',
       data: [],
       columns: [],
       onCheckboxChange(props) {
@@ -81,28 +76,24 @@ import {useSlots, h, ref, toRefs, onMounted, nextTick} from 'vue';
     },
     pagerConfig: {
       enable: false,
+      isFixed: false,
     },
   };
 
   const bsTableBean = useBsTable({ tableOptions: bsTableAdapter });
 
-  const {
-    tableOptions,
-    setTablePropsValue: setValue,
-    getTablePropsValue: getValue,
-    getGridRef,
-  } = bsTableBean;
+  const { tableOptions, setTablePropsValue: setValue, getTablePropsValue: getValue, getGridRef } = bsTableBean;
 
   const selectAndSeqCols = [
-    { type: 'checkbox', width: 30, align: 'center' },
-    { type: 'seq', width: 50, align: 'center' },
+    { type: 'checkbox', width: 60, align: 'center', fixed: 'left' },
+    { type: 'seq', width: 50, align: 'center', fixed: 'left' },
   ];
 
   const optionsCols = [
     {
       field: 'btnOption',
       headerAlign: 'center',
-      width: "50",
+      width: '50',
       align: 'center',
       fixed: 'right',
       slots: {
@@ -118,7 +109,9 @@ import {useSlots, h, ref, toRefs, onMounted, nextTick} from 'vue';
                     const tableData = getValue('gridOptions.data');
                     const deepCloneData = cloneDeep(tableData);
                     deepCloneData.splice(rowIndex, 1);
-                    setValue('gridOptions.data', deepCloneData);
+                    // setValue('gridOptions.data', deepCloneData);
+                    emits('update:value', deepCloneData);
+                    emits('change', deepCloneData);
                   }}
                 ></a-button>
               </a-tooltip>
@@ -135,26 +128,35 @@ import {useSlots, h, ref, toRefs, onMounted, nextTick} from 'vue';
       },
     },
   ];
-
   const gridOptions = getValue('gridOptions');
-
   gridOptions.columns = [...selectAndSeqCols, ...optionsCols];
-
   if (!isEmpty(props.customColumns)) {
     gridOptions.columns = [...selectAndSeqCols, ...props.customColumns, ...optionsCols];
   }
+  const handleAddBtn = () => {
+    const tableData = getValue('gridOptions.data');
+    emits('add', {
+      gridRef: getGridRef(),
+      bsTable: bsTableBean,
+      tableData,
+    });
+  };
 
   const handleBatchDelete = () => {
     const tableData = getValue('gridOptions.data');
     const newTableData = tableData.filter((v) => selectedData.value.findIndex((v1) => v[props.rowKey] === v1[props.rowKey]) === -1);
-    setValue('gridOptions.data', newTableData);
-    selectedData.value = [];
+    // setValue('gridOptions.data', newTableData);
+    emits('update:value', newTableData);
+    emits('change', newTableData);
   };
 
-  onMounted(() => {
-    props.getBsTableAdapter && props.getBsTableAdapter(bsTableBean);
-    setValue("gridOptions.data", props.value);
-  })
+  watch(
+    () => props.value,
+    (val) => {
+      setValue('gridOptions.data', val);
+    },
+    { immediate: true }
+  );
 </script>
 
 <style scoped lang="scss">
@@ -166,6 +168,17 @@ import {useSlots, h, ref, toRefs, onMounted, nextTick} from 'vue';
     :deep(.vxe-grid--table-container) {
       padding: 0;
     }
+
+    :deep(.vxe-grid--layout-body-content-wrapper) {
+      padding: 0;
+    }
+    :deep(.vxe-grid--layout-body-wrapper) {
+      padding: 10px;
+    }
+
+    :deep(.top-bottom) {
+      padding: 0;
+    }
     .table-bottom-btns {
       display: flex;
       width: 100%;

+ 2 - 2
src/components/BsUi/Table/Table.vue

@@ -27,8 +27,8 @@
 
           <slot name="toolbarTop"></slot>
         </div>
-        <div class="top-bottom">
-          <div class="top-left" v-if="toolbarConfig && toolbarConfig.enable">
+        <div class="top-bottom" v-if="toolbarConfig && toolbarConfig.enable">
+          <div class="top-left">
             <Toolbar :toolbarConfig="toolbarConfig">
               <a-space v-if="toolbarConfig.leftButtons" style="margin-right: 8px">
                 <a-button v-for="(btn, idx) in toolbarConfig.leftButtons" :key="btn.code" size="middle" v-bind="btn.props">{{

+ 58 - 0
src/components/BsUi/Tabs/index.vue

@@ -0,0 +1,58 @@
+<template>
+  <a-tabs v-model:activeKey="activeKey">
+    <a-tab-pane :key="tab.key" v-for="tab in tabs">
+      <template #tab>
+        <div class="tab-title">
+          <span>
+            <img :src="tab.key === activeKey ? tab.selectedIcon : tab.unSelectedIcon" alt="" />
+          </span>
+
+          <span class="tab-title-t">{{ tab.title }}</span>
+        </div>
+      </template>
+      <div class="tabs-content">
+        <slot :name="tab.slotName" v-if="tab.key === activeKey"></slot>
+      </div>
+    </a-tab-pane>
+  </a-tabs>
+</template>
+<script setup>
+import { ref, watch } from 'vue';
+import { isEmpty } from 'lodash';
+const emits = defineEmits(['update:tabActiveKey', 'change']);
+const props = defineProps({
+  tabs: {
+    required: true,
+    default: [],
+  },
+  tabActiveKey: {
+    required: false,
+    default: '',
+  },
+});
+
+const activeKey = ref(isEmpty(props.tabActiveKey) ? props.tabs[0].key : props.tabActiveKey);
+
+watch(
+    activeKey,
+    (val) => {
+      emits('change', val);
+    },
+    { immediate: true }
+);
+</script>
+
+<style lang="scss" scoped>
+.tab-title {
+  display: flex;
+  align-items: center;
+  gap: 5px;
+  .tab-title-t {
+    font-size: 16px;
+    font-weight: 400;
+  }
+}
+.tabs-content {
+  padding: 0 20px 10px 20px;
+}
+</style>

+ 9 - 0
src/components/BsUi/index.js

@@ -7,6 +7,9 @@ import BsTable, { useBsTable } from './Table/index.js';
 import BsDrawer, { useBsDrawer } from './Drawer/index.js';
 import BsForm, { useBsForm } from './Form/index.js';
 import BsModalTableSelector from './ModalTableSelector/index.vue';
+import BsDescriptions from "./Descriptions/index.vue"
+import BsContentsWrapper from "./ContentsWrapper/index.vue"
+import BsTabs from "./Tabs/index.vue"
 
 const BsUi = {
   install(app) {
@@ -17,6 +20,9 @@ const BsUi = {
     app.component('BsTable', BsTable);
     app.component('BsDrawer', BsDrawer);
     app.component('BsModalTableSelector', BsModalTableSelector);
+    app.component('BsDescriptions', BsDescriptions)
+    app.component('BsContentsWrapper', BsContentsWrapper)
+    app.component('BsTabs', BsTabs)
   },
 };
 
@@ -36,4 +42,7 @@ export {
   BsModalTableSelector,
   BsForm,
   useBsForm,
+  BsDescriptions,
+  BsContentsWrapper,
+  BsTabs
 };

+ 99 - 0
src/components/business/page-detail-layout/index.vue

@@ -0,0 +1,99 @@
+<template>
+  <div class="page-detail-layout">
+    <div class="header-cont">
+      占位
+      <div class="header-top">
+        <div class="header-t">
+          <div class="header-t-left"></div>
+          <div class="header-t-right"></div>
+        </div>
+
+        <div class="header-index"></div>
+      </div>
+      <div class="header-bottom"></div>
+    </div>
+
+    <div class="header-flow">占位</div>
+
+    <div class="header-tabs">
+      <bs-tabs :tabs="tabs" :tab-active-key="tabActiveKey" @change="handleChangeTabActiveKey">
+        <template v-for="tab in tabs" #[tab.slotName]>
+          <slot :name="tab.slotName"></slot>
+        </template>
+      </bs-tabs>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { BsTabs } from '/@/components/BsUi/index.js';
+  const props = defineProps({
+    sceneType: {
+      required: false,
+      default: '',
+    },
+    tabs: {
+      required: false,
+      default: [],
+    },
+    tabActiveKey: {
+      required: false,
+      default: '',
+    },
+  });
+
+  const emits = defineEmits(['update:tabActiveKey'])
+
+  const handleChangeTabActiveKey =  (val) => {
+    emits('update:tabActiveKey', val);
+  }
+
+</script>
+
+<style lang="scss" scoped>
+  .page-detail-layout {
+    width: 100%;
+    height: 100%;
+    .header-cont {
+      width: 100%;
+      border-radius: 8px;
+      background: #fff;
+      .header-top {
+        width: 100%;
+        .header-t {
+          width: 100%;
+        }
+        .header-index {
+          width: 100%;
+        }
+      }
+
+      .header-bottom {
+        width: 100%;
+      }
+    }
+
+    .header-flow {
+      margin-top: 10px;
+      width: 100%;
+      height: 100%;
+      border-radius: 8px;
+      background: #fff;
+    }
+
+    .header-tabs {
+      margin-top: 10px;
+      width: 100%;
+      height: 100%;
+      border-radius: 8px;
+      background: #fff;
+      :deep(.ant-tabs-nav-wrap) {
+        height: 40px;
+        padding: 0 30px;
+      }
+      :deep(.ant-tabs-nav) {
+        margin: 0 0 10px 0;
+      }
+    }
+  }
+</style>

+ 37 - 12
src/views/form-demo/index.vue

@@ -1,9 +1,8 @@
 <template>
   <div>
     <a-form name="form">
-
       <a-form-item label="人员单选选择器" name="userSelector">
-        <OrgUserSelector  v-model:selected-data="userSelector" :multiple="SELECT_MULTIPLE.ONE" />
+        <OrgUserSelector v-model:selected-data="userSelector" :multiple="SELECT_MULTIPLE.ONE" />
       </a-form-item>
 
       <a-form-item label="人员多选选择器" name="userSelectors">
@@ -11,42 +10,70 @@
       </a-form-item>
 
       <a-form-item label="组织单选选择器" name="orgSelector">
-        <OrgUserSelector  v-model:selected-data="orgSelector" :multiple="SELECT_MULTIPLE.ONE"  :scene-type="SCENE_TYPE.ORG"/>
+        <OrgUserSelector v-model:selected-data="orgSelector" :multiple="SELECT_MULTIPLE.ONE" :scene-type="SCENE_TYPE.ORG" />
       </a-form-item>
 
       <a-form-item label="组织多选选择器" name="orgSelectors">
-        <OrgUserSelector v-model:selected-data="orgSelectors" :multiple="SELECT_MULTIPLE.MORE"  :scene-type="SCENE_TYPE.ORG"/>
+        <OrgUserSelector v-model:selected-data="orgSelectors" :multiple="SELECT_MULTIPLE.MORE" :scene-type="SCENE_TYPE.ORG" />
       </a-form-item>
 
+      <a-form-item label="子表" name="batchTable">
+        <BsSubTableInput :customColumns="columns" v-model:value="batchTable" :row-key="id" @add="handleAdd" />
+      </a-form-item>
     </a-form>
   </div>
 </template>
 
 <script setup>
   import OrgUserSelector from '/@/components/BsUi/OrgUserSelector/index.vue';
-  import { ref } from 'vue';
-  import {SCENE_TYPE, SELECT_MULTIPLE} from '/@/components/BsUi/constant.js';
+  import { reactive, ref } from 'vue';
+  import { SCENE_TYPE, SELECT_MULTIPLE } from '/@/components/BsUi/constant.js';
+  import { BsSubTableInput } from '/@/components/BsUi/index.js';
+
+  const batchTable = ref([
+    {
+      id: '1',
+      name: '韩晓辉',
+      sex: '男',
+    },
+  ]);
+
+  const columns = ref([
+    {
+      title: '姓名',
+      field: 'name',
+    },
+    {
+      title: '性别',
+      field: 'sex',
+    },
+  ]);
+
+  const handleAdd = ({ tableData }) => {
+    batchTable.value = [...tableData, { name: '甘雨' + new Date().getTime(), sex: '女' + new Date().getTime(), id: new Date().getTime() }];
+  };
+
   const userSelectors = ref([
     {
       name: '韩晓辉0',
       id: 'eqwifjqiwejf',
       parentName: '无限畅联',
       parentId: '1',
-      nodeType: "USER"
+      nodeType: 'USER',
     },
     {
       name: '韩晓辉1',
       id: 'fqioweoiq',
       parentName: '无限畅联',
       parentId: '1',
-      nodeType: "USER"
+      nodeType: 'USER',
     },
     {
       name: '韩晓辉55',
       id: 'jfslkf',
       parentName: '无限畅联',
       parentId: '1',
-      nodeType: "USER"
+      nodeType: 'USER',
     },
   ]);
 
@@ -55,12 +82,10 @@
     id: '1',
     parentName: '无限畅联',
     parentId: '1',
-    nodeType: "USER"
+    nodeType: 'USER',
   });
 
   const orgSelector = ref({});
 
   const orgSelectors = ref([]);
-
-
 </script>

+ 97 - 0
src/views/page-detail/index.vue

@@ -0,0 +1,97 @@
+<template>
+  <div class="page-detail">
+    <page-detail-layout :tabs="tabs" v-model:tab-active-key="tabActiveKey">
+      <template #tab1>
+        <bs-contents-wrapper>
+          <bs-descriptions :items="bsDescriptionItems" title="表头1">
+            <template #name1_label_slot>
+              <span style="color: red">label插槽</span>
+            </template>
+            <template #name1_value_slot> <span style="color: blue">value插槽</span></template>
+          </bs-descriptions>
+          <bs-descriptions :items="bsDescriptionItems" title="表头2">
+            <template #name1_label_slot>
+              <span style="color: red">label插槽</span>
+            </template>
+            <template #name1_value_slot> <span style="color: blue">value插槽</span></template>
+          </bs-descriptions>
+          <bs-descriptions :items="bsDescriptionItems" title="表头3">
+            <template #name1_label_slot>
+              <span style="color: red">label插槽</span>
+            </template>
+            <template #name1_value_slot> <span style="color: blue">value插槽</span></template>
+          </bs-descriptions>
+          <bs-descriptions title="表头4" >
+            内容4
+          </bs-descriptions>
+        </bs-contents-wrapper>
+
+      </template>
+      <template #tab2>tab2</template>
+      <template #tab3>tab3</template>
+    </page-detail-layout>
+  </div>
+</template>
+
+<script setup>
+  import { ref } from 'vue';
+  import PageDetailLayout from '/@/components/business/page-detail-layout/index.vue';
+  import { BsDescriptions, BsContentsWrapper } from '/@/components/BsUi/index.js';
+
+  import selectedIcon from '/@/assets/images/page-detail-layout/selectedIcon.png';
+  import unSelectedIcon from '/@/assets/images/page-detail-layout/unSelectedIcon.png';
+  const tabActiveKey = ref('tab1');
+
+  const tabs = ref([
+    {
+      title: 'tab1',
+      key: 'tab1',
+      slotName: 'tab1',
+      selectedIcon: selectedIcon,
+      unSelectedIcon: unSelectedIcon,
+    },
+    {
+      title: 'tab2',
+      key: 'tab2',
+      slotName: 'tab2',
+      selectedIcon: selectedIcon,
+      unSelectedIcon: unSelectedIcon,
+    },
+    {
+      title: 'tab3',
+      key: 'tab3',
+      slotName: 'tab3',
+      selectedIcon: selectedIcon,
+      unSelectedIcon: unSelectedIcon,
+    },
+  ]);
+
+  const bsDescriptionItems = ref([
+    {
+      label: '姓名',
+      field: 'name',
+      value: '韩晓辉',
+      extraProps: {},
+    },
+    {
+      label: '姓名1',
+      field: 'name1',
+      value: '韩晓辉1',
+      labelSlot: 'name1_label_slot',
+      valueSlot: 'name1_value_slot',
+      extraProps: {},
+    },
+    {
+      label: '姓名1',
+      field: 'name1',
+      value: '韩晓辉1',
+      extraProps: {},
+    },
+  ]);
+</script>
+
+<style lang="scss" scoped>
+  .page-detail {
+    width: 100%;
+  }
+</style>

+ 3 - 4
src/views/table-demo/index.vue

@@ -65,8 +65,6 @@
   };
 
   const openModalBase = () => {
-    console.log('demoBaseModal', demoBaseModal);
-
     demoBaseModal.value.showModal();
   };
 
@@ -88,7 +86,7 @@
     fetchTableData,
   } = useBsTable({
     tableOptions: {
-      url: '/api/get-table',
+      url: '/supports/customer/queryPage',
       toolbarTopConfig: {
         enable: true,
         buttons: [
@@ -359,6 +357,7 @@
 </script>
 
 <style scoped lang="scss">
-  .table-demo {
+  .sub-table-input {
+    padding: 10px;
   }
 </style>