|
@@ -0,0 +1,422 @@
|
|
|
+<template>
|
|
|
+ <div class="content">
|
|
|
+ <el-form :inline="true" :model="sourceList.pagination" class="demo-form-inline">
|
|
|
+ <el-form-item label="销售合同号:" prop="code">
|
|
|
+ <el-input v-model="sourceList.pagination.code" placeholder="请输入销售合同号" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="业务员:" prop="userId">
|
|
|
+ <el-select v-model="sourceList.pagination.userId" placeholder="请选择业务员">
|
|
|
+ <el-option v-for="item in userList" :key="item.value" :label="item.label" :value="item.value"> </el-option>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" @click="getList">搜索</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <div style="padding: 10px 20px; width: 100%; box-sizing: border-box">
|
|
|
+ <div style="width: 100%; display: flex; padding-bottom: 5px; overflow-x: auto" class="topScrollDom" @scroll="handleScroll($event, true)">
|
|
|
+ <div v-for="(item, index) in purchaseTrackType" :key="index" style="display: flex; align-items: center">
|
|
|
+ <div style="width: 200px; height: 80px; position: relative" @click="selectRegion(item)">
|
|
|
+ <div :class="selectRecordDocumentaryId === item.id ? 'regionSelect' : 'regionNoSelect'" style="cursor: pointer">
|
|
|
+ <div style="color: #333333; font-size: 13px; font-weight: 700">{{ item.count }}</div>
|
|
|
+ <div style="color: #333333; font-size: 12px; font-weight: 700">{{ item.name }}</div>
|
|
|
+ <div class="right-bottom" style="position: absolute; right: 0; bottom: 0">
|
|
|
+ {{ index + 1 }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <el-icon class="beacon" v-if="index < purchaseTrackType.length - 1"><CaretRight /></el-icon>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="main-content" v-loading="loading">
|
|
|
+ <el-card class="box-card" v-for="(item, index) in sourceList.data" :key="index" style="margin-bottom: 20px">
|
|
|
+ <template #header>
|
|
|
+ <el-row>
|
|
|
+ <el-col :span="4">
|
|
|
+ <span>销售合同号: </span>
|
|
|
+ <span style="cursor: pointer; color: #66b1ff">
|
|
|
+ {{ item.code }}
|
|
|
+ </span>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="4">
|
|
|
+ <span>业务员: {{ item.userName }}</span>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="4">
|
|
|
+ <span>客户名称: {{ item.customerName }}</span>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <span>合同金额: {{ item.currency }}{{ item.amount }}</span>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </template>
|
|
|
+ <div style="overflow-x: auto; width: 100%" class="scrollDom" @scroll="handleScroll($event, false, index)">
|
|
|
+ <el-steps :space="200" align-center>
|
|
|
+ <el-step v-for="(itemType, key) in purchaseTrackType" :key="key" :title="itemType.name" :status="getStatus(itemType, item)" />
|
|
|
+ </el-steps>
|
|
|
+ <div style="display: flex">
|
|
|
+ <div v-for="(itemType, key) in purchaseTrackType" :key="key" style="width: 200px !important; min-width: 200px !important">
|
|
|
+ <div style="padding: 8px 0; text-align: center">
|
|
|
+ <el-icon style="color: #409eff; cursor: pointer" @click="clickAdd(item, itemType)"><EditPen /></el-icon>
|
|
|
+ </div>
|
|
|
+ <div style="text-align: center" v-if="item.documentaryRecord && item.documentaryRecord[itemType.id]">
|
|
|
+ <el-tooltip class="box-item" effect="light" placement="bottom">
|
|
|
+ <template #content>
|
|
|
+ <div style="max-width: 400px; max-height: 50vh; overflow-y: auto">
|
|
|
+ <el-timeline>
|
|
|
+ <el-timeline-item
|
|
|
+ v-for="(activity, index) in item.documentaryRecord[itemType.id]"
|
|
|
+ :key="index"
|
|
|
+ :timestamp="activity.createTime"
|
|
|
+ :hide-timestamp="true">
|
|
|
+ <div style="background-color: #e2fbe8; padding: 8px">
|
|
|
+ <div style="font-size: 12px">{{ activity.createTime }}</div>
|
|
|
+ <div style="font-size: 12px" v-html="getStyle(activity.remark)"></div>
|
|
|
+ <div v-if="activity.fileList && activity.fileList.length > 0">
|
|
|
+ <div v-for="(file, index) in activity.fileList" :key="index">
|
|
|
+ <a style="color: #409eff; cursor: pointer" @click="openFile(file.fileUrl)">{{ file.fileName }}</a>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-timeline-item>
|
|
|
+ </el-timeline>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <span style="color: #409eff; cursor: pointer; padding: 8px 0; font-size: 12px">跟单详情</span>
|
|
|
+ </el-tooltip>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-dialog :title="title" v-if="dialogVisible" v-model="dialogVisible" width="600" v-loading="loadingDialog">
|
|
|
+ <byForm :formConfig="formConfig" :formOption="formOption" v-model="formData.data" :rules="rules" ref="submit">
|
|
|
+ <template #completionTime>
|
|
|
+ <div>
|
|
|
+ <el-date-picker v-model="formData.data.completionTime" type="datetime" placeholder="请选择完成时间" value-format="YYYY-MM-DD HH:mm:ss" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template #file>
|
|
|
+ <div style="width: 100%">
|
|
|
+ <el-upload
|
|
|
+ v-model:fileList="fileList"
|
|
|
+ action="https://winfaster.obs.cn-south-1.myhuaweicloud.com"
|
|
|
+ :data="uploadData"
|
|
|
+ multiple
|
|
|
+ :before-upload="uploadFile"
|
|
|
+ :on-preview="onPreviewFile">
|
|
|
+ <el-button>选择</el-button>
|
|
|
+ </el-upload>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </byForm>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="dialogVisible = false" size="large">取 消</el-button>
|
|
|
+ <el-button type="primary" @click="submitForm()" size="large">确 定</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, computed } from "vue";
|
|
|
+import useUserStore from "@/store/modules/user";
|
|
|
+import byForm from "@/components/byForm/index";
|
|
|
+import { ElMessage } from "element-plus";
|
|
|
+
|
|
|
+const { proxy } = getCurrentInstance();
|
|
|
+const userList = ref([]);
|
|
|
+const purchaseTrackType = ref([]);
|
|
|
+const sourceList = ref({
|
|
|
+ data: [],
|
|
|
+ pagination: {
|
|
|
+ total: 0,
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ code: "",
|
|
|
+ userId: "",
|
|
|
+ },
|
|
|
+});
|
|
|
+const timer = ref(null);
|
|
|
+const loading = ref(false);
|
|
|
+const handleScroll = (event, flag, index) => {
|
|
|
+ const listDom = document.querySelectorAll(".scrollDom");
|
|
|
+ let scroll_left;
|
|
|
+ if (index !== " " && index !== undefined) {
|
|
|
+ scroll_left = listDom[index].scrollLeft;
|
|
|
+ }
|
|
|
+ if (timer.value) {
|
|
|
+ clearTimeout(timer.value);
|
|
|
+ }
|
|
|
+ if (flag) {
|
|
|
+ timer.value = setTimeout(() => {
|
|
|
+ const list = document.querySelectorAll(".scrollDom");
|
|
|
+ for (let i = 0; i < list.length; i++) {
|
|
|
+ const ele = list[i];
|
|
|
+ ele.scrollLeft = event.target.scrollLeft;
|
|
|
+ }
|
|
|
+ }, 200);
|
|
|
+ } else {
|
|
|
+ timer.value = setTimeout(() => {
|
|
|
+ const topScroll = document.querySelector(".topScrollDom");
|
|
|
+ topScroll.scrollLeft = scroll_left;
|
|
|
+ const list = document.querySelectorAll(".scrollDom");
|
|
|
+ for (let i = 0; i < list.length; i++) {
|
|
|
+ const ele = list[i];
|
|
|
+ ele.scrollLeft = scroll_left;
|
|
|
+ }
|
|
|
+ }, 200);
|
|
|
+ }
|
|
|
+};
|
|
|
+const getDict = () => {
|
|
|
+ proxy.get("/tenantUser/list", { pageNum: 1, pageSize: 10000, tenantId: useUserStore().user.tenantId }).then((res) => {
|
|
|
+ if (res.rows && res.rows.length > 0) {
|
|
|
+ userList.value = res.rows.map((item) => {
|
|
|
+ return {
|
|
|
+ label: item.userName,
|
|
|
+ value: item.userId,
|
|
|
+ };
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+const selectRecordDocumentaryId = ref("");
|
|
|
+const getList = () => {
|
|
|
+ loading.value = true;
|
|
|
+ proxy
|
|
|
+ .post("/documentaryRecord/info", {
|
|
|
+ type: "1",
|
|
|
+ pageNum: sourceList.value.pagination.pageNum,
|
|
|
+ pageSize: sourceList.value.pagination.pageSize,
|
|
|
+ condition: {
|
|
|
+ code: sourceList.value.pagination.code,
|
|
|
+ userId: sourceList.value.pagination.userId,
|
|
|
+ },
|
|
|
+ noRecordDocumentaryId: selectRecordDocumentaryId.value,
|
|
|
+ })
|
|
|
+ .then(
|
|
|
+ (res) => {
|
|
|
+ purchaseTrackType.value = res.documentaryList;
|
|
|
+ sourceList.value.data = res.page.rows;
|
|
|
+ sourceList.value.pagination.total = res.page.total;
|
|
|
+ setTimeout(() => {
|
|
|
+ loading.value = false;
|
|
|
+ }, 200);
|
|
|
+ },
|
|
|
+ (err) => {
|
|
|
+ console.log(err);
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+ );
|
|
|
+};
|
|
|
+getDict();
|
|
|
+getList();
|
|
|
+const selectRegion = (item) => {
|
|
|
+ sourceList.value.pagination.pageNum = 1;
|
|
|
+ if (selectRecordDocumentaryId.value && selectRecordDocumentaryId.value === item.id) {
|
|
|
+ selectRecordDocumentaryId.value = "";
|
|
|
+ } else {
|
|
|
+ selectRecordDocumentaryId.value = item.id;
|
|
|
+ }
|
|
|
+ getList();
|
|
|
+};
|
|
|
+const getStatus = (typeItem, row) => {
|
|
|
+ if (row.documentaryRecord && row.documentaryRecord[typeItem.id] && row.documentaryRecord[typeItem.id].length > 0) {
|
|
|
+ return "success";
|
|
|
+ }
|
|
|
+ return "process";
|
|
|
+};
|
|
|
+const title = ref("");
|
|
|
+const dialogVisible = ref(false);
|
|
|
+const loadingDialog = ref(false);
|
|
|
+const submit = ref(null);
|
|
|
+const formOption = reactive({
|
|
|
+ inline: true,
|
|
|
+ labelWidth: 100,
|
|
|
+ itemWidth: 100,
|
|
|
+ rules: [],
|
|
|
+});
|
|
|
+const formData = reactive({
|
|
|
+ data: {
|
|
|
+ fileList: [],
|
|
|
+ },
|
|
|
+});
|
|
|
+const fileList = ref([]);
|
|
|
+const uploadData = ref({});
|
|
|
+const formConfig = computed(() => {
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ type: "slot",
|
|
|
+ prop: "completionTime",
|
|
|
+ slotName: "completionTime",
|
|
|
+ label: "完成时间",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: "slot",
|
|
|
+ prop: "file",
|
|
|
+ slotName: "file",
|
|
|
+ label: "上传附件",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: "input",
|
|
|
+ prop: "remark",
|
|
|
+ label: "摘要",
|
|
|
+ itemType: "textarea",
|
|
|
+ },
|
|
|
+ ];
|
|
|
+});
|
|
|
+const rules = ref({
|
|
|
+ completionTime: [{ required: true, message: "请选择完成时间", trigger: "change" }],
|
|
|
+});
|
|
|
+const clickAdd = (item, row) => {
|
|
|
+ title.value = "编辑: " + row.name;
|
|
|
+ formData.data = {
|
|
|
+ documentaryId: row.id,
|
|
|
+ businessId: item.id,
|
|
|
+ completionTime: "",
|
|
|
+ remark: "",
|
|
|
+ fileList: [],
|
|
|
+ };
|
|
|
+ loadingDialog.value = false;
|
|
|
+ dialogVisible.value = true;
|
|
|
+};
|
|
|
+const uploadFile = async (file) => {
|
|
|
+ const res = await proxy.post("/fileInfo/getSing", { fileName: file.name });
|
|
|
+ uploadData.value = res.uploadBody;
|
|
|
+ file.id = res.id;
|
|
|
+ file.fileName = res.fileName;
|
|
|
+ file.fileUrl = res.fileUrl;
|
|
|
+ return true;
|
|
|
+};
|
|
|
+const onPreviewFile = (file) => {
|
|
|
+ window.open(file.raw.fileUrl, "_blank");
|
|
|
+};
|
|
|
+const submitForm = () => {
|
|
|
+ submit.value.handleSubmit(() => {
|
|
|
+ if (fileList.value && fileList.value.length > 0) {
|
|
|
+ formData.data.fileList = fileList.value.map((item) => {
|
|
|
+ return {
|
|
|
+ id: item.raw.id,
|
|
|
+ fileName: item.raw.fileName,
|
|
|
+ fileUrl: item.raw.fileUrl,
|
|
|
+ };
|
|
|
+ });
|
|
|
+ }
|
|
|
+ loadingDialog.value = true;
|
|
|
+ proxy.post("/documentaryRecord/add", formData.data).then(
|
|
|
+ () => {
|
|
|
+ ElMessage({
|
|
|
+ message: "编辑成功",
|
|
|
+ type: "success",
|
|
|
+ });
|
|
|
+ dialogVisible.value = false;
|
|
|
+ getList();
|
|
|
+ },
|
|
|
+ (err) => {
|
|
|
+ console.log(err);
|
|
|
+ loadingDialog.value = false;
|
|
|
+ }
|
|
|
+ );
|
|
|
+ });
|
|
|
+};
|
|
|
+const getStyle = (text) => {
|
|
|
+ if (text) {
|
|
|
+ return text.replace(/\n|\r\n/g, "<br>");
|
|
|
+ } else {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+};
|
|
|
+const openFile = (path) => {
|
|
|
+ window.open(path, "_blank");
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.content {
|
|
|
+ margin: 20px;
|
|
|
+ padding: 20px;
|
|
|
+ background-color: white;
|
|
|
+ height: calc(100vh - 140px);
|
|
|
+}
|
|
|
+.right-bottom {
|
|
|
+ color: #fff;
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ line-height: 20px;
|
|
|
+ text-align: center;
|
|
|
+ background: url("/img/jc.png") no-repeat;
|
|
|
+ background-size: 20px 20px;
|
|
|
+}
|
|
|
+.topScrollDom {
|
|
|
+ .regionSelect {
|
|
|
+ background: linear-gradient(#ffd9d9, #eff6ff);
|
|
|
+ border-radius: 10px;
|
|
|
+ flex: 1;
|
|
|
+ width: 160px;
|
|
|
+ height: 80px;
|
|
|
+ text-align: center;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: space-around;
|
|
|
+ position: relative;
|
|
|
+ margin-left: 20px;
|
|
|
+ }
|
|
|
+ .regionNoSelect {
|
|
|
+ background: linear-gradient(#d9edff, #eff6ff);
|
|
|
+ border-radius: 10px;
|
|
|
+ flex: 1;
|
|
|
+ width: 160px;
|
|
|
+ height: 80px;
|
|
|
+ text-align: center;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: space-around;
|
|
|
+ position: relative;
|
|
|
+ margin-left: 20px;
|
|
|
+ }
|
|
|
+ .beacon {
|
|
|
+ margin: 0px 10px;
|
|
|
+ color: #46a6ff;
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ line-height: 20px;
|
|
|
+ text-align: center;
|
|
|
+ background-color: #eff6ff;
|
|
|
+ border-radius: 10px;
|
|
|
+ position: absolute;
|
|
|
+ right: -20px;
|
|
|
+ top: 30px;
|
|
|
+ }
|
|
|
+}
|
|
|
+.main-content {
|
|
|
+ margin-top: 20px;
|
|
|
+ height: calc(100vh - 140px - 232px);
|
|
|
+ overflow: auto;
|
|
|
+ &::-webkit-scrollbar {
|
|
|
+ width: 0px;
|
|
|
+ height: 0px;
|
|
|
+ }
|
|
|
+}
|
|
|
+.scrollDom {
|
|
|
+ &::-webkit-scrollbar {
|
|
|
+ width: 0px;
|
|
|
+ height: 0px;
|
|
|
+ }
|
|
|
+}
|
|
|
+::v-deep(.el-card__header) {
|
|
|
+ background-color: #f4f4f5;
|
|
|
+ padding: 14px 15px !important;
|
|
|
+ font-size: 14px !important;
|
|
|
+}
|
|
|
+.el-steps {
|
|
|
+ display: block !important;
|
|
|
+}
|
|
|
+::v-deep(.el-step__title) {
|
|
|
+ width: 200px;
|
|
|
+}
|
|
|
+::v-deep(.el-timeline) {
|
|
|
+ padding: 8px !important;
|
|
|
+}
|
|
|
+</style>
|