12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916 |
- <template>
- <div class="data-list">
- <el-table
- :data="dataList"
- style="width: 100%"
- header-row-class-name="headerBg"
- empty-text="没有项目信息"
- >
- <el-table-column prop="id" label="项目编号" align="center" width="80" />
- <el-table-column prop="customer_id" label="客户名称" align="left">
- <template #default="scope">
- <span>{{ getCusomName(scope.row.customer_id) }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="name" label="项目名称" align="left" />
- <el-table-column prop="categoryId" label="产品类别" align="left">
- <template #default="scope">
- <span>{{ getCategoryTypeName(scope.row.product_id) }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="productName" label="产品名称" align="left">
- <template #default="scope">
- <span>{{ getCategoryName(scope.row.product_id) }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="service_content" label="服务内容" align="left" />
- <el-table-column prop="start_date" label="项目开始日期" align="center" />
- <el-table-column prop="updateTime" label="最后更新日期" align="center" />
- <el-table-column prop="calculate" label="完成度" align="center">
- <template #default="scope">
- <span :class="getCompletionClass(scope.row.calculate)">
- {{ formatCompletion(scope.row.calculate) }}
- </span>
- </template>
- </el-table-column>
- <el-table-column prop="current_phase" label="当前阶段" align="left">
- <template #default="scope">
- <el-tag :type="getPhaseType(scope.row.current_phase)" effect="plain">
- {{ getPhaseName(scope.row.current_phase) }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="project_status" label="目前状态" align="center">
- <template #default="scope">
- <el-tag
- :type="getStatusType(scope.row.project_status)"
- effect="plain"
- >
- {{ getStatusName(scope.row.project_status) }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="操作" align="center" fixed="right">
- <template #default="scope">
- <el-dropdown @command="handleCommand" :data="scope.row">
- <span class="el-dropdown-link">
- <el-button
- type="text"
- size="small"
- circle
- class="operation-button"
- style="font-size: 15px; margin-left: 10px"
- icon="el-icon-more"
- ></el-button>
- </span>
- <template #dropdown>
- <el-dropdown-menu>
- <el-dropdown-item
- :command="{ type: 'template', row: scope.row }"
- >选择模版</el-dropdown-item
- >
- <el-dropdown-item :command="{ type: 'enter', row: scope.row }"
- >输入项目</el-dropdown-item
- >
- <el-dropdown-item
- :command="{ type: 'viewTemplates', row: scope.row }"
- v-if="
- scope.row.documentIds && scope.row.documentIds.length > 0
- "
- >
- 已绑定模版
- </el-dropdown-item>
- <el-dropdown-item :command="{ type: 'edit', row: scope.row }">
- <svg-icon icon-class="edit" />编辑
- </el-dropdown-item>
- <el-dropdown-item :command="{ type: 'delete', row: scope.row }">
- <svg-icon icon-class="delete" />删除
- </el-dropdown-item>
- </el-dropdown-menu>
- </template>
- </el-dropdown>
- </template>
- <!-- <template #default="scope">
- <div class="btns">
- <el-button type="primary" size="small" @click="selTemplate(scope.row)"
- >选择模版</el-button
- >
- <el-button type="primary" size="small" @click="enterPro(scope.row)"
- >输入项目</el-button
- >
- <el-button type="primary" size="small" @click="btnEdit(scope.row)"
- ><svg-icon icon-class="edit" />编辑</el-button
- >
- <el-button
- type="danger"
- size="small"
- @click="btnDelete(scope.row.id)"
- ><svg-icon icon-class="delete" />删除</el-button
- >
- </div>
- </template> -->
- </el-table-column>
- </el-table>
- <div class="page-info">
- <el-pagination
- :currentPage="queryForm.page"
- :page-size="queryForm.pageSize"
- :total="recordCount"
- :page-count="pageTotal"
- background
- layout="prev, pager, next"
- @current-change="ChangePage"
- >
- </el-pagination>
- </div>
- <el-dialog
- :visible.sync="dialogVisible"
- append-to-body
- v-el-drag-dialog
- custom-class="prod-verify"
- title="创建项目信息"
- >
- <dataInfo :id="currentDataId" @onClose="onClose"></dataInfo>
- </el-dialog>
- <!-- 选择模版 -->
- <el-dialog
- title="选择模版"
- :visible.sync="templateDialogVisible"
- width="50%"
- append-to-body
- >
- <el-table
- :data="templateList"
- style="width: 100%"
- @selection-change="handleTemplateSelect"
- @select-all="handleSelectAll"
- ref="templateTable"
- >
- <el-table-column type="selection" width="55"></el-table-column>
- <el-table-column prop="id" label="ID" width="80" align="center" />
- <el-table-column prop="title" label="模版名称" align="left" />
- <el-table-column prop="create_time" label="创建时间" align="center" />
- </el-table>
- <div class="pagination-container">
- <el-pagination
- :current-page="templateQueryForm.page"
- :page-size="templateQueryForm.pageSize"
- :total="templateTotal"
- background
- layout="prev, pager, next"
- @current-change="handleTemplatePageChange"
- >
- </el-pagination>
- </div>
- <div class="dialog-footer" slot="footer">
- <el-button @click="templateDialogVisible = false">取 消</el-button>
- <el-button type="primary" @click="confirmTemplateSelection"
- >确 定</el-button
- >
- </div>
- </el-dialog>
- <el-dialog
- title="已绑定模版"
- :visible.sync="boundTemplatesDialogVisible"
- width="50%"
- append-to-body
- >
- <el-table
- :data="boundTemplateList"
- style="width: 100%"
- @selection-change="handleBoundTemplateSelect"
- ref="boundTemplateTable"
- >
- <el-table-column type="selection" width="55"></el-table-column>
- <el-table-column prop="id" label="ID" width="80" align="center" />
- <el-table-column prop="title" label="模版名称" align="left" />
- <el-table-column prop="createTime" label="绑定时间" align="center" />
- <el-table-column label="操作" align="center" width="320">
- <template #default="scope">
- <el-button type="primary" @click="previewTemplate(scope.row)"
- >预览</el-button
- >
- <el-button type="primary" @click="replaceTemplate(scope.row)"
- >替换</el-button
- >
- <el-dropdown
- style="margin-left: 10px"
- trigger="click"
- @command="(command) => handleExport(command, scope.row)"
- >
- <el-button type="success">
- 导出文档<i class="el-icon-arrow-down el-icon--right"></i>
- </el-button>
- <template #dropdown>
- <el-dropdown-menu>
- <el-dropdown-item command="word">Word格式</el-dropdown-item>
- <el-dropdown-item command="pdf">PDF格式</el-dropdown-item>
- <!-- <el-dropdown-item command="pdf-to-word"
- >PDF转Word</el-dropdown-item
- > -->
- </el-dropdown-menu>
- </template>
- </el-dropdown>
- </template>
- </el-table-column>
- </el-table>
- <div class="dialog-footer" style="margin-top: 20px; text-align: right">
- <el-button
- type="primary"
- @click="batchReplace"
- :loading="batchReplaceLoading"
- :disabled="selectedBoundTemplates.length === 0"
- >
- 批量替换
- </el-button>
- </div>
- </el-dialog>
- <el-dialog
- title=""
- :visible.sync="replaceVisible"
- width="50%"
- append-to-body
- >
- <!-- 显示匹配到的结果 -->
- <div v-if="matchResults.length" class="match-results">
- <div
- v-for="(item, index) in matchResults"
- :key="index"
- class="match-item"
- >
- <div class="match-content">
- <span>原始值: {{ item }}</span>
- <!-- <span>替换后: {{ item }}</span> -->
- </div>
- <div class="actions">
- <el-button size="small" @click="replaceItem(item)">替换</el-button>
- </div>
- </div>
- <div style="display: flex; justify-content: flex-end; margin-top: 20px">
- <el-button type="primary" @click="replaceAll">批量替换</el-button>
- </div>
- </div>
- </el-dialog>
- </div>
- </template>
- <script>
- import {
- searchProject,
- deleteProject,
- searchCategory,
- relate,
- searchTemplateList,
- getstatus,
- calculate,
- getphases,
- } from "@/api/project";
- import {
- searchList,
- updateDocument,
- exportDocument,
- exportToPDF,
- } from "@/api/document";
- import { searchCustomer } from "@/api/customer";
- import { searchProduct } from "@/api/product";
- /* import {searchTemplate} from "@/api/template" */
- import dataInfo from "./dataInfo.vue";
- import elDragDialog from "@/directive/el-drag-dialog";
- import axios from "axios";
- import jsPDF from "jspdf";
- import html2canvas from "html2canvas";
- export default {
- components: {
- dataInfo,
- },
- directives: { elDragDialog },
- props: {
- queryForm: {
- type: Object,
- default: () => {
- return {
- page: 1,
- pageSize: 10,
- name: "",
- category_id: "",
- status: "",
- };
- },
- },
- },
- watch: {
- queryForm: {
- handler: function (val) {
- this.search();
- },
- // immediate: true,//立即执行
- deep: true,
- },
- },
- data() {
- return {
- templateQueryForm: {
- page: 1,
- pageSize: 10,
- },
- templateTotal: 0, // 模版总数
- dialogVisible: false, //控制产品审核
- currentDataId: 0,
- recordCount: 0,
- pageTotal: 1,
- dataList: [],
- currentData: {},
- categoryList: [],
- templateDialogVisible: false,
- templateList: [],
- selectedTemplates: [], // 改为数组以支持多选
- boundTemplatesDialogVisible: false,
- previewDialogVisible: false,
- boundTemplateList: [],
- templatePreviewContent: "",
- currentTemplateId: null,
- replaceVisible: false,
- loading: false, // 增加 loading 状态
- searchResult: {},
- matchResults: [], // 存储匹配到的结果
- TemList: [],
- docAttr: {},
- templateLoading: false, // 添加模版加载状态
- boundTemplateLoading: false, // 添加已绑定模版加载状态
- selectedBoundTemplates: [], // 新增:已选择的绑定模版
- batchReplaceLoading: false, // 新增:批量替换loading状态
- getstatusList: [], //状态列表
- customList: [], //客户列表
- productList: [], //产品列表
- currentPhaseList: [], //阶段表
- selectedTemplateIds: new Set(), // 新增: 用于存储所有已选择的模版ID
- isAllSelected: false, // 新增: 标记是否全选所有页
- selectedPageIds: new Map(), // 新增: 存储每页选中的ID
- };
- },
- mounted() {
- this.search();
- this.init();
- },
- methods: {
- // 添加 PDF 转 Word 的方法
- async convertPDFToWord(pdfPath) {
- try {
- // 创建FormData对象
- const formData = new FormData();
- formData.append("oss_url", pdfPath);
- // 调用后端转换接口
- const res = await axios.post(
- `${process.env.VUE_APP_BASE_API}/convert/pdftoword`,
- formData,
- {
- headers: {
- "Content-Type": "multipart/form-data", // 设置请求头为form-data
- },
- }
- );
- if (res.status === 200) {
- // 将base64转换为Blob
- const byteCharacters = atob(res.data.data.file_content);
- const byteNumbers = new Array(byteCharacters.length);
- for (let i = 0; i < byteCharacters.length; i++) {
- byteNumbers[i] = byteCharacters.charCodeAt(i);
- }
- const byteArray = new Uint8Array(byteNumbers);
- const blob = new Blob([byteArray], {
- type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
- });
- // 创建下载链接
- const link = document.createElement("a");
- link.href = URL.createObjectURL(blob);
- link.download = "converted.docx"; // 或使用服务器返回的文件名
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- // 清理URL对象
- URL.revokeObjectURL(link.href);
- this.$message.success("转换成功");
- } else {
- throw new Error(res.data.message || "转换失败");
- }
- } catch (error) {
- console.error("PDF转Word失败:", error);
- this.$message.error("PDF转Word失败,请稍后重试");
- }
- },
- // 修改后的导出文档方法
- async exportDocument(template) {
- try {
- // 解析模版内容
- const templateData = JSON.parse(template.data || "[]");
- // 创建一个临时的容器来存放内容
- const contentContainer = document.createElement("div");
- contentContainer.className = "content";
- // 处理每个模版段落
- templateData.forEach((item) => {
- const templateSection = document.createElement("div");
- templateSection.className = "template-textarea";
- // 创建临时div来解析HTML
- const tempDiv = document.createElement("div");
- tempDiv.innerHTML = item.content;
- // 处理标题格式
- tempDiv
- .querySelectorAll("h1, h2, h3, h4, h5, h6")
- .forEach((heading) => {
- heading.style.fontWeight = "bold";
- heading.style.margin = "1em 0";
- switch (heading.tagName.toLowerCase()) {
- case "h1":
- heading.style.fontSize = "24pt";
- break;
- case "h2":
- heading.style.fontSize = "18pt";
- break;
- case "h3":
- heading.style.fontSize = "14pt";
- break;
- case "h4":
- case "h5":
- case "h6":
- heading.style.fontSize = "12pt";
- break;
- }
- });
- // 处理表格
- tempDiv.querySelectorAll("table").forEach((table) => {
- // 设置表格属性
- table.setAttribute("border", "1");
- table.setAttribute("cellspacing", "0");
- table.setAttribute("cellpadding", "5");
- table.style.borderCollapse = "collapse";
- table.style.width = "100%";
- table.style.border = "1px solid black";
- // 处理所有单元格
- const cells = table.querySelectorAll("td, th");
- cells.forEach((cell) => {
- cell.style.border = "1px solid black";
- cell.style.padding = "5px";
- if (cell.tagName === "TH") {
- cell.style.backgroundColor = "#f0f0f0";
- cell.style.fontWeight = "bold";
- }
- });
- });
- templateSection.innerHTML = tempDiv.innerHTML;
- contentContainer.appendChild(templateSection);
- });
- // 构建HTML内容
- let contentData = `
- <!DOCTYPE html>
- <html xmlns:o='urn:schemas-microsoft-com:office:office'
- xmlns:w='urn:schemas-microsoft-com:office:word'
- xmlns='http://www.w3.org/TR/REC-html40'>
- <head>
- <meta charset="UTF-8">
- <title>${template.title}</title>
- <xml>
- <w:WordDocument>
- <w:View>Print</w:View>
- <w:Zoom>100</w:Zoom>
- <w:DoNotOptimizeForBrowser/>
- </w:WordDocument>
- </xml>
- <style>
- /* 重置样式 */
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- }
-
- /* 页面设置 */
- @page {
- size: A4;
- margin: 2cm;
- }
-
- /* 基本样式 */
- body {
- font-family: "Times New Roman", SimSun, serif;
- font-size: 12pt;
- line-height: 1.5;
- }
-
- /* 标题样式 */
- h1 { font-size: 24pt; margin: 1em 0; }
- h2 { font-size: 18pt; margin: 1em 0; }
- h3 { font-size: 14pt; margin: 1em 0; }
- h4, h5, h6 { font-size: 12pt; margin: 1em 0; }
-
- /* 表格样式 */
- table {
- width: 100%;
- border-collapse: collapse;
- border: 1px solid black;
- margin: 1em 0;
- mso-table-lspace: 0pt;
- mso-table-rspace: 0pt;
- mso-border-alt: solid windowtext .5pt;
- }
-
- td, th {
- border: 1px solid black;
- padding: 5px;
- mso-border-alt: solid windowtext .5pt;
- }
-
- th {
- background-color: #f0f0f0;
- font-weight: bold;
- }
-
- /* Word特定样式 */
- .MsoNormal {
- margin-bottom: .0001pt;
- text-align: justify;
- text-justify: inter-ideograph;
- }
-
- /* 分页控制 */
- .template-textarea {
- page-break-after: always;
- }
-
- /* 隐藏目录 */
- .TOC {
- display: none !important;
- }
- </style>
- <!--[if gte mso 9]>
- <xml>
- <w:WordDocument>
- <w:View>Print</w:View>
- <w:Zoom>100</w:Zoom>
- <w:DoNotOptimizeForBrowser/>
- </w:WordDocument>
- </xml>
- <![endif]-->
- </head>
- <body>
- ${contentContainer.innerHTML}
- </body>
- </html>`;
- // 调用导出接口
- const res = await exportDocument({
- content: contentData,
- title: template.title,
- options: {
- format: "docx",
- encoding: "utf-8",
- preserveStyles: true,
- },
- });
- if (res.status !== 200) {
- this.$alert(res.errMsg);
- return;
- }
- // 处理文件下载
- const link = document.createElement("a");
- link.href = res.data.file_path;
- link.download = `${template.title}.docx`;
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- this.$message.success("导出成功");
- } catch (error) {
- console.error("导出文档失败:", error);
- this.$alert("导出文档时发生错误,请稍后重试。");
- }
- },
- /* 客户名称 */
- getCusomName(phaseId) {
- const product = this.customList.find((item) => item.id === phaseId);
- return product ? product.name : "未知客户";
- },
- /* 当前阶段 */
- getPhaseName(phaseId) {
- const product = this.currentPhaseList.find(
- (item) => item.value === phaseId
- );
- return product ? product.label : "未知阶段";
- },
- getPhaseType(phaseId) {
- const product = this.currentPhaseList.find(
- (item) => item.value === phaseId
- );
- if (!product) return "info";
- // 根据不同阶段返回不同的类型
- // 这里的判断逻辑需要根据实际的产品阶来调整
- switch (product.status) {
- case 1:
- return "success"; // 已完成
- case 2:
- return "warning"; // 进行中
- case 3:
- return "danger"; // 待处理
- default:
- return "info"; // 其他状态
- }
- },
- /*目前状态 */
- getStatusName(statusId) {
- const status = this.getstatusList.find((item) => item.value === statusId);
- return status ? status.label : "未知状态";
- },
- getStatusType(statusId) {
- // 这里可以根据不同状态返回不同的类型
- // 可用的类型有:success/warning/danger/info
- const status = this.getstatusList.find((item) => item.value === statusId);
- if (!status) return "info";
- // 根据状态值返回对应的类型
- switch (status.value) {
- case 5:
- return "success";
- case 4:
- return "warning";
- case 3:
- return "danger";
- default:
- return "info";
- }
- },
- // 格式化完成度显示
- formatCompletion(rate) {
- if (rate === undefined || rate === null) {
- return "0%";
- }
- return `${rate}%`;
- },
- // 根据完成度返回对应的CSS类名
- getCompletionClass(rate) {
- if (!rate) return "completion-low";
- if (rate >= 100) return "completion-high";
- if (rate >= 50) return "completion-medium";
- return "completion-low";
- },
- handleCommand(command) {
- switch (command.type) {
- case "template":
- this.selTemplate(command.row);
- break;
- case "enter":
- this.enterPro(command.row);
- break;
- case "edit":
- this.btnEdit(command.row);
- break;
- case "delete":
- this.btnDelete(command.row.id);
- break;
- case "viewTemplates":
- this.viewBoundTemplates(command.row);
- break;
- }
- },
- // Add new methods
- // 查看已绑定模版
- viewBoundTemplates(row) {
- this.currentData = row;
- this.boundTemplateLoading = true;
- searchTemplateList({ ids: row.documentIds.join(",") || 0 })
- .then((res) => {
- if (res.status === 200) {
- this.boundTemplateList = res.data;
- this.boundTemplatesDialogVisible = true;
- }
- })
- .finally(() => {
- this.boundTemplateLoading = false;
- });
- },
- previewTemplate(template) {
- /* this.currentTemplateId = template.id; */
- let _this = this;
- let a = document.createElement("a");
- a.href = "#/document/viewCreate?articleId=" + template.id;
- a.target = "_blank";
- a.click();
- /* getTemplateContent(template.id).then((res) => {
- if (res.status === 200) {
- this.templatePreviewContent = res.data.content;
- this.previewDialogVisible = true;
- }
- }); */
- },
- replaceTemplate(template) {
- this.docAttr = template;
- const TemList = JSON.parse(template.data);
- /* this.$confirm("确定要替换此模版吗?", "提示", {
- confirmButtonText: "确定",
- cancelButtonText: "取消",
- type: "warning",
- }).then(() => { */
- this.replaceVisible = true;
- this.searchEx(TemList);
- /* this.closeTemplateDialog();
- this.selTemplate(this.currentData); */
- /* }); */
- },
- // 新增:处理已绑定模版的选择
- handleBoundTemplateSelect(selection) {
- this.selectedBoundTemplates = selection;
- },
- // 新增:批量替换方法
- async batchReplace() {
- if (this.selectedBoundTemplates.length === 0) {
- this.$message.warning("请选择需要替换的模版");
- return;
- }
- this.batchReplaceLoading = true;
- try {
- // 依次处理每个选中的模版
- for (const template of this.selectedBoundTemplates) {
- this.docAttr = template;
- const TemList = JSON.parse(template.data);
- await this.searchEx(TemList);
- if (this.matchResults.length > 0) {
- await this.replaceAll();
- }
- }
- this.$message.success("批量替换完成");
- this.boundTemplatesDialogVisible = false;
- } catch (error) {
- console.error("批量替换失败:", error);
- this.$message.error("批量替换过程中发生错误");
- } finally {
- this.batchReplaceLoading = false;
- }
- },
- searchEx(val) {
- return new Promise((resolve) => {
- let allMatches = [];
- this.TemList = val;
- this.TemList.forEach((el) => {
- // 创建临时 div 来解析 HTML 内容
- const tempDiv = document.createElement("div");
- tempDiv.innerHTML = el.content;
- const plainText = tempDiv.textContent || tempDiv.innerText;
- // 分别匹配中文括号和英文括号
- const patterns = [
- /【([A-Z]{1,4}[0-9]{0,2})-([A-Z]{1,4}[0-9]{0,2})-([0-9]{2})-([0-9]{2,3})】/g,
- /\[([A-Z]{1,4}[0-9]{0,2})-([A-Z]{1,4}[0-9]{0,2})-([0-9]{2})-([0-9]{2,3})\]/g,
- ];
- patterns.forEach((pattern) => {
- let match;
- while ((match = pattern.exec(plainText)) !== null) {
- const fullMatch = match[0];
- const segments = [match[1], match[2], match[3], match[4]];
- // 验证每个段落的格式
- const isValid = segments.every((segment) => segment.length > 0);
- if (isValid) {
- allMatches.push(fullMatch);
- }
- }
- });
- });
- this.matchResults = [...new Set(allMatches)];
- // 调试用:打印每个匹配的详细信息
- this.matchResults.forEach((match) => {
- // 根据括号类型选择不同的切片方式
- const content = match.startsWith("【")
- ? match.slice(1, -1)
- : match.slice(1, -1);
- const parts = content.split("-");
- });
- resolve();
- });
- },
- // 添加新的辅助方法来判断文档类型
- getDocumentType(code) {
- if (code.includes("-RP-")) return "template_location";
- if (code.includes("-IR-")) return "middle_layer_location";
- if (code.includes("-WE-")) return "tech_report_location";
- return "other_location";
- },
- // 修改格式化请求参数的方法
- formatRequestParams(locations) {
- const locationArray = Array.isArray(locations) ? locations : [locations];
- // 初始化请求参数对象
- const params = {
- /* project_id:'', */
- template_location: [],
- middle_layer_location: [],
- tech_report_location: [],
- other_location: [],
- };
- // 根据不同类型分类存储
- locationArray.forEach((location) => {
- const cleanLocation = location.replace(/[\[\]]/g, "");
- const type = this.getDocumentType(cleanLocation);
- console.log("params", type);
- // 为每种类型创建对应长度的空数组
- Object.keys(params).forEach((key) => {
- if (type === key) {
- params[key].push(cleanLocation);
- } else {
- params[key].push("");
- }
- });
- });
- return params;
- },
- // 解析内容,处理特殊标记
- parseContent(content) {
- if (!content) return "";
- let parsed = content;
- // 处理图片标记
- parsed = parsed.replace(
- /\[图片:(.*?)\|style=(.*?)\|class=(.*?)\]/g,
- (match, src, style, className) => {
- return `<img src="${src}" alt="图片" style="${style}" class="${className}">`;
- }
- );
- // 处理表格标记
- parsed = parsed.replace(
- /\[表格\|style=(.*?)\|class=(.*?)\]\n([\s\S]*?)(?=\[|$)/g,
- (match, tableStyle, tableClass, tableContent) => {
- const rows = tableContent.trim().split("\n");
- let tableHtml = `<table style="${tableStyle}" class="${tableClass}">`;
- rows.forEach((row) => {
- tableHtml += "<tr>";
- const cells = row.split(" | ").map((cell) => {
- const [content, ...styleInfo] = cell.split("[style=");
- if (styleInfo.length) {
- const [style, className] = styleInfo[0]
- .slice(0, -1)
- .split("|class=");
- return `<td style="${style}" class="${className}">${content}</td>`;
- }
- return `<td>${content}</td>`;
- });
- tableHtml += cells.join("") + "</tr>";
- });
- tableHtml += "</table>";
- return tableHtml;
- }
- );
- // 处理文本样式
- parsed = parsed.replace(
- /\[文本:(.*?)\|style=(.*?)\|class=(.*?)\]/g,
- (match, content, style, className) => {
- return `<span style="${style}" class="${className}">${content}</span>`;
- }
- );
- return parsed;
- },
- // 检查内容是否包含表格或图片
- containsTableOrImage(content) {
- return content.includes("<table") || content.includes("<img");
- },
- // 获取所有文本节点
- getTextNodes(node) {
- const textNodes = [];
- const walk = document.createTreeWalker(
- node,
- NodeFilter.SHOW_TEXT,
- null,
- false
- );
- let currentNode;
- while ((currentNode = walk.nextNode())) {
- textNodes.push(currentNode);
- }
- return textNodes;
- },
- /*更新 */
- onUpload() {
- let _this = this;
- if (_this.TemList.length <= 0) {
- _this.$message.error("增加组件");
- return;
- }
- if (_this.docAttr.title == "") {
- _this.$message.error("请填写模版标题");
- return;
- }
- _this.docAttr.links = JSON.stringify(_this.docAttr.linkProduct);
- _this.docAttr.projects = JSON.stringify(_this.docAttr.linkProject);
- _this.docAttr.data = JSON.stringify(_this.TemList);
- _this.docAttr.is_template = 1;
- _this.docAttr.id = _this.docAttr.id;
- _this.docAttr.user_name = _this.docAttr.name;
- _this.docAttr.user_id = _this.docAttr.id;
- updateDocument(_this.docAttr).then((res) => {
- if (res.status != 200) return; //更新文档
- _this.docAttr.id = res.data;
- _this.$message.success("模版更新成功");
- });
- },
- // 添加统一的内容处理方法
- async processAndReplaceContent(content, originalMatch) {
- try {
- const params = this.formatRequestParams(originalMatch);
- params.project_id = this.currentData.id;
- const response = await axios.post(
- `${process.env.VUE_APP_BASE_API}/project-raw-data/batch-content`,
- params,
- {
- headers: {
- "Content-Type": "application/json",
- },
- }
- );
- if (response.status === 200 && response.data) {
- const replacementContent =
- response.data.data.matched_data[0]?.content;
- if (replacementContent) {
- // 处理富文本内容
- this.TemList.forEach((el, index) => {
- console.log(el, originalMatch, replacementContent);
- const processedContent = this.processTableContent(
- el.content,
- originalMatch,
- replacementContent
- );
- this.$set(this.TemList[index], "content", processedContent);
- });
- return true;
- }
- }
- return false;
- } catch (error) {
- console.error("处理内容失败:", error);
- return false;
- }
- },
- // 修改单个替换方法
- async replaceItem(item) {
- try {
- const success = await this.processAndReplaceContent(this.TemList, item);
- if (success) {
- this.matchResults = this.matchResults.filter(
- (match) => match !== item
- );
- this.onUpload();
- this.$message.success("替换成功");
- } else {
- this.$message.warning("未获取到替换内容");
- }
- } catch (error) {
- console.error("替换失败:", error);
- this.$message.error(error.response?.data?.message || "替换失败");
- }
- },
- // 修改批量替换方法
- async replaceAll() {
- this.batchReplaceLoading = true;
- try {
- // 收集所有匹配
- const allLocations = this.matchResults.map((item) => {
- // 清理括号
- return item.replace(/[\[\]【】]/g, "");
- });
- // 格式化所有位置的请求参数
- const params = {
- template_location: [],
- middle_layer_location: [],
- tech_report_location: [],
- other_location: [],
- };
- // 遍历所有位置并分类
- allLocations.forEach((location) => {
- // 为每种类型创建对应长度的数组
- const type = this.getDocumentType(location);
- Object.keys(params).forEach((key) => {
- if (type === key) {
- params[key].push(location);
- } else {
- params[key].push("");
- }
- });
- });
- params.project_id = this.currentData.id;
- // 发送单个批量请求
- const response = await axios.post(
- `${process.env.VUE_APP_BASE_API}/project-raw-data/batch-content`,
- params,
- {
- headers: {
- "Content-Type": "application/json",
- },
- }
- );
- if (response.status === 200 && response.data) {
- const replacementContents = response.data.data.matched_data;
- if (replacementContents && replacementContents.length > 0) {
- // 更新所有内容
- this.TemList.forEach((el, index) => {
- let updatedContent = el.content;
- // 遍历所有替内容和对应的匹配项
- this.matchResults.forEach((item, idx) => {
- const replacement = replacementContents[idx]?.content;
- if (replacement) {
- // 处理表格和其他内容
- updatedContent = this.processTableContent(
- updatedContent,
- item,
- replacement
- );
- }
- });
- // 更新内容
- this.$set(this.TemList[index], "content", updatedContent);
- });
- // 更新文档
- await this.onUpload();
- this.matchResults = [];
- this.$message.success("批量替换成功");
- this.replaceVisible = false;
- } else {
- this.$message.warning("未获取到替换内容");
- }
- }
- } catch (error) {
- console.error("批量替换失败:", error);
- this.$message.error(error.response?.data?.message || "批量替换失败");
- } finally {
- this.batchReplaceLoading = false;
- }
- },
- // 辅助方法:获取文档类型
- getDocumentType(code) {
- if (code.includes("-RP-")) return "template_location";
- if (code.includes("-IR-")) return "middle_layer_location";
- if (code.includes("-WE-")) return "tech_report_location";
- return "other_location";
- },
- // 修改处理表格内容的方法,添加对多个替换的支持
- processTableContent(content, searchItem, replacement) {
- // 直接使用字符串替换,不处理 DOM
- if (!content || !searchItem) return content;
- try {
- const regex = new RegExp(this.escapeRegExp(searchItem), "g");
- return content.replace(regex, replacement);
- } catch (error) {
- console.error("替换内容时出错:", error);
- return content;
- }
- },
- // 添加样式到 convertTextToHtml 方法
- convertTextToHtml(text) {
- if (!text) return "";
- let html = text;
- // 处理特定格式的表格
- html = html.replace(
- /\[表格\|style=(.*?)\|class=(.*?)\]([\s\S]*?)(?=\[|$)/g,
- (match, tableStyle, tableClass, tableContent) => {
- const rows = tableContent.trim().split("\n");
- let tableHtml = `<table style="${tableStyle}; border-collapse: collapse;" class="${tableClass}">`;
- rows.forEach((row, index) => {
- const [label, value] = row.split("|").map((item) => item.trim());
- if (label) {
- tableHtml += "<tr>";
- if (value) {
- // 如果有值,创建两列的行
- tableHtml += `
- <td style="border: 1px solid #000; padding: 8px;">${label}</td>
- <td style="border: 1px solid #000; padding: 8px;">${value}</td>
- `;
- } else {
- // 如果只有标签,创建单列的行(可能是表头)
- tableHtml += `
- <td style="border: 1px solid #000; padding: 8px; font-weight: ${
- index === 0 ? "bold" : "normal"
- }">${label}</td>
- `;
- }
- tableHtml += "</tr>";
- }
- });
- tableHtml += "</table>";
- return tableHtml;
- }
- );
- // 保持原有的图片和文本处理逻辑不变
- html = html.replace(
- /\[图片:(.*?)\|style=(.*?)\|class=(.*?)\]/g,
- (match, src, style, className) => {
- return `<img src="${src}" alt="图片" style="${style}" class="${className}">`;
- }
- );
- html = html.replace(
- /\[文本:(.*?)\|style=(.*?)\|class=(.*?)\]/g,
- (match, content, style, className) => {
- return `<span style="${style}" class="${className}">${content}</span>`;
- }
- );
- return html;
- },
- // 更新 escapeRegExp 方法以支持两种括号类型
- escapeRegExp(string) {
- const isChinese = string.startsWith("【");
- const content = isChinese
- ? string.replace(/^【|】$/g, "")
- : string.replace(/^\[|\]$/g, "");
- const escaped = content.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
- return isChinese ? `\\【${escaped}\\】` : `\\[${escaped}\\]`;
- },
- /* 选择模版 */
- selTemplate(row) {
- this.currentData = row;
- this.selectedTemplates = []; // 重置选择
- this.selectedTemplateIds = new Set(row.documentIds || []); // 初始化已绑定的模版ID
- this.templateQueryForm.page = 1; // 重置���码
- this.loadTemplateList();
- },
- // 处理模版分页变化
- handleTemplatePageChange(page) {
- // 保存当前页的选择状态
- const currentSelection = this.$refs.templateTable.selection;
- if (currentSelection.length > 0) {
- this.selectedPageIds.set(
- this.templateQueryForm.page,
- new Set(currentSelection.map((item) => item.id))
- );
- }
- this.templateQueryForm.page = page;
- this.loadTemplateList();
- },
- // 加载模版列表
- loadTemplateList() {
- this.templateLoading = true;
- searchList(this.templateQueryForm)
- .then((res) => {
- if (res.status === 200) {
- this.templateList = res.data.dataList;
- this.templateTotal = res.data.totalRecord;
- this.templateDialogVisible = true;
- // 恢复选中状态
- this.$nextTick(() => {
- const currentPage = this.templateQueryForm.page;
- const currentPageSelected = this.selectedPageIds.get(currentPage);
- this.templateList.forEach((row) => {
- const shouldSelect =
- this.isAllSelected ||
- (currentPageSelected && currentPageSelected.has(row.id)) ||
- this.selectedTemplateIds.has(row.id) ||
- (this.currentData.documentIds &&
- this.currentData.documentIds.includes(row.id));
- if (shouldSelect) {
- this.$refs.templateTable.toggleRowSelection(row, true);
- }
- });
- });
- }
- })
- .finally(() => {
- this.templateLoading = false;
- });
- },
- handleTemplateSelect(selection) {
- const currentPage = this.templateQueryForm.page;
- const currentPageIds = selection.map((item) => item.id);
- // 更新当前页的选择状态
- if (currentPageIds.length > 0) {
- this.selectedPageIds.set(currentPage, new Set(currentPageIds));
- } else {
- this.selectedPageIds.delete(currentPage);
- }
- // 如果之前是全选状态,现在取消了某些选择
- if (this.isAllSelected && selection.length < this.templateList.length) {
- this.isAllSelected = false;
- // 保留其他页面的选择和已绑定的模版
- const allSelectedIds = new Set(this.currentData.documentIds || []);
- this.selectedPageIds.forEach((pageIds, page) => {
- if (page !== currentPage) {
- pageIds.forEach((id) => allSelectedIds.add(id));
- }
- });
- // 添加当前页选中的ID
- currentPageIds.forEach((id) => allSelectedIds.add(id));
- this.selectedTemplateIds = allSelectedIds;
- } else {
- // 正常选择模式,保留已绑定的模版
- const existingIds = new Set(this.currentData.documentIds || []);
- this.templateList.forEach((item) => {
- if (currentPageIds.includes(item.id)) {
- this.selectedTemplateIds.add(item.id);
- } else if (!existingIds.has(item.id)) {
- // 只删除非已绑定的模版
- this.selectedTemplateIds.delete(item.id);
- }
- });
- }
- },
- // 处理全选按点击
- handleSelectAll(selection) {
- const currentPage = this.templateQueryForm.page;
- if (selection.length > 0) {
- this.$confirm("是否选择所有页的模版?", "提示", {
- confirmButtonText: "全部选择",
- cancelButtonText: "仅选择当前页",
- type: "info",
- })
- .then(() => {
- // 选择所有页
- this.selectAllPages();
- })
- .catch(() => {
- // 仅选择当前页
- this.selectCurrentPage(selection);
- // 更新当前页的选择记录
- this.selectedPageIds.set(
- currentPage,
- new Set(selection.map((item) => item.id))
- );
- });
- } else {
- // 取消全选
- this.isAllSelected = false;
- this.selectedTemplateIds.clear();
- this.selectedPageIds.clear();
- this.$refs.templateTable.clearSelection();
- }
- },
- // 选择所有页
- async selectAllPages() {
- this.isAllSelected = true;
- this.selectedTemplateIds.clear();
- // 获取所有模版ID
- try {
- const allTemplates = await this.getAllTemplates();
- allTemplates.forEach((template) => {
- this.selectedTemplateIds.add(template.id);
- });
- // 选中当前页显示的所有行
- this.templateList.forEach((row) => {
- this.$refs.templateTable.toggleRowSelection(row, true);
- });
- this.$message.success(`已选择全部 ${this.templateTotal} 条数据`);
- } catch (error) {
- console.error("获取所有模版失败:", error);
- this.$message.error("获取所有模版失败");
- }
- },
- // 仅选择当前页
- selectCurrentPage(selection) {
- this.isAllSelected = false;
- // 清除之前的选择
- this.selectedTemplateIds.clear();
- // 添加当前页选中项
- selection.forEach((item) => {
- this.selectedTemplateIds.add(item.id);
- });
- },
- // 获取所有模版数据
- async getAllTemplates() {
- const pageSize = 999; // 使用较大的页码一次性获取
- const params = {
- ...this.templateQueryForm,
- page: 1,
- pageSize,
- };
- try {
- const res = await searchList(params);
- if (res.status === 200) {
- return res.data.dataList;
- }
- return [];
- } catch (error) {
- console.error("获取所有模版失败:", error);
- return [];
- }
- },
- // 确认选择
- confirmTemplateSelection() {
- if (this.selectedTemplateIds.size === 0) {
- this.$message.warning("请至少选择一个模版");
- return;
- }
- // 合并已有的文档ID和新选择的ID
- const allDocIds = new Set([
- ...(this.currentData.documentIds || []),
- ...this.selectedTemplateIds,
- ]);
- relate({
- project_id: this.currentData.id,
- doc_ids: JSON.stringify([...allDocIds]),
- }).then((res) => {
- if (res.status !== 200) return;
- this.$message.success("模版关联成功");
- this.closeTemplateDialog();
- this.search();
- });
- },
- // 关对话框
- closeTemplateDialog() {
- this.templateDialogVisible = false;
- this.selectedTemplateIds.clear();
- this.selectedPageIds.clear();
- this.isAllSelected = false;
- this.$refs.templateTable.clearSelection();
- },
- // 输入项目
- enterPro(row) {
- if (row.documentIds.length == 0) {
- this.$message.warning("请先绑定模版!");
- return;
- }
- // 获取已绑定模版的替换位置信息
- searchTemplateList({ ids: row.documentIds.join(",") || 0 })
- .then(async (res) => {
- if (res.status === 200) {
- const templates = res.data;
- const locations = [];
- // 遍历所有模版,提取替换位置
- templates.forEach((template) => {
- if (template.data) {
- const templateData = JSON.parse(template.data);
- templateData.forEach((item) => {
- const tempDiv = document.createElement("div");
- tempDiv.innerHTML = item.content;
- const plainText = tempDiv.textContent || tempDiv.innerText;
- const patterns = [
- /【([A-Z]{1,4}[0-9]{0,2})-([A-Z]{1,4}[0-9]{0,2})-([0-9]{2})-([0-9]{2,3})】/g,
- /\[([A-Z]{1,4}[0-9]{0,2})-([A-Z]{1,4}[0-9]{0,2})-([0-9]{2})-([0-9]{2,3})\]/g,
- ];
- patterns.forEach((pattern) => {
- let match;
- while ((match = pattern.exec(plainText)) !== null) {
- locations.push({
- template_id: template.id,
- template_title: template.title,
- location: match[0],
- type: this.getDocumentType(match[0]),
- });
- }
- });
- });
- }
- });
- try {
- // 按类型分组位置信息
- const params = {
- template_location: [],
- middle_layer_location: [],
- tech_report_location: [],
- other_location: [],
- };
- // 遍历所有位置并分类
- locations.forEach((loc) => {
- Object.keys(params).forEach((key) => {
- if (loc.type === key) {
- params[key].push(loc.location.replace(/[\[\]【】]/g, "")); // 清除括号
- } else {
- params[key].push("");
- }
- });
- });
- params.project_id = row.id;
- // 获取替换内容
- const response = await axios.post(
- `${process.env.VUE_APP_BASE_API}/project-raw-data/batch-content`,
- params,
- {
- headers: {
- "Content-Type": "application/json",
- },
- }
- );
- if (response.status === 200 && response.data) {
- const replacementContents = response.data.data.matched_data;
- // 将位置信息和对应的替换内容组合
- const enrichedLocations = locations.map((loc, index) => ({
- ...loc,
- content: replacementContents[index]?.content || null,
- template_location:
- replacementContents[index]?.template_location || null,
- tech_report_location:
- replacementContents[index]?.tech_report_location || [],
- }));
- // 对整个 enrichedLocations 进行去重
- const uniqueEnrichedLocations = enrichedLocations.reduce(
- (acc, current) => {
- const isDuplicate = acc.find(
- (item) =>
- item.template_id === current.template_id &&
- item.location === current.location
- );
- if (!isDuplicate) {
- // 如果不是重复项,则添加到结果数组中
- return [...acc, current];
- }
- return acc;
- },
- []
- );
- // 使用去重后的数据
- this.$router.push({
- path: "/project/ProjectInput",
- query: {
- id: row.id,
- projectData: encodeURIComponent(
- JSON.stringify({
- locations: uniqueEnrichedLocations, // 使用去重后的数据
- templates: templates.map((t) => ({
- id: t.id,
- title: t.title,
- })),
- })
- ),
- },
- });
- }
- } catch (error) {
- console.error("获取替换内容失败:", error);
- this.$message.error("获取替换内容失败");
- }
- }
- })
- .catch((error) => {
- console.error("获取模版位置信息失败:", error);
- this.$message.error("获取模版位置信息失败");
- });
- },
- /* 产品分类名称 */
- getCategoryTypeName(categoryId) {
- if (!categoryId) return "未分类";
- const product = this.productList.find((item) => item.id === categoryId);
- return product && product.category ? product.category.name : "未知分类";
- },
- getCategoryName(categoryId) {
- if (!categoryId) return "未知产品";
- const product = this.productList.find((item) => item.id === categoryId);
- return product ? product.name : "未知产品";
- },
- searchData() {
- let _this = this;
- _this.dialogVisible = false;
- _this.search();
- },
- btnDelete(e) {
- let _this = this;
- let par = {
- id: e,
- };
- _this
- .$confirm("您是否确认删除该记录?", "提示", {
- confirmButtonText: "确认",
- cancelButtonText: "取消",
- type: "warning",
- })
- .then((res) => {
- // console.log("AAA")
- deleteProject(par).then((res) => {
- _this.search();
- });
- })
- .catch(() => {});
- },
- //编辑
- btnEdit(e) {
- let _this = this;
- _this.currentDataId = e.id;
- _this.dialogVisible = true;
- },
- onClose(needRefresh) {
- console.log("onClose triggered with needRefresh:", needRefresh); // 添加调试日志
- this.currentDataId = 0;
- this.dialogVisible = false;
- if (needRefresh) {
- this.search();
- }
- },
- handleUpdate() {
- this.search(); // 添加额外的更新处理方法
- },
- //搜索
- async search() {
- try {
- const res = await searchProject(this.queryForm);
- if (!res) return;
- // 获取所有项目的完成度
- const calculatePromises = res.data.dataList.map(async (item) => {
- try {
- const calcRes = await calculate({ id: item.id });
- if (calcRes.status === 200) {
- item.calculate = calcRes.data.completionRate;
- }
- } catch (err) {
- console.error(`获取项目${item.id}完成度失败:`, err);
- item.calculate = 0;
- }
- });
- await Promise.all(calculatePromises);
- this.dataList = res.data.dataList;
- this.recordCount = res.data.totalRecord;
- this.pageTotal = res.data.totalPage;
- } catch (err) {
- console.error("搜索失败:", err);
- this.$message.error("获取项目列表失");
- }
- },
- //修改分页
- ChangePage(e) {
- let _this = this;
- _this.queryForm.page = e;
- _this.search();
- },
- init() {
- searchCategory().then((res) => {
- if (res.status !== 200) return;
- this.categoryList = res.data.dataList;
- });
- /* 获取阶段 */
- getphases().then((res) => {
- this.currentPhaseList = res.data.options;
- });
- /* 获取目前态 */
- getstatus().then((res) => {
- this.getstatusList = res.data.options;
- });
- /* 获取客户列表 */
- searchCustomer({ page: 1, pageSize: 999 }).then((res) => {
- this.customList = res.data.dataList;
- });
- /* 获取产品列表 */
- searchProduct({ page: 1, pageSize: 999 }).then((res) => {
- this.productList = res.data.dataList;
- });
- },
- // 新增:处理导出命令
- async handleExport(command, row) {
- switch (command) {
- case "word":
- await this.exportDocument(row);
- break;
- case "pdf":
- await this.exportToPDF(row);
- break;
- case "pdf-to-word": // 添加新的命令类型
- const pdfResult = await this.exportToPDF_url(row);
- if (pdfResult && pdfResult.file_path) {
- await this.convertPDFToWord(pdfResult.file_path);
- }
- break;
- }
- },
- /* 过去pdf链接 */
- async exportToPDF_url(template) {
- try {
- // 解析模版内容
- const templateData = JSON.parse(template.data || "[]");
- // 创建一个临时的容器来存放内容
- const contentContainer = document.createElement("div");
- contentContainer.className = "content";
- // 处理每个模版段落
- templateData.forEach((item) => {
- const templateSection = document.createElement("div");
- templateSection.className = "template-textarea";
- templateSection.innerHTML = item.content;
- contentContainer.appendChild(templateSection);
- });
- // 处理所有输入框
- contentContainer
- .querySelectorAll('input[type="text"]')
- .forEach((input) => {
- const span = document.createElement("span");
- span.textContent = input.value;
- input.parentNode.replaceChild(span, input);
- });
- // 构建HTML内容
- let contentData = `<!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <style>
- @page {
- size: A4;
- margin: 2cm;
- }
- body {
- font-family: Arial, sans-serif;
- font-size: 12pt;
- line-height: 1.5;
- }
- table {
- width: 100%;
- border-collapse: collapse;
- }
- td, th {
- border: 1px solid black;
- padding: 5px;
- }
- .template-textarea {
- page-break-after: always;
- }
- .TOC {
- display: none !important;
- }
- </style>
- </head>
- <body>
- ${contentContainer.innerHTML}
- </body>
- </html>`;
- /* // 处理表格样式
- contentData = contentData
- .replaceAll("<table ", `<table style="border-collapse: collapse;" `)
- .replaceAll("<td>", `<td style="border: 1px solid black;">`); */
- // 调用导出接口,指定导出类型为 PDF
- const res = await exportToPDF({
- content: contentData,
- title: template.title,
- });
- if (res.status !== 200) {
- this.$alert(res.errMsg);
- return;
- }
- // 处理文件下载
- return res.data; // 返回结果,包含 file_path
- } catch (error) {
- console.error("导出PDF失败:", error);
- this.$alert("导出PDF时发生错误,请稍后重试。");
- }
- },
- // 添加新的 exportToPDF 方法
- async exportToPDF(template) {
- try {
- // 解析模版内容
- const templateData = JSON.parse(template.data || "[]");
- // 创建一个临时的容器来存放内容
- const contentContainer = document.createElement("div");
- contentContainer.className = "content";
- // 处理每个模版段落
- templateData.forEach((item) => {
- const templateSection = document.createElement("div");
- templateSection.className = "template-textarea";
- templateSection.innerHTML = item.content;
- contentContainer.appendChild(templateSection);
- });
- // 处理所有输入框
- contentContainer
- .querySelectorAll('input[type="text"]')
- .forEach((input) => {
- const span = document.createElement("span");
- span.textContent = input.value;
- input.parentNode.replaceChild(span, input);
- });
- // 构建HTML内容
- let contentData = `<!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <style>
- @page {
- size: A4;
- margin: 2cm;
- }
- body {
- font-family: Arial, sans-serif;
- font-size: 12pt;
- line-height: 1.5;
- }
- table {
- width: 100%;
- border-collapse: collapse;
- }
- td, th {
- border: 1px solid black;
- padding: 5px;
- }
- .template-textarea {
- page-break-after: always;
- }
- .TOC {
- display: none !important;
- }
- </style>
- </head>
- <body>
- ${contentContainer.innerHTML}
- </body>
- </html>`;
- /* // 处理表格样式
- contentData = contentData
- .replaceAll("<table ", `<table style="border-collapse: collapse;" `)
- .replaceAll("<td>", `<td style="border: 1px solid black;">`); */
- // 调用导出接口,指定导出类型为 PDF
- const res = await exportToPDF({
- content: contentData,
- title: template.title,
- });
- if (res.status !== 200) {
- this.$alert(res.errMsg);
- return;
- }
- // 处理文件下载
- const link = document.createElement("a");
- link.href = res.data.file_path;
- link.download = res.data.file_name;
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- this.$message.success("导出成功");
- return res.data; // 返回结果,包含 file_path
- } catch (error) {
- console.error("导出PDF失败:", error);
- this.$alert("导出PDF时发生错误,请稍后重试。");
- }
- },
- },
- };
- </script>
- <style lang="scss" scoped>
- @import "./dataList.scss";
- .match-results {
- margin-top: 20px;
- .match-item {
- padding: 10px;
- border-bottom: 1px solid #eee;
- display: flex;
- justify-content: space-between;
- align-items: center;
- .match-content {
- flex: 1;
- display: flex;
- gap: 20px;
- }
- .actions {
- margin-left: 10px;
- }
- }
- }
- // 完成度文字样式
- .completion-high {
- color: #67c23a;
- font-weight: bold;
- }
- .completion-medium {
- color: #e6a23c;
- font-weight: bold;
- }
- .completion-low {
- color: #f56c6c;
- font-weight: bold;
- }
- // 添加PDF导出相关样式
- .content {
- background: white;
- .template-textarea {
- page-break-after: always;
- table {
- width: 100%;
- border-collapse: collapse;
- td,
- th {
- border: 1px solid black;
- padding: 5px;
- }
- }
- }
- }
- </style>
|