Procházet zdrojové kódy

feat: 线索公海页面新增 线索列表相关开发完善

liyang před 4 měsíci
rodič
revize
da2e878d68

+ 134 - 0
src/views/market-manage/external-manage/clue-management/clues-tothe-high-seas/components/clueAssignment.vue

@@ -0,0 +1,134 @@
+<template>
+  <bs-modal :visible="modalOptions.visible" :width="modalOptions.width" :title="modalOptions.title"
+            :modal-extra-props="modalOptions.modalExtraProps" @cancel="handleCancel" @ok="handleOk">
+    <a-form :rules="rules" :model="formData">
+      <a-row :gutter="16">
+        <a-col :span="24">
+          <a-form-item label="线索名称" name="marketer">
+            <a-input
+                v-model:value="formData.clueName"
+                placeholder="自动生成的项目名称信息展示位置"
+            />
+          </a-form-item>
+        </a-col>
+      </a-row>
+      <a-row :gutter="16">
+        <a-col :span="24">
+          <a-form-item label="分派营销经理" name="sectionName">
+            <a-select
+                v-model:value="formData.sectionName"
+                placeholder="请选择"
+                :options="serviceProviderOptions"
+            />
+          </a-form-item>
+        </a-col>
+      </a-row>
+      <a-row :gutter="16">
+        <a-col :span="24">
+          <a-form-item label="营销经理团队" name="notes">
+            <a-input
+                disabled
+                v-model:value="formData.notes"
+                placeholder="自动带入"
+            />
+          </a-form-item>
+        </a-col>
+      </a-row>
+      <a-row :gutter="16">
+        <a-col :span="24">
+          <a-form-item label="营销经理区域" name="notesA">
+            <a-input
+                disabled
+                v-model:value="formData.notes"
+                placeholder="自动带入"
+            />
+          </a-form-item>
+        </a-col>
+      </a-row>
+    </a-form>
+  </bs-modal>
+</template>
+<script setup lang="jsx">
+import {ref, watch} from 'vue';
+import BsModal, {useBsModal} from '/@/components/BsUi/Modal/index.js';
+import {addSectionSreation, getPersonalOriganization} from "/@/api/market-manage/activity-manage";
+import {isEmpty} from 'lodash';
+
+const emit = defineEmits(['fetchTableData'])
+const props = defineProps(['title'])
+const formData = ref({
+  id: "",
+  sectionName: "",
+  budgetAmount: 0,
+  tenderDate: "",
+  competitor: "",
+  notes: ""
+})
+const rules = {
+  sectionName: [{required: true, message: '请选择分派营销经理'}],
+  marketer: [{required: true, message: '请输入线索名称'}],
+  notes: [{required: true, message: '请输入营销经理团队'}],
+  notesA: [{required: true, message: '请输入营销经理区域'}]
+}
+watch(() => formData.value.marketer, (value) => {
+  getPersonalOriganization(value.id).then((res) => {
+    formData.value.marketingTeam = res
+  })
+  console.log(value, formData.value);
+})
+const {
+  modalOptions,
+  getModalPropsValue: getMVal,
+  setModalPropsValue: setMVal,
+} = useBsModal({
+  modalOptions: {
+    width: '30%',
+    title: '',
+    visible: false,
+    modalExtraProps: {
+      destroyOnClose: true,
+      okButtonProps: {
+        loading: false
+      }
+    },
+  },
+});
+const serviceProviderOptions = ref([
+  {value: 'serviceProvider', label: '服务商提供'},
+  {value: 'marketActivity', label: '市场活动'},
+  {value: 'referral', label: '转介绍'}
+]);
+const showModal = (title, rowData) => {
+  // setMVal("width", "100%")
+  if (!isEmpty(rowData)) {
+    formData.value = rowData
+  }
+  setMVal('title', title);
+  setMVal('visible', true);
+};
+
+const handleCancel = () => {
+  setMVal('visible', false);
+};
+
+const handleOk = () => {
+  formData.value.competitor = formData.value.competitor?.id || null;
+  setMVal("modalExtraProps.okButtonProps.loading", true)
+  addSectionSreation(formData.value).then((res) => {
+    setMVal("modalExtraProps.okButtonProps.loading", false)
+    setMVal('visible', false);
+    emit('fetchTableData')
+  })
+};
+
+defineExpose({
+  showModal,
+});
+</script>
+
+<style lang="scss" scoped>
+.content {
+  width: 100%;
+  height: 900px;
+}
+</style>

+ 233 - 0
src/views/market-manage/external-manage/clue-management/clues-tothe-high-seas/index.vue

@@ -0,0 +1,233 @@
+<template>
+  <div class="table-demo">
+    <bs-table v-bind="tableOptions">
+    </bs-table>
+    <add-or-edit-drawer ref="addOrEditDrawerRef"/>
+  </div>
+  <clueAssignment ref="clueModalRef" />
+</template>
+
+<script setup lang="jsx">
+import BsTable, {useBsTable} from '/@/components/BsUi/Table/index.js';
+import {onMounted, ref} from 'vue';
+import AddOrEditDrawer from '/@/views/table-demo/components/AddOrEditDrawer.vue';
+import {useRouter} from 'vue-router';
+import clueAssignment from './components/clueAssignment.vue'
+import {DISPLAY_STATE} from "/@/components/BsUi/constant.js";
+
+const addOrEditDrawerRef = ref(null);
+const router = useRouter();
+onMounted(() => {
+  refreshTable();
+});
+const clueModalRef = ref(null)
+function openModal(title, data) {
+  clueModalRef.value.showModal(title, data)
+}
+const {
+      tableOptions,
+      setTablePropsValue: setValue,
+      getTablePropsValue: getValue,
+      refreshTable
+    } = useBsTable({
+      tableOptions: {
+        url: '/supports/project/queryPage',
+        gridOptions: {
+          // data:[],
+          loading: false,
+          columns: [
+            {
+              field: 'projectName',
+              title: '项目名称',
+              width: 150
+            },
+            {
+              field: 'projectId',
+              title: '项目ID',
+              width: 150
+            },
+            {
+              field: 'projectAddress',
+              title: '项目地址',
+              width: 150
+            },
+            {
+              field: 'partAUnit',
+              title: '甲方',
+              width: 150
+            },
+            {
+              field: 'tenderDate',
+              title: '预计招标日期',
+              width: 150
+            },
+            {
+              field: 'projectBudgets',
+              title: '概算金额(万)',
+              width: 150
+            },
+            {
+              field: 'factory',
+              title: '分厂/几期/机组',
+              width: 150
+            },
+            {
+              field: 'projectSource',
+              title: '项目来源',
+              width: 150
+            },
+            {
+              field: 'projectType',
+              title: '项目类型',
+              width: 150,
+              slots: {
+                default({ row, column }) {
+                  return <span>{row?.projectType?.[0].valueName}</span>;
+                },
+              },
+            },
+            {
+              field: 'engineeringAttribute',
+              title: '工程属性',
+              width: 150,
+              slots: {
+                default({ row, column }) {
+                  return <span>{row?.engineeringAttribute?.[0].valueName}</span>;
+                },
+              },
+            },
+            {
+              field: 'businessType',
+              title: '业务类型',
+              width: 150
+            },
+            {
+              field: 'belongMarketer',
+              title: '归属营销经理',
+              width: 150
+            },
+            {
+              field: 'belongMarketingDepartment',
+              title: '归属营销部门',
+              width: 150
+            },
+            {
+              field: 'name',
+              title: '创建人',
+              width: 150
+            },
+            {
+              field: 'name',
+              title: '创建时间',
+              width: 150
+            },
+            {
+              field: 'opt',
+              title: '操作',
+              width: '120px',
+              fixed: 'right',
+              align: 'center',
+            },
+            {
+              // fixed: 'right',
+              cellRender: {
+                name: 'CellOption',
+                extraProps: {
+                  buttons: [
+                    {
+                      title: '查看详情',
+                      code: 'view',
+                      display: ({row}) => {
+                        return DISPLAY_STATE.VISIBLE;
+                      },
+                      disabled({row}) {
+                        return false;
+                      },
+                      onClick({row}) {
+                        goDetailPage(row)
+                      },
+                      extraProps: {},
+                    },
+                    {
+                      title: '线索分配',
+                      display: ({row}) => {
+                        return DISPLAY_STATE.VISIBLE;
+                      },
+                      disabled({row}) {
+                        return false;
+                      },
+                      onClick({row}) {
+                        openModal('线索分派',row)
+                      },
+                      extraProps: {},
+                    },
+                  ],
+                },
+              },
+            }
+          ],
+        },
+        searchConfig: {
+          enabled: true,
+          fieldSpan:
+              4,
+          fields:
+              [
+                {
+                  field: 'projectName',
+                  component: 'a-input',
+                  componentProps: {
+                    placeholder: '请输入项目名称',
+                  },
+                },
+                {
+                  field: 'projectId',
+                  component: 'a-input',
+                  componentProps: {
+                    placeholder: '请输入项目ID',
+                  },
+                },
+                {
+                  field: 'projectAddress',
+                  component: 'a-select',
+                  componentProps: {
+                    placeholder: '请选择地址',
+                  },
+                },
+                {
+                  field: 'status',
+                  component: 'a-select',
+                  componentProps: {
+                    placeholder: '请选择项目状态',
+                  },
+                },
+              ],
+        }
+        ,
+        pagerConfig: {
+          enabled: true,
+          pageSize:
+              10,
+          pageNum:
+              1,
+          total:
+              0,
+        }
+        ,
+      },
+    })
+;
+const goDetailPage = (record) => {
+  router.push({
+    path: "/market-manage/external-manage/clue-management/view-details",
+    query: {
+      id: record.id
+    }
+  });
+}
+</script>
+
+<style scoped lang="scss">
+.table-demo {
+}
+</style>

+ 30 - 248
src/views/market-manage/external-manage/clue-management/view-details/components/CooperativeProject.vue

@@ -1,253 +1,35 @@
 <template>
-  <a-layout class="management-container">
-    <!-- 左侧单位树形结构 -->
-<!--    <a-layout-sider width="300" class="left-sider">-->
-<!--      <div class="decision-framework">-->
-<!--        <h3 class="section-title">决策框架构</h3>-->
-
-<!--        &lt;!&ndash; 业主单位 &ndash;&gt;-->
-<!--        <div class="company-section">-->
-<!--          <h4 class="company-title">业主单位</h4>-->
-<!--          <div class="group-section">-->
-<!--            <h5 class="group-title">集团公司名称信息展示位置</h5>-->
-<!--            <a-list :data-source="ownerSubsidiaries" :bordered="false" size="small">-->
-<!--              <template #renderItem="{ item }">-->
-<!--                <a-list-item class="subsidiary-item">{{ item }}</a-list-item>-->
-<!--              </template>-->
-<!--            </a-list>-->
-<!--          </div>-->
-<!--        </div>-->
-
-<!--        &lt;!&ndash; 总承包单位 &ndash;&gt;-->
-<!--        <div class="company-section">-->
-<!--          <h4 class="company-title">总承包单位</h4>-->
-<!--          <div class="group-section">-->
-<!--            <h5 class="group-title">集团公司名称信息展示位置</h5>-->
-<!--            <a-list :data-source="contractorSubsidiaries" :bordered="false" size="small">-->
-<!--              <template #renderItem="{ item }">-->
-<!--                <a-list-item class="subsidiary-item">{{ item }}</a-list-item>-->
-<!--              </template>-->
-<!--            </a-list>-->
-<!--          </div>-->
-<!--        </div>-->
-<!--      </div>-->
-<!--    </a-layout-sider>-->
-
-    <!-- 右侧人员表格 -->
-    <a-layout-content class="right-content">
-      <div class="association-chain">
-        <h3 class="section-title">关联链列表</h3>
-        <bs-table v-bind="tableOptions">
-          <template #toolbarLeft>
-            <a-space>
-              <span>累计留资客户 283人</span>
-            </a-space>
-          </template>
-        </bs-table>
-<!--        <a-table-->
-<!--            :columns="columns"-->
-<!--            :data-source="dataSource"-->
-<!--            :pagination="false"-->
-<!--            size="middle"-->
-<!--            bordered-->
-<!--        >-->
-<!--          <template #bodyCell="{ column, record }">-->
-<!--            <template v-if="column.key === 'operation'">-->
-<!--              <a-space>-->
-<!--                <a-button type="link" size="small" @click="handleEdit(record)">编辑</a-button>-->
-<!--                <a-button type="link" size="small" danger @click="handleDelete(record)">删除</a-button>-->
-<!--              </a-space>-->
-<!--            </template>-->
-<!--          </template>-->
-<!--        </a-table>-->
-      </div>
-    </a-layout-content>
-  </a-layout>
+  <div class="link-company">
+    <div class="link-flow">
+      <bs-catalog title="决策链架构">
+        <template #content>
+          <org-struct-chart />
+        </template>
+      </bs-catalog>
+    </div>
+    <div class="link-list">
+      <link-list />
+    </div>
+  </div>
 </template>
-
 <script setup>
-import { ref } from 'vue';
-import {
-  Layout,
-  LayoutSider,
-  LayoutContent,
-  List,
-  ListItem,
-  Table,
-  Button,
-  Space,
-  message
-} from 'ant-design-vue';
-import BsTable, {useBsTable} from "/@/components/BsUi/Table/index.js";
-
-const ownerSubsidiaries = [
-  '下属公司名称信息展示',
-  '下属公司名称信息展示位置',
-  '下属公司名称信息展示位置',
-  '下属公司名称信息展示位置',
-  '下属公司名称信息展示位置',
-  '下属公司名称信息展示位置',
-  '下属公司名称信息展示位置',
-  '下属公司名称信息展示位置'
-];
-
-const contractorSubsidiaries = [
-  '下属公司名称信息展示位置',
-  '下属公司名称信息展示位置'
-];
-const {
-  tableOptions,
-  setTablePropsValue: setValue,
-  getTablePropsValue: getValue,
-} = useBsTable({
-  tableOptions: {
-    url: '',
-    gridOptions: {
-      loading: false,
-      columns: [
-        {
-          field: 'knowRunyang',
-          title: '姓名',
-        },
-        {
-          field: 'knowRunyang',
-          title: '性别',
-        },
-        {
-          field: 'companyName',
-          title: '年龄',
-
-        },
-        {
-          field: 'address',
-          title: '联系电话',
-        },
-        {
-          field: 'contact',
-          title: '类型',
-        },
-        {
-          field: 'contactPhone',
-          title: '职务',
-        },
-        {
-          field: 'post',
-          title: '邮箱',
-        },
-        {
-          field: 'needDescription',
-          title: '关系深度',
-        },
-        {
-          field: 'communicativeProject',
-          title: '爱好',
-        },
-        {
-          field: 'projectDescription',
-          title: '家庭情况',
-        },
-        {
-          field: 'follow',
-          title: '家庭住址',
-        },
-        {
-          field: 'follow',
-          title: '操作',
-        },
-      ],
-    },
-    pagerConfig: {
-      enabled: true,
-      pageSize: 10,
-      pageNum: 1,
-      total: 0,
-      isFixed: false
-    },
-    toolbarConfig: {
-      enable: false,
-    },
-    tableSearchBeforeBiz() {
-      const searchParams = getValue('searchConfig.data');
-      setValue('searchConfig.data', {...searchParams, id: id});
-    },
-  },
-});
-
-
-const handleEdit = (record) => {
-  message.info(`编辑 ${record.name}`);
-  // 这里添加实际的编辑逻辑
-};
-
-const handleDelete = (record) => {
-  // dataSource.value = dataSource.value.filter(item => item.key !== record.key);
-  // message.success(`已删除 ${record.name}`);
-};
+import OrgStructChart from './OrgStructChart/index.vue';
+import LinkList from './LinkList/index.vue';
+import { BsCatalog } from '/@/components/BsUi/index.js';
 </script>
 
-<style scoped>
-.management-container {
-  height: 100vh;
-  background: #fff;
-}
-
-.left-sider {
-  background: #fff;
-  border-right: 1px solid #f0f0f0;
-  padding: 16px;
-  overflow-y: auto;
-}
-
-.right-content {
-  padding: 16px;
-  overflow-y: auto;
-}
-
-.section-title {
-  font-size: 16px;
-  font-weight: bold;
-  margin-bottom: 16px;
-  color: #333;
-}
-
-.company-section {
-  margin-bottom: 24px;
-}
-
-.company-title {
-  font-size: 15px;
-  font-weight: bold;
-  margin: 12px 0 8px 0;
-  color: #333;
-}
-
-.group-title {
-  font-size: 14px;
-  font-weight: normal;
-  margin: 8px 0;
-  color: #666;
-  padding-left: 8px;
-}
-
-.subsidiary-item {
-  padding: 4px 0 4px 16px;
-  font-size: 13px;
-  color: #666;
-}
-
-/* 调整列表项样式 */
-:deep(.ant-list-item) {
-  padding: 0 !important;
-  border: none !important;
-}
-
-/* 表格样式调整 */
-:deep(.ant-table) {
-  background: #fff;
-}
-
-:deep(.ant-table-thead > tr > th) {
-  background: #fafafa;
-  font-weight: bold;
-}
-</style>
+<style scoped lang="scss">
+.link-company {
+  width: 100%;
+  display: flex;
+  gap: 30px;
+  .link-flow {
+    border-radius: 8px;
+    flex: 1;
+  }
+  .link-list {
+    border-radius: 8px;
+    width: 800px;
+  }
+}
+</style>

+ 153 - 0
src/views/market-manage/external-manage/clue-management/view-details/components/LinkList/TableInfo.vue

@@ -0,0 +1,153 @@
+<template>
+  <div class="table-wrapper">
+    <bs-table v-bind="tableOptions"> </bs-table>
+  </div>
+</template>
+
+<script setup lang="jsx">
+import BsTable, { useBsTable } from '/@/components/BsUi/Table/index.js';
+import { h, onMounted, ref } from 'vue';
+import { message } from 'ant-design-vue';
+import { BorderOuterOutlined, SearchOutlined, PlusSquareOutlined } from '@ant-design/icons-vue';
+import { DISPLAY_STATE } from '/@/components/BsUi/constant.js';
+
+const handleEdit = (row) => {};
+
+const {
+  tableOptions,
+  setTablePropsValue: setValue,
+  getTablePropsValue: getValue,
+  fetchTableData,
+} = useBsTable({
+  tableOptions: {
+    gridOptions: {
+      loading: false,
+      headerAlign: "center",
+      columns: [
+        {
+          title: "姓名",
+          field: "name",
+          width: '150px'
+        },
+        {
+          title: "性别",
+          field: "name1",
+          width: '150px'
+        },
+        {
+          title: "年龄",
+          field: "name2",
+          width: '150px'
+        },
+        {
+          title: "联系电话",
+          field: "name3",
+          width: '150px'
+        },
+        {
+          title: "类型",
+          field: "name4",
+          width: '150px'
+        },
+        {
+          title: "职务",
+          field: "name5",
+          width: '150px'
+        },
+        {
+          title: "决策链说明",
+          field: "name6",
+          width: '150px'
+        },
+        {
+          title: "邮箱",
+          field: "name8",
+          width: '150px'
+        },
+        {
+          title: "关系深度",
+          field: "name9",
+          width: '150px'
+        },
+        {
+          title: "爱好",
+          field: "name10",
+          width: '150px'
+        },
+        {
+          title: "家庭情况",
+          field: "name12",
+          width: '150px'
+        },
+        {
+          title: "家庭地址",
+          field: "name13",
+          width: '150px'
+        },
+        {
+          title: "备注",
+          field: "name14",
+          width: '150px'
+        },
+        {
+          fixed: 'right',
+          title: "操作",
+          width: '150px',
+          cellRender: {
+            name: 'CellOption',
+            extraProps: {
+              buttons: [
+                {
+                  title: '编辑',
+                  code: 'edit',
+                  display: ({ row }) => {
+                    return DISPLAY_STATE.VISIBLE;
+                  },
+                  disabled({ row }) {
+                    return false;
+                  },
+                  onClick({ row }) {},
+                  extraProps: {},
+                },
+                {
+                  title: '删除',
+                  code: 'delete',
+                  display: ({ row }) => {
+                    return DISPLAY_STATE.VISIBLE;
+                  },
+                  disabled({ row }) {
+                    return false;
+                  },
+                  onClick({ row }) {},
+                  extraProps: {},
+                },
+              ],
+            },
+          },
+        },
+      ],
+      data: [], // 模拟数据源
+    },
+    searchConfig: {
+      enable: false,
+    },
+    pagerConfig: {
+      enable: false,
+    },
+    toolbarConfig: {
+      enable: false,
+      displayToolbar: DISPLAY_STATE.HIDDEN,
+      leftButtons: [],
+    },
+  },
+});
+</script>
+
+<style scoped lang="scss">
+.table-wrapper {
+  width: 100%;
+  :deep(.vxe-grid--table-container) {
+    padding: 0;
+  }
+}
+</style>

+ 29 - 0
src/views/market-manage/external-manage/clue-management/view-details/components/LinkList/index.vue

@@ -0,0 +1,29 @@
+<template>
+  <bs-catalog title="关联链列表">
+
+    <template #headerRight>
+      <a-button type="primary">
+        <template #icon>
+          <PlusSquareOutlined />
+        </template>
+        <span>添加成员</span>
+      </a-button>
+    </template>
+
+    <template #content>
+      <table-info></table-info>
+    </template>
+  </bs-catalog>
+</template>
+
+<script setup>
+  import { BsCatalog } from '/@/components/BsUi/index.js';
+  import TableInfo from './TableInfo.vue';
+</script>
+
+<style scoped lang="scss">
+  .org-struct-chart {
+    width: 100%;
+    height: 100%;
+  }
+</style>

+ 283 - 0
src/views/market-manage/external-manage/clue-management/view-details/components/OrgStructChart/index.vue

@@ -0,0 +1,283 @@
+<template>
+  <div ref="containerRef" class="org-chart-container" />
+</template>
+
+<script>
+import { ref, onMounted, onBeforeUnmount } from 'vue'
+import { Graph, Node, Point } from '@antv/x6'
+
+export default {
+  name: 'OrgChart',
+  setup() {
+    const containerRef = ref(null)
+    let graph = null
+
+    // 注册节点和边的类型
+    const registerChartElements = () => {
+      Graph.registerNode(
+          'org-node',
+          {
+            width: 180,
+            height: 60,
+            markup: [
+              {
+                tagName: 'rect',
+                selector: 'body',
+              },
+              {
+                tagName: 'image',
+                selector: 'avatar',
+              },
+              {
+                tagName: 'text',
+                selector: 'rank',
+              },
+              {
+                tagName: 'text',
+                selector: 'name',
+              },
+            ],
+            attrs: {
+              body: {
+                refWidth: '100%',
+                refHeight: '100%',
+                fill: '#5F95FF',
+                stroke: '#5F95FF',
+                strokeWidth: 1,
+                rx: 10,
+                ry: 10,
+                pointerEvents: 'visiblePainted',
+              },
+              avatar: {
+                width: 48,
+                height: 48,
+                refX: 8,
+                refY: 6,
+              },
+              rank: {
+                refX: 0.9,
+                refY: 0.2,
+                fill: '#fff',
+                fontFamily: 'Courier New',
+                fontSize: 14,
+                textAnchor: 'end',
+                textDecoration: 'underline',
+              },
+              name: {
+                refX: 0.9,
+                refY: 0.6,
+                fill: '#fff',
+                fontFamily: 'Courier New',
+                fontSize: 14,
+                fontWeight: '800',
+                textAnchor: 'end',
+              },
+            },
+          },
+          true
+      )
+
+      Graph.registerEdge(
+          'org-edge',
+          {
+            zIndex: -1,
+            attrs: {
+              line: {
+                fill: 'none',
+                strokeLinejoin: 'round',
+                strokeWidth: 2,
+                stroke: '#A2B1C3',
+                sourceMarker: null,
+                targetMarker: null,
+              },
+            },
+          },
+          true
+      )
+    }
+
+    // 创建节点
+    const createNode = (x, y, rank, name, image) => {
+      return graph.addNode({
+        x,
+        y,
+        shape: 'org-node',
+        attrs: {
+          avatar: {
+            opacity: 0.7,
+            // 'xlink:href': image,
+          },
+          rank: {
+            text: rank,
+            wordSpacing: '-5px',
+            letterSpacing: 0,
+          },
+          name: {
+            text: name,
+            fontSize: 13,
+            fontFamily: 'Arial',
+            letterSpacing: 0,
+          },
+        },
+        // 禁用节点拖动
+        movable: false,
+      })
+    }
+
+    // 创建连线
+    const createLink = (source, target, vertices) => {
+      return graph.addEdge({
+        vertices,
+        source: {
+          cell: source,
+        },
+        target: {
+          cell: target,
+        },
+        shape: 'org-edge',
+        // 禁用连线拖动
+        connector: {
+          name: 'normal',
+          args: {
+            style: {
+              pointerEvents: 'none',
+            },
+          },
+        },
+        // 禁用连线调整
+        router: {
+          name: 'manhattan',
+          args: {
+            padding: 10,
+          },
+        },
+        attrs: {
+          line: {
+            // 禁止选中连线
+            pointerEvents: 'none',
+          },
+        },
+      })
+    }
+
+    // 初始化图表
+    const initChart = () => {
+      if (!containerRef.value) return
+
+      // 创建图表实例
+      graph = new Graph({
+        container: containerRef.value,
+        connecting: {
+          anchor: 'orth',
+        },
+        // 禁用鼠标滚轮缩放
+        mousewheel: {
+          enabled: true,
+        },
+        // 禁用平移
+        panning: true,
+        // 禁用框选
+        selecting: {
+          enabled: false,
+        },
+        interacting: {
+          nodeMovable: false,
+          edgeMovable: false,
+        },
+      })
+
+      // 图表数据
+      const male =
+          'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*kUy8SrEDp6YAAAAAAAAAAAAAARQnAQ'
+      const female =
+          'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*f6hhT75YjkIAAAAAAAAAAAAAARQnAQ'
+
+      // 创建节点
+      const bart = createNode(300, 70, 'CEO', 'Bart Simpson', male)
+      const homer = createNode(90, 200, 'VP Marketing', 'Homer Simpson', male)
+      const marge = createNode(300, 200, 'VP Sales', 'Marge Simpson', female)
+      const lisa = createNode(500, 200, 'VP Production', 'Lisa Simpson', female)
+      const maggie = createNode(400, 350, 'Manager', 'Maggie Simpson', female)
+      const lenny = createNode(190, 350, 'Manager', 'Lenny Leonard', male)
+      const carl = createNode(190, 500, 'Manager', 'Carl Carlson', male)
+
+      // 创建连线
+      createLink(bart, marge, [
+        {
+          x: 385,
+          y: 180,
+        },
+      ])
+      createLink(bart, homer, [
+        {
+          x: 385,
+          y: 180,
+        },
+        {
+          x: 175,
+          y: 180,
+        },
+      ])
+      createLink(bart, lisa, [
+        {
+          x: 385,
+          y: 180,
+        },
+        {
+          x: 585,
+          y: 180,
+        },
+      ])
+      createLink(homer, lenny, [
+        {
+          x: 175,
+          y: 380,
+        },
+      ])
+      createLink(homer, carl, [
+        {
+          x: 175,
+          y: 530,
+        },
+      ])
+      createLink(marge, maggie, [
+        {
+          x: 385,
+          y: 380,
+        },
+      ])
+
+      // 调整视图以适应所有节点
+      graph.zoomToFit({ padding: 20, maxScale: 1 })
+    }
+
+    // 组件挂载后初始化图表
+    onMounted(() => {
+      registerChartElements()
+      initChart()
+    })
+
+    // 组件卸载前清理图表
+    onBeforeUnmount(() => {
+      if (graph) {
+        graph.dispose()
+        graph = null
+      }
+    })
+
+    return {
+      containerRef,
+    }
+  },
+}
+</script>
+
+<style scoped>
+.org-chart-container {
+  width: 100%;
+  height: 600px;
+  border: 1px solid #e5e7eb;
+  border-radius: 6px;
+  overflow: hidden;
+}
+</style>

+ 100 - 0
src/views/market-manage/external-manage/clue-management/view-details/components/TransferPublicSeaModal/index.vue

@@ -0,0 +1,100 @@
+<template>
+  <bs-modal
+    :visible="modalOptions.visible"
+    :width="modalOptions.width"
+    :title="modalOptions.title"
+    :modal-extra-props="modalOptions.modalExtraProps"
+    @cancel="handleCancel"
+    @ok="handleOk"
+  >
+    <div class="content">
+      <a-alert message="请谨慎操作,释放到公海,该客户将会被别人认领!" type="warning" />
+      <bs-form
+        :form-data="formOptions.formData"
+        :form-fields="formOptions.formFields"
+        :form-extra-props="formOptions.formExtraProps"
+        :footer-render="() => null"
+      />
+    </div>
+  </bs-modal>
+</template>
+<script setup lang="jsx">
+  import { BsModal, useBsModal, useBsForm, BsForm } from '/@/components/BsUi/index.js';
+  import { REQUIRED_STATE, SCENE_TYPE, SELECT_MULTIPLE } from '/@/components/BsUi/constant.js';
+  const {
+    modalOptions,
+    getModalPropsValue: getMVal,
+    setModalPropsValue: setMVal,
+  } = useBsModal({
+    modalOptions: {
+      title: '释放公海',
+      visible: false,
+      width: '500px',
+      modalExtraProps: {
+        destroyOnClose: true,
+        okButtonProps: {
+          loading: false,
+        },
+      },
+    },
+  });
+
+  const { formOptions } = useBsForm({
+    formOptions: {
+      formId: 'formId',
+      formExtraProps: {},
+      formData: {},
+      formFields: [
+        {
+          id: '1',
+          label: '释放原因',
+          component: 'a-textarea',
+          componentProps: {
+            placeholder: '请输入',
+            disabled: false,
+          },
+          field: 'name',
+          sort: '1',
+          visible: '1',
+          required: REQUIRED_STATE.REQUIRED,
+          formItemExtraProps: {
+            rules: [
+              {
+                validator: (_, value) => {
+                  // return Promise.reject(new Error('报错'));
+                  return Promise.resolve();
+                },
+              },
+            ],
+          },
+        },
+      ],
+    },
+  });
+
+  const showModal = () => {
+    setMVal('visible', true);
+  };
+
+  const handleCancel = () => {
+    setMVal('visible', false);
+  };
+
+  const handleOk = ({}) => {
+    setMVal('modalExtraProps.okButtonProps.loading', true);
+    setTimeout(() => {
+      setMVal('modalExtraProps.okButtonProps.loading', false);
+      setMVal('visible', false);
+    }, 1000);
+  };
+
+  defineExpose({
+    showModal,
+  });
+</script>
+
+<style lang="scss" scoped>
+  .content {
+    width: 100%;
+  }
+</style>

+ 9 - 2
src/views/market-manage/external-manage/clue-management/view-details/index.vue

@@ -10,7 +10,7 @@
       <template #toolBtn>
         <a-button ghost type="primary" size="small">转移</a-button>
         <a-button type="primary" size="small">预进场</a-button>
-        <a-button danger ghost type="primary" size="small">释放公海</a-button>
+        <a-button danger ghost type="primary" size="small" @click="handleTransferPublicSea">释放公海</a-button>
         <a-button danger ghost type="primary" size="small">放弃跟踪</a-button>
       </template>
 
@@ -54,9 +54,10 @@
       </template>
 
       <template #cooperativeProject>
-        <CooperativeProject />
+        <CooperativeProject :id="id"/>
       </template>
     </page-detail-layout>
+    <TransferPublicSeaModal ref="transferPublicSeaModalRef"/>
   </div>
 </template>
 
@@ -71,6 +72,7 @@ import {useRoute} from 'vue-router';
 import SectionManagement from './components/SectionManagement.vue'
 import CooperativeProject from './components/CooperativeProject.vue'
 import InfoMaterial from './components/InfoMaterial.vue'
+import TransferPublicSeaModal from './components/TransferPublicSeaModal/index.vue';
 
 const route = useRoute();
 const {id} = route.query
@@ -316,6 +318,11 @@ const tabs = ref([
     unSelectedIcon: getImgUrl('icon-bianjijilu'),
   },
 ]);
+const transferPublicSeaModalRef = ref(null);
+
+const handleTransferPublicSea = () => {
+  transferPublicSeaModalRef.value.showModal();
+};
 const {
   tableOptions,
   setTablePropsValue: setValue,