Bladeren bron

fix: 添加组件

fan 2 jaren geleden
bovenliggende
commit
fb591c1c0c

+ 233 - 0
src/components/XTCard/src/ChartsCard.vue

@@ -0,0 +1,233 @@
+<template>
+  <div class="card">
+    <div class="card-tit">
+      <div class="card-tit_label"> {{ title }}</div>
+      <div class="card-tit_value">
+        <div class="card-tit_value-item" v-for="item in colors" :key="item.color">
+          <div class="card-tit_value-color" :style="{ background: item.color }" />
+          <div class="card-tit_value-info">
+            {{ item.label }}
+          </div>
+        </div>
+        <div class="card-tit_value-item" v-if="hasSafe">
+          <div class="card-tit_value-color" :style="{ background: safeColor }" />
+          <div class="card-tit_value-info"> 正常范围 </div>
+        </div>
+      </div>
+    </div>
+    <div class="card-charts" ref="chartRef" :style="{ height, width }" />
+  </div>
+</template>
+<script lang="ts" setup>
+  import { onMounted, PropType, ref, Ref } from 'vue';
+  import { useECharts } from '/@/hooks/web/useECharts';
+  // import { clinicStats } from '/@/api/infusion/busClinicApi';
+  const props = defineProps({
+    // 宽
+    width: {
+      type: String as PropType<string>,
+      default: '100%',
+    },
+    // 高
+    height: {
+      type: String as PropType<string>,
+      default: '400px',
+    },
+    // 标题
+    title: {
+      type: String,
+      default: '',
+    },
+    // 标题右侧颜色值
+    colors: {
+      type: Array as any,
+      default: () => [],
+    },
+    // 是否有正常范围
+    hasSafe: {
+      type: Boolean,
+      default: false,
+    },
+    safeColor: {
+      type: String,
+      default: 'rgba(33, 201, 153, 0.12)',
+    },
+    safeRange: {
+      type: Array,
+      default: () => [
+        {
+          // name: '60分到80分',
+          yAxis: 0,
+        },
+        {
+          yAxis: 10,
+        },
+      ],
+    },
+    // 图表数据
+    data: {
+      type: Object,
+      default: () => {},
+    },
+  });
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+  // 各种信息数据集
+  const infoData = ref([]);
+  // 上传时间
+  const dateData = ref([]);
+  const start = ref();
+  const markArea = ref();
+  const series = ref([]);
+  onMounted(async () => {
+    // const resData = await clinicStats({
+    //   type: '',
+    //   id: '123',
+    // });
+    // console.log(resData);
+    // infoData.value = resData.validCount || [];
+    // dateData.value = resData.uploadTimes || [];
+    infoData.value = [10, 25, 30, 16, 36, 10];
+    dateData.value = [
+      '2023-05-10',
+      '2023-05-11',
+      '2023-05-12',
+      '2023-05-13',
+      '2023-05-14',
+      '2023-05-15',
+    ];
+    // 数据显示
+    start.value = infoData.value.length > 10 ? 80 : 0;
+    markArea.value = props.hasSafe
+      ? {
+          silent: false,
+          itemStyle: {
+            color: props.safeColor,
+          },
+          data: [props.safeRange],
+        }
+      : { silent: false };
+    series.value = props.colors.map(ele => {
+      return {
+        type: 'line',
+        symbolSize: 10,
+        symbol: 'circle',
+        name: ele.label,
+        // label: {
+        //   show: true,
+        //   position: 'top',
+        // },
+        lineStyle: {
+          color: ele.color,
+        },
+        itemStyle: {
+          color: ele.dot,
+        },
+        data: infoData.value || [],
+        markArea: markArea.value,
+      };
+    });
+    setOptions({
+      tooltip: {
+        trigger: 'axis',
+      },
+      toolbox: {
+        show: true,
+        feature: {
+          saveAsImage: {
+            title: '保存为图片',
+          },
+        },
+      },
+      grid: {
+        left: '2%',
+        right: '2%',
+        bottom: '10%',
+        containLabel: true,
+      },
+      xAxis: {
+        type: 'category',
+        boundaryGap: false,
+        data: dateData.value,
+      },
+      yAxis: {
+        type: 'value',
+        splitLine: {
+          lineStyle: {
+            color: ['rgba(235, 235, 235, 1)'],
+            type: 'dashed',
+          },
+        },
+      },
+      dataZoom: start.value
+        ? [
+            {
+              type: 'inside',
+              start: start.value,
+              end: 100,
+            },
+            {
+              start: start.value,
+              end: 100,
+            },
+          ]
+        : [],
+      series: series.value,
+    });
+  });
+</script>
+<style lang="less" scoped>
+  .card {
+    border-radius: 4px;
+    border: 1px solid #efefef;
+    background-color: #fff;
+
+    &-tit {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 0 18px;
+      height: 56px;
+      line-height: 56px;
+      background: #f6f8fa;
+      margin-bottom: 20px;
+
+      &_label {
+        font-size: 18px;
+        font-weight: 600;
+        color: #000a18;
+      }
+
+      &_value {
+        display: flex;
+        align-items: center;
+
+        &-item {
+          display: flex;
+          align-items: center;
+          margin-right: 40px;
+
+          &:last-child {
+            margin-right: 0;
+          }
+        }
+
+        &-color {
+          display: block;
+          width: 10px;
+          height: 10px;
+          border-radius: 2px;
+          margin-right: 6px;
+        }
+
+        &-info {
+          color: #818694;
+        }
+      }
+    }
+
+    &-charts {
+      padding: 10px;
+    }
+  }
+</style>

+ 259 - 0
src/components/XTCard/src/DescCard.vue

@@ -0,0 +1,259 @@
+<template>
+  <div class="card">
+    <div class="card-head">
+      <div class="card-head_label"> {{ title }}</div>
+      <div class="card-head_value">
+        <i :class="['iconfont', icon]" v-if="icon" @click="handleIcon" />
+        <slot name="headRight" />
+      </div>
+    </div>
+    <Row class="card-body" v-if="data.length">
+      <Col class="card-body_left" :span="right.show ? 20 : 24">
+        <Row>
+          <Col
+            :span="item.span || wrapSpan"
+            class="card-body-item"
+            v-for="item in data"
+            :key="item.label"
+          >
+            <div class="card-body-item_label" v-if="item.label">{{ item.label }}</div>
+            <div class="card-body-item_value" v-if="item.value">
+              <div>{{ item.value }}</div>
+              <div v-if="item.tags" class="flex">
+                <div
+                  v-for="t in item.tags"
+                  :key="t.id"
+                  :class="['card-body-item_tag', 'card-body-item_tag--' + t.type]"
+                >
+                  {{ t.label }}
+                </div>
+              </div>
+              <div>
+                <slot name="tags" />
+              </div>
+            </div>
+          </Col>
+        </Row>
+      </Col>
+      <Col class="card-body_right" v-if="right.show" span="4">
+        <div>{{ right.date ? right.date + '/' : '' }} {{ right.doctor }}</div>
+        <div class="flex">
+          <div v-if="right.edit" class="card-body_right-btn" @click="handleEdit">
+            <i class="iconfont icon-xt-details_edit_default" />
+          </div>
+          <div v-if="right.delete" class="card-body_right-btn" @click="handleDel">
+            <!-- 删除 -->
+            <i class="iconfont icon-xt-details_delete_default" />
+          </div>
+        </div>
+      </Col>
+    </Row>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { Row, Col } from 'ant-design-vue';
+  interface Props {
+    id?: string;
+    // 标题
+    title: string;
+    // icon
+    icon?: string;
+    iconType?: string;
+    // 每行宽度
+    wrapSpan?: number;
+    type?: string;
+    right?: {
+      show: boolean;
+      date?: string;
+      doctor?: string;
+      edit?: boolean;
+      delete?: boolean;
+      span?: number;
+    };
+    // 数据集
+    data: Array<{
+      label?: string;
+      value?: string;
+      span?: number;
+      tags?:
+        | Array<{
+            id: string;
+            label: string;
+            type: string | 'warning' | 'error' | 'success' | 'primary' | 'muted';
+          }>
+        | undefined
+        | null;
+    }>;
+  }
+  const props = withDefaults(defineProps<Props>(), {
+    title: '标题',
+    wrapSpan: 6,
+    type: '',
+    data: () => [],
+    right: () => {
+      return {
+        show: false,
+        span: 4,
+        edit: true,
+        delete: false,
+      };
+    },
+  });
+  const emit = defineEmits(['edit', 'delete', 'icon']);
+
+  function handleEdit() {
+    const info = { id: props.id, type: props.type, action: 'edit' };
+    console.log('🚀 ~ file: DescCard.vue:100 ~ handleEdit ~ info:', info);
+    emit('edit', info);
+  }
+  function handleDel() {
+    const info = { id: props.id, type: props.type, action: 'delete' };
+    emit('delete', info);
+    console.log('🚀 ~ file: DescCard.vue:105 ~ handleDel ~ info:', info);
+  }
+
+  function handleIcon() {
+    const info = { id: props.id, type: props.type, action: props.iconType || 'icon' };
+    emit('icon', info);
+    console.log('🚀 ~ file: DescCard.vue:113 ~ handleIcon ~ info:', info);
+  }
+</script>
+
+<style lang="less" scoped>
+  .card {
+    border-radius: 4px;
+    border: 1px solid #efefef;
+    background-color: #fff;
+
+    &-head {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      height: 56px;
+      background: #f6f8fa;
+      box-shadow: 0 1px 0 0 #efefef;
+      border-radius: 3px 3px 0 0;
+      padding: 0 20px;
+
+      &_label {
+        font-size: 18px;
+        font-weight: 600;
+        color: #000a18;
+      }
+
+      &_value {
+        & .iconfont {
+          display: block;
+          margin-right: 16px;
+          background: #fff;
+          border-radius: 50%;
+          width: 32px;
+          height: 32px;
+          text-align: center;
+          line-height: 32px;
+          cursor: pointer;
+
+          &:last-child {
+            margin-right: 0;
+          }
+
+          &:hover {
+            color: #0075ff;
+          }
+        }
+      }
+    }
+
+    &-body {
+      display: flex;
+      align-items: center;
+      padding: 20px;
+
+      &_right {
+        display: flex;
+        align-items: center;
+        justify-content: flex-end;
+        font-size: 12px;
+        color: #818694;
+
+        &-btn {
+          margin-right: 16px;
+          color: #3d4155;
+          background: #f4f6f9;
+          border-radius: 50%;
+          width: 32px;
+          height: 32px;
+          text-align: center;
+          line-height: 32px;
+          cursor: pointer;
+          margin-left: 10px;
+
+          &:last-child {
+            margin: 0;
+          }
+
+          &:hover {
+            color: #0075ff;
+          }
+        }
+      }
+
+      &-item {
+        display: flex;
+        flex-direction: column;
+
+        &_label {
+          font-size: 12px;
+          font-weight: 400;
+          color: #818694;
+        }
+
+        &_value {
+          display: flex;
+          font-size: 14px;
+          font-weight: 500;
+          color: #000a18;
+        }
+
+        &_tag {
+          font-size: 12px;
+          height: 20px;
+          border-radius: 12px;
+          padding: 1px 10px;
+          margin: 0 8px;
+          white-space: nowrap;
+
+          &:last-child {
+            margin-right: 0;
+          }
+
+          &--warning {
+            background: #fff6e7;
+            color: #f90;
+          }
+
+          &--error {
+            background: #ffeee3;
+            color: #ff5d39;
+          }
+
+          &--success {
+            color: #19be6b;
+            background: #ecf8f2;
+          }
+
+          &--primary {
+            color: #0075ff;
+            background: #e5f1ff;
+          }
+
+          &--muted {
+            color: #828890;
+            background: #e1e3e7;
+          }
+        }
+      }
+    }
+  }
+</style>

+ 45 - 7
src/components/XTForm/src/XTForm.vue

@@ -1,13 +1,11 @@
 <template>
   <div v-if="formShow">
-    <Form :model="formRef" class="flex justify-end">
+    <Form :model="formRef" class="flex justify-end" layout="inline">
       <div class="ml-2" v-for="item in formData" :key="item.name">
         <FormItem class="flex">
-          <template #label v-if="item.label">
-            <span :title="item.label">
-              {{ item.label }}
-            </span>
-          </template>
+          <div :title="item.label" v-if="item.label" class="xt-from_label">
+            {{ item.label }}
+          </div>
           <div v-if="item.componentType == ComponentEnum.Input">
             <Input
               v-model:value="formRef[item.name]"
@@ -16,6 +14,7 @@
               :style="{ width: item.width + 'px' }"
               :defaultValue="item.defaultValue"
               @change="handleChange"
+              :size="item.size || 'default'"
             />
           </div>
           <div v-if="item.componentType == ComponentEnum.Select">
@@ -26,6 +25,7 @@
               :style="{ width: item.width + 'px' }"
               :defaultValue="item.defaultValue"
               @change="handleChange"
+              :size="item.size || 'default'"
             >
               <SelectOption
                 v-for="r in item.dicts"
@@ -55,8 +55,14 @@
               :style="{ width: item.width + 'px' }"
               :defaultValue="item.defaultValue"
               @change="handleChange"
+              :size="item.size || 'default'"
             />
           </div>
+          <div v-if="item.componentType == ComponentEnum.IconBtn">
+            <div class="icon-btn">
+              <i class="iconfont icon-lock" @click="handleClick(item)" />
+            </div>
+          </div>
         </FormItem>
       </div>
     </Form>
@@ -107,13 +113,14 @@
       }>;
       disabled?: boolean;
       defaultValue?: string;
+      size?: string;
     }>;
   }
   withDefaults(defineProps<Props>(), {
     formData: () => [],
     formShow: true,
   });
-  const emit = defineEmits(['change']);
+  const emit = defineEmits(['change', 'click']);
   // 表单
   const formRef = ref<any>({});
   function handleChange() {
@@ -125,6 +132,11 @@
 
     emit('change', formRef.value);
   }, 1000);
+
+  function handleClick(data) {
+    console.log('🚀 ~ file: XTForm.vue:138 ~ handleClick ~ data:', data);
+    emit('click', data);
+  }
 </script>
 
 <style lang="less" scoped>
@@ -134,4 +146,30 @@
     border-radius: 50%;
     margin-right: 4px;
   }
+
+  .icon-btn {
+    width: 40px;
+    height: 40px;
+    background: #fff;
+    box-shadow: 0 0 18px 0 rgb(0 37 74 / 12%);
+    border-radius: 4px;
+    text-align: center;
+    line-height: 40px;
+    cursor: pointer;
+
+    & .iconfont {
+      font-size: 24px;
+      color: #0075ff;
+      margin-right: 0;
+    }
+  }
+
+  ::v-deep(.ant-form-item-control-input-content) {
+    display: flex;
+    align-items: center;
+  }
+
+  .xt-from_label {
+    margin-right: 10px;
+  }
 </style>

+ 2 - 0
src/components/XTForm/src/componentEnum.ts

@@ -19,6 +19,8 @@ export enum ComponentEnum {
   TreeSelect = 'TreeSelect',
   // Upload 上传
   Upload = 'Upload',
+  // 按钮图标组件
+  IconBtn = 'IconBtn',
 }
 
 /**

+ 0 - 0
src/components/XTList/index.ts


+ 182 - 0
src/components/XTList/src/List.vue

@@ -0,0 +1,182 @@
+<template>
+  <div class="menu">
+    <div
+      :class="[
+        'menu-item',
+        item.id == selected ? 'menu-item--selected' : '',
+        type == 'attachment' ? 'menu-item--attachment' : '',
+      ]"
+      :style="{ width: width + 'px', height: height + 'px' }"
+      v-for="item in data"
+      :key="item.id"
+      @click="handleClick(item)"
+    >
+      <div class="menu-item_left">
+        <div class="menu-item_left-t">{{ item.title }}</div>
+        <div class="menu-item_left-b">
+          <span v-if="item.startTime">
+            <span>化疗时间: </span>
+            <span>{{ item.startTime }} </span>
+          </span>
+          <span v-if="item.doctor">
+            <span>{{ item.doctor }} </span>
+          </span>
+        </div>
+      </div>
+      <div class="menu-item_right">
+        <div class="menu-item_right-t" v-if="item.status">
+          {{ item.status }}
+        </div>
+        <div class="menu-item_right-t" v-if="item.attachment">
+          <i class="iconfont icon-attachemnt" />
+          {{ item.attachment }}
+        </div>
+        <div class="menu-item_right-b">
+          <span v-if="item.endTime">
+            <span v-if="type == 'default'">报告时间: </span>
+            <span>{{ item.endTime }} </span>
+          </span>
+        </div>
+      </div>
+
+      <div :class="['menu-item_right', 'menu-item_right--edit', type == 'default' ? 'hidden' : '']">
+        <div class="menu-item_right-btn" @click="handleEdit(item)">
+          <i class="iconfont icon-xt-details_edit_default" />
+        </div>
+        <div class="menu-item_right-btn" @click="handleDel(item)">
+          <!-- 删除 -->
+          <i class="iconfont icon-xt-details_delete_default" />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  interface Props {
+    data: Array<{
+      id: string;
+      // 标题
+      title: string;
+      // 化验时间
+      startTime?: string;
+      // 报告时间 | 创建时间
+      endTime?: string;
+      // 医生名字
+      doctor?: string;
+      // 附件
+      attachment?: string;
+      // 状态
+      status?: string;
+    }>;
+    // 选中项
+    selected: string;
+    // 类型
+    type: 'default' | 'attachment';
+    // 宽度
+    width?: number;
+    // 高度
+    height?: number;
+  }
+  withDefaults(defineProps<Props>(), {
+    data: () => [],
+    selected: '0',
+    type: 'default',
+    width: 445,
+    height: 82,
+  });
+  const emit = defineEmits(['itemClick']);
+  function handleClick(data) {
+    emit('itemClick', data);
+  }
+  // 编辑
+  function handleEdit(data) {
+    console.log('🚀 ~ file: handleEdit List.vue:70 ~ data:', data);
+  }
+  // 删除
+  function handleDel(data) {
+    console.log('🚀 ~ file: handleDel List.vue:74 ~ data:', data);
+  }
+</script>
+
+<style lang="less" scoped>
+  .menu {
+    &-item {
+      display: flex;
+      justify-content: space-between;
+      padding: 20px;
+      cursor: pointer;
+      color: #818694;
+      font-size: 12px;
+      font-weight: 400;
+      background-color: #fff;
+
+      &_left {
+        &-t {
+          font-size: 14px;
+          font-weight: 500;
+          color: #000a18;
+          margin-bottom: 6px;
+        }
+        // &-b {
+        // }
+      }
+
+      &_right {
+        text-align: right;
+        transition: all 0.3s ease-in-out;
+
+        &-t {
+          margin-bottom: 6px;
+        }
+
+        &-btn {
+          margin-right: 16px;
+          background: #fff;
+          border-radius: 50%;
+          width: 32px;
+          height: 32px;
+          text-align: center;
+          line-height: 32px;
+
+          &:last-child {
+            margin-right: 0;
+          }
+
+          &:hover {
+            color: #0075ff;
+          }
+        }
+
+        &--edit {
+          display: none;
+        }
+      }
+
+      &:hover {
+        background: #f6f8fa;
+      }
+
+      &--selected {
+        background: #f6f8fa;
+        box-shadow: 1px 0 0 0 #efefef, inset 3px 0 0 0 #0075ff;
+      }
+
+      &--attachment {
+        justify-content: space-between;
+        align-items: center;
+        transition: all 0.3s ease-in-out;
+
+        &:hover {
+          .menu-item_right {
+            display: none;
+          }
+
+          .menu-item_right--edit {
+            display: flex !important;
+          }
+        }
+      }
+    }
+  }
+</style>

+ 53 - 0
src/components/XTList/src/Menu.vue

@@ -0,0 +1,53 @@
+<template>
+  <div class="menu">
+    <div
+      :class="['menu-item', item.value == selected ? 'menu-item--selected' : '']"
+      v-for="item in data"
+      :key="item.value"
+      @click="handleClick(item)"
+    >
+      {{ item.label }}
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  interface Props {
+    data: Array<{
+      label: string;
+      value: string;
+    }>;
+    selected: string;
+  }
+  withDefaults(defineProps<Props>(), {
+    data: () => [],
+    selected: '0',
+  });
+  const emit = defineEmits(['itemClick']);
+  function handleClick(data) {
+    emit('itemClick', data);
+  }
+</script>
+
+<style lang="less" scoped>
+  .menu {
+    &-item {
+      display: block;
+      width: 160px;
+      height: 48px;
+      cursor: pointer;
+
+      &:hover {
+        background: #f6f8fa;
+      }
+
+      &--selected {
+        background: #f6f8fa;
+        font-size: 14px;
+        font-weight: 600;
+        color: #0075ff;
+        box-shadow: 1px 0 0 0 #efefef, inset 3px 0 0 0 #0075ff;
+      }
+    }
+  }
+</style>

+ 27 - 3
src/components/XTTab/src/XTTab.vue

@@ -2,7 +2,11 @@
   <div class="xt-tab">
     <div class="tab-list">
       <div
-        :class="['tab-list_item', selected == item.key ? 'tab-list_item--selected' : '']"
+        :class="[
+          'tab-list_item',
+          selected == item.key ? 'tab-list_item--selected' : '',
+          item.disabled ? 'tab-list_item--disabled' : '',
+        ]"
         v-for="item in data"
         :key="item.key"
         @click="handleClick(item)"
@@ -39,6 +43,8 @@
     data: Array<{
       // 唯一值
       key: string;
+      // 是否disabled
+      disabled?: boolean;
       // 名称
       label: string;
       // 后缀的值
@@ -65,8 +71,10 @@
   const emit = defineEmits(['itemClick']);
 
   async function handleClick(data) {
-    console.log('🚀 ~ file: XTTab.vue:36 ~ handleClick ~ data:', data);
-    emit('itemClick', { type: props.type, value: data.key });
+    if (!data.disabled) {
+      console.log('🚀 ~ file: XTTab.vue:36 ~ handleClick ~ data:', data);
+      emit('itemClick', { type: props.type, value: data.key });
+    }
   }
 </script>
 
@@ -105,6 +113,22 @@
         }
       }
 
+      &--disabled {
+        color: #ccc;
+
+        &:hover {
+          color: #ccc !important;
+          font-size: 14px;
+          font-weight: 400;
+          background-color: transparent;
+          cursor: auto;
+
+          span {
+            color: #ccc !important;
+          }
+        }
+      }
+
       &-prefix {
         width: 10px;
         height: 10px;

+ 0 - 0
src/components/XTTimeLine/index.ts


+ 142 - 0
src/components/XTTimeLine/src/TimeLine.vue

@@ -0,0 +1,142 @@
+<template>
+  <div class="timeline">
+    <div class="timeline-head" v-if="head">
+      <i
+        class="iconfont icon-xt-add_default"
+        v-if="headIcon"
+        @mousemove="handleMouseMove"
+        @click="handleClick"
+      />
+      <slot name="head" />
+    </div>
+    <div class="timeline-body">
+      <div class="timeline-item" v-for="item in data" :key="item.id">
+        <!-- 线 -->
+        <div class="timeline-item-tail" />
+        <!-- 圆点 -->
+        <div class="timeline-item-dot"> {{ item.dot }} </div>
+        <div class="timeline-item-cnt">
+          <DescCard
+            :id="item.cnt.id"
+            icon="icon-xt-edit_default"
+            icon-type="edit"
+            :type="item.cnt.type"
+            :title="item.cnt.title"
+            :data="item.cnt.data"
+            @icon="handleIcon"
+          />
+        </div>
+        <div class="timeline-item-date"> {{ item.date }} </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import DescCard from '/@/components/XTCard/src/DescCard.vue';
+
+  interface Props {
+    head?: boolean;
+    headIcon?: boolean;
+    data: Array<{
+      id?: string;
+      dot?: string;
+      date: string;
+      cnt: any;
+    }>;
+  }
+  withDefaults(defineProps<Props>(), {
+    head: true,
+    headIcon: true,
+    data: () => [],
+  });
+
+  const emit = defineEmits(['icon', 'add', 'hover']);
+
+  function handleIcon(data) {
+    console.log('🚀 ~ file: TimeLine.vue:44 ~ handleIcon ~ data:', data);
+    emit('icon', data);
+  }
+  function handleMouseMove() {
+    console.log('mousemove');
+    emit('hover');
+  }
+
+  function handleClick() {
+    console.log('mousemove');
+    emit('add');
+  }
+</script>
+
+<style lang="less" scoped>
+  .timeline {
+    background-color: #fff;
+    padding: 10px 0;
+
+    &-head {
+      display: flex;
+      font-size: 12px;
+      line-height: 40px;
+      font-weight: 600;
+      text-align: center;
+      margin: 20px 0 20px 96px;
+
+      & .iconfont {
+        display: block;
+        width: 40px;
+        height: 40px;
+        border-radius: 50%;
+        color: #fff;
+        background: #0075ff;
+        cursor: pointer;
+      }
+    }
+
+    &-item {
+      position: relative;
+      margin: 0;
+      padding-bottom: 20px;
+      font-size: 14px;
+      list-style: none;
+
+      &-tail {
+        position: absolute;
+        top: 10px;
+        left: 115px;
+        height: calc(100% - 10px);
+        border-left: 2px solid #f0f0f0;
+      }
+
+      &-dot {
+        position: absolute;
+        width: 40px;
+        height: 40px;
+        font-size: 12px;
+        line-height: 40px;
+        font-weight: 600;
+        color: #fff;
+        background: #21c999;
+        border-radius: 50%;
+        left: 96px;
+      }
+
+      &-cnt {
+        position: relative;
+        left: 120px;
+        text-align: left;
+        top: -8px;
+        margin: 0 0 0 26px;
+        word-break: break-word;
+        width: calc(100% - 162px);
+      }
+
+      &-date {
+        width: 86px;
+        margin: 0;
+        text-align: right;
+        position: absolute;
+        top: 10px;
+      }
+    }
+  }
+</style>

+ 2 - 2
src/enums/colorEnum.ts

@@ -3,7 +3,7 @@
  */
 export enum ColorEnum {
   // 未称量 | 透析室待确认
-  PRIMARY = '#2E5BFF',
+  PRIMARY = '#2D5AFF',
   PRIMARY_BG = '#DCE4FF',
 
   // 待确认 | 透析室待核对
@@ -23,7 +23,7 @@ export enum ColorEnum {
   MUTED_DOT = '#D3D8DD',
 
   // 透析室准备上机
-  PURPLE = '#864AFF',
+  PURPLE = '#854AFF',
   PURPLE_BG = '#EEE6FF',
   // 非透中患者
   PURPLE_DOT = '#854AFF',

+ 6 - 0
src/utils/lib/echarts.ts

@@ -25,6 +25,9 @@ import {
   TimelineComponent,
   CalendarComponent,
   GraphicComponent,
+  MarkAreaComponent,
+  SingleAxisComponent,
+  AxisPointerComponent,
 } from 'echarts/components';
 
 import { SVGRenderer, CanvasRenderer } from 'echarts/renderers';
@@ -33,6 +36,9 @@ echarts.use([
   LegendComponent,
   TitleComponent,
   TooltipComponent,
+  MarkAreaComponent,
+  SingleAxisComponent,
+  AxisPointerComponent,
   GridComponent,
   PolarComponent,
   AriaComponent,

+ 97 - 9
src/views/biz/visit/ready/data.ts

@@ -1,3 +1,4 @@
+import { BasicColumn } from '/@/components/Table';
 import { FormSchema } from '/@/components/Form';
 import { radioBoolean } from '/@/utils/filters';
 
@@ -10,15 +11,21 @@ export const dataFormSchema: FormSchema[] = [
       span: 24,
     },
   },
-  // {
-  //   field: 'PlainText',
-  //   component: 'PlainText',
-  //   label: '检测时间',
-  //   defaultValue: '2023-12-12 12:12:12',
-  //   colProps: {
-  //     span: 24,
-  //   },
-  // },
+  {
+    field: 'PlainText1111',
+    component: 'RadioDescGroup',
+    label: '1、干体重在过去3~6个月总的变化',
+    componentProps: {
+      options: [
+        { label: '干体重没有减少或体重丢失 <0.5kg (0分)', value: 0 },
+        { label: '体重丢失 ≥0.5kg,但 <1kg (1分)', value: 1 },
+      ],
+    },
+    colProps: {
+      span: 24,
+    },
+    defaultValue: 1,
+  },
   {
     field: 'configName',
     label: '参数名称',
@@ -118,3 +125,84 @@ export const dataFormSchema: FormSchema[] = [
     },
   },
 ];
+export const columns: BasicColumn[] = [
+  {
+    title: '名称',
+    dataIndex: 'name',
+    width: 100,
+    edit: true,
+    editable: true,
+    editComponent: 'Input',
+  },
+  {
+    title: '类型',
+    dataIndex: 'type',
+    width: 150,
+  },
+  {
+    title: '联系人',
+    dataIndex: 'contractUser',
+    width: 150,
+  },
+  {
+    title: '联系人电话',
+    dataIndex: 'contactMobile',
+    width: 150,
+  },
+  {
+    title: '名称',
+    dataIndex: 'name',
+    width: 100,
+  },
+  {
+    title: '类型',
+    dataIndex: 'type',
+    width: 150,
+  },
+  {
+    title: '联系人',
+    dataIndex: 'contractUser',
+    width: 150,
+  },
+  {
+    title: '联系人电话',
+    dataIndex: 'contactMobile',
+    width: 150,
+  },
+  {
+    title: '管理账号',
+    dataIndex: 'username',
+    width: 150,
+  },
+  {
+    title: '套餐名称',
+    dataIndex: 'packageName',
+    width: 150,
+  },
+  {
+    title: '联系人',
+    dataIndex: 'contractUser',
+    width: 150,
+  },
+  {
+    title: '联系人电话',
+    dataIndex: 'contactMobile',
+    width: 150,
+  },
+  {
+    title: '管理账号',
+    dataIndex: 'username',
+    width: 150,
+  },
+  {
+    title: '套餐名称',
+    dataIndex: 'packageName',
+    width: 150,
+    fixed: 'right',
+  },
+  {
+    title: '状态',
+    dataIndex: 'disable',
+    fixed: 'right',
+  },
+];

+ 264 - 1
src/views/biz/visit/ready/index.vue

@@ -1,6 +1,9 @@
 <template>
   <div>
     透前准备
+    <a-button type="primary" @click="plusFn">
+      {{ countRef }}
+    </a-button>
     <XTTab
       type="illness"
       :width="120"
@@ -11,12 +14,64 @@
     <div class="mt-6" />
     <XTTab type="illness11" :selected="tabSelected" :data="tabData1" @item-click="callTab" />
     <div class="mt-6" />
+    <div class="mt-6" />
+    <XTTab type="illness11" :selected="tabSelected" :data="tabData2" @item-click="callTab" />
+    <div class="mt-6" />
     <div class="m-6">
       <BasicForm @register="registerForm" @field-value-change="filedChange" />
     </div>
+    <div>
+      <BasicTable @register="registerTable">
+        <template #headerTop>
+          <span>headerTop</span>
+        </template>
+        <template #toolbar>
+          <span>toolbar</span>
+        </template>
+      </BasicTable>
+    </div>
     <div>
       <XTForm :form-data="formData" />
     </div>
+    <div class="mx-6 my-2">
+      <ChartsCard
+        title="透前血压趋势"
+        :has-safe="true"
+        :colors="chartData.colors"
+        :safe-range="chartData.safeRange"
+      />
+    </div>
+    <div class="mx-6 my-2">
+      <DescCard
+        id="1"
+        icon="icon-xt-add_default"
+        title="透析测量"
+        type="touxi"
+        :data="descData"
+        :right="descRight"
+      />
+    </div>
+    <div class="mx-6 my-2">
+      <TimeLine :data="timeLineData" @hover="callHover">
+        <template #head>
+          <div class="timeline-outer" v-if="timeOuter">
+            <div
+              class="timeline-outer_item animate__animated animate__slideInLeft"
+              @click="handleAdd"
+              >AVF</div
+            >
+            <div class="timeline-outer_item animate__animated animate__slideInLeft">AVH</div>
+            <div class="timeline-outer_item animate__animated animate__slideInLeft">TCG</div>
+          </div>
+        </template>
+      </TimeLine>
+    </div>
+    <div class="mx-6 my-2">
+      <List type="default" :data="listData" selected="0" />
+    </div>
+    <div class="mx-6 my-2">
+      <List type="attachment" :data="listData1" selected="0" :width="320" />
+    </div>
     <div class="flex justify-between">
       <XTCard class="m-2" :data="cardData1" @item-click="cellCard" />
     </div>
@@ -33,9 +88,171 @@
   import { XTForm } from '/@/components/XTForm/index';
   import { ColorEnum } from '/@/enums/colorEnum';
   import { BasicForm, useForm } from '/@/components/Form';
-  import { dataFormSchema } from './data';
+  import { dataFormSchema, columns } from './data';
+  import { BasicTable, useTable } from '/@/components/Table';
+  import { onMounted } from 'vue';
+  import ChartsCard from '/@/components/XTCard/src/ChartsCard.vue';
+  import DescCard from '/@/components/XTCard/src/DescCard.vue';
+  import TimeLine from '/@/components/XTTimeLine/src/TimeLine.vue';
+  import List from '/@/components/XTList/src/List.vue';
+  import { TransitionPresets, useTransition } from '@vueuse/core';
 
   const tabSelected = ref('0');
+  const dataSource = ref([]);
+  const chartData = {
+    colors: [
+      {
+        color: 'rgba(0, 117, 255, 1)',
+        label: '化验值',
+        dot: 'rgba(0, 117, 255, 1)',
+      },
+    ],
+    safeRange: [
+      {
+        // name: '60分到80分',
+        yAxis: 0,
+      },
+      {
+        yAxis: 20,
+      },
+    ],
+  };
+  const listData = [
+    {
+      id: '0',
+      title: '红细胞笔迹测定',
+      startTime: '2023-11-11 12:00',
+      endTime: '2023-12-11 12:00',
+      status: 'default',
+    },
+    {
+      id: '2',
+      title: '红细胞笔迹测定2',
+      startTime: '2023-11-11 12:00',
+      endTime: '2023-12-11 12:00',
+      status: 'default',
+    },
+  ];
+  const listData1 = [
+    {
+      id: '0',
+      title: '红细胞笔迹测定',
+      startTime: '2023-11-11 12:00',
+      endTime: '2023-12-11 12:00',
+      status: 'default',
+    },
+    {
+      id: '2',
+      title: '红细胞笔迹测定2',
+      startTime: '2023-11-11 12:00',
+      endTime: '2023-12-11 12:00',
+      status: 'default',
+    },
+  ];
+
+  const descData = [
+    {
+      label: '诊断名称/病史/状态',
+      value: '乙肝 / 2022-01-19 ~',
+      tags: [{ id: '12', label: '活动中', type: 'error' }],
+    },
+    {
+      label: '备注',
+      value: '药物治疗或其他治疗',
+    },
+    {
+      label: '测试',
+      value: '药物治疗或其他治疗',
+    },
+    {
+      label: '诊断名称/病史/状态备份',
+      value: '结核 / 2022-02-10 ~ 20',
+      tags: [
+        { id: '1', label: '未活动', type: 'muted' },
+        { id: '2', label: '未活动1', type: 'primary' },
+      ],
+    },
+  ];
+  const descRight = {
+    show: true,
+    date: '2023-04-23',
+    doctor: '张医生',
+    edit: true,
+    delete: true,
+  };
+
+  const timeLineData = [
+    {
+      id: '1',
+      dot: '王医生',
+      date: '2023-05-20',
+      cnt: {
+        title: '测试' + Math.round(Math.random() * 1000),
+        id: '123',
+        type: '123',
+        data: descData,
+      },
+    },
+    {
+      id: '12',
+      dot: '范医生',
+      date: '2023-03-20',
+      cnt: {
+        title: '测试' + Math.round(Math.random() * 1000),
+        id: '123',
+        type: '123',
+        data: descData,
+      },
+    },
+  ];
+  const timeOuter = ref(false);
+  onMounted(() => {
+    for (let i = 0; i < 10; i++) {
+      const obj = {
+        createTime: '2023-05-23 10:09:48',
+        updateTime: '2023-05-23 19:00:32',
+        id: '1660830352886149125' + Math.round(Math.random() * 10000),
+        name: '驼人',
+        packageId: '1655202440997244930',
+        packageName: '测试套餐',
+        username: 'tuoren',
+        type: 'custom',
+        contractUser: 'Lf',
+        contactMobile: '18339543638',
+        remark: null,
+        disable: 0,
+      };
+      dataSource.value.push(obj);
+    }
+  });
+  const count = ref(0);
+  const countRef = useTransition(count, {
+    duration: 1000,
+    transition: TransitionPresets.easeInCirc,
+  });
+  function plusFn() {
+    count.value = Math.round(Math.random() * 100);
+  }
+  // const [registerTable, { reload, getCacheColumns, setColumns }] = useTable({
+  const [registerTable] = useTable({
+    id: 'sys_config',
+    title: '参数列表2121',
+    columns,
+    dataSource: dataSource,
+    useSearchForm: false,
+    titleStyle: {
+      fontSize: '14px',
+      color: '#000a18',
+      cursor: 'default',
+    },
+    titleLined: true,
+    bordered: false,
+    showIndexColumn: false,
+    pagination: false,
+    // maxHeight: 350,
+    canResize: true,
+  });
+
   const [registerForm] = useForm({
     layout: 'vertical',
     labelWidth: '100%',
@@ -84,6 +301,21 @@
       hasBracket: true,
     },
   ];
+  const tabData2 = [
+    {
+      key: '0',
+      label: 'A1',
+    },
+    {
+      key: '1',
+      label: 'A2',
+      disabled: true,
+    },
+    {
+      key: '2',
+      label: 'B3',
+    },
+  ];
   const tabData1 = [
     {
       key: '0',
@@ -102,6 +334,7 @@
   const formData = [
     {
       name: 'text',
+      label: '全部',
       componentType: 'Select',
       placeholder: '请选择',
       width: 80,
@@ -120,6 +353,7 @@
       placeholder: '请选择',
       width: 120,
       defaultValue: '1',
+      label: '班次',
       dicts: [
         { label: '第一班', value: '1' },
         { label: '第二班', value: '2' },
@@ -139,6 +373,10 @@
       format: 'YYYY-MM-DD',
       valueFormat: 'YYYY-MM-DD',
     },
+    {
+      name: 'filter',
+      componentType: 'IconBtn',
+    },
   ];
   // card 标签组
   const cardData = [
@@ -261,6 +499,10 @@
       ],
     },
   ];
+
+  function handleAdd() {
+    timeOuter.value = false;
+  }
   // 回调
   function callTab(data) {
     console.log('🚀 ~ file: index.vue:41 ~ callTab ~ data:', data);
@@ -270,10 +512,31 @@
   function cellCard(data) {
     console.log('🚀 ~ file: index.vue:106 ~ cellCard ~ data:', data);
   }
+
+  function callHover() {
+    timeOuter.value = true;
+  }
 </script>
 
 <style lang="less" scoped>
   ::v-deep(.ant-form-item-label > label) {
     width: 100% !important;
   }
+
+  .timeline-outer {
+    display: flex;
+    margin-left: 20px;
+    transition: all 0.3s ease-in-out;
+
+    &_item {
+      padding: 0 30px;
+      height: 40px;
+      line-height: 40px;
+      border-radius: 30px;
+      color: #fff;
+      background: #0075ff;
+      margin-right: 20px;
+      cursor: pointer;
+    }
+  }
 </style>