Sanakey
2024-09-21 f353bc62423323d3b2bf24934b98cc05176f5583
feat:新建客户相关内容
21个文件已添加
10个文件已修改
5231 ■■■■ 已修改文件
src/components/TagSelector/src/TagSelector.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/clues/components/Drawer.vue 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/clues/components/Table.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/clues/components/drawer/index.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/clues/components/tableData.tsx 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/clues/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/Drawer.vue 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/DrawerForm.vue 197 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/LeftNav.vue 135 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/List.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/Table.vue 255 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/change-status/index.vue 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/drawer-form/annualPurchaseAmount.ts 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/drawer-form/companyType.ts 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/drawer-form/index.vue 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/drawer-form/timeZone.ts 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/drawer/Business.vue 184 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/drawer/Detail.vue 205 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/drawer/Document.vue 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/drawer/Dynamic.vue 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/drawer/ScheduleDetail.vue 269 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/drawer/Tips.vue 184 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/drawer/data.tsx 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/drawer/index.vue 160 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/drawer/scheduleCommentFormData.tsx 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/drawerContacterFormData.tsx 699 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/drawerData.ts 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/drawerFormData.tsx 583 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/reallocate/index.vue 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/components/tableData.tsx 684 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/customer/index.vue 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/TagSelector/src/TagSelector.vue
@@ -10,12 +10,13 @@
    <a-select
      v-model:value="tagsValue"
      mode="multiple"
      style="width: 300px"
      style="width: 36px"
      :searchValue="name"
      :labelInValue="true"
      :defaultOpen="false"
      :bordered="false"
      :showSearch="false"
      :dropdownMatchSelectWidth="false"
      :options="items.map(item => ({ value: item.name, label: item.name, color: item.color}))"
    >
      <template #option="{ value,  color }">
@@ -44,6 +45,7 @@
            </template>
            新建标签
          </a-button>
          <SettingOutlined class="cursor-pointer" />
        </a-space>
        <a-divider style="margin: 4px 0" />
        <v-nodes :vnodes="menu" />
@@ -59,7 +61,7 @@
</template>
<script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue';
import { PlusOutlined,SettingOutlined } from '@ant-design/icons-vue';
import { defineComponent, ref } from 'vue';
import Icon from "@/components/Icon/Icon.vue";
const prefixCls = 'tag-selector';
src/views/clues/components/Drawer.vue
@@ -45,12 +45,28 @@
          @click=""
          :size="20"
        />
        <Icon
          icon="ri:more-2-fill"
          class="cursor-pointer"
          @click=""
          :size="20"
        />
        <a-dropdown :trigger="['click']">
          <Icon
            icon="ri:more-2-fill"
            class="cursor-pointer"
            @click=""
            :size="20"
          />
          <template #overlay>
            <a-menu>
              <a-menu-item key="0">
                <span >转移</span>
              </a-menu-item>
              <a-menu-item key="1" @click="handleReallocate">
                <span>重新分配</span>
              </a-menu-item>
<!--              <a-menu-divider />-->
              <a-menu-item key="2">合并线索</a-menu-item>
              <a-menu-item key="3">无效</a-menu-item>
              <a-menu-item key="4">删除</a-menu-item>
            </a-menu>
          </template>
        </a-dropdown>
      </div>
    </template>
@@ -65,6 +81,8 @@
  import Icon from "@/components/Icon/Icon.vue";
  import {Tooltip} from "ant-design-vue";
  import Content from "./drawer/index.vue";
  import EventBus from "@/utils/eventBus";
  import {reactive, defineEmits} from 'vue';
  const emit = defineEmits(['success', 'register']);
@@ -76,8 +94,10 @@
  // });
  // const [registerDrawer] = useDrawer();
  let currentClues = reactive({});
  const [registerDrawer] = useDrawerInner((data) => {
    Logger.log('Drawer 注册回调', data);
    currentClues = data.clue;
    // // 方式1
    // setFieldsValue({
    //   field2: data.data,
@@ -85,12 +105,10 @@
    // });
  });
  // function handleRowClick(e) {
  //   Logger.log('handleRowClick',e)
  //   openDrawer(true, {
  //     isUpdate: false,
  //   });
  // }
  function handleReallocate() {
    Logger.log('点击了重新分配', currentClues);
    EventBus.emit('openReallocateModal', currentClues);
  }
</script>
src/views/clues/components/Table.vue
@@ -342,10 +342,11 @@
  console.log('cancel');
}
function handleRowClick(e) {
  Logger.log('handleRowClick', e);
function handleRowClick(data) {
  Logger.log('handleRowClick', data);
  openDrawer(true, {
    isUpdate: false,
    clue: data
  });
}
src/views/clues/components/drawer/index.vue
@@ -36,24 +36,7 @@
              {{ tag }}
            </Tag>
          </template>
          <TagSelector class="pb-10px"></TagSelector>
          <a-popconfirm title="Title" >
            <Icon
              icon="uil:focus-add"
              class="mr-15px  cursor-pointer"
              @click=""
              :style="{ color: 'hotpink' }"
              :size="20"
            />
            <template #description>
            </template>
          </a-popconfirm>
<!--          <a-dropdown :trigger="['click']">-->
<!--            <template #overlay>-->
<!--              <TagSelector></TagSelector>-->
<!--            </template>-->
<!--          </a-dropdown>-->
          <TagSelector class="pb-10px inline-block mt-10px"></TagSelector>
        </div>
      </Col>
src/views/clues/components/tableData.tsx
@@ -1,7 +1,6 @@
import { FormProps,  BasicColumn } from '@/components/Table';
import {treeOptionsListApi} from "@/api/demo/tree";
import {getAdvanceSchema} from "@/views/customer/components/tableData";
export function getEditCellColumns(): BasicColumn[] {
  return [
@@ -253,12 +252,17 @@
export function getFormConfig(): Partial<FormProps> {
  return {
    labelWidth: 100,
    layout: 'horizontal',
    rowProps: {
      justify: 'end',
    },
    showResetButton: false,
    showSubmitButton: false,
    schemas: [
      ...getAdvanceSchema(1),
      {
        field: `field11`,
        label: ``,
        slot: 'custom',
        label: `搜索`,
        component: 'Input',
        colProps: {
          xl: 12,
          xxl: 8,
src/views/clues/index.vue
@@ -27,7 +27,6 @@
      </Pane>
      <Pane min-size="50" size="88">
        <ScrollContainer class="p-8">
          <TagSelector></TagSelector>
<!--          <div><a-button class="mr-2" type="primary" shape="round" @click="openModal1"> 新建日程 </a-button></div>-->
<!--          <div><a-button class="mr-2" type="primary" shape="round" @click="openModal2"> 选择人员 </a-button></div>-->
          <Table></Table>
@@ -67,7 +66,6 @@
import {useModal} from "@/components/Modal";
import {NewFollowUp} from "@/components/NewFollowUp";
import {NewSchedule} from "@/components/NewSchedule";
import {TagSelector} from "@/components/TagSelector/index";
import PersonnelModal from "@/components/NewSchedule/src/PersonnelModal.vue";
import ChangeStatusModal from "./components/change-status/index.vue";
import ReallocateModal from "./components/reallocate/index.vue";
src/views/customer/components/Drawer.vue
New file
@@ -0,0 +1,114 @@
<template>
  <BasicDrawer
    v-bind="$attrs"
    title="Basic Drawer"
    @register="registerDrawer"
    :maskClosable="false"
    :keyboard="false"
    width="500px"
  >
    <template #title>
      <div class="text-right">
        <span>
          <Tooltip title="已关注">
            <Icon
              icon="mingcute:heart-fill"
              class="mr-15px cursor-pointer"
              @click=""
              :size="20"
            />
          </Tooltip>
          <Tooltip title="未关注">
            <Icon
              icon="mingcute:heart-line"
              class="mr-15px cursor-pointer"
              @click=""
              :size="20"
            />
          </Tooltip>
        </span>
        <Icon
          icon="ri:edit-line"
          class="mr-15px cursor-pointer"
          @click=""
          :size="20"
        />
        <Icon
          icon="mdi:email-outline"
          class="mr-15px cursor-pointer"
          @click=""
          :size="20"
        />
        <Icon
          icon="gg:add-r"
          class="mr-15px cursor-pointer"
          @click=""
          :size="20"
        />
        <a-dropdown :trigger="['click']">
          <Icon
            icon="ri:more-2-fill"
            class="cursor-pointer"
            @click=""
            :size="20"
          />
          <template #overlay>
            <a-menu>
              <a-menu-item key="0">
                <span >转移</span>
              </a-menu-item>
              <a-menu-item key="1" @click="handleReallocate">
                <span>重新分配</span>
              </a-menu-item>
<!--              <a-menu-divider />-->
              <a-menu-item key="2">合并线索</a-menu-item>
              <a-menu-item key="3">无效</a-menu-item>
              <a-menu-item key="4">删除</a-menu-item>
            </a-menu>
          </template>
        </a-dropdown>
      </div>
    </template>
    <Content></Content>
<!--    <BasicForm @register="registerForm"></BasicForm>-->
  </BasicDrawer>
</template>
<script lang="ts" setup>
  // import {  useForm } from '@/components/Form';
  // import { formSchema } from './drawerData';
  import {BasicDrawer, useDrawerInner} from '@/components/Drawer';
  import Icon from "@/components/Icon/Icon.vue";
  import {Tooltip} from "ant-design-vue";
  import Content from "./drawer/index.vue";
  import EventBus from "@/utils/eventBus";
  import {reactive, defineEmits} from 'vue';
  const emit = defineEmits(['success', 'register']);
  // const [registerForm, { setFieldsValue, }] = useForm({
  //   labelWidth: 90,
  //   baseColProps: { span: 24 },
  //   schemas: formSchema,
  //   showActionButtonGroup: false,
  // });
  // const [registerDrawer] = useDrawer();
  let currentClues = reactive({});
  const [registerDrawer] = useDrawerInner((data) => {
    Logger.log('Drawer 注册回调', data);
    currentClues = data.clue;
    // // 方式1
    // setFieldsValue({
    //   field2: data.data,
    //   field1: data.info,
    // });
  });
  function handleReallocate() {
    Logger.log('点击了重新分配', currentClues);
    EventBus.emit('openReallocateModal', currentClues);
  }
</script>
src/views/customer/components/DrawerForm.vue
New file
@@ -0,0 +1,197 @@
<template>
  <BasicDrawer
    v-bind="$attrs"
    title="新建客户"
    @register="registerDrawer"
    :maskClosable="false"
    :keyboard="false"
    width="860px"
    showFooter
    @ok="handleSubmit"
  >
    <template #title>
      <div class="flex flex-justify-between">
        <div>{{ getTitle }}</div>
        <div class="text-right">
        <span>
          <Tooltip title="已关注">
            <Icon
              icon="mingcute:heart-fill"
              class="mr-15px cursor-pointer"
              @click=""
              :size="20"
            />
          </Tooltip>
          <Tooltip title="未关注">
            <Icon
              icon="mingcute:heart-line"
              class="mr-15px cursor-pointer"
              @click=""
              :size="20"
            />
          </Tooltip>
        </span>
        </div>
      </div>
    </template>
<!--    <Content :schemas="schemas"></Content>-->
    <div :class="prefixCls">
      <Alert
        v-if="visible"
        message="线索增强:填写公司网址或邮箱后缀后,如系统能查找到该公司的其他更多信息,则会为您自动补全。"
        type="info"
        show-icon
        closable
        :after-close="handleClose"
        class="mb-10px"
      />
      <Row :class="`${prefixCls}-top`">
        <Col :span="11" :class="`${prefixCls}-col`">
          <div class="mb-10px font-size-16px">常用信息</div>
          <div class="pt-3px pr-3px">
            <BasicForm @register="registerForm" :model="modelRef" />
          </div>
        </Col>
        <Col :span="11" :offset="2" :class="`${prefixCls}-col`">
          <div class="mb-10px font-size-16px">联系人</div>
          <div class="p-10px bg-gray-1">
            <BasicForm @register="registerForm2" />
          </div>
        </Col>
      </Row>
    </div>
  </BasicDrawer>
</template>
<script lang="ts" setup>
  // import {  useForm } from '@/components/Form';
  // import { formSchema } from './drawerData';
  import {BasicDrawer, useDrawerInner} from '@/components/Drawer';
  import Icon from "@/components/Icon/Icon.vue";
  import {Alert, Col, Row, Tooltip} from "ant-design-vue";
  // import Content from "./drawer-form/index.vue";
  import {computed, ref, unref} from "vue";
  import {getMenuList} from "@/api/demo/system";
  import {TreeItem} from "@/components/Tree";
  import {BasicForm, useForm} from "@/components/Form";
  import {schemas} from './drawerFormData'
  import {schemas as schemas2} from './drawerContacterFormData'
  const emit = defineEmits(['success', 'register']);
  const isUpdate = ref(true);
  const getTitle = computed(() => (!unref(isUpdate) ? '新建线索' : '编辑线索'));
  // const [registerForm, { setFieldsValue, }] = useForm({
  //   labelWidth: 90,
  //   baseColProps: { span: 24 },
  //   schemas: formSchema,
  //   showActionButtonGroup: false,
  // });
  // const [registerDrawer] = useDrawer();
  const modelRef = ref({});
  // 左侧表单
  const [
    registerForm,
    { resetFields, setFieldsValue, validate }
    // {
    //   // setFieldsValue,
    //   // setProps
    // },
  ] = useForm({
    layout: 'vertical',
    // labelWidth: 100,
    showAdvancedButton: true, //开启折叠
    autoAdvancedLine: 9, // 超过多少行折叠
    alwaysShowLines: 8, // 始终显示多少行
    schemas,
    // showActionButtonGroup: false, // 默认显示操作按钮,开启才会显示折叠按钮
    showResetButton:false, // 隐藏重置按钮
    showSubmitButton:false,  // 隐藏提交按钮
    actionColOptions: {
      span: 24,
    },
  });
  // 右侧表单
  const [
    registerForm2,
    { resetFields:resetFields2, setFieldsValue:setFieldsValue2, validate:validate2 }
    // {
    //   // setFieldsValue,
    //   // setProps
    // },
  ] = useForm({
    layout: 'vertical',
    // labelWidth: 100,
    showAdvancedButton: true, //开启折叠
    autoAdvancedLine: 5, // 超过多少行折叠
    alwaysShowLines: 4, // 始终显示多少行
    schemas:schemas2,
    // showActionButtonGroup: false, // 默认显示操作按钮,开启才会显示折叠按钮
    showResetButton:false, // 隐藏重置按钮
    showSubmitButton:false,  // 隐藏提交按钮
    actionColOptions: {
      span: 24,
    },
  });
  const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
    Logger.log('打开了DrawerForm', data);
    isUpdate.value = !!data?.isUpdate;
    // // 方式1
    // setFieldsValue({
    //   field2: data.data,
    //   field1: data.info,
    // });
    // setDrawerProps({ confirmLoading: true });
    // setTimeout(() => {
    //   setDrawerProps({ confirmLoading: false });
    // }, 1000);
    resetFields();
    resetFields2();
    if (unref(isUpdate)) {
      setFieldsValue({
        ...data.record,
      });
      setFieldsValue2({
        ...data.record,
      });
    }
  });
  async function handleSubmit() {
  try {
    // const values = await validate();
    const [values, values2] = await Promise.all([validate(), validate2()]);
    // const values = getFieldsValue();
    Logger.log('点击submit 左侧表单values:',values);
    Logger.log('点击submit 右侧表单values2:', values2);
    setDrawerProps({ confirmLoading: true });
    // TODO custom api
    closeDrawer();
    emit('success');
  } finally {
    setDrawerProps({ confirmLoading: false });
  }
}
  const prefixCls = 'clues-drawer';
  // 关闭提示信息
  const visible = ref<boolean>(true);
  const handleClose = () => {
    visible.value = false;
  };
</script>
src/views/customer/components/LeftNav.vue
@@ -25,58 +25,108 @@
const items = ref([
  {
    key: '1',
    label: 'Navigation One',
    title: 'Navigation One',
    key: '0',
    label: '我的关注',
    title: '我的关注',
  },
  {
    key: '2',
    label: 'Navigation Two',
    title: 'Navigation Two',
    key: '1',
    label: '全部客户',
    title: '全部客户',
  },
  {
    key: 'sub1',
    label: 'Navigation Three',
    title: 'Navigation Three',
    label: '分组',
    title: '分组',
    children: [
      {
        key: '3',
        label: 'Option 3',
        title: 'Option 3',
        label: '待处理',
        title: '待处理',
      },
      {
        key: '4',
        label: 'Option 4',
        title: 'Option 4',
        label: '完善信息',
        title: '完善信息',
      },
      {
        key: 'sub1-2',
        label: 'Submenu',
        title: 'Submenu',
        children: [
          {
            key: '5',
            label: 'Option 5',
            title: 'Option 5',
          },
          {
            key: '6',
            label: 'Option 6',
            title: 'Option 6',
          },
        ],
        key: '5',
        label: '初步触达',
        title: '初步触达',
      },
    ],
  },
  {
    key: '2-1',
    label: '近7天联系客户',
    title: '近7天联系客户',
  },
  {
    key: '2-2',
    label: '近7天收到新询盘',
    title: '近7天收到新询盘',
  },
  {
    key: '2-3',
    label: '7天内移入公海客户',
    title: '7天内移入公海客户',
  },
  {
    key: 'sub2',
    label: 'Navigation Four',
    title: 'Navigation Four',
    label: '客户阶段',
    title: '客户阶段',
    children: [
      {
        key: '7',
        label: 'Option 7',
        title: 'Option 7',
        label: '打开了营销',
        title: '打开了营销',
      },
      {
        key: '8',
        label: '回复了营销',
        title: '回复了营销',
      },
      {
        key: '9',
        label: '点击了链接',
        title: '点击了链接',
      },
      // {
      //   key: '10',
      //   label: 'Option 10',
      //   title: 'Option 10',
      // },
    ],
  },
  {
    key: 'sub3',
    label: '国家地区(州)',
    title: '线索来源',
    children: [
      // {
      //   key: '7',
      //   label: '常规获客',
      //   title: '常规获客',
      // },
      {
        key: '7',
        label: '常规获客',
        title: '常规获客',
        children: [
          {
            key: '6-1',
            label: '全部',
            title: '全部',
          },
          {
            key: '6-2',
            label: '熟人介绍',
            title: '熟人介绍',
          },
        ],
      },
      {
        key: '8',
@@ -95,6 +145,29 @@
      },
    ],
  },
  {
    key: '2',
    label: '分组-1',
    title: '分组-1',
    children: [
      {
        key: '3-1',
        label: '待处理',
        title: '待处理',
      },
      {
        key: '4-1',
        label: '完善信息',
        title: '完善信息',
      },
      {
        key: '5-1',
        label: '初步触达',
        title: '初步触达',
      },
    ],
  },
]);
const handleClick: MenuProps['onClick'] = e => {
src/views/customer/components/List.vue
New file
@@ -0,0 +1,9 @@
<template>
  <PageWrapper title="线索页">
    666
  </PageWrapper>
</template>
<script lang="ts" setup>
  import { PageWrapper } from '@/components/Page';
</script>
src/views/customer/components/Table.vue
@@ -1,21 +1,22 @@
<template>
  <div class="p-4 flex flex-col">
<!--    <div class="mb-4">-->
<!--      <a-button class="mr-2" @click="reloadTable"> 还原 </a-button>-->
<!--      <a-button class="mr-2" @click="changeLoading"> 开启loading </a-button>-->
<!--      <a-button class="mr-2" @click="changeColumns"> 更改Columns </a-button>-->
<!--      <a-button class="mr-2" @click="getColumn"> 获取Columns </a-button>-->
<!--      <a-button class="mr-2" @click="getTableData"> 获取表格数据 </a-button>-->
<!--      <a-button class="mr-2" @click="getTableRawData"> 获取接口原始数据 </a-button>-->
<!--      <a-button class="mr-2" @click="setPaginationInfo"> 跳转到第2页 </a-button>-->
<!--    </div>-->
<!--    <div class="mb-4">-->
<!--      <a-button class="mr-2" @click="getSelectRowList"> 获取选中行 </a-button>-->
<!--      <a-button class="mr-2" @click="getSelectRowKeyList"> 获取选中行Key </a-button>-->
<!--      <a-button class="mr-2" @click="setSelectedRowKeyList"> 设置选中行 </a-button>-->
<!--      <a-button class="mr-2" @click="clearSelect"> 清空选中行 </a-button>-->
<!--      <a-button class="mr-2" @click="getPagination"> 获取分页信息 </a-button>-->
<!--    </div>-->
    <Drawer @register="registerDrawer" @success="handleSuccess"/>
    <!--    <div class="mb-4">-->
    <!--      <a-button class="mr-2" @click="reloadTable"> 还原 </a-button>-->
    <!--      <a-button class="mr-2" @click="changeLoading"> 开启loading </a-button>-->
    <!--&lt;!&ndash;      <a-button class="mr-2" @click="changeColumns"> 更改Columns </a-button>&ndash;&gt;-->
    <!--      <a-button class="mr-2" @click="getColumn"> 获取Columns </a-button>-->
    <!--      <a-button class="mr-2" @click="getTableData"> 获取表格数据 </a-button>-->
    <!--      <a-button class="mr-2" @click="getTableRawData"> 获取接口原始数据 </a-button>-->
    <!--      <a-button class="mr-2" @click="setPaginationInfo"> 跳转到第2页 </a-button>-->
    <!--    </div>-->
    <!--    <div class="mb-4">-->
    <!--      <a-button class="mr-2" @click="getSelectRowList"> 获取选中行 </a-button>-->
    <!--      <a-button class="mr-2" @click="getSelectRowKeyList"> 获取选中行Key </a-button>-->
    <!--      <a-button class="mr-2" @click="setSelectedRowKeyList"> 设置选中行 </a-button>-->
    <!--      <a-button class="mr-2" @click="clearSelect"> 清空选中行 </a-button>-->
    <!--      <a-button class="mr-2" @click="getPagination"> 获取分页信息 </a-button>-->
    <!--    </div>-->
    <BasicTable
      @register="registerTable"
      @row-click="handleRowClick"
@@ -24,7 +25,7 @@
      :beforeEditSubmit="beforeEditSubmit"
    >
      <template #bodyCell="{ column, record }">
        <template v-if="column.key === 'tags'" >
        <template v-if="column.key === 'tags'">
          <div @click="handleCellClick">
            <span class="mr-1" v-for="tag in record.tags">
              <Tag
@@ -35,78 +36,118 @@
              </Tag>
            </span>
<!--            <Dropdown-->
<!--              :dropMenuList="getDropMenuList"-->
<!--              :trigger="['click']"-->
<!--              placement="bottomCenter"-->
<!--              overlayClassName="multiple-tabs__dropdown"-->
<!--            >-->
<!--              <Icon icon="ion:chevron-down"/>-->
<!--            </Dropdown>-->
            <!--            <Dropdown-->
            <!--              :dropMenuList="getDropMenuList"-->
            <!--              :trigger="['click']"-->
            <!--              placement="bottomCenter"-->
            <!--              overlayClassName="multiple-tabs__dropdown"-->
            <!--            >-->
            <!--              <Icon icon="ion:chevron-down"/>-->
            <!--            </Dropdown>-->
          </div>
        </template>
        <template v-else-if="column.key === 'action'">
          <TableAction
            :actions="[
              {
                label: '删除',
                icon: 'ic:outline-delete-outline',
                onClick: handleDelete.bind(null, record),
              },
            ]"
            stopButtonPropagation
            :dropDownActions="[
              {
                label: '启用',
                popConfirm: {
                  title: '是否启用?',
                  confirm: handleOpen.bind(null, record),
                },
                label: '转为客户',
                onClick: handleConvertToCustomer.bind(null, record),
              },
              {
                label: '写邮件',
                onClick: handleWriteEmail.bind(null, record),
              },
              {
                label: '写跟进',
                onClick: handleWriteAFollowup.bind(null, record),
              },
              {
                label: '新建日程',
                onClick: handleNewSchedule.bind(null, record),
              },
              {
                label: '变更状态',
                onClick: handleChangeStatus.bind(null, record),
              },
              {
                label: '合并线索',
                onClick: handleMergeClues.bind(null, record),
              },
              {
                label: '重新分配',
                onClick: handleReallocate.bind(null, record),
              },
            ]"
          />
        </template>
      </template>
      <template #toolbar>
        <a-button type="primary" @click="toggleCanResize">
          {{ !canResize ? '自适应高度' : '取消自适应' }}
        </a-button>
        <a-button type="primary" @click="toggleBorder">
          {{ !border ? '显示边框' : '隐藏边框' }}
        </a-button>
        <a-button type="primary" @click="toggleLoading"> 开启loading </a-button>
        <a-button type="primary" @click="toggleStriped">
          {{ !striped ? '显示斑马纹' : '隐藏斑马纹' }}
        </a-button>
      </template>
    </BasicTable>
  </div>
</template>
<script lang="ts" setup>
import { ref,} from 'vue';
import {ref,} from 'vue';
import {
  BasicTable,
  ColumnChangeParam,
  TableAction,
  useTable
} from '@/components/Table';
import {getBasicShortColumns, getEditCellColumns,getFormConfig} from './tableData';
import { useMessage } from '@/hooks/web/useMessage';
import { demoListApi } from '@/api/demo/table';
import { Tag } from 'ant-design-vue';
import {getEditCellColumns, getFormConfig} from './tableData';
import {useMessage} from '@/hooks/web/useMessage';
import {Tag} from 'ant-design-vue';
import {cluesListApi} from "@/api/clues/table";
// import {useModal} from "@/components/Modal";
import Drawer from "./Drawer.vue";
import {useDrawer} from "@/components/Drawer";
import EventBus from "@/utils/eventBus";
// import Icon from "@/components/Icon/Icon.vue";
// import {Dropdown, type DropMenu} from "@/components/Dropdown";
const { createMessage } = useMessage();
// const [register, { openModal: openModal }] = useModal();
// const [registerPersonnelModal, { openModal: openPersonnelModal }] = useModal();
const [registerDrawer, {openDrawer}] = useDrawer();
const {createMessage} = useMessage();
function handleDelete(record: Recordable) {
  console.log('点击了删除', record);
}
function handleOpen(record: Recordable) {
  console.log('点击了启用', record);
function handleConvertToCustomer(record: Recordable) {
  Logger.log('点击了转为客户', record);
}
function handleWriteEmail(record: Recordable) {
  Logger.log('点击了写邮件', record);
}
function handleWriteAFollowup(record: Recordable) {
  Logger.log('点击了写跟进', record);
  EventBus.emit('openFollowUpModal', record);
}
function handleNewSchedule(record: Recordable) {
  Logger.log('点击了新建日程', record);
  EventBus.emit('openScheduleModal', record);
}
function handleChangeStatus(record: Recordable) {
  Logger.log('点击了变更状态', record);
  EventBus.emit('openChangeStatusModal', record);
}
function handleMergeClues(record: Recordable) {
  Logger.log('点击了合并线索', record);
}
function handleReallocate(record: Recordable) {
  Logger.log('点击了重新分配', record);
  EventBus.emit('openReallocateModal', record);
}
const handleChange = (value: string) => {
  console.log(`selected ${value}`);
};
// const openModal = (value: string) => {
//   console.log(`openModal ${value}`);
// };
function handleSuccess() {
  Logger.log('打开drawer成功');
}
// const getDropMenuList = computed(() => {
//   const dropMenuList: DropMenu[] = [
@@ -133,6 +174,7 @@
function onChange() {
  console.log('onChange', arguments);
}
const [
  registerTable,
  {
@@ -151,14 +193,21 @@
  },
] = useTable({
  canResize: true,
  title: 'useTable示例',
  titleHelpMessage: '使用useTable调用表格内方法',
  api: demoListApi,
  // title: 'useTable示例',
  // titleHelpMessage: '使用useTable调用表格内方法',
  api: cluesListApi,
  // beforeFetch: (params) => {
  //   console.log('beforeFetch', params);
  //   params.pageNo = params.page;
  //   // return Promise.resolve(params);
  // },
  columns: getEditCellColumns(),
  defSort: {
    field: 'name',
    order: 'ascend',
  },
  // defSort: {
  //   pageNo: 1,
  //   pageSize: 20,
  //   field: 'name',
  //   order: 'ascend',
  // },
  rowKey: 'id',
  showTableSetting: true,
  showIndexColumn: false, // 是否显示序号列
@@ -175,6 +224,12 @@
    // 是否显示全屏按钮
    fullScreen: true
  },
  pagination: {
    // pageSize: 20,
    pageSizeOptions: ['10', '20', '50', '100'],
    defaultPageSize: 20,
    // showSizeChanger: true,
  },
  onChange,
  rowSelection: {
    type: 'checkbox',
@@ -184,7 +239,7 @@
  },
  showSelectionBar: true, // 显示多选状态栏
  actionColumn: {
    width: 120,
    width: 50,
    title: '操作',
    dataIndex: 'action',
    fixed: 'right',
@@ -197,15 +252,16 @@
    setLoading(false);
  }, 1000);
}
function changeColumns() {
  setProps({
    columns: getBasicShortColumns(),
    rowSelection: {
      type: 'checkbox',
    },
    showIndexColumn: true,
  });
}
// function changeColumns() {
//   setProps({
//     columns: getBasicShortColumns(),
//     rowSelection: {
//       type: 'checkbox',
//     },
//     showIndexColumn: true,
//   });
// }
function reloadTable() {
  setProps({
    columns: getEditCellColumns(),
@@ -218,6 +274,7 @@
    page: 1,
  });
}
function getColumn() {
  createMessage.info('请在控制台查看!');
  console.log(getColumns());
@@ -244,17 +301,21 @@
  });
  reload();
}
function getSelectRowList() {
  createMessage.info('请在控制台查看!');
  console.log(getSelectRows());
}
function getSelectRowKeyList() {
  createMessage.info('请在控制台查看!');
  console.log(getSelectRowKeys());
}
function setSelectedRowKeyList() {
  setSelectedRowKeys(['0', '1', '2']);
}
function clearSelect() {
  clearSelectedRowKeys();
}
@@ -267,46 +328,34 @@
const pagination = ref<any>(false);
function toggleCanResize() {
  canResize.value = !canResize.value;
}
function toggleStriped() {
  striped.value = !striped.value;
}
function toggleLoading() {
  loading.value = true;
  setTimeout(() => {
    loading.value = false;
    pagination.value = { pageSize: 20 };
  }, 3000);
}
function toggleBorder() {
  border.value = !border.value;
}
function handleEditEnd({ record, index, key, value }: Recordable) {
function handleEditEnd({record, index, key, value}: Recordable) {
  console.log(record, index, key, value);
  return false;
}
async function beforeEditSubmit({ record, index, key, value }) {
  console.log('单元格数据正在准备提交', { record, index, key, value });
  return await feakSave({ id: record.id, key, value });
async function beforeEditSubmit({record, index, key, value}) {
  console.log('单元格数据正在准备提交', {record, index, key, value});
  return await feakSave({id: record.id, key, value});
}
function handleEditCancel() {
  console.log('cancel');
}
function handleRowClick() {
  console.log('handleRowClick');
function handleRowClick(data) {
  Logger.log('handleRowClick', data);
  openDrawer(true, {
    isUpdate: false,
    clue: data
  });
}
function handleCellClick() {
  console.log('handleCellClick..');
}
// 模拟将指定数据保存
function feakSave({ value, key, id }) {
function feakSave({value, key, id}) {
  createMessage.loading({
    content: `正在模拟保存${key}`,
    key: '_save_fake_data',
src/views/customer/components/change-status/index.vue
New file
@@ -0,0 +1,129 @@
<template>
  <BasicModal
    v-bind="$attrs"
    @register="register"
    title="变更状态"
    @visible-change="handleVisibleChange"
    @ok="handleSubmit"
  >
    <div class="mb-10px">
      将线索【{{cluesName}}】状态变更为:
    </div>
    <div :class="'pt-3px pr-3px '+ prefixCls">
      <BasicForm @register="registerForm"></BasicForm>
    </div>
    <div class="mt-10px c-gray">
      注:如需切换线索状态为「已转化」,请使用"转为客户“功能
    </div>
  </BasicModal>
</template>
<script lang="ts" setup>
import {ref, nextTick, h, unref} from 'vue';
import {BasicModal, useModalInner} from '@/components/Modal';
import {BasicForm, useForm} from '@/components/Form';
import {PlusOutlined, MinusOutlined,PaperClipOutlined} from '@ant-design/icons-vue';
import {TreeSelect, Upload} from "ant-design-vue";
import {optionsListApi} from "@/api/demo/select";
const prefixCls = 'new-follow-up';
const props = defineProps({
  userData: {type: Object},
});
const modelRef = ref({});
const [registerForm,{validate:validate}] = useForm({
  schemas:[{
    field: 'cluesStatus',
    component: 'ApiSelect',
    label: '线索状态',
    colProps: {
      span: 24,
    },
    required: true,
    componentProps: {
      api: optionsListApi,
      params: {
        id: 1,
      },
      resultField: 'list',
      // use name as label
      labelField: 'name',
      // use id as value
      valueField: 'id',
      // not request untill to select
      immediate: true,
      onChange: (e, v) => {
        console.log('ApiSelect====>:', e, v);
      },
      // atfer request callback
      onOptionsChange: (options) => {
        console.log('get options', options.length, options);
      },
    },
    defaultValue: '1',
  },],
  showActionButtonGroup: false,
  layout: 'vertical',
});
const [register,{ setModalProps, closeModal }] = useModalInner((data) => {
  Logger.log('useModalInner data...', data);
  data && onDataReceive(data);
  setModalProps({
    // width: 800,
    minHeight: 150,
    canFullscreen: false,
    destroyOnClose: true,
  });
});
let cluesName = ref('测试1');
function onDataReceive(data) {
  console.log('Data Received', data);
  cluesName.value = data.data.cluesName;
  // 方式1;
  // setFieldsValue({
  //   field2: data.data,
  //   field1: data.info,
  // });
  // // 方式2
  modelRef.value = {field2: data.data, field1: data.info};
  // setProps({
  //   model:{ field2: data.data, field1: data.info }
  // })
}
function handleVisibleChange(v) {
  v && props.userData && nextTick(() => onDataReceive(props.userData));
}
// let currentFollowUpType = ref('1');
// function handleFollowUpTypeChange(value: any) {
//   Logger.log('handleFollowUpTypeChange...', value);
// }
function removeFile(file: any) {
  Logger.log('remove file...', file);
}
async function handleSubmit() {
  try {
    const values = await validate();
    Logger.log('submit values', values);
    setModalProps({ confirmLoading: true });
    // TODO custom api
    // console.log(values);
    closeModal();
  } finally {
    setModalProps({ confirmLoading: false });
  }
}
</script>
<style lang="less">
</style>
src/views/customer/components/drawer-form/annualPurchaseAmount.ts
New file
@@ -0,0 +1,51 @@

export default [
  {
    label: '无采购额',
    value: '1',
  },
  {
    label: '0~1千美元',
    value: '2',
  },
  {
    label: '1千~5千美元',
    value: '3',
  },
  {
    label: '5千~1万美元',
    value: '4',
  },
  {
    label: '1万~3万美元',
    value: '5',
  },
  {
    label: '3万~5万美元',
    value: '6',
  },
  {
    label: '5万~10万美元',
    value: '7',
  },{
    label: '10万~30万美元',
    value: '8',
  },
  {
    label: '30万~50万美元',
    value: '9',
  },
  {
    label: '50万~100万美元',
    value: '10',
  },
  {
    label: '100万~500万美元',
    value: '11',
  },
  {
    label: '500万美元以上',
    value: '12',
  },
]
src/views/customer/components/drawer-form/companyType.ts
New file
@@ -0,0 +1,66 @@
export default [
  {
    label: '原材料供应商',
    value: '1',
  },
  {
    label: '生产商',
    value: '2',
  },
  {
    label: '加盟商',
    value: '3',
  },
  {
    label: '渠道商',
    value: '4',
  },
  {
    label: '贸易商',
    value: '5',
  },
  {
    label: '代理商',
    value: '6',
  },
  {
    label: '批发商',
    value: '7',
  },{
    label: '零售商',
    value: '8',
  },
  {
    label: '采购办事处',
    value: '9',
  },
  {
    label: '采购咨询公司',
    value: '10',
  },
  {
    label: '出口商',
    value: '11',
  },
  {
    label: '进口商',
    value: '12',
  },
  {
    label: '个人消费者',
    value: '13',
  },
  {
    label: '机构/团体消费者',
    value: '14',
  },
  {
    label: '工程商',
    value: '15',
  },
  {
    label: '其他',
    value: '16',
  },
]
src/views/customer/components/drawer-form/index.vue
New file
@@ -0,0 +1,94 @@
<template>
  <div :class="prefixCls">
    <Alert
      v-if="visible"
      message="线索增强:填写公司网址或邮箱后缀后,如系统能查找到该公司的其他更多信息,则会为您自动补全。"
      type="info"
      show-icon
      closable
      :after-close="handleClose"
      class="mb-10px"
    />
    <Row :class="`${prefixCls}-top`">
      <Col :span="11" :class="`${prefixCls}-col`">
        <div class="pt-3px pr-3px">
          <BasicForm @register="registerForm" :model="modelRef" />
        </div>
      </Col>
      <Col :span="11" :offset="2" :class="`${prefixCls}-col`">
5
      </Col>
<!--      <Col :span="8" :class="`${prefixCls}-col`">-->
<!--        <CollapseContainer :class="`${prefixCls}-top__team`" title="团队" :canExpand="false">-->
<!--          <div v-for="(team, index) in teams" :key="index" :class="`${prefixCls}-top__team-item`">-->
<!--            <Icon :icon="team.icon" :color="team.color" />-->
<!--            <span>{{ team.title }}</span>-->
<!--          </div>-->
<!--        </CollapseContainer>-->
<!--      </Col>-->
    </Row>
  </div>
</template>
<script lang="ts" setup>
  import Icon from '@/components/Icon/Icon.vue';
  import { Col, Row, Alert  } from 'ant-design-vue';
  import {computed, unref, onMounted, type PropType} from 'vue';
  import {BasicHelp} from "@/components/Basic";
  import { BasicForm, FormSchema, useForm } from '@/components/Form';
  const prefixCls = 'clues-drawer';
  const visible = ref<boolean>(true);
  const handleClose = () => {
    visible.value = false;
  };
  import { ref } from 'vue';
  import { defineProps } from 'vue'
  const props = defineProps({
    schemas: {
      type: Array as PropType<FormSchema[]>,
    },
  })
  const modelRef = ref({});
  const [
    registerForm,
    // {
    //   // setFieldsValue,
    //   // setProps
    // },
  ] = useForm({
    layout: 'vertical',
    // labelWidth: 100,
    schemas:props.schemas,
    showActionButtonGroup: false,
    actionColOptions: {
      span: 24,
    },
  });
  onMounted(() => {
    Logger.log('线索onMounted');
  });
</script>
<style lang="less" scoped>
  .clues-drawer {
    &-col:not(:last-child) {
      //padding: 0 10px;
      //&:not(:last-child) {
      //  border-right: 1px dashed rgb(206 206 206 / 50%);
      //}
    }
  }
</style>
src/views/customer/components/drawer-form/timeZone.ts
New file
@@ -0,0 +1,98 @@
export default [
  {
    label: '零时区:伦敦',
    value: '1',
  },
  {
    label: '东一区:罗马,巴黎',
    value: '2',
  },
  {
    label: '东二区:雅典,以色列',
    value: '3',
  },
  {
    label: '东三区:莫斯科,科威特',
    value: '4',
  },
  {
    label: '东四区:喀布尔',
    value: '5',
  },
  {
    label: '东五区:伊斯兰堡,卡拉奇',
    value: '6',
  },
  {
    label: '东六区:阿拉木图,科伦坡',
    value: '7',
  },{
    label: '东七区:曼谷,雅加达',
    value: '8',
  },
  {
    label: '东八区:北京,香港,台湾',
    value: '9',
  },
  {
    label: '东九区:东京',
    value: '10',
  },
  {
    label: '东十区:悉尼',
    value: '11',
  },
  {
    label: '东十一区:霍尼亚拉,马加丹',
    value: '12',
  },
  {
    label: '东西十二区:奥克兰',
    value: '13',
  },
  {
    label: '西十一区:帕果帕果,阿洛菲',
    value: '14',
  },
  {
    label: '西十区:夏威夷',
    value: '15',
  },
  {
    label: '西九区:阿拉斯加',
    value: '16',
  },
  {
    label: '西八区:洛杉矶,旧金山',
    value: '17',
  },
  {
    label: '西七区:盐湖城、丹佛、凤凰城',
    value: '18',
  },
  {
    label: '西六区:芝加哥,休斯顿,亚特兰大',
    value: '19',
  },
  {
    label: '西五区:纽约,华盛顿,波士顿',
    value: '20',
  },
  {
    label: '西四区:加拿大,加拉加斯',
    value: '21',
  },
  {
    label: '西三区:巴西利亚',
    value: '22',
  },
  {
    label: '西二区:协调世界时',
    value: '23',
  },
  {
    label: '西一区:佛得角群岛',
    value: '24',
  },
]
src/views/customer/components/drawer/Business.vue
New file
@@ -0,0 +1,184 @@
<template>
  <Row class="mb-10px">
    <Col span="12">
      <Dropdown :trigger="['click']" arrow @openChange="openChange">
        <span class="cursor-pointer" @click.prevent>
          {{ dropDownTitle }}
          <BasicArrow :expand="false" down/>
        </span>
        <template #overlay>
          <Menu @click="onMenuItemClick">
            <MenuItem key="0">
              <span>未完成</span>
            </MenuItem>
            <MenuItem key="1">
              <span>已完成</span>
            </MenuItem>
          </Menu>
        </template>
      </Dropdown>
    </Col>
    <Col span="12" :style="'text-align:right'">
      <Tooltip title="新建日程">
        <PlusSquareOutlined class="cursor-pointer" style="fontSize:24px;color: #aaa"
                            @click="openScheduleModal"/>
      </Tooltip>
    </Col>
  </Row>
  <BasicTable
    @register="registerTable"
    @row-click="handleScheduleRowClick"
  >
    <template #bodyCell="{ column, record }">
      <template v-if="column.key === 'cluesName'">
        <div>
          <Avatar :size="16" :style="'background-color:'+ record.color"></Avatar>
          <span class="ml-5px">
            {{ record.cluesName }}
          </span>
        </div>
      </template>
    </template>
  </BasicTable>
  <ScheduleDetailModal @register="registerScheduleModal"></ScheduleDetailModal>
  <div>
    <template v-for="item in projectList" :key="item.title">
      <Row class="cursor-pointer" :gutter="[22,26]" align="middle">
        <Col span="12">
          <Avatar :size="16" :style="'background-color:'+ item.color"></Avatar>
          <span class="ml-5px">{{ item.title }}</span>
        </Col>
        <Col span="12" :style="'text-align:right'">
          <span>{{ item.content }}</span>
        </Col>
      </Row>
    </template>
  </div>
  <!--  <List :class="prefixCls" item-layout="horizontal" :data-source="projectList" :grid="{ column: 1, gutter: 12}" :pagination="pagination" >-->
  <!--    <template #renderItem="{ item }">-->
  <!--      <ListItem>-->
  <!--        -->
  <!--      </ListItem>-->
  <!--    </template>-->
  <!--&lt;!&ndash;    <template v-for="item in projectList" :key="item.title"></template>&ndash;&gt;-->
  <!--  </List>-->
</template>
<script lang="ts" setup>
import {List, Avatar, Card, Row, Col, Dropdown, Menu, Tooltip, Tag} from 'ant-design-vue';
import {projectList} from './data';
import {PlusSquareOutlined} from '@ant-design/icons-vue';
import {BasicArrow} from "@/components/Basic";
import {ref} from 'vue';
import ScheduleDetailModal from './ScheduleDetail.vue';
const ListItem = List.Item;
const MenuItem = Menu.Item;
import EventBus from '@/utils/eventBus';
import {BasicTable, ColumnChangeParam, TableAction, useTable} from "@/components/Table";
import {cluesListApi} from "@/api/clues/table";
import {getEditCellColumns, getFormConfig} from "@/views/clues/components/tableData";
import {useModal} from "@/components/Modal";
const [registerScheduleModal, {openModal, setModalProps}] = useModal();
const openScheduleModal = () => {
  Logger.log('点击openScheduleModal');
  EventBus.emit('openScheduleModal', {
    title: '新建任务',
    content: '新建任务内容'
  });
};
const pagination = {
  onChange: (page: number) => {
    console.log(page);
  },
  pageSize: 3,
};
const openChange = (status: boolean) => {
  Logger.log('openChange', status);
};
const dropDownTitle = ref('未完成');
const onMenuItemClick = (e: any) => {
  let titles = ['未完成', '已完成'];
  Logger.log('onMenuItemClick', e);
  dropDownTitle.value = titles[e.key];
};
const [
  registerTable,
  {
    // setLoading,
    // setProps,
    // getColumns,
    // getDataSource,
    // getRawDataSource,
    // reload,
    // getPaginationRef,
    // setPagination,
    // getSelectRows,
    // getSelectRowKeys,
    // setSelectedRowKeys,
    // clearSelectedRowKeys,
  },
] = useTable({
  canResize: true,
  api: cluesListApi,
  // beforeFetch: (params) => {
  //   console.log('beforeFetch', params);
  //   params.pageNo = params.page;
  //   // return Promise.resolve(params);
  // },
  showIndexColumn: false,
  columns: [
    {
      // title: '',
      // defaultHidden: true,
      dataIndex: 'cluesName',
      width: 200
    },
    {
      // title: '',
      dataIndex: 'archiveTime',
      width: 200,
    },
  ],
  // defSort: {
  //   pageNo: 1,
  //   pageSize: 20,
  //   field: 'name',
  //   order: 'ascend',
  // },
  rowKey: 'id',
  // showTableSetting: false,
  // showIndexColumn: false, // 是否显示序号列
  // useSearchForm: false, // 使用搜索表单
  // clickToRowSelect: false,
  // formConfig: getFormConfig(), // 搜索表单配置
  pagination: {
    // pageSize: 20,
    pageSizeOptions: ['10', '20', '50', '100'],
    defaultPageSize: 20,
    // showSizeChanger: true,
  },
});
function handleScheduleRowClick(e) {
  Logger.log('handleScheduleRowClick', e);
  openModal(true, {
    // record,
    // isUpdate: true,
  });
}
const prefixCls = 'account-center-project';
</script>
<style lang="less" >
</style>
src/views/customer/components/drawer/Detail.vue
New file
@@ -0,0 +1,205 @@
<template>
  <div :class="prefixCls">
    <BasicForm @register="registerForm"></BasicForm>
    <div class="mb-15px">
      <div class="mb-15px ">
        <div :class="`${prefixCls}__label`">系统标签</div>
        <div>暂无数据</div>
      </div>
      <div class="mb-15px">
        <div :class="`${prefixCls}__label`">首次跟进时间</div>
        <div>暂无数据</div>
      </div>
      <div class="">
        <div :class="`${prefixCls}__label`">未联系天数</div>
        <div>10</div>
      </div>
    </div>
    <Divider />
    <h3>公司信息</h3>
    <Description @register="register" class="mt-4" />
    <Divider />
    <h3>联系人</h3>
    <Description @register="register" class="mt-4" />
    <Divider />
    <h3>系统信息</h3>
    <Description @register="register" class="mt-4" />
  </div>
</template>
<script lang="ts" setup>
  import { List, Tag, Divider } from 'ant-design-vue';
  import Icon from '@/components/Icon/Icon.vue';
  import {BasicForm, FormSchema, useForm} from '@/components/Form';
  import dayjs from "dayjs";
  import {treeOptionsListApi} from "@/api/demo/tree";
  import {getAllRoleList} from "@/api/demo/system";
  import {DescItem, Description, useDescription} from "@/components/Description";
  const formSchema: FormSchema[] = [
    {
      field: 'field33',
      component: 'ApiTreeSelect',
      label: '线索来源',
      // helpMessage: ['ApiTreeSelect组件', '使用接口提供的数据生成选项'],
      required: true,
      componentProps: {
        api: treeOptionsListApi,
        treeCheckable: true,
        resultField: 'list',
        onChange: (e, v) => {
          console.log('ApiTreeSelect====>:', e, v);
        },
      },
      colProps: {
        span: 24,
      },
    },
    {
      component: 'Select',
      label: '线索状态',
      field: 'date1',
      colProps: { span: 24 },
      componentProps: {
        options: [
          {
            label: '待处理',
            value: '1',
          },
          {
            label: '完善信息',
            value: '2',
          },
          {
            label: '初步触达',
            value: '3',
          },
          {
            label: '联系互动',
            value: '4',
          },
        ],
      },
      defaultValue:'1'
    },
    {
      label: '国家地区',
      field: 'country',
      component: 'ApiSelect',
      componentProps: {
        api: getAllRoleList,
        labelField: 'roleName',
        valueField: 'roleValue',
      },
    },
    {
      label: '线索备注',
      field: 'field0',
      component: 'InputTextArea',
      componentProps:{
        placeholder: '请输入备注内容',
      },
      colProps: {
        span: 24,
      },
    },
  ];
  const mockData: Recordable = {
    cluesName: 'test',
    nickName: 'VB',
    age: '123',
    phone: '15695909xxx',
    email: '190848757@qq.com',
    companyname: '公司名称111',
    sex: '男',
    certy: '3504256199xxxxxxxxx',
    tag: 'orange',
  };
  const schema: DescItem[] = [
    {
      field: 'cluesName',
      label: '线索名称',
    },
    {
      field: 'companyname',
      label: '公司名称',
    },
    {
      field: 'phone',
      label: '联系电话',
    },
    {
      field: 'email',
      label: '邮箱',
    },
    {
      field: 'phone',
      label: '座机',
      render: (curVal, data) => {
        return `${data.nickName}-${curVal}`;
      },
    },
  ];
  const [registerForm] = useForm({
    layout: 'vertical',
    baseColProps: { span: 24 },
    schemas: formSchema,
    showActionButtonGroup: false,
  });
  const [register] = useDescription({
    // title: '无边框',
    layout: 'vertical',
    bordered: false,
    column: 1,
    labelStyle:{
      color: '#909090',
      // fontSize: '12px',
    },
    data: mockData,
    schema: schema,
  });
  const prefixCls = 'clues-tab-detail';
</script>
<style lang="less" scoped>
  .clues-tab-detail {
    background-color: @component-background;
    &__title {
      margin-bottom: 12px;
      font-size: 18px;
    }
    &__content {
      color: @text-color-secondary;
    }
    &__label {
      color: @text-color-secondary;
    }
    &__action {
      display: inline-block;
      padding: 0 16px;
      color: @text-color-secondary;
      &:nth-child(1),
      &:nth-child(2) {
        border-right: 1px solid rgb(206 206 206 / 40%);
      }
      &-icon {
        margin-right: 3px;
      }
    }
    &__time {
      position: absolute;
      right: 20px;
      color: rgb(0 0 0 / 45%);
    }
  }
</style>
src/views/customer/components/drawer/Document.vue
New file
@@ -0,0 +1,138 @@
<template>
  <a-collapse v-model:activeKey="activeKey" ghost>
    <a-collapse-panel key="1" header="文档">
      <BasicTable
        @register="registerTable"
        @row-click="handleScheduleRowClick"
      >
        <template #bodyCell="{ column, record }">
          <template v-if="column.key === 'cluesName'">
            <div>
              <a-avatar :size="16" :style="'background-color:'+ record.color"></a-avatar>
              <span class="ml-5px">
            {{ record.cluesName }}
          </span>
            </div>
          </template>
        </template>
      </BasicTable>
    </a-collapse-panel>
  </a-collapse>
  <ScheduleDetailModal @register="registerScheduleModal"></ScheduleDetailModal>
</template>
<script lang="ts" setup>
import {projectList} from './data';
import {PlusSquareOutlined} from '@ant-design/icons-vue';
import {BasicArrow} from "@/components/Basic";
import {ref} from 'vue';
import ScheduleDetailModal from './ScheduleDetail.vue';
import EventBus from '@/utils/eventBus';
import {BasicTable, ColumnChangeParam, TableAction, useTable} from "@/components/Table";
import {cluesListApi} from "@/api/clues/table";
import {getEditCellColumns, getFormConfig} from "@/views/clues/components/tableData";
import {useModal} from "@/components/Modal";
const [registerScheduleModal, {openModal, setModalProps}] = useModal();
const openScheduleModal = () => {
  Logger.log('点击openScheduleModal');
  EventBus.emit('openScheduleModal', {
    title: '新建任务',
    content: '新建任务内容'
  });
};
const activeKey = ref(['1']);
const pagination = {
  onChange: (page: number) => {
    console.log(page);
  },
  pageSize: 3,
};
const openChange = (status: boolean) => {
  Logger.log('openChange', status);
};
const dropDownTitle = ref('未完成');
const onMenuItemClick = (e: any) => {
  let titles = ['未完成', '已完成'];
  Logger.log('onMenuItemClick', e);
  dropDownTitle.value = titles[e.key];
};
const [
  registerTable,
  {
    // setLoading,
    // setProps,
    // getColumns,
    // getDataSource,
    // getRawDataSource,
    // reload,
    // getPaginationRef,
    // setPagination,
    // getSelectRows,
    // getSelectRowKeys,
    // setSelectedRowKeys,
    // clearSelectedRowKeys,
  },
] = useTable({
  canResize: true,
  api: cluesListApi,
  // beforeFetch: (params) => {
  //   console.log('beforeFetch', params);
  //   params.pageNo = params.page;
  //   // return Promise.resolve(params);
  // },
  showIndexColumn: false,
  columns: [
    {
      // title: '',
      // defaultHidden: true,
      dataIndex: 'cluesName',
      width: 200
    },
    {
      // title: '',
      dataIndex: 'archiveTime',
      width: 200,
    },
  ],
  // defSort: {
  //   pageNo: 1,
  //   pageSize: 20,
  //   field: 'name',
  //   order: 'ascend',
  // },
  rowKey: 'id',
  // showTableSetting: false,
  // showIndexColumn: false, // 是否显示序号列
  // useSearchForm: false, // 使用搜索表单
  // clickToRowSelect: false,
  // formConfig: getFormConfig(), // 搜索表单配置
  pagination: {
    // pageSize: 20,
    pageSizeOptions: ['10', '20', '50', '100'],
    defaultPageSize: 20,
    // showSizeChanger: true,
  },
});
function handleScheduleRowClick(e) {
  Logger.log('handleScheduleRowClick', e);
  openModal(true, {
    // record,
    // isUpdate: true,
  });
}
const prefixCls = 'account-center-project';
</script>
<style lang="less" >
</style>
src/views/customer/components/drawer/Dynamic.vue
New file
@@ -0,0 +1,163 @@
<template>
  <div :class="prefixCls" >
    <div class="mb-20px">
      <a-button type="primary" shape="round" block @click="openFollowUpModal">
        <template #icon>
          <PlusCircleOutlined />
        </template>
        添加跟进
      </a-button>
    </div>
    <div ref="wrapEl">
      <div class="mb-20px flex justify-between flex-items-center">
        <span class="font-bold">共 {{totalData}} 条</span>
        <TreeSelect
          v-model:value="currentDynamicType"
          :dropdownStyle="{color: 'red'}"
          style="width: 120px"
          placeholder="Please select"
          :allow-clear="false"
          :bordered="false"
          tree-default-expand-all
          :tree-data="dynamicTypeTree"
          @change="handleDynamicTypeChange"
        >
        </TreeSelect>
      </div>
      <Timeline >
        <TimelineItem v-for="item in dynamicList" :key="item.id">
          <Row>
            <Col span="16" class="c-gray-4">
              {{item.cluesName}}
            </Col>
            <Col :offset="2" span="6" class="c-gray-4">
              {{item.privateTime}}
            </Col>
          </Row>
          <div>
            {{item.failStatusName}}
          </div>
          <div>
            <ImagePreviewGroup>
              <template v-for="(img,index) in item.imageList" :key="index">
                <Image :width="100" class="pr-10px" :src="img" />
              </template>
            </ImagePreviewGroup>
          </div>
        </TimelineItem>
      </Timeline>
    </div>
<!--    show-size-changer-->
<!--    show-quick-jumper-->
<!--    :show-total="total => `共 ${total} 条数据`"-->
    <Pagination
      class="text-right"
      v-model:current="currentPage"
      v-model:page-size="pageSize"
      :total="totalData"
      responsive
      show-less-items
      :pageSizeOptions="['10','20', '30', '50']"
      @change="handlePageChange"
    />
  </div>
</template>
<script lang="ts" setup>
  import { Timeline,Row,Col,ImagePreviewGroup,Image,Pagination,TreeSelect } from 'ant-design-vue';
  import Icon from '@/components/Icon/Icon.vue';
  import { applicationList } from './data';
  import {cluesDynamicApi} from "@/api/clues/dynamic";
  import {ref,unref,getCurrentInstance} from "vue";
  import {useLoading} from "@/components/Loading";
  import {PlusCircleOutlined} from "@ant-design/icons-vue";
  import {treeOptionsListApi} from "@/api/demo/tree";
  import EventBus from "@/utils/eventBus";
  const TimelineItem = Timeline.Item;
  const prefixCls = 'clues-tab-dynamic';
  let dynamicList = ref([] as any[]);
  let totalData = ref(0);
  let currentPage = ref(1);
  let pageSize = ref(10);
  const handlePageChange = (current: number, size: number) => {
    currentPage.value = current;
    pageSize.value = size;
    // console.log('PageChange',currentPage, pageSize);
    getCluesDynamicData();
  };
  // 打开跟进弹窗
  const openFollowUpModal = () => {
    Logger.log('openFollowUpModal...');
    EventBus.emit('openFollowUpModal', {
      title: '新建任务',
      content: '新建任务内容'
    });
  };
  // let _this = getCurrentInstance();
  // 动态类型treeData
  let currentDynamicType = ref('0-0');
  let dynamicTypeTree = ref([]);
  getTreeData();
  async function getTreeData() {
    try {
      const data = await treeOptionsListApi();
      dynamicTypeTree.value = data?.list || [];
      Logger.log('treeOptionsListApi...dynamicTypeTree', dynamicTypeTree);
    } catch (e) {
      Logger.error(e);
    } finally {
    }
  }
  function handleDynamicTypeChange(value: any) {
    Logger.log('handleDynamicTypeChange...', value);
  }
  // 动态列表加载
  const wrapEl = ref<ElRef>(null);
  const [openWrapLoading, closeWrapLoading] = useLoading({
    target: wrapEl,
    props: {
      tip: '加载中...',
      absolute: true,
    },
  });
  // 动态列表数据
  getCluesDynamicData();
  async function getCluesDynamicData(){
    openWrapLoading();
    let params = {
      page: currentPage.value,
      pageSize: pageSize.value,
    };
    try {
      // cluesDynamicApi(params).then(res => {
      //
      // })
      let res = await cluesDynamicApi(params)
      Logger.log('cluesDynamicApi...',res);
      // console.log(_this);
      // _this.$set(dynamicList,res.items)
      dynamicList.value = res.items;
      totalData.value = res.total;
    } catch (e) {
      Logger.error(e);
    } finally {
      closeWrapLoading();
    }
  }
</script>
<style lang="less">
  .clues-tab-dynamic {
    .ant-select-arrow{
      color: #000;
    }
  }
</style>
src/views/customer/components/drawer/ScheduleDetail.vue
New file
@@ -0,0 +1,269 @@
<template>
  <BasicModal
    v-bind="$attrs"
    @register="register"
    @visible-change="handleVisibleChange"
    class="schedule-detail-modal"
  >
    <template #title></template>
    <template #closeIcon></template>
    <Tabs v-model:activeKey="activeKey" @change="onTabsChange">
      <TabPane key="1" tab="详情">
        <div>
          <div>
            <Row class="mb-10px">
              <Col span="12" class="font-bold">2024-09</Col>
              <Col span="12" class="c-gray text-right">
                <ClockCircleOutlined />
                2024-09-09 09:00:00
              </Col>
            </Row>
            <Row>
              <Col span="20" class="">
                <Checkbox v-model:checked="checked">
                  <Avatar class="align-inherit" :size="10" :style="'background-color:'+ '#ddd'"></Avatar>
                  <span class="pl-10px">测试1</span>
                </Checkbox>
                <Row class="c-gray ml-22px">今天</Row>
              </Col>
              <Col span="4" class="font-bold text-right">
                <HeartOutlined class="font-size-18px" />
                <HeartFilled class="font-size-18px c-red-5" />
              </Col>
            </Row>
          </div>
          <Divider />
          <div>
            <div class="mb-10px">
              <div class="c-gray">关联线索:</div>
              <a href="javascript:void(0)">测试1</a>
            </div>
            <div class="mb-10px">
              <div class="c-gray">参与人:</div>
              <span >测试1</span>
            </div>
            <div class="mb-10px">
              <div class="c-gray">创建人:</div>
              <span >测试1</span>
            </div>
          </div>
          <Divider />
        </div>
      </TabPane>
      <TabPane key="2" tab="评论">
        <div class="mb-10px scroll-wrap">
          <ScrollContainer class="">
            <Empty></Empty>
            <div>
              <Row>
                <Col span="12" class="font-size-18px">
                  Tuku
                </Col>
                <Col span="12" class="text-right">
                  <span class="c-gray pr-10px">2024-09-09 09:00:00</span>
                  <EditOutlined class="font-size-18px cursor-pointer c-blue-5 mr-10px"/>
                  <CloseOutlined class="font-size-18px cursor-pointer c-red-5"/>
                </Col>
              </Row>
              <Row>
                <Col span="24">测试评论2</Col>
              </Row>
<!--              <Flex >-->
<!--                <div-->
<!--                  v-for="(item, index) in new Array(4)"-->
<!--                  :key="item"-->
<!--                  :style="{ ...baseStyle, background: `${index % 2 ? '#1677ff' : '#1677ffbf'}` }"-->
<!--                />-->
<!--              </Flex>-->
              <ImagePreviewGroup>
                <Image :width="120" src="https://aliyuncdn.antdv.com/vue.png"></Image>
              </ImagePreviewGroup>
              <Upload
                v-model:file-list="fileList"
                action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
                list-type="picture"
                @remove="removeFile"
                class="upload-list-inline"
                :showUploadList="{ showDownloadIcon: true,showRemoveIcon: false }"
              >
              </Upload>
            </div>
          </ScrollContainer>
        </div>
        <div class="scroll-wrap">
          <Divider />
          <ScrollContainer>
            <BasicForm @register="registerForm">
            </BasicForm>
          </ScrollContainer>
        </div>
      </TabPane>
    </Tabs>
    <template #footer>
      <Row class="text-center" v-if="activeKey==='1'">
        <Col span="8">
          <a-button type="link">编辑</a-button>
        </Col>
        <Col span="8">
          <a-button type="text" danger>删除</a-button>
        </Col>
        <Col span="8">
          <a-button type="link" >完成</a-button>
        </Col>
      </Row>
      <Row class="" v-if="activeKey==='2'" justify="end">
        <Col>
          <a-button type="default">取消</a-button>
          <a-button type="primary">保存</a-button>
        </Col>
      </Row>
    </template>
  </BasicModal>
</template>
<script lang="ts" setup>
  import { ref, nextTick } from 'vue';
  import {BasicModal, useModal, useModalInner} from '@/components/Modal';
  import { BasicForm, FormSchema, useForm } from '@/components/Form';
  import {TabPane, Tabs, Row, Col, Divider, Checkbox, Avatar,Empty,Flex,ImagePreviewGroup,Image,Upload} from 'ant-design-vue';
  import type { UploadProps } from 'ant-design-vue';
  import { ClockCircleOutlined,HeartOutlined,HeartFilled,EditOutlined,CloseOutlined } from '@ant-design/icons-vue';
  import ScrollContainer from "@/components/Container/src/ScrollContainer.vue";
  import { schemas } from './scheduleCommentFormData';
  const activeKey = ref('1');
  const checked = ref(false);
  const props = defineProps({
    userData: { type: Object },
  });
  const modelRef = ref({});
  const [
    registerForm,
    // {
    //   // setFieldsValue,
    //   // setProps
    // },
  ] = useForm({
    // labelWidth: 120,
    layout: 'vertical',
    schemas,
    showActionButtonGroup: false,
    actionColOptions: {
      span: 24,
    },
  });
  const fileList = ref<UploadProps['fileList']>([
    {
      uid: '-1',
      name: 'xxx.png',
      status: 'done',
      url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
      thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
    },
    {
      uid: '-2',
      name: 'yyy.png',
      status: 'done',
      url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
      thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
    },
  ]);
  const removeFile = (file) => {
    Logger.log('点击删除',file);
    return false;
  }
  const [register,{setModalProps}] = useModalInner((data) => {
    setModalProps({
      canFullscreen: false,
      showCancelBtn: false,
      showOkBtn: false,
      useWrapper: false,
      wrapperFooterOffset: 0,
      // draggable: false,
      minHeight: 400,
    });
    data && onDataReceive(data);
  });
  function onTabsChange(key:string) {
    Logger.log('点击切换',key);
    if (key === '1') {
      setModalProps({
        minHeight: 400,
      });
    }
    if (key === '2') {
      setModalProps({
        minHeight: 600,
      });
    }
  }
  function onDataReceive(data) {
    console.log('Data Received', data);
    // 方式1;
    // setFieldsValue({
    //   field2: data.data,
    //   field1: data.info,
    // });
    // // 方式2
    modelRef.value = { field2: data.data, field1: data.info };
    // setProps({
    //   model:{ field2: data.data, field1: data.info }
    // })
  }
  function handleVisibleChange(v) {
    v && props.userData && nextTick(() => onDataReceive(props.userData));
  }
</script>
<style lang="less">
.schedule-detail-modal {
  .scroll-wrap{
    height: 240px;
  }
  .ant-modal-body,.ant-modal-header {
    padding: 0;
    margin: 0;
  }
  .ant-modal-footer{
    margin: 0;
  }
  .ant-modal-header,.ant-modal-footer{
    border: none;
  }
  .ant-tabs {
    margin: -16px;
    padding: 0 24px;
  }
  .scroll-container{
    padding-bottom: 0 !important;
  }
  .comment-card{
    border-bottom: 5px solid #ccc;
  }
  .upload-list-inline {
    .ant-upload-list-picture{
      display:flex;
      .ant-upload-list-item{
        margin-right: 8px;
      }
      .ant-upload-list-item-container{
        width: 50%;
      }
    }
  }
}
</style>
src/views/customer/components/drawer/Tips.vue
New file
@@ -0,0 +1,184 @@
<template>
  <Row class="mb-10px">
    <Col span="12">
      <Dropdown :trigger="['click']" arrow @openChange="openChange">
        <span class="cursor-pointer" @click.prevent>
          {{ dropDownTitle }}
          <BasicArrow :expand="false" down/>
        </span>
        <template #overlay>
          <Menu @click="onMenuItemClick">
            <MenuItem key="0">
              <span>未完成</span>
            </MenuItem>
            <MenuItem key="1">
              <span>已完成</span>
            </MenuItem>
          </Menu>
        </template>
      </Dropdown>
    </Col>
    <Col span="12" :style="'text-align:right'">
      <Tooltip title="新建日程">
        <PlusSquareOutlined class="cursor-pointer" style="fontSize:24px;color: #aaa"
                            @click="openScheduleModal"/>
      </Tooltip>
    </Col>
  </Row>
  <BasicTable
    @register="registerTable"
    @row-click="handleScheduleRowClick"
  >
    <template #bodyCell="{ column, record }">
      <template v-if="column.key === 'cluesName'">
        <div>
          <Avatar :size="16" :style="'background-color:'+ record.color"></Avatar>
          <span class="ml-5px">
            {{ record.cluesName }}
          </span>
        </div>
      </template>
    </template>
  </BasicTable>
  <ScheduleDetailModal @register="registerScheduleModal"></ScheduleDetailModal>
  <div>
    <template v-for="item in projectList" :key="item.title">
      <Row class="cursor-pointer" :gutter="[22,26]" align="middle">
        <Col span="12">
          <Avatar :size="16" :style="'background-color:'+ item.color"></Avatar>
          <span class="ml-5px">{{ item.title }}</span>
        </Col>
        <Col span="12" :style="'text-align:right'">
          <span>{{ item.content }}</span>
        </Col>
      </Row>
    </template>
  </div>
  <!--  <List :class="prefixCls" item-layout="horizontal" :data-source="projectList" :grid="{ column: 1, gutter: 12}" :pagination="pagination" >-->
  <!--    <template #renderItem="{ item }">-->
  <!--      <ListItem>-->
  <!--        -->
  <!--      </ListItem>-->
  <!--    </template>-->
  <!--&lt;!&ndash;    <template v-for="item in projectList" :key="item.title"></template>&ndash;&gt;-->
  <!--  </List>-->
</template>
<script lang="ts" setup>
import {List, Avatar, Card, Row, Col, Dropdown, Menu, Tooltip, Tag} from 'ant-design-vue';
import {projectList} from './data';
import {PlusSquareOutlined} from '@ant-design/icons-vue';
import {BasicArrow} from "@/components/Basic";
import {ref} from 'vue';
import ScheduleDetailModal from './ScheduleDetail.vue';
const ListItem = List.Item;
const MenuItem = Menu.Item;
import EventBus from '@/utils/eventBus';
import {BasicTable, ColumnChangeParam, TableAction, useTable} from "@/components/Table";
import {cluesListApi} from "@/api/clues/table";
import {getEditCellColumns, getFormConfig} from "@/views/clues/components/tableData";
import {useModal} from "@/components/Modal";
const [registerScheduleModal, {openModal, setModalProps}] = useModal();
const openScheduleModal = () => {
  Logger.log('点击openScheduleModal');
  EventBus.emit('openScheduleModal', {
    title: '新建任务',
    content: '新建任务内容'
  });
};
const pagination = {
  onChange: (page: number) => {
    console.log(page);
  },
  pageSize: 3,
};
const openChange = (status: boolean) => {
  Logger.log('openChange', status);
};
const dropDownTitle = ref('未完成');
const onMenuItemClick = (e: any) => {
  let titles = ['未完成', '已完成'];
  Logger.log('onMenuItemClick', e);
  dropDownTitle.value = titles[e.key];
};
const [
  registerTable,
  {
    // setLoading,
    // setProps,
    // getColumns,
    // getDataSource,
    // getRawDataSource,
    // reload,
    // getPaginationRef,
    // setPagination,
    // getSelectRows,
    // getSelectRowKeys,
    // setSelectedRowKeys,
    // clearSelectedRowKeys,
  },
] = useTable({
  canResize: true,
  api: cluesListApi,
  // beforeFetch: (params) => {
  //   console.log('beforeFetch', params);
  //   params.pageNo = params.page;
  //   // return Promise.resolve(params);
  // },
  showIndexColumn: false,
  columns: [
    {
      // title: '',
      // defaultHidden: true,
      dataIndex: 'cluesName',
      width: 200
    },
    {
      // title: '',
      dataIndex: 'archiveTime',
      width: 200,
    },
  ],
  // defSort: {
  //   pageNo: 1,
  //   pageSize: 20,
  //   field: 'name',
  //   order: 'ascend',
  // },
  rowKey: 'id',
  // showTableSetting: false,
  // showIndexColumn: false, // 是否显示序号列
  // useSearchForm: false, // 使用搜索表单
  // clickToRowSelect: false,
  // formConfig: getFormConfig(), // 搜索表单配置
  pagination: {
    // pageSize: 20,
    pageSizeOptions: ['10', '20', '50', '100'],
    defaultPageSize: 20,
    // showSizeChanger: true,
  },
});
function handleScheduleRowClick(e) {
  Logger.log('handleScheduleRowClick', e);
  openModal(true, {
    // record,
    // isUpdate: true,
  });
}
const prefixCls = 'account-center-project';
</script>
<style lang="less" >
</style>
src/views/customer/components/drawer/data.tsx
New file
@@ -0,0 +1,142 @@
export interface ListItem {
  title: string;
  icon: string;
  color?: string;
}
export interface TabItem {
  key: string;
  name: string;
  component: string;
}
export const tags: string[] = [
  '很有想法的',
  '专注设计',
  '川妹子',
  '海纳百川',
  '前端开发',
  'vue3',
];
export const teams: ListItem[] = [
  {
    icon: 'ri:alipay-fill',
    title: '科学搬砖组',
    color: '#ff4000',
  },
  {
    icon: 'emojione-monotone:letter-a',
    title: '中二少年团',
    color: '#7c51b8',
  },
  {
    icon: 'ri:alipay-fill',
    title: '高逼格设计',
    color: '#00adf7',
  },
  {
    icon: 'jam:codepen-circle',
    title: '程序员日常',
    color: '#00adf7',
  },
  {
    icon: 'fa:behance-square',
    title: '科学搬砖组',
    color: '#7c51b8',
  },
  {
    icon: 'jam:codepen-circle',
    title: '程序员日常',
    color: '#ff4000',
  },
];
export const details: ListItem[] = [
  {
    icon: 'ic:outline-contacts',
    title: '交互专家',
  },
  {
    icon: 'grommet-icons:cluster',
    title: '某某某事业群',
  },
  {
    icon: 'bx:bx-home-circle',
    title: '福建省厦门市',
  },
];
export const achieveList: TabItem[] = [
  {
    key: '1',
    name: '动态',
    component: 'Dynamic',
  },
  {
    key: '2',
    name: '资料',
    component: 'Detail',
  },
  {
    key: '3',
    name: '商机',
    component: 'Business',
  },
  {
    key: '4',
    name: 'Tips',
    component: 'Tips',
  },
  {
    key: '5',
    name: '文档',
    component: 'Document',
  },
];
export const actions: any[] = [
  { icon: 'clarity:star-line', text: '156', color: '#018ffb' },
  { icon: 'bx:bxs-like', text: '156', color: '#459ae8' },
  { icon: 'bx:bxs-message-dots', text: '2', color: '#42d27d' },
];
export const articleList = (() => {
  const result: any[] = [];
  for (let i = 0; i < 4; i++) {
    result.push({
      title: 'Vben Admin',
      description: ['Vben', '设计语言', 'Typescript'],
      content: '基于Vue Next, TypeScript, Ant Design实现的一套完整的企业级后台管理系统。',
      time: '2020-11-14 11:20',
    });
  }
  return result;
})();
export const applicationList = (() => {
  const result: any[] = [];
  for (let i = 0; i < 8; i++) {
    result.push({
      title: 'Vben Admin',
      icon: 'emojione-monotone:letter-a',
      color: '#1890ff',
      active: '100',
      new: '1,799',
      download: 'bx:bx-download',
    });
  }
  return result;
})();
export const projectList = (() => {
  const result: any[] = [];
  for (let i = 0; i < 8; i++) {
    result.push({
      color: '#1890ff',
      title: `测试日程${i + 1}`,
      content: `测试时间 ${i + 1} - ${i + 2}`,
    });
  }
  return result;
})();
src/views/customer/components/drawer/index.vue
New file
@@ -0,0 +1,160 @@
<template>
  <div :class="prefixCls">
    <Row :class="`${prefixCls}-top`">
      <Col :span="4" :class="`${prefixCls}-col`">
        <div :class="`${prefixCls}-top__avatar`">
          <img width="50" :src="avatar" />
        </div>
      </Col>
      <Col :span="20" :class="`${prefixCls}-col`">
        <div class="mb-10px">
          <div class="mb-5px">
            Vben
          <Icon
            icon="majesticons:open"
            class="mr-15px cursor-pointer"
            @click=""
            :style="{ color: 'hotpink' }"
            :size="16"
          />
          </div>
          <div>海纳百川,有容乃大</div>
        </div>
        <div class="mb-10px flex">
          <div>
            <span>跟进人:</span>
            <span>111</span>
          </div>
          <div class="ml-10px flex">
            <span>客户评分<BasicHelp :text="['评分规则:1,评分=各维度分数(所打分数*该维度权重)的总和;', '2,如只有首次评分,则以首次评分为准;如有两次评分,则取两次评分的平均值。']"/>:</span>
            <span>10</span>
          </div>
        </div>
        <div class="">
          <template v-for="tag in tags" :key="tag">
            <Tag class="mb-2">
              {{ tag }}
            </Tag>
          </template>
          <TagSelector class="pb-10px inline-block mt-10px"></TagSelector>
        </div>
      </Col>
<!--      <Col :span="8" :class="`${prefixCls}-col`">-->
<!--        <CollapseContainer :class="`${prefixCls}-top__team`" title="团队" :canExpand="false">-->
<!--          <div v-for="(team, index) in teams" :key="index" :class="`${prefixCls}-top__team-item`">-->
<!--            <Icon :icon="team.icon" :color="team.color" />-->
<!--            <span>{{ team.title }}</span>-->
<!--          </div>-->
<!--        </CollapseContainer>-->
<!--      </Col>-->
    </Row>
    <div :class="`${prefixCls}-bottom`">
      <Tabs>
        <template v-for="item in achieveList" :key="item.key">
          <TabPane :tab="item.name">
            <ScrollContainer class="scroll-wrap">
              <component :is="tabs[item.component]" />
            </ScrollContainer>
          </TabPane>
        </template>
      </Tabs>
    </div>
  </div>
</template>
<script lang="ts" setup>
  import Icon from '@/components/Icon/Icon.vue';
  import { Col, Row, Tabs, Tag  } from 'ant-design-vue';
  import { computed } from 'vue';
  import Dynamic from './Dynamic.vue';
  import Detail from './Detail.vue';
  import Business from './Business.vue';
  import Tips from './Tips.vue';
  import Document from './Document.vue';
  import headerImg from '@/assets/images/header.jpg';
  import { useUserStore } from '@/store/modules/user';
  import { achieveList, tags } from './data';
  import {BasicHelp} from "@/components/Basic";
  import ScrollContainer from "@/components/Container/src/ScrollContainer.vue";
  import {TagSelector} from "@/components/TagSelector";
  const userStore = useUserStore();
  const TabPane = Tabs.TabPane;
  const tabs = {
    Detail,
    Dynamic,
    Business,
    Tips,
    Document,
  };
  const prefixCls = 'customer-drawer';
  const avatar = computed(() => userStore.getUserInfo.avatar || headerImg);
</script>
<style lang="less" scoped>
  .customer-drawer {
    &-col:not(:last-child) {
      padding: 0 10px;
      //&:not(:last-child) {
      //  border-right: 1px dashed rgb(206 206 206 / 50%);
      //}
    }
    &-top {
      //margin: 16px 16px 12px;
      //padding: 10px;
      border-radius: 3px;
      background-color: @component-background;
      &__avatar {
        text-align: center;
        img {
          margin: auto;
          border-radius: 50%;
        }
        span {
          display: block;
          font-size: 20px;
          font-weight: 500;
        }
        div {
          margin-top: 3px;
          font-size: 12px;
        }
      }
      &__detail {
        margin-top: 15px;
        padding-left: 20px;
      }
      &__team {
        &-item {
          display: inline-block;
          padding: 4px 24px;
        }
        span {
          margin-left: 3px;
        }
      }
    }
    &-bottom {
      //margin: 0 16px 16px;
      //padding: 10px;
      border-radius: 3px;
      background-color: @component-background;
    }
    .scroll-wrap{
      height: calc(100vh - 290px);
    }
  }
</style>
src/views/customer/components/drawer/scheduleCommentFormData.tsx
New file
@@ -0,0 +1,163 @@
import dayjs from 'dayjs';
import type {Dayjs} from 'dayjs';
import {treeOptionsListApi} from "@/api/demo/tree";
import {ApiSelect, FormSchema} from '@/components/Form';
import { SearchOutlined,CloseOutlined,PlusCircleOutlined } from '@ant-design/icons-vue';
import {h} from 'vue';
import {PlusOutlined, MinusOutlined, UploadOutlined } from '@ant-design/icons-vue';
import {
  FormItem,
  FormItemRest,
  Input,
  Select,
  RadioGroup,
  RadioButton,
  Avatar,
  Image,
  message,
  Upload
} from "ant-design-vue";
import {BasicModal,useModal,useModalInner} from '@/components/Modal';
import {optionsListApi} from "@/api/demo/select";
import {uploadApi} from "@/api/sys/upload";
const [registerPersonnelModal, { openModal: openPersonnelModal,setModalProps }] = useModal();
type RangeValue = [Dayjs, Dayjs];
export const schemas: FormSchema[] = [
  {
    field: 'remark',
    component: 'InputTextArea',
    componentProps: {
      placeholder: '请输入内容',
    },
    label: '添加内容',
    colProps: {
      span: 24,
    },
  },
  {
    field: 'field30',
    component: 'ApiSelect',
    label: '通知',
    helpMessage: ['评论通知仅影响谁可以收到评论的通知,', '不影响查看权限,所有日程相关人均可以查看评论内容'],
    // required: true,
    componentProps: {
      // more details see /src/components/Form/src/components/ApiSelect.vue
      api: optionsListApi,
      params: {
        id: 1,
      },
      mode: 'multiple',
      resultField: 'list',
      // use name as label
      labelField: 'name',
      // use id as value
      valueField: 'id',
      // not request untill to select
      immediate: true,
      onChange: (e, v) => {
        console.log('ApiSelect====>:', e, v);
      },
      // atfer request callback
      onOptionsChange: (options) => {
        // console.log('get options', options.length, options);
      },
    },
    colProps: {
      span: 24,
    },
    // defaultValue: '0',
  },
  {
    field: 'remark_image',
    component: 'ImageUpload',
    label: '评论图片',
    subLabel: '(单张图片最大3M,最多上传5张图片)',
    // required: true,
    defaultValue: [
      'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
    ],
    componentProps: {
      api: uploadApi,
      accept: ['png', 'jpeg', 'jpg'],
      helpText: '单张图片最大3M,最多上传5张图片',
      maxSize: 3, //单个文件最大体积,单位 M
      maxNumber: 5,
    },
    // rules: [
    //   {
    //     required: true,
    //     trigger: 'change',
    //     validator(_, value) {
    //       if (isArray(value) && value.length > 0) {
    //         return Promise.resolve();
    //       } else {
    //         return Promise.reject('请选择上传图片');
    //       }
    //     },
    //   },
    // ],
  },
  // {
  //   field: 'remark_file',
  //   component: 'Upload',
  //   label: '备注附件',
  //   colProps: {
  //     span: 24,
  //   },
  //   rules: [{ message: '请选择上传文件' }],
  //   componentProps: {
  //     api: uploadApi,
  //   },
  // },
  {
    field: 'comment_file',
    component: 'Input',
    // componentProps: {
    //   style: {lineHeight: '0'},
    // },
    // label: 'renderColContent渲染',
    /**!!!renderColContent 没有FormItem 包裹, 若想要 Form 提交需要带上数据须 <FormItem name={}></FormItem> 包裹: 示例如下*/
    renderColContent({model, field}, {disabled}) {
      return (
        <FormItem>
          <Upload>
            <a-button type="primary">
              <UploadOutlined></UploadOutlined>
              上传附件
            </a-button>
          </Upload>
        </FormItem>
      );
    },
    colProps: {
      span: 24,
      style: {
        lineHeight: '0',
      },
    },
  },
  // {
  //   field: 'field33',
  //   component: 'ApiTreeSelect',
  //   label: '远程下拉树',
  //   helpMessage: ['ApiTreeSelect组件', '使用接口提供的数据生成选项'],
  //   required: true,
  //   componentProps: {
  //     api: treeOptionsListApi,
  //     treeCheckable: true,
  //     resultField: 'list',
  //     onChange: (e, v) => {
  //       console.log('ApiTreeSelect====>:', e, v);
  //     },
  //   },
  //   colProps: {
  //     span: 14,
  //   },
  // },
];
src/views/customer/components/drawerContacterFormData.tsx
New file
@@ -0,0 +1,699 @@
// import {FormSchema} from '@/components/Table';
import {treeOptionsListApi} from "@/api/demo/tree";
import {areaRecord} from "@/api/demo/cascader";
import dayjs from "dayjs";
import {optionsListApi} from "@/api/demo/select";
import {Input, FormItem, FormItemRest, Select} from "ant-design-vue";
import { ApiSelect, FormSchema } from '@/components/Form';
import companyType from './drawer-form/companyType';
import annualPurchaseAmount from './drawer-form/annualPurchaseAmount';
import timeZone from './drawer-form/timeZone';
import {uploadApi} from "@/api/sys/upload";
const custom_typeKey2typeValueRules = (model) => {
  return [
    {
      // required: true,
      // trigger: 'blur',
      validator: async () => {
        Logger.log('custom_typeKey2typeValueRules', model);
        if (!model.telephoneAreaCode) return Promise.reject('请选择电话区号');
        if (!model.telephoneNumber) return Promise.reject('请输入电话号码');
        Promise.resolve();
      },
    },
  ];
};
const add = (model) => {
  console.log('add-',model);
  Logger.log('add=', model);
};
export const schemas: FormSchema[] = [
  {
    field: 'contactName',
    component: 'Input',
    label: '联系人名称',
    colProps: {
      span: 24,
    },
    rules: [{required: true, trigger: 'blur'}],
  },
  {
    field: 'email',
    component: 'Input',
    label: '邮箱',
    colProps: {
      span: 24,
    },
  },
  {
    field: 'socialPlatform',
    fields: ['socialAccount','socialCount'],
    component: 'Input',
    // label: 'renderColContent渲染',
    /**!!!renderColContent 没有FormItem 包裹, 若想要 Form 提交需要带上数据须 <FormItem name={}></FormItem> 包裹: 示例如下*/
    renderColContent({model, field}, {disabled}) {
      return (
        <FormItem
          name="social"
          label="社交平台"
          // rules={[{ trigger: 'blur',required: true, }]}
          // rules={custom_typeKey2typeValueRules(model)}
        >
          <Input.Group compact>
            {/*<Select*/}
            {/*  allowClear*/}
            {/*  disabled={disabled}*/}
            {/*  style="width: 120px"*/}
            {/*  v-model:value={model[field]}*/}
            {/*>*/}
            {/*  <Select.Option value="公司名称">公司名称</Select.Option>*/}
            {/*  <Select.Option value="产品名称">产品名称</Select.Option>*/}
            {/*</Select>*/}
            <ApiSelect
              style="width: 120px"
              v-model:api={optionsListApi}
              placeholder="社交平台"
              showSearch
              v-model:value={model['socialPlatform']}
              optionFilterProp="label"
              resultField="list"
              labelField="name"
              valueField="id"
            />
            <FormItemRest>
              <Input
                style="width: calc(100% - 120px); margin-left: -1px;"
                placeholder="社交账号"
                v-model:value={model['socialAccount']}
                disabled={disabled}
                // rules={[{ type: 'number',trigger: 'blur',required: true, }]}
                // rules={custom_typeKey2typeValueRules(model)}
              />
            </FormItemRest>
            {/*<div>*/}
            {/*  <a-button shape="circle" onClick={add(model)}>+</a-button>*/}
            {/*  /!*<a-button shape="circle" v-if={Number(model['socialCount']) > 0}>-</a-button>*!/*/}
            {/*  /!*<a-button shape="circle" v-if={Number(model['socialCount']) === 0} onClick={add}>+</a-button>*!/*/}
            {/*  /!*<a-button shape="circle" v-if={Number(model['socialCount']) > 0} onClick={() => del(field)}>-</a-button>*!/*/}
            {/*</div>*/}
          </Input.Group>
        </FormItem>
      );
    },
    colProps: {
      span: 18,
    },
    dynamicDisabled: ({values}) => {
      return !!values.field_disabled;
    },
  },
  {
    field: '0',
    label: ' ',
    component: 'Input',
    render({model, field}, {disabled}) {
      return (
        <div>
          <a-button shape="circle" onClick={add(model)}>+</a-button>
          {/*<a-button shape="circle" v-if={Number(model['socialCount']) > 0}>-</a-button>*/}
          {/*<a-button shape="circle" v-if={Number(model['socialCount']) === 0} onClick={add}>+</a-button>*/}
          {/*<a-button shape="circle" v-if={Number(model['socialCount']) > 0} onClick={() => del(field)}>-</a-button>*/}
        </div>
      );
    },
    colProps: {
      span: 6,
    },
  },
  {
    field: 'telephoneAreaCode',
    fields: ['telephoneNumber'],
    component: 'Input',
    // label: 'renderColContent渲染',
    /**!!!renderColContent 没有FormItem 包裹, 若想要 Form 提交需要带上数据须 <FormItem name={}></FormItem> 包裹: 示例如下*/
    renderColContent({model, field}, {disabled}) {
      return (
        <FormItem
          name="telephone"
          label="联系电话"
          // rules={[{ trigger: 'blur',required: true, }]}
          // rules={custom_typeKey2typeValueRules(model)}
        >
          <Input.Group compact>
            <ApiSelect
              style="width: 120px"
              v-model:api={optionsListApi}
              placeholder="电话区号"
              showSearch
              v-model:value={model['telephoneAreaCode']}
              optionFilterProp="label"
              resultField="list"
              labelField="name"
              valueField="id"
              // rules={[{ trigger: 'blur',required: true, }]}
            />
            <FormItemRest>
              <Input
                style="width: calc(100% - 120px); margin-left: -1px;"
                placeholder="电话号码"
                v-model:value={model['telephoneNumber']}
                disabled={disabled}
                // rules={[{ trigger: 'blur',required: true, }]}
                // rules={custom_typeKey2typeValueRules(model)}
              />
            </FormItemRest>
          </Input.Group>
        </FormItem>
      );
    },
    colProps: {
      span: 16,
    },
    dynamicDisabled: ({values}) => {
      return !!values.field_disabled;
    },
  },
  // {
  //   field: 'typeKey2',
  //   fields: ['typeValue2'],
  //   component: 'Input',
  //   label: '复合',
  //   /**!!!renderColContent 没有FormItem 包裹, 若想要 Form 提交需要带上数据须 <FormItem name={}></FormItem> 包裹: 示例如下*/
  //   render({ model, field }, { disabled }) {
  //     return (
  //       <Input.Group>
  //         <Select
  //           disabled={disabled}
  //           style="width: 120px"
  //           allowClear
  //           v-model:value={model[field]}
  //         >
  //           <Select.Option value="测试类型">测试类型</Select.Option>
  //           <Select.Option value="测试名称">测试名称</Select.Option>
  //         </Select>
  //         <FormItem name="typeValue2"  rules={[{ required: true }]}>
  //           <FormItemRest>
  //             <Input
  //               placeholder="请输入"
  //               v-model:value={model['typeValue2']}
  //               disabled={disabled}
  //             />
  //           </FormItemRest>
  //         </FormItem>
  //       </Input.Group>
  //     );
  //   },
  //   colProps: {
  //     span: 12,
  //   },
  //   dynamicDisabled: ({values}) => {
  //     return !!values.field_disabled;
  //   },
  // },
  {
    field: 'cluesFrom',
    component: 'ApiTreeSelect',
    label: '线索来源',
    componentProps: {
      api: treeOptionsListApi,
      treeCheckable: true,
      treeCheckStrictly: true, // 父子节点是否不再关联
      resultField: 'list',
      onChange: (e, v) => {
        console.log('ApiTreeSelect====>:', e, v);
      },
    },
    colProps: {
      span: 24,
    },
  },
  {
    field: 'abbreviation',
    component: 'Input',
    label: '简称',
    colProps: {
      span: 24,
    },
  },
  {
    field: 'field8',
    component: 'ApiCascader',
    label: '国家地区',
    colProps: {
      span: 16,
    },
    componentProps: {
      api: areaRecord,
      apiParamKey: 'parentCode',
      // dataField: 'data',
      labelField: 'name',
      valueField: 'code',
      initFetchParams: {
        parentCode: '',
      },
      isLeaf: (record) => {
        return !(record.levelType < 3);
      },
      onChange: (e, ...v) => {
        console.log('ApiCascader====>:', e, v);
      },
    },
  },
  // {
  //   field: 'field9',
  //   component: 'ApiCascader',
  //   label: '联动ApiCascader',
  //   required: true,
  //   colProps: {
  //     span: 8,
  //   },
  //   componentProps: {
  //     api: areaRecord,
  //     apiParamKey: 'parentCode',
  //     // dataField: 'data',
  //     labelField: 'name',
  //     valueField: 'code',
  //     initFetchParams: {
  //       parentCode: '',
  //     },
  //     isLeaf: (record) => {
  //       return !(record.levelType < 3);
  //     },
  //     onChange: (e, ...v) => {
  //       console.log('ApiCascader====>:', e, v);
  //     },
  //   },
  // },
  {
    field: 'field30',
    component: 'ApiSelect',
    label: '线索标签',
    // required: true,
    componentProps: {
      // more details see /src/components/Form/src/components/ApiSelect.vue
      api: optionsListApi,
      params: {
        id: 1,
      },
      mode: 'multiple',
      resultField: 'list',
      // use name as label
      labelField: 'name',
      // use id as value
      valueField: 'id',
      // not request untill to select
      immediate: true,
      onChange: (e, v) => {
        console.log('ApiSelect====>:', e, v);
      },
      // atfer request callback
      onOptionsChange: (options) => {
        // console.log('get options', options.length, options);
      },
    },
    colProps: {
      span: 16,
    },
    // defaultValue: '0',
  },
  {
    field: 'companyName',
    component: 'Input',
    label: '公司名称',
    colProps: {
      span: 16,
    },
  },
  {
    field: 'customerCode',
    component: 'Input',
    label: '客户代码(与生产单的客户代码一致)',
    colProps: {
      span: 24,
    },
  },
  {
    field: '',
    defaultValue: '',
    component: 'Input',
    // label: 'renderColContent渲染',
    /**!!!renderColContent 没有FormItem 包裹, 若想要 Form 提交需要带上数据须 <FormItem name={}></FormItem> 包裹: 示例如下*/
    renderColContent({model, field}, {disabled}) {
      return (
        <div class="font-size-16px mb-10px">其他</div>
      );
    },
    colProps: {
      span: 24,
    },
  },
  {
    component: 'Select',
    label: '公司类型',
    field: 'companyType',
    colProps: {span: 16},
    componentProps: {
      options: companyType,
    },
    // defaultValue: '1'
  },
  {
    component: 'Select',
    label: '采购意向',
    field: 'purchasingIntention',
    colProps: {span: 16},
    componentProps: {
      options: [
        {
          label: '未知',
          value: '1',
        },
        {
          label: '低',
          value: '2',
        },
        {
          label: '中',
          value: '3',
        },
        {
          label: '高',
          value: '4',
        },
      ],
    },
  },
  {
    component: 'Select',
    label: '年采购额',
    field: 'annualPurchaseAmount',
    colProps: {span: 16},
    componentProps: {
      options: annualPurchaseAmount,
    },
  },
  {
    component: 'Select',
    label: '时区',
    field: 'timeZone',
    colProps: {span: 16},
    componentProps: {
      options: timeZone,
    },
  },
  {
    field: 'searchKeywords',
    component: 'Input',
    label: '搜索关键词',
    colProps: {
      span: 16,
    },
  },
  {
    field: 'detailedAddress',
    component: 'Input',
    label: '详细地址',
    colProps: {
      span: 24,
    },
  },
  {
    field: 'image',
    component: 'ImageUpload',
    label: '上传图片',
    subLabel: '(单张图片最大3M,最多上传5张图片)',
    // required: true,
    defaultValue: [
      'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
    ],
    componentProps: {
      api: uploadApi,
      accept: ['png', 'jpeg', 'jpg'],
      helpText: '单张图片最大3M,最多上传5张图片',
      maxSize: 3, //单个文件最大体积,单位 M
      maxNumber: 5,
    },
    // rules: [
    //   {
    //     required: true,
    //     trigger: 'change',
    //     validator(_, value) {
    //       if (isArray(value) && value.length > 0) {
    //         return Promise.resolve();
    //       } else {
    //         return Promise.reject('请选择上传图片');
    //       }
    //     },
    //   },
    // ],
  },
  {
    field: 'cluesNotes',
    component: 'InputTextArea',
    label: '线索备注',
    colProps: {
      span: 24,
    },
  },
  {
    component: 'Select',
    label: '规模',
    field: 'numberOfPeople',
    colProps: {span: 16},
    componentProps: {
      options: [
        {
          label: '少于59人',
          value: '1',
        },
        {
          label: '60-149人',
          value: '2',
        },
        {
          label: '150-499人',
          value: '3',
        },
        {
          label: '500-999人',
          value: '4',
        },
        {
          label: '1000-4999人',
          value: '5',
        },
        {
          label: '5000人以上',
          value: '6',
        },
      ],
    },
  },
  {
    field: 'salesman',
    component: 'Input',
    label: '业务员',
    colProps: {
      span: 16,
    },
  },
  {
    component: 'Select',
    label: '访问来源',
    field: 'visitSource',
    colProps: {span: 24},
    componentProps: {
      options: [
        {
          label: '广告投放',
          value: '1',
        },
        {
          label: '自然流量',
          value: '2',
        },
        {
          label: '社交网站',
          value: '3',
        },
        {
          label: '直接访问',
          value: '4',
        },
        {
          label: '邮件',
          value: '5',
        },
        {
          label: '其他',
          value: '6',
        },
      ],
    },
  },
  {
    field: 'field12',
    component: 'ApiCascader',
    label: '主营产品',
    colProps: { span: 24 },
    componentProps: {
      api: areaRecord,
      apiParamKey: 'parentCode',
      showCheckedStrategy: 'SHOW_CHILD',
      labelField: 'name',
      valueField: 'code',
      multiple: true,
      initFetchParams: {
        parentCode: '',
      },
      isLeaf: (record) => {
        return !(record.levelType < 3);
      },
      onChange: (e, ...v) => {
        Logger.log('ApiCascader====> e:', e);
        Logger.log('ApiCascader====> v:', v);
      },
    },
  },
  {
    field: 'ipAddress',
    component: 'ApiSelect',
    label: '访客IP所在地',
    // required: true,
    componentProps: {
      // more details see /src/components/Form/src/components/ApiSelect.vue
      api: optionsListApi,
      params: {
        id: 1,
      },
      resultField: 'list',
      // use name as label
      labelField: 'name',
      // use id as value
      valueField: 'id',
      // not request untill to select
      immediate: true,
      onChange: (e, v) => {
        console.log('ApiSelect====>:', e, v);
      },
      // atfer request callback
      onOptionsChange: (options) => {
        // console.log('get options', options.length, options);
      },
    },
    colProps: {
      span: 20,
    },
    // defaultValue: '0',
  },
  {
    field: 'field1',
    component: 'RadioButtonGroup',
    componentProps: {
      options: [
        {label: 'Apple', value: 'Apple'},
        {label: 'Pear', value: 'Pear'},
        {label: 'Orange', value: 'Orange', disabled: true},
      ],
    },
    label: '',
    colProps: {
      span: 24,
    },
  },
  {
    field: 'field2',
    component: 'Checkbox',
    // suffix:'全天事件',
    subLabel: '',
    colProps: {
      span: 6,
    },
    renderComponentContent: '全天事件',
    // componentProps: {
    //   options: [
    //     {
    //       label: '全天事件',
    //       value: '1',
    //     }
    //   ],
    // },
  },
  {
    field: '[startDate, endDate]',
    label: '起止时间',
    component: 'RangePicker',
    componentProps: {
      format: 'YYYY-MM-DD',
      placeholder: ['开始日期', '结束日期'],
    },
    colProps: {span: 24},
  },
  {
    field: 'field3',
    component: 'DatePicker',
    label: '直到',
    colProps: {
      span: 10,
      offset: 2,
    },
    componentProps: {
      style: {width: '100%'},
      disabledDate: (currentDate) => {
        // Logger.log('currentDate',currentDate)
        // 禁用今天之前的天数
        return currentDate && currentDate < dayjs().subtract(1, 'day');
      }
    },
    required: true,
    ifShow: ({values}) => {
      // Logger.log('vvv',values)
      return ['2', '3', '4'].includes(values.date1);
    },
  },
  {
    field: 'remindTime2',
    component: 'DatePicker',
    label: '',
    colProps: {
      span: 6,
      offset: 2,
    },
    componentProps: {
      // disabledDate:(currentDate)=>{
      //   // Logger.log('currentDate',currentDate)
      //   // 禁用今天之前的天数
      //   return currentDate && currentDate < dayjs().subtract(1, 'day');
      // }
    },
    dynamicRules: ({values}) => {
      return [
        {
          // required: true,
          validator: (_, value) => {
            if (values.remindTime === '6') {
              if (!value) {
                return Promise.reject('不能为空');
              }
            }
            return Promise.resolve();
          },
        },
      ];
    },
    show: ({values}) => {
      // Logger.log('vvv',values)
      return values.remindTime === '6';
    },
  },
];
src/views/customer/components/drawerData.ts
New file
@@ -0,0 +1,123 @@
import { BasicColumn, FormSchema } from '@/components/Table';
import { h } from 'vue';
import { Switch } from 'ant-design-vue';
import { setRoleStatus } from '@/api/demo/system';
import { useMessage } from '@/hooks/web/useMessage';
type CheckedType = boolean | string | number;
export const columns: BasicColumn[] = [
  {
    title: '角色名称',
    dataIndex: 'roleName',
    width: 200,
  },
  {
    title: '角色值',
    dataIndex: 'roleValue',
    width: 180,
  },
  {
    title: '排序',
    dataIndex: 'orderNo',
    width: 50,
  },
  {
    title: '状态',
    dataIndex: 'status',
    width: 120,
    customRender: ({ record }) => {
      if (!Reflect.has(record, 'pendingStatus')) {
        record.pendingStatus = false;
      }
      return h(Switch, {
        checked: record.status === '1',
        checkedChildren: '停用',
        unCheckedChildren: '启用',
        loading: record.pendingStatus,
        onChange(checked: CheckedType) {
          record.pendingStatus = true;
          const newStatus = checked ? '1' : '0';
          const { createMessage } = useMessage();
          setRoleStatus(record.id, newStatus)
            .then(() => {
              record.status = newStatus;
              createMessage.success(`已成功修改角色状态`);
            })
            .catch(() => {
              createMessage.error('修改角色状态失败');
            })
            .finally(() => {
              record.pendingStatus = false;
            });
        },
      });
    },
  },
  {
    title: '创建时间',
    dataIndex: 'createTime',
    width: 180,
  },
  {
    title: '备注',
    dataIndex: 'remark',
  },
];
export const searchFormSchema: FormSchema[] = [
  {
    field: 'roleNme',
    label: '角色名称',
    component: 'Input',
    colProps: { span: 8 },
  },
  {
    field: 'status',
    label: '状态',
    component: 'Select',
    componentProps: {
      options: [
        { label: '启用', value: '1' },
        { label: '停用', value: '0' },
      ],
    },
    colProps: { span: 8 },
  },
];
export const formSchema: FormSchema[] = [
  {
    field: 'roleName',
    label: '角色名称',
    required: true,
    component: 'Input',
  },
  {
    field: 'roleValue',
    label: '角色值',
    required: true,
    component: 'Input',
  },
  {
    field: 'status',
    label: '状态',
    component: 'RadioButtonGroup',
    defaultValue: '0',
    componentProps: {
      options: [
        { label: '启用', value: '1' },
        { label: '停用', value: '0' },
      ],
    },
  },
  {
    label: '备注',
    field: 'remark',
    component: 'InputTextArea',
  },
  {
    label: ' ',
    field: 'menu',
    slot: 'menu',
  },
];
src/views/customer/components/drawerFormData.tsx
New file
@@ -0,0 +1,583 @@
// import {FormSchema} from '@/components/Table';
import {treeOptionsListApi} from "@/api/demo/tree";
import {areaRecord} from "@/api/demo/cascader";
import dayjs from "dayjs";
import {optionsListApi} from "@/api/demo/select";
import {Input, FormItem, FormItemRest} from "ant-design-vue";
import { ApiSelect, FormSchema } from '@/components/Form';
import companyType from './drawer-form/companyType';
import annualPurchaseAmount from './drawer-form/annualPurchaseAmount';
import timeZone from './drawer-form/timeZone';
import {uploadApi} from "@/api/sys/upload";
// const custom_typeKey2typeValueRules = (model) => {
//   return [
//     {
//       // required: true,
//       // trigger: 'blur',
//       validator: async () => {
//         Logger.log('custom_typeKey2typeValueRules', model);
//         if (!model.typeKey) return Promise.reject('请选择类型');
//         if (!model.typeValue) return Promise.reject('请输入数据');
//         Promise.resolve();
//       },
//     },
//   ];
// };
export const schemas: FormSchema[] = [
  {
    field: 'field0',
    component: 'Input',
    label: '公司网址',
    colProps: {
      span: 24,
    },
  },
  {
    field: 'cluesName',
    component: 'Input',
    label: '线索名称',
    colProps: {
      span: 24,
    },
    rules: [{required: true, trigger: 'blur'}],
  },
  {
    field: 'cluesFrom',
    component: 'ApiTreeSelect',
    label: '线索来源',
    componentProps: {
      api: treeOptionsListApi,
      treeCheckable: true,
      treeCheckStrictly: true, // 父子节点是否不再关联
      resultField: 'list',
      onChange: (e, v) => {
        console.log('ApiTreeSelect====>:', e, v);
      },
    },
    colProps: {
      span: 24,
    },
  },
  {
    field: 'abbreviation',
    component: 'Input',
    label: '简称',
    colProps: {
      span: 24,
    },
  },
  {
    field: 'field8',
    component: 'ApiCascader',
    label: '国家地区',
    colProps: {
      span: 16,
    },
    componentProps: {
      api: areaRecord,
      apiParamKey: 'parentCode',
      // dataField: 'data',
      labelField: 'name',
      valueField: 'code',
      initFetchParams: {
        parentCode: '',
      },
      isLeaf: (record) => {
        return !(record.levelType < 3);
      },
      onChange: (e, ...v) => {
        console.log('ApiCascader====>:', e, v);
      },
    },
  },
  // {
  //   field: 'field9',
  //   component: 'ApiCascader',
  //   label: '联动ApiCascader',
  //   required: true,
  //   colProps: {
  //     span: 8,
  //   },
  //   componentProps: {
  //     api: areaRecord,
  //     apiParamKey: 'parentCode',
  //     // dataField: 'data',
  //     labelField: 'name',
  //     valueField: 'code',
  //     initFetchParams: {
  //       parentCode: '',
  //     },
  //     isLeaf: (record) => {
  //       return !(record.levelType < 3);
  //     },
  //     onChange: (e, ...v) => {
  //       console.log('ApiCascader====>:', e, v);
  //     },
  //   },
  // },
  {
    field: 'field30',
    component: 'ApiSelect',
    label: '线索标签',
    // required: true,
    componentProps: {
      // more details see /src/components/Form/src/components/ApiSelect.vue
      api: optionsListApi,
      params: {
        id: 1,
      },
      mode: 'multiple',
      resultField: 'list',
      // use name as label
      labelField: 'name',
      // use id as value
      valueField: 'id',
      // not request untill to select
      immediate: true,
      onChange: (e, v) => {
        console.log('ApiSelect====>:', e, v);
      },
      // atfer request callback
      onOptionsChange: (options) => {
        // console.log('get options', options.length, options);
      },
    },
    colProps: {
      span: 16,
    },
    // defaultValue: '0',
  },
  {
    field: 'companyName',
    component: 'Input',
    label: '公司名称',
    colProps: {
      span: 16,
    },
  },
  {
    field: 'typeKey',
    defaultValue: '公司名称',
    fields: ['typeValue'],
    defaultValueObj: {typeValue: ''},
    component: 'Input',
    // label: 'renderColContent渲染',
    /**!!!renderColContent 没有FormItem 包裹, 若想要 Form 提交需要带上数据须 <FormItem name={}></FormItem> 包裹: 示例如下*/
    renderColContent({model, field}, {disabled}) {
      return (
        <FormItem
          name="typeKey"
          label="座机"
          // rules={[{ trigger: 'blur',required: true, }]}
          // rules={custom_typeKey2typeValueRules(model)}
        >
          <Input.Group compact>
            {/*<Select*/}
            {/*  allowClear*/}
            {/*  disabled={disabled}*/}
            {/*  style="width: 120px"*/}
            {/*  v-model:value={model[field]}*/}
            {/*>*/}
            {/*  <Select.Option value="公司名称">公司名称</Select.Option>*/}
            {/*  <Select.Option value="产品名称">产品名称</Select.Option>*/}
            {/*</Select>*/}
            <ApiSelect
              style="width: 120px"
              v-model:api={optionsListApi}
              showSearch
              v-model:value={model['field']}
              optionFilterProp="label"
              resultField="list"
              labelField="name"
              valueField="id"
            />
            <FormItemRest>
              <Input
                style="width: calc(100% - 120px); margin-left: -1px;"
                placeholder="电话号码"
                v-model:value={model['typeValue']}
                disabled={disabled}
                // rules={[{ type: 'number',trigger: 'blur',required: true, }]}
                // rules={custom_typeKey2typeValueRules(model)}
              />
            </FormItemRest>
          </Input.Group>
        </FormItem>
      );
    },
    colProps: {
      span: 16,
    },
    dynamicDisabled: ({values}) => {
      return !!values.field_disabled;
    },
  },
  {
    field: 'customerCode',
    component: 'Input',
    label: '客户代码(与生产单的客户代码一致)',
    colProps: {
      span: 24,
    },
  },
  {
    field: '',
    defaultValue: '',
    component: 'Input',
    // label: 'renderColContent渲染',
    /**!!!renderColContent 没有FormItem 包裹, 若想要 Form 提交需要带上数据须 <FormItem name={}></FormItem> 包裹: 示例如下*/
    renderColContent({model, field}, {disabled}) {
      return (
        <div class="font-size-16px mb-10px">其他</div>
      );
    },
    colProps: {
      span: 24,
    },
  },
  {
    component: 'Select',
    label: '公司类型',
    field: 'companyType',
    colProps: {span: 16},
    componentProps: {
      options: companyType,
    },
    defaultValue: '1'
  },
  {
    component: 'Select',
    label: '采购意向',
    field: 'purchasingIntention',
    colProps: {span: 16},
    componentProps: {
      options: [
        {
          label: '未知',
          value: '1',
        },
        {
          label: '低',
          value: '2',
        },
        {
          label: '中',
          value: '3',
        },
        {
          label: '高',
          value: '4',
        },
      ],
    },
  },
  {
    component: 'Select',
    label: '年采购额',
    field: 'annualPurchaseAmount',
    colProps: {span: 16},
    componentProps: {
      options: annualPurchaseAmount,
    },
  },
  {
    component: 'Select',
    label: '时区',
    field: 'timeZone',
    colProps: {span: 16},
    componentProps: {
      options: timeZone,
    },
  },
  {
    field: 'searchKeywords',
    component: 'Input',
    label: '搜索关键词',
    colProps: {
      span: 16,
    },
  },
  {
    field: 'detailedAddress',
    component: 'Input',
    label: '详细地址',
    colProps: {
      span: 24,
    },
  },
  {
    field: 'image',
    component: 'ImageUpload',
    label: '上传图片',
    subLabel: '(单张图片最大3M,最多上传5张图片)',
    // required: true,
    defaultValue: [
      'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
    ],
    componentProps: {
      api: uploadApi,
      accept: ['png', 'jpeg', 'jpg'],
      helpText: '单张图片最大3M,最多上传5张图片',
      maxSize: 3, //单个文件最大体积,单位 M
      maxNumber: 5,
    },
    // rules: [
    //   {
    //     required: true,
    //     trigger: 'change',
    //     validator(_, value) {
    //       if (isArray(value) && value.length > 0) {
    //         return Promise.resolve();
    //       } else {
    //         return Promise.reject('请选择上传图片');
    //       }
    //     },
    //   },
    // ],
  },
  {
    field: 'cluesNotes',
    component: 'InputTextArea',
    label: '线索备注',
    colProps: {
      span: 24,
    },
  },
  {
    component: 'Select',
    label: '规模',
    field: 'numberOfPeople',
    colProps: {span: 16},
    componentProps: {
      options: [
        {
          label: '少于59人',
          value: '1',
        },
        {
          label: '60-149人',
          value: '2',
        },
        {
          label: '150-499人',
          value: '3',
        },
        {
          label: '500-999人',
          value: '4',
        },
        {
          label: '1000-4999人',
          value: '5',
        },
        {
          label: '5000人以上',
          value: '6',
        },
      ],
    },
  },
  {
    field: 'salesman',
    component: 'Input',
    label: '业务员',
    colProps: {
      span: 16,
    },
  },
  {
    component: 'Select',
    label: '访问来源',
    field: 'visitSource',
    colProps: {span: 24},
    componentProps: {
      options: [
        {
          label: '广告投放',
          value: '1',
        },
        {
          label: '自然流量',
          value: '2',
        },
        {
          label: '社交网站',
          value: '3',
        },
        {
          label: '直接访问',
          value: '4',
        },
        {
          label: '邮件',
          value: '5',
        },
        {
          label: '其他',
          value: '6',
        },
      ],
    },
  },
  {
    field: 'field12',
    component: 'ApiCascader',
    label: '主营产品',
    colProps: { span: 24 },
    componentProps: {
      api: areaRecord,
      apiParamKey: 'parentCode',
      showCheckedStrategy: 'SHOW_CHILD',
      labelField: 'name',
      valueField: 'code',
      multiple: true,
      initFetchParams: {
        parentCode: '',
      },
      isLeaf: (record) => {
        return !(record.levelType < 3);
      },
      onChange: (e, ...v) => {
        Logger.log('ApiCascader====> e:', e);
        Logger.log('ApiCascader====> v:', v);
      },
    },
  },
  {
    field: 'ipAddress',
    component: 'ApiSelect',
    label: '访客IP所在地',
    // required: true,
    componentProps: {
      // more details see /src/components/Form/src/components/ApiSelect.vue
      api: optionsListApi,
      params: {
        id: 1,
      },
      resultField: 'list',
      // use name as label
      labelField: 'name',
      // use id as value
      valueField: 'id',
      // not request untill to select
      immediate: true,
      onChange: (e, v) => {
        console.log('ApiSelect====>:', e, v);
      },
      // atfer request callback
      onOptionsChange: (options) => {
        // console.log('get options', options.length, options);
      },
    },
    colProps: {
      span: 20,
    },
    // defaultValue: '0',
  },
  {
    field: 'field1',
    component: 'RadioButtonGroup',
    componentProps: {
      options: [
        {label: 'Apple', value: 'Apple'},
        {label: 'Pear', value: 'Pear'},
        {label: 'Orange', value: 'Orange', disabled: true},
      ],
    },
    label: '',
    colProps: {
      span: 24,
    },
  },
  {
    field: 'field2',
    component: 'Checkbox',
    // suffix:'全天事件',
    subLabel: '',
    colProps: {
      span: 6,
    },
    renderComponentContent: '全天事件',
    // componentProps: {
    //   options: [
    //     {
    //       label: '全天事件',
    //       value: '1',
    //     }
    //   ],
    // },
  },
  {
    field: '[startDate, endDate]',
    label: '起止时间',
    component: 'RangePicker',
    componentProps: {
      format: 'YYYY-MM-DD',
      placeholder: ['开始日期', '结束日期'],
    },
    colProps: {span: 24},
  },
  {
    field: 'field3',
    component: 'DatePicker',
    label: '直到',
    colProps: {
      span: 10,
      offset: 2,
    },
    componentProps: {
      style: {width: '100%'},
      disabledDate: (currentDate) => {
        // Logger.log('currentDate',currentDate)
        // 禁用今天之前的天数
        return currentDate && currentDate < dayjs().subtract(1, 'day');
      }
    },
    required: true,
    ifShow: ({values}) => {
      // Logger.log('vvv',values)
      return ['2', '3', '4'].includes(values.date1);
    },
  },
  {
    field: 'remindTime2',
    component: 'DatePicker',
    label: '',
    colProps: {
      span: 6,
      offset: 2,
    },
    componentProps: {
      // disabledDate:(currentDate)=>{
      //   // Logger.log('currentDate',currentDate)
      //   // 禁用今天之前的天数
      //   return currentDate && currentDate < dayjs().subtract(1, 'day');
      // }
    },
    dynamicRules: ({values}) => {
      return [
        {
          // required: true,
          validator: (_, value) => {
            if (values.remindTime === '6') {
              if (!value) {
                return Promise.reject('不能为空');
              }
            }
            return Promise.resolve();
          },
        },
      ];
    },
    show: ({values}) => {
      // Logger.log('vvv',values)
      return values.remindTime === '6';
    },
  },
];
src/views/customer/components/reallocate/index.vue
New file
@@ -0,0 +1,126 @@
<template>
  <BasicModal
    v-bind="$attrs"
    @register="register"
    title="重新分配"
    @visible-change="handleVisibleChange"
    @ok="handleSubmit"
  >
    <div class="mb-10px">
      将线索【{{cluesName}}】
    </div>
    <div :class="'pt-3px pr-3px '+ prefixCls">
      <BasicForm @register="registerForm"></BasicForm>
    </div>
  </BasicModal>
</template>
<script lang="ts" setup>
import {ref, nextTick, h, unref} from 'vue';
import {BasicModal, useModalInner} from '@/components/Modal';
import {BasicForm, useForm} from '@/components/Form';
import {PlusOutlined, MinusOutlined,PaperClipOutlined} from '@ant-design/icons-vue';
import {TreeSelect, Upload} from "ant-design-vue";
import {optionsListApi} from "@/api/demo/select";
const prefixCls = 'new-follow-up';
const props = defineProps({
  userData: {type: Object},
});
const modelRef = ref({});
const [registerForm,{validate:validate}] = useForm({
  schemas:[{
    field: 'cluesStatus',
    component: 'ApiSelect',
    label: '重新分配给',
    colProps: {
      span: 24,
    },
    required: true,
    componentProps: {
      api: optionsListApi,
      params: {
        id: 1,
      },
      resultField: 'list',
      // use name as label
      labelField: 'name',
      // use id as value
      valueField: 'id',
      // not request untill to select
      immediate: true,
      onChange: (e, v) => {
        console.log('ApiSelect====>:', e, v);
      },
      // atfer request callback
      onOptionsChange: (options) => {
        console.log('get options', options.length, options);
      },
    },
    defaultValue: '1',
  },],
  showActionButtonGroup: false,
  layout: 'vertical',
});
const [register,{ setModalProps, closeModal }] = useModalInner((data) => {
  Logger.log('useModalInner data...', data);
  data && onDataReceive(data);
  setModalProps({
    // width: 800,
    minHeight: 100,
    canFullscreen: false,
    destroyOnClose: true,
  });
});
let cluesName = ref('测试1');
function onDataReceive(data) {
  console.log('Data Received', data);
  cluesName.value = data.data.cluesName;
  // 方式1;
  // setFieldsValue({
  //   field2: data.data,
  //   field1: data.info,
  // });
  // // 方式2
  modelRef.value = {field2: data.data, field1: data.info};
  // setProps({
  //   model:{ field2: data.data, field1: data.info }
  // })
}
function handleVisibleChange(v) {
  v && props.userData && nextTick(() => onDataReceive(props.userData));
}
// let currentFollowUpType = ref('1');
// function handleFollowUpTypeChange(value: any) {
//   Logger.log('handleFollowUpTypeChange...', value);
// }
function removeFile(file: any) {
  Logger.log('remove file...', file);
}
async function handleSubmit() {
  try {
    const values = await validate();
    Logger.log('submit values', values);
    setModalProps({ confirmLoading: true });
    // TODO custom api
    // console.log(values);
    closeModal();
  } finally {
    setModalProps({ confirmLoading: false });
  }
}
</script>
<style lang="less">
</style>
src/views/customer/components/tableData.tsx
@@ -1,56 +1,208 @@
import { optionsListApi } from '@/api/demo/select';
import { FormProps, FormSchema, BasicColumn } from '@/components/Table';
import { VxeFormItemProps, VxeGridPropTypes } from '@/components/VxeTable';
import { ref } from 'vue';
import {Input} from 'ant-design-vue';
import { FormProps,  BasicColumn } from '@/components/Table';
import {treeOptionsListApi} from "@/api/demo/tree";
export function getEditCellColumns(): BasicColumn[] {
  return [
    {
      title: '公司名称',
      dataIndex: 'name',
      edit: true,
      title: '线索名称',
      // defaultHidden: true,
      dataIndex: 'cluesName',
      sorter: true,
      editComponentProps: {
        prefix: '$',
      },
      width: 200,
      // filters:{
      //   text: '公司名称',
      //   value: 'name',
      // }
      // filterSearch: true,
      width: 200
    },
    {
      title: '客户标签',
      dataIndex: 'tags',
      edit: true,
      // editComponent: 'ApiSelect',
      // editComponentProps: {
      //   api: optionsListApi,
      //   resultField: 'list',
      //   labelField: 'name',
      //   valueField: 'id',
      // },
      width: 200,
      // editRender: ({ text }) => {
      //   return h(Tag, { percent: Number(text) });
      // },
    },
    {
      title: '主要联系人',
      dataIndex: 'name1',
      edit: true,
      // 默认必填校验
      editRule: true,
      title: '存档时间',
      dataIndex: 'archiveTime',
      sorter: true,
      width: 200,
    },
    {
      title: '最近动态',
      dataIndex: 'name2',
      title: '状态id',
      dataIndex: 'statusId',
      width: 200,
    },
    {
      title: '状态内容',
      dataIndex: 'status',
      width: 200,
    },
    {
      title: '跟进时间',
      dataIndex: 'followUpTime',
      width: 200,
    },
    {
      title: '无效类型',
      dataIndex: 'failType',
      width: 200,
    },
    {
      title: '无效状态',
      dataIndex: 'failStatus',
      width: 200,
    },
    {
      title: '无效状态内容',
      dataIndex: 'failStatusName',
      width: 200,
    },
    {
      title: '无效原因',
      dataIndex: 'failReason',
      width: 200,
    },
    {
      title: '进入私海时间',
      dataIndex: 'privateTime',
      width: 200,
    },
    {
      title: '进入公海时间',
      dataIndex: 'publicTime',
      width: 200,
    },
    {
      title: '是否为公海线索',
      dataIndex: 'isPublic',
      width: 200,
    },
    {
      title: '评分',
      dataIndex: 'star',
      width: 200,
    },
    {
      title: '下次跟进时间',
      dataIndex: 'nextFollowUpTime',
      width: 200,
    },
    {
      title: '是否有重复内容',
      dataIndex: 'duplicateFlag',
      width: 200,
    },
    {
      title: '标签',
      dataIndex: 'tagList',
      width: 200,
    },
    {
      title: '公司网址',
      dataIndex: 'homepage',
      width: 200,
    },
    {
      title: '线索来源',
      dataIndex: 'originList',
      width: 200,
    },
    {
      title: '简称',
      dataIndex: 'shortName',
      width: 200,
    },
    {
      title: '国家',
      dataIndex: 'country',
      width: 200,
    },
    {
      title: '省份',
      dataIndex: 'province',
      width: 200,
    },
    {
      title: '地区',
      dataIndex: 'city',
      width: 200,
    },
    {
      title: '公司名称',
      dataIndex: 'corporate',
      width: 200,
    },
    {
      title: '公司类型',
      dataIndex: 'bizType',
      width: 200,
    },
    {
      title: '地区号',
      dataIndex: 'telAreaCode',
      width: 200,
    },
    {
      title: '座机',
      dataIndex: 'tel',
      width: 200,
    },
    {
      title: '采购意向',
      dataIndex: 'intentionLevel',
      width: 200,
    },
    {
      title: '年采购额',
      dataIndex: 'annualProcurement',
      width: 200,
    },
    {
      title: '时区',
      dataIndex: 'timezone',
      width: 200,
    },
    {
      title: '详细地址',
      dataIndex: 'address',
      width: 200,
    },
    {
      title: '搜索关键字',
      dataIndex: 'adKeyword',
      width: 200,
    },
    {
      title: '图片',
      dataIndex: 'imageList',
      width: 200,
    },
    {
      title: '线索备注',
      dataIndex: 'hdmeno',
      width: 200,
    },
    {
      title: '规模',
      dataIndex: 'scaleId',
      width: 200,
    },
    {
      title: '业务员',
      dataIndex: 'seller',
      width: 200,
    },
    {
      title: '访问来源',
      dataIndex: 'inquiryOrigin',
      width: 200,
    },
    {
      title: '主营产品',
      dataIndex: 'categoryIds',
      width: 200,
    },
    {
      title: '访问IP所在地',
      dataIndex: 'inquiryCountry',
      width: 200,
    },
    {
      title: '是否已关注',
      dataIndex: 'pinFlag',
      width: 200,
    },
    {
      title: '最近联系人',
      dataIndex: 'id',
@@ -97,253 +249,20 @@
export function getBasicColumns(): BasicColumn[] {
  return [
    {
      title: 'ID',
      dataIndex: 'id',
      fixed: 'left',
      width: 200,
    },
    {
      title: '姓名',
      dataIndex: 'name',
      width: 150,
      filters: [
        { text: 'Male', value: 'male' },
        { text: 'Female', value: 'female' },
      ],
    },
    {
      title: '地址',
      dataIndex: 'address',
    },
    {
      title: '编号',
      dataIndex: 'no',
      width: 150,
      sorter: true,
      defaultHidden: true,
    },
    {
      title: '开始时间',
      width: 150,
      sorter: true,
      dataIndex: 'beginTime',
    },
    {
      title: '结束时间',
      width: 150,
      sorter: true,
      dataIndex: 'endTime',
    },
  ];
}
export function getBasicShortColumns(): BasicColumn[] {
  return [
    {
      title: 'ID',
      width: 150,
      dataIndex: 'id',
      sorter: true,
      sortOrder: 'ascend',
    },
    {
      title: '姓名',
      dataIndex: 'name',
      width: 120,
    },
    {
      title: '地址',
      dataIndex: 'address',
    },
    {
      title: '编号',
      dataIndex: 'no',
      width: 80,
    },
  ];
}
export function getMultipleHeaderColumns(): BasicColumn[] {
  const testRef = ref('姓名:');
  return [
    {
      title: 'ID',
      dataIndex: 'id',
      width: 200,
    },
    {
      title: '姓名',
      customHeaderRender() {
        return (
          <Input placeholder="输入值 更新 自定义title" size="small" v-model:value={testRef.value} />
        );
      },
      dataIndex: 'name',
      width: 120,
    },
    {
      title: '地址',
      dataIndex: 'address',
      sorter: true,
      children: [
        {
          title: '编号',
          customHeaderRender(column) {
            // 【自定义渲染的】
            return (
              <div>
                _ <span style="background: #f00; color: #fff;">{testRef.value}</span> _
                {column.customTitle}
              </div>
            );
          },
          dataIndex: 'no',
          width: 120,
          filters: [
            { text: 'Male', value: 'male', children: [] },
            { text: 'Female', value: 'female', children: [] },
          ],
        },
        {
          title: '开始时间',
          dataIndex: 'beginTime',
          width: 120,
        },
        {
          title: '结束时间',
          dataIndex: 'endTime',
          width: 120,
        },
      ],
    },
  ];
}
export function getCustomHeaderColumns(): BasicColumn[] {
  return [
    {
      title: 'ID',
      dataIndex: 'id',
      helpMessage: 'headerHelpMessage方式1',
      width: 200,
    },
    {
      // title: '姓名',
      dataIndex: 'name',
      width: 120,
    },
    {
      // title: '地址',
      dataIndex: 'address',
      width: 120,
      sorter: true,
    },
    {
      title: '编号',
      dataIndex: 'no',
      width: 120,
      filters: [
        { text: 'Male', value: 'male', children: [] },
        { text: 'Female', value: 'female', children: [] },
      ],
    },
    {
      title: '开始时间',
      dataIndex: 'beginTime',
      width: 120,
    },
    {
      title: '结束时间',
      dataIndex: 'endTime',
      width: 120,
    },
  ];
}
const cellContent = (_, index) => ({
  colSpan: index === 9 ? 0 : 1,
});
export function getMergeHeaderColumns(): BasicColumn[] {
  return [
    {
      title: 'ID',
      dataIndex: 'id',
      width: 300,
      customCell: (_, index) => ({
        colSpan: index === 9 ? 6 : 1,
      }),
    },
    {
      title: '姓名',
      dataIndex: 'name',
      width: 300,
      customCell: cellContent,
    },
    {
      title: '地址',
      dataIndex: 'address',
      colSpan: 2,
      width: 120,
      sorter: true,
      customCell: (_, index) => ({
        rowSpan: index === 2 ? 2 : 1,
        colSpan: index === 3 || index === 9 ? 0 : 1,
      }),
    },
    {
      title: '编号',
      dataIndex: 'no',
      colSpan: 0,
      filters: [
        { text: 'Male', value: 'male', children: [] },
        { text: 'Female', value: 'female', children: [] },
      ],
      customCell: cellContent,
    },
    {
      title: '开始时间',
      dataIndex: 'beginTime',
      width: 200,
      customCell: cellContent,
    },
    {
      title: '结束时间',
      dataIndex: 'endTime',
      width: 200,
      customCell: cellContent,
    },
  ];
}
export const getAdvanceSchema = (itemNumber = 6): FormSchema[] => {
  const arr: FormSchema[] = [];
  for (let index = 0; index < itemNumber; index++) {
    arr.push({
      field: `field${index}`,
      label: `字段${index}`,
      component: 'Input',
      colProps: {
        xl: 12,
        xxl: 8,
      },
    });
  }
  return arr;
};
export function getFormConfig(): Partial<FormProps> {
  return {
    labelWidth: 100,
    layout: 'horizontal',
    rowProps: {
      justify: 'end',
    },
    showResetButton: false,
    showSubmitButton: false,
    schemas: [
      ...getAdvanceSchema(5),
      {
        field: `field11`,
        label: ``,
        slot: 'custom',
        label: `搜索`,
        component: 'Input',
        colProps: {
          xl: 12,
          xxl: 8,
@@ -352,206 +271,3 @@
    ],
  };
}
export function getBasicData() {
  return (() => {
    const arr: any = [];
    for (let index = 0; index < 40; index++) {
      arr.push({
        id: `${index}`,
        name: 'John Brown',
        age: `1${index}`,
        no: `${index + 10}`,
        address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park',
        beginTime: new Date().toLocaleString(),
        endTime: new Date().toLocaleString(),
      });
    }
    return arr;
  })();
}
export function getTreeTableData() {
  return (() => {
    const arr: any = [];
    for (let index = 0; index < 40; index++) {
      arr.push({
        id: `${index}`,
        name: 'John Brown',
        age: `1${index}`,
        no: `${index + 10}`,
        address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park',
        beginTime: new Date().toLocaleString(),
        endTime: new Date().toLocaleString(),
        children: [
          {
            id: `l2-${index}-1`,
            name: 'John Brown',
            age: `1`,
            no: `${index + 10}`,
            address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park',
            beginTime: new Date().toLocaleString(),
            endTime: new Date().toLocaleString(),
            children: [
              {
                id: `l3-${index}-1-1`,
                name: 'John Brown',
                age: `11`,
                no: `11`,
                address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park',
                beginTime: new Date().toLocaleString(),
                endTime: new Date().toLocaleString(),
              },
              {
                id: `l3-${index}-1-2`,
                name: 'John Brown',
                age: `12`,
                no: `12`,
                address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park',
                beginTime: new Date().toLocaleString(),
                endTime: new Date().toLocaleString(),
              },
            ],
          },
          {
            id: `l2-${index}-2`,
            name: 'John Brown',
            age: `2`,
            no: `${index + 10}`,
            address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park',
            beginTime: new Date().toLocaleString(),
            endTime: new Date().toLocaleString(),
            children: [
              {
                id: `l3-${index}-2-1`,
                name: 'John Brown',
                age: `21`,
                no: `21`,
                address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park',
                beginTime: new Date().toLocaleString(),
                endTime: new Date().toLocaleString(),
              },
              {
                id: `l3-${index}-2-2`,
                name: 'John Brown',
                age: `22`,
                no: `22`,
                address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park',
                beginTime: new Date().toLocaleString(),
                endTime: new Date().toLocaleString(),
              },
            ],
          },
        ],
      });
    }
    return arr;
  })();
}
export const vxeTableColumns: VxeGridPropTypes.Columns = [
  {
    title: '序号',
    type: 'seq',
    fixed: 'left',
    width: '50',
    align: 'center',
  },
  {
    title: '固定列',
    field: 'name',
    width: 150,
    showOverflow: 'tooltip',
    fixed: 'left',
  },
  {
    title: '自适应列',
    field: 'address',
  },
  {
    title: '自定义列(自定义导出)',
    field: 'no',
    width: 200,
    showOverflow: 'tooltip',
    align: 'center',
    slots: {
      default: ({ row }) => {
        const text = `自定义${row.no}`;
        return [<div class="text-red-500">{text}</div>];
      },
    },
    exportMethod: ({ row }) => {
      return `自定义${row.no}导出`;
    },
  },
  {
    title: '自定义编辑',
    width: 150,
    field: 'name1',
    align: 'center',
    editRender: {
      name: 'AInput',
      placeholder: '请点击输入',
    },
  },
  {
    title: '开始时间',
    width: 150,
    field: 'beginTime',
    showOverflow: 'tooltip',
    align: 'center',
  },
  {
    title: '结束时间',
    width: 150,
    field: 'endTime',
    showOverflow: 'tooltip',
    align: 'center',
  },
  {
    width: 160,
    title: '操作',
    align: 'center',
    slots: { default: 'action' },
    fixed: 'right',
  },
];
export const vxeTableFormSchema: VxeFormItemProps[] = [
  {
    field: 'field0',
    title: 'field0',
    itemRender: {
      name: 'AInput',
    },
    span: 6,
  },
  {
    field: 'field1',
    title: 'field1',
    itemRender: {
      name: 'AApiSelect',
      props: {
        api: optionsListApi,
        resultField: 'list',
        labelField: 'name',
        valueField: 'id',
      },
    },
    span: 6,
  },
  {
    span: 12,
    align: 'right',
    className: '!pr-0',
    itemRender: {
      name: 'AButtonGroup',
      children: [
        {
          props: { type: 'primary', content: '查询', htmlType: 'submit' },
          attrs: { class: 'mr-2' },
        },
        { props: { type: 'default', htmlType: 'reset', content: '重置' } },
      ],
    },
  },
];
src/views/customer/index.vue
@@ -1,33 +1,11 @@
<template>
  <PageWrapper title="客户列表" :contentStyle="contentStyle" :class="`${prefixCls}`" dense
               contentFullHeight fixedHeight>
  <PageWrapper title="客户" :contentStyle="contentStyle" :class="`${prefixCls}`" dense contentFullHeight fixedHeight>
<!--    <template #subTitle></template>-->
<!--    <template #headerContent>-->
<!--      <a-button type="primary" @click="$emit('add')">新增</a-button>-->
<!--    </template>-->
    <template #extra>
      <PopConfirmButton>按钮文本</PopConfirmButton>
      <BasicHelp text="提示"/>
      <BasicHelp :text="['提示1', '提示2']"/>
      <BasicTitle :helpMessage="['提示1', '提示2']">标题</BasicTitle>
      <a-button shape="round" key="1" type="primary">Primary</a-button>
      <Dropdown
        :dropMenuList="getDropMenuList"
        :trigger="['click']"
        placement="bottomLeft"
        overlayClassName="multiple-tabs__dropdown"
      >
        <Icon icon="ion:chevron-down"/>
      </Dropdown>
      <!--      <a-dropdown>-->
      <!--        <template #overlay>-->
      <!--          <a-menu @click="handleMenuClick">-->
      <!--            <a-menu-item key="1">1st item</a-menu-item>-->
      <!--            <a-menu-item key="2">2nd item</a-menu-item>-->
      <!--            <a-menu-item key="3">3rd item</a-menu-item>-->
      <!--          </a-menu>-->
      <!--        </template>-->
      <!--        <a-button>-->
      <!--          Actions-->
      <!--          <DownOutlined />-->
      <!--        </a-button>-->
      <!--      </a-dropdown>-->
      <a-button shape="round" key="1" type="primary" @click="openNewCustomer">新建客户</a-button>
    </template>
    <!--    <template #headerContent>left</template>-->
    <Splitpanes class="default-theme" :push-other-panes="false" style="height: 100%">
@@ -36,69 +14,153 @@
      </Pane>
      <Pane min-size="50" size="88">
        <ScrollContainer class="p-8">
<!--          <div><a-button class="mr-2" type="primary" shape="round" @click="openModal1"> 新建日程 </a-button></div>-->
<!--          <div><a-button class="mr-2" type="primary" shape="round" @click="openModal2"> 选择人员 </a-button></div>-->
          <Table></Table>
        </ScrollContainer>
      </Pane>
    </Splitpanes>
    <DrawerForm @register="registerDrawer" @success="handleSuccess"></DrawerForm>
    <NewFollowUp @register="registerNewFollowUp" />
    <NewSchedule @register="registerNewSchedule" />
    <PersonnelModal @register="registerPersonnelModal" />
    <ChangeStatusModal @register="registerChangeStatusModal" />
    <ReallocateModal @register="registerReallocateModal" />
  </PageWrapper>
</template>
<script lang="ts" setup>
import {PageWrapper} from '@/components/Page';
import {computed, onMounted } from 'vue';
import {computed, onMounted,onUnmounted} from 'vue';
import {useDesign} from "@/hooks/web/useDesign";
import {Splitpanes, Pane} from 'splitpanes';
import 'splitpanes/dist/splitpanes.css';
import LeftNav from './components/LeftNav.vue';
import Table from './components/Table.vue';
import DrawerForm from './components/DrawerForm.vue';
import ScrollContainer from "@/components/Container/src/ScrollContainer.vue";
import {BasicHelp, BasicTitle} from '@/components/Basic';
import {PopConfirmButton} from '@/components/Button';
const [registerDrawer, { openDrawer }] = useDrawer();
function handleSuccess() {
  Logger.log('提交drawer成功')
}
// import {CollapseContainer} from '@/components/Container';
// import {Menu, MenuProps} from 'ant-design-vue';
// import { DownOutlined } from '@ant-design/icons-vue';
import {Dropdown, type DropMenu} from '@/components/Dropdown';
import Icon from "@/components/Icon/Icon.vue";
import { BasicArrow } from '@/components/Basic';
import {useDrawer} from "@/components/Drawer";
import {useModal} from "@/components/Modal";
import {NewFollowUp} from "@/components/NewFollowUp";
import {NewSchedule} from "@/components/NewSchedule";
import PersonnelModal from "@/components/NewSchedule/src/PersonnelModal.vue";
import ChangeStatusModal from "./components/change-status/index.vue";
import ReallocateModal from "./components/reallocate/index.vue";
const [registerNewFollowUp,{ openModal:openFollowUpModal,setModalProps:setFollowUpModalProps }] = useModal();
const [registerNewSchedule, { openModal:openScheduleModal,setModalProps:setScheduleModalProps }] = useModal();
const [registerPersonnelModal, { openModal: openPersonnelModal }] = useModal();
const [registerChangeStatusModal, { openModal: openChangeStatusModal }] = useModal();
const [registerReallocateModal, { openModal: openReallocateModal }] = useModal();
import EventBus from "@/utils/eventBus";
function openNewCustomer() {
  // eventMitter.emit('openNewCustomer');
  openDrawer(true, {
    isUpdate: false,
  });
}
function openModal1() {
  // openModal(true, {
  //   // data: 'content2',
  //   // info: 'Info',
  // });
  EventBus.emit('openScheduleModal',{
    title:'新建任务12',
    content:'新建任务内容12'
  });
}
function openModal2() {
  openPersonnelModal(true, {
    // data: 'content2',
    // info: 'Info',
  });
}
onMounted(() => {
  Logger.log('Hello,  客户页');
  EventBus.on('openScheduleModal', (data) => {
    Logger.log('监听openScheduleModal',data);
    setScheduleModalProps({
      zIndex: 1001,
    })
    openScheduleModal(true, {
      // data: 'content2',
      // info: 'Info',
    });
  });
  EventBus.on('openFollowUpModal', (data) => {
    Logger.log('监听openFollowUpModal',data);
    setFollowUpModalProps({
      zIndex: 1001,
    })
    openFollowUpModal(true, {
      // data: 'content2',
      // info: 'Info',
    });
  });
  EventBus.on('openChangeStatusModal', (data) => {
    Logger.log('监听openChangeStatusModal',data);
    openChangeStatusModal(true, {
      data,
    });
  });
  EventBus.on('openReallocateModal', (data) => {
    Logger.log('监听openReallocateModal',data);
    openReallocateModal(true, {
      data,
    });
  });
});
const {prefixCls} = useDesign('customer');
onUnmounted(() => {
  Logger.log('Goodbye, 线索页');
  EventBus.off('openScheduleModal',() => {
    Logger.log('取消监听openScheduleModal');
  });
  EventBus.off('openFollowUpModal',() => {
    Logger.log('取消监听openFollowUpModal');
  });
  EventBus.off('openChangeStatusModal',() => {
    Logger.log('取消监听openChangeStatusModal');
  });
});
const {prefixCls} = useDesign('clues');
// const AMenu = Menu;
// const AMenuItem = Menu.Item;
const contentStyle = {
  borderTop: '1px solid #ddd',
};
// const openChange = (openKeys: string[]) => {
//   console.log(666,openKeys);
// };
// const handleMenuClick: MenuProps['onClick'] = e => {
//   console.log('click', e);
// };
const getDropMenuList = computed(() => {
  const dropMenuList: DropMenu[] = [
    {
      text: '导入客户',
      event: 'refresh',
      onClick: (e) => {
        console.log('click');
      },
    },
    {
      text: '导入阿里客户',
      event: 'drop',
      onClick: (e) => {
        console.log('click2');
      },
    },
  ];
  return dropMenuList;
});
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-customer';
@prefix-cls: ~'@{namespace}-clues';
.@{prefix-cls} {
  .splitpanes__pane {
    display: flex;
@@ -106,5 +168,9 @@
    align-items: center;
    background-color: var(--component-background-color)
  }
 :deep(.ant-page-header) {
    padding: 15px 24px;
  }
}
</style>