index.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. <template>
  2. <div class="template-textarea">
  3. <template v-if="isEdit == 1">
  4. <div class="editor-area">
  5. <ckeditor
  6. ref="editor"
  7. v-model="com.content"
  8. @focus="onFocus"
  9. @blur="onBlur"
  10. @input="onInputText"
  11. @ready="onEditorReady"
  12. :config="editorConfig"
  13. :editorUrl="editorUrl"
  14. ></ckeditor>
  15. </div>
  16. </template>
  17. <template v-else>
  18. <div class="rich-editor" v-html="content"></div>
  19. </template>
  20. <div v-if="loading" class="overlay">
  21. <el-progress
  22. :percentage="progress"
  23. class="full-width-progress"
  24. ></el-progress>
  25. </div>
  26. </div>
  27. </template>
  28. <script>
  29. // import CKEditor from "ckeditor4-vue";
  30. import { findData } from "@/api/sourceData";
  31. export default {
  32. name: "app",
  33. emits: ["onUpdate", "onUpdateAttr", "onUpdateProdAttr"],
  34. compnents: {
  35. // ckeditor: CKEditor.component
  36. },
  37. props: {
  38. coms: {
  39. type: Array,
  40. default: () => {
  41. return [];
  42. },
  43. },
  44. isEdit: {
  45. type: Number,
  46. default: 2,
  47. },
  48. isAdmin: {
  49. type: Number,
  50. default: 2,
  51. },
  52. currentIndex: {
  53. type: Number,
  54. default: 0,
  55. },
  56. com: {
  57. type: Object,
  58. default: null,
  59. },
  60. insertCmd: {
  61. type: Object,
  62. default: null,
  63. },
  64. },
  65. watch: {
  66. isEdit: {
  67. handler(val) {
  68. let _this = this;
  69. if (_this.com != null) return;
  70. _this.replaceData(_this.com.content).then((res) => {
  71. _this.content = res;
  72. _this.$nextTick(() => {
  73. _this.bindEvents();
  74. });
  75. });
  76. },
  77. immediate: true,
  78. deep: true,
  79. },
  80. com: {
  81. async handler(val) {
  82. let _this = this;
  83. if (val == null) return;
  84. if (val.content == undefined || val.content == null) return;
  85. try {
  86. console.log("com",val);
  87. let res = await _this.replaceData(val.content);
  88. _this.content = res;
  89. _this.$nextTick(() => {
  90. _this.bindEvents();
  91. });
  92. } catch (error) {
  93. console.error("处理 com 时出错:", error);
  94. }
  95. },
  96. immediate: true,
  97. deep: true,
  98. },
  99. insertCmd: {
  100. handler(val) {
  101. if (val == null || this.isEdit != 1) return;
  102. let data = this.$refs.editor.instance.getSelection().getSelectedText();
  103. if (val.content.indexOf("Directory", 0) >= 0) {
  104. //执行的是目录
  105. this.$emit(
  106. "onUpdateAttr",
  107. this.currentIndex,
  108. this.com.attrs.length - 1,
  109. data
  110. );
  111. this.$refs.editor.instance.execCommand("delete");
  112. }
  113. this.$refs.editor.instance.insertHtml(val.content);
  114. },
  115. immediate: true,
  116. deep: true,
  117. },
  118. },
  119. data() {
  120. return {
  121. editorUrl: "/ckeditor/ckeditor.js",
  122. editorConfig: {
  123. language: "zh-cn",
  124. height: "800px",
  125. },
  126. content: "",
  127. editor: null,
  128. loading: false,
  129. progress: 0,
  130. isEditing: false,
  131. focusedInputId: null,
  132. };
  133. },
  134. mounted() {},
  135. // 记得在组件销毁时移除事件监听器
  136. beforeDestroy() {
  137. this.$el.removeEventListener("input", this.handleInputChange);
  138. },
  139. methods: {
  140. async replaceData(data) {
  141. let _this = this;
  142. for (var i = 0; i < _this.com.attrs.length; i++) {
  143. let attrId = _this.com.attrs[i].id;
  144. if (_this.com.attrs[i].type == "variable") {
  145. let item = _this.com.attrs[i];
  146. if (item.data.value_type == 2) {
  147. //多选
  148. let dataItem = item.data.value_item.split(",");
  149. let selectHtml =
  150. '<select id="' +
  151. attrId +
  152. '" data-index="' +
  153. i +
  154. '" class="text-input-box">';
  155. for (var l = 0; l < dataItem.length; l++) {
  156. if (item.content == dataItem[l]) {
  157. selectHtml +=
  158. '<option value="' +
  159. dataItem[l] +
  160. '" selected>' +
  161. dataItem[l] +
  162. "</option>";
  163. } else {
  164. selectHtml +=
  165. '<option value="' +
  166. dataItem[l] +
  167. '">' +
  168. dataItem[l] +
  169. "</option>";
  170. }
  171. }
  172. selectHtml += "</select>";
  173. data = data.replace("{{" + item.id + "}}", selectHtml);
  174. } else {
  175. //文本
  176. data = data.replace(
  177. "{{" + attrId + "}}",
  178. /* `<input type="text" class="text-input-box" data-index="${i}" value="${_this.com.attrs[i].content}" @input="updateContent('${_this.com.attrs[i].id}', $event)" id="${attrId}">`, */
  179. '<input type="text" ref="input_' +
  180. attrId +
  181. '" name="' +
  182. _this.com.attrs[i].name +
  183. '" id="' +
  184. attrId +
  185. '" data-index="' +
  186. i +
  187. '" class="text-input-box" value="' +
  188. _this.com.attrs[i].content +
  189. '">'
  190. );
  191. }
  192. } else if (_this.com.attrs[i].type == "ProductAttr") {
  193. //处理产品属性值
  194. let item = _this.com.attrs[i];
  195. let prodAttrId = item.id + "_" + i;
  196. if (item.content == "") {
  197. item.content = item.attrs.value;
  198. }
  199. if (item.attrs.type == 1) {
  200. //输入框
  201. data = data.replace(
  202. "{{" + item.id + "}}",
  203. '<input type="text" id="' +
  204. prodAttrId +
  205. '" data-index="' +
  206. i +
  207. '" class="text-input-box" value="' +
  208. item.content +
  209. '">'
  210. );
  211. } else {
  212. //选择框
  213. let dataItem = item.attrs.valueItems.split(",");
  214. let selectHtml =
  215. '<select id="' +
  216. prodAttrId +
  217. '" data-index="' +
  218. i +
  219. '" class="text-input-box">';
  220. for (var l = 0; l < dataItem.length; l++) {
  221. if (item.content == dataItem[l]) {
  222. selectHtml +=
  223. '<option value="' +
  224. dataItem[l] +
  225. '" selected>' +
  226. dataItem[l] +
  227. "</option>";
  228. } else {
  229. selectHtml +=
  230. '<option value="' +
  231. dataItem[l] +
  232. '">' +
  233. dataItem[l] +
  234. "</option>";
  235. }
  236. }
  237. selectHtml += "</select>";
  238. data = data.replace("{{" + item.id + "}}", selectHtml);
  239. }
  240. } else if (_this.com.attrs[i].type == "formual") {
  241. //// console.log('attrs',_this.com.attrs[i]);
  242. //处理计算公式 不处理
  243. let formual = await _this.analysisFormual(_this.com.attrs[i]);
  244. formual = await _this.getRemote(formual);
  245. _this.com.attrs[i].content = eval(formual);
  246. data = data.replace("{{" + attrId + "}}", eval(formual));
  247. /* let formual = await _this.analysisFormual(_this.com.attrs[i]);
  248. formual = await _this.getRemote(formual);
  249. // 使用 try-catch 来处理可能的计算错误
  250. try {
  251. let result = eval(formual);
  252. data = data.replace("{{" + attrId + "}}", result);
  253. } catch (error) {
  254. data = data.replace("{{" + attrId + "}}", "计算错误");
  255. } */
  256. } else if (_this.com.attrs[i].type == "sourceData") {
  257. let result = await _this.getRemote1(_this.com.attrs[i].formula);
  258. data = data.replace("{{" + attrId + "}}", result);
  259. } else if (_this.com.attrs[i].type == "Directory") {
  260. const directoryContent = _this.com.attrs[i].content;
  261. const attrId = _this.com.attrs[i].id;
  262. // Create a more flexible regex pattern
  263. const directoryRegex = new RegExp(`{{\\s*${attrId}\\s*}}`, "g");
  264. // Replace the content, preserving surrounding HTML tags
  265. data = data.replace(directoryRegex, (match, offset, string) => {
  266. // Check if the match is inside a tag
  267. const precedingText = string.substring(0, offset);
  268. const followingText = string.substring(offset + match.length);
  269. const precedingTag = precedingText.match(/<([^>]+)>$/);
  270. const followingCloseTag = followingText.match(/^<\/([^>]+)>/);
  271. if (
  272. precedingTag &&
  273. followingCloseTag &&
  274. precedingTag[1] === followingCloseTag[1]
  275. ) {
  276. // If it's inside matching tags, replace the entire element
  277. return `<h1>${directoryContent}</h1>`;
  278. } else {
  279. // Otherwise, just replace the placeholder
  280. return directoryContent;
  281. }
  282. });
  283. //处理目录
  284. /* data = data.replace(
  285. "<p>{{" + attrId + "}}</p>",
  286. "<h1>" + _this.com.attrs[i].content + "</h1>"
  287. );
  288. data = data.replace(
  289. "<div>{{" + attrId + "}}</div>",
  290. "<h1>" + _this.com.attrs[i].content + "</h1>"
  291. );
  292. data = data.replace(
  293. "<span>{{" + attrId + "}}</span>",
  294. "<h1>" + _this.com.attrs[i].content + "</h1>"
  295. ); */
  296. //处理目录
  297. /* const directoryContent = _this.com.attrs[i].content;
  298. const directoryRegex = new RegExp(
  299. `<(p|div|span)>{{${attrId}}}</(p|div|span)>`,
  300. "g"
  301. );
  302. data = data.replace(directoryRegex, `<h1>${directoryContent}</h1>`); */
  303. } else {
  304. //处理常量及其它
  305. data = data.replace("{{" + attrId + "}}", _this.com.attrs[i].content);
  306. }
  307. }
  308. this.$nextTick(() => {
  309. this.addInputListeners();
  310. });
  311. return data;
  312. },
  313. addInputListeners() {
  314. // 使用事件委托来处理所有的 .text-input-box 元素
  315. this.$el.addEventListener("blur", this.handleInputChange, true);
  316. },
  317. handleInputChange(event) {
  318. if (event.target.classList.contains("text-input-box")) {
  319. const attrId = event.target.id;
  320. const attrName = event.target.name;
  321. const dataIndex = parseInt(event.target.dataset.index, 10);
  322. const newValue = event.target.value;
  323. // 更新数据
  324. if (this.com && this.com.attrs && this.com.attrs[dataIndex]) {
  325. this.$set(this.com.attrs[dataIndex], "content", newValue);
  326. this.com.attrs.forEach((el) => {
  327. if (el.name === attrName) {
  328. this.$set(el, "content", newValue);
  329. }
  330. });
  331. this.$emit("onUpdateAttr", this.currentIndex, dataIndex, newValue);
  332. // 使用 $nextTick 确保在 DOM 更新后执行
  333. } else {
  334. console.warn(`Unable to find attribute at index ${dataIndex}`);
  335. }
  336. }
  337. },
  338. bindEvents() {
  339. let _this = this;
  340. for (var i = 0; i < _this.com.attrs.length; i++) {
  341. let attrId = _this.com.attrs[i].id;
  342. let item = _this.com.attrs[i];
  343. if (_this.com.attrs[i].type == "variable") {
  344. //变量事件
  345. let input = _this.$el.querySelector("#" + attrId);
  346. if (input) {
  347. if (item.data.value_type == 2) {
  348. //下拉选择
  349. input.addEventListener("change", _this.handleInput);
  350. } else {
  351. //文本输入
  352. input.addEventListener("blur", _this.handleInput);
  353. }
  354. }
  355. } else if (_this.com.attrs[i].type == "ProductAttr") {
  356. //绑定产品属性事件
  357. let prodAttrId = item.id + "_" + i;
  358. let input = _this.$el.querySelector("#" + prodAttrId);
  359. if (input) {
  360. if (item.attrs.type == 1) {
  361. input.addEventListener("blur", _this.handleInputProduct);
  362. } else {
  363. input.addEventListener("change", _this.handleChangeProduct);
  364. }
  365. }
  366. }
  367. }
  368. },
  369. //分析公式
  370. async analysisFormual(attrs) {
  371. let _this = this;
  372. // console.log("开始分析公式:", attrs.formula);
  373. var pattern = /(\[.*?\]){3}/;
  374. var formual = attrs.formula;
  375. var reg = new RegExp(pattern);
  376. while (true) {
  377. var items = formual.match(reg);
  378. if (items == null) break;
  379. let itemName = items[0];
  380. // console.log("处理项:", itemName);
  381. try {
  382. let data = await _this.getFormualData(itemName);
  383. // console.log(`获取到的数据: ${itemName} = ${data}`);
  384. if (data === null || data === undefined || isNaN(data)) {
  385. console.warn(`获取到的数据无效: ${itemName}`);
  386. formual = formual.replace(itemName, "(0)");
  387. } else {
  388. formual = formual.replace(itemName, `(${parseFloat(data)})`);
  389. }
  390. } catch (error) {
  391. console.error(`处理 ${itemName} 时出错:`, error);
  392. formual = formual.replace(itemName, "(0)");
  393. }
  394. }
  395. // console.log("分析后的公式:", formual);
  396. return formual;
  397. },
  398. async getFormualData(formualItem) {
  399. let _this = this;
  400. // console.log("开始获取公式数据:", formualItem);
  401. var pattern = /\[(.*?)\]\[(.*?)\]\[(.*?)\]/;
  402. var reg = new RegExp(pattern);
  403. let items = formualItem.match(reg);
  404. // console.log("解析结果:", items);
  405. let calResult = 0;
  406. if (items && items[1] == "T") {
  407. calResult = await _this.getModuleData(items[2], items[3]);
  408. } else {
  409. console.warn("无法解析公式项:", formualItem);
  410. }
  411. // console.log("计算结果:", calResult);
  412. return calResult;
  413. },
  414. async getRemote(formual) {
  415. let _this = this;
  416. var pattern = /\[(.*?)\]\[(.*?)\]\[(.*?)\]\[(.*?)\]/;
  417. var reg = new RegExp(pattern);
  418. while (true) {
  419. let items = formual.match(reg);
  420. if (items == null) break;
  421. if (items[1] == "R") {
  422. //表示获取远程数据
  423. var dataIndex = items[4];
  424. dataIndex = items[4].split(",");
  425. let calResult = await _this.getRemoteData(
  426. items[2],
  427. items[3],
  428. dataIndex[0],
  429. dataIndex[1]
  430. );
  431. let itemName = items[0];
  432. // 检查calResult是否为数字
  433. if (!isNaN(calResult)) {
  434. formual = formual.replace(
  435. itemName,
  436. "(" + parseFloat(calResult) + ")"
  437. );
  438. } else {
  439. formual = formual.replace(itemName, `"${calResult}"`);
  440. }
  441. formual = formual.replace(
  442. itemName,
  443. "(" + parseFloat(calResult) + ")"
  444. );
  445. }
  446. }
  447. return formual;
  448. },
  449. async getRemote1(formual) {
  450. let _this = this;
  451. var pattern = /\[(.*?)\]\[(.*?)\]\[(.*?)\]\[(.*?)\]/;
  452. var reg = new RegExp(pattern);
  453. while (true) {
  454. let items = formual.match(reg);
  455. if (items == null) break;
  456. if (items[1] == "R") {
  457. //表示获取远程数据
  458. var dataIndex = items[4];
  459. dataIndex = items[4].split(",");
  460. let calResult = await _this.getRemoteData(
  461. items[2],
  462. items[3],
  463. dataIndex[0],
  464. dataIndex[1]
  465. );
  466. let itemName = items[0];
  467. // 检查calResult是否为数字
  468. if (!isNaN(calResult)) {
  469. formual = formual.replace(
  470. itemName,
  471. "(" + parseFloat(calResult) + ")"
  472. );
  473. } else {
  474. formual = formual.replace(itemName, `"${calResult}"`);
  475. }
  476. /* formual=formual.replace(itemName,parseFloat(calResult)); */
  477. }
  478. }
  479. return formual;
  480. },
  481. onFocus() {
  482. this.isEditing = true;
  483. },
  484. onBlur() {
  485. this.isEditing = false;
  486. },
  487. async getRemoteData(code, sheet, row, col) {
  488. let _this = this;
  489. let result = 0;
  490. let par = {
  491. code: code,
  492. sheetName: sheet,
  493. row: row,
  494. col: col,
  495. };
  496. /* if (!this.isEditing) {
  497. this.loading = true;
  498. this.progress = 0;
  499. try {
  500. const updateProgress = setInterval(() => {
  501. if (this.progress < 90) {
  502. this.progress += 10;
  503. }
  504. }, 100);
  505. let res = await findData(par);
  506. if (res.status == 200) {
  507. result = res.data.result;
  508. }
  509. this.progress = 100;
  510. clearInterval(updateProgress);
  511. } catch (error) {
  512. console.error("Error fetching remote data:", error);
  513. } finally {
  514. setTimeout(() => {
  515. this.loading = false;
  516. this.progress = 0;
  517. }, 1000);
  518. }
  519. } else { */
  520. // 如果正在编辑,直接获取数据而不显示进度条
  521. try {
  522. let res = await findData(par);
  523. if (res.status == 200) {
  524. result = res.data.result;
  525. // 添加数据类型检查和转换
  526. if (typeof result === "string" && !isNaN(result)) {
  527. result = parseFloat(result);
  528. }
  529. }
  530. } catch (error) {
  531. console.error("获取远程数据错误:", error);
  532. }
  533. /* } */
  534. return result;
  535. },
  536. //获取本地文档模块数据
  537. async getModuleData(code, attrName) {
  538. let _this = this;
  539. // console.log(`尝试获取模块数据: code=${code}, attrName=${attrName}`);
  540. // console.log("当前 coms:", _this.coms);
  541. let item = _this.coms.filter((o) => o.name == code);
  542. // console.log(`找到的模块:`, item);
  543. if (item.length > 0) {
  544. let attr = item[0].attrs.filter((o) => o.name == attrName);
  545. // console.log(`找到的属性:`, attr);
  546. if (attr.length > 0) {
  547. // console.log(`返回的值:`, attr[0].content);
  548. return parseFloat(attr[0].content);
  549. }
  550. }
  551. // console.log("未找到匹配的模块或属性,返回 0");
  552. return 0;
  553. },
  554. handleChangeProduct(e) {
  555. let dataIndex = e.target.dataset.index;
  556. this.$emit(
  557. "onUpdateProdAttr",
  558. this.currentIndex,
  559. dataIndex,
  560. e.target.value
  561. );
  562. },
  563. handleInputProduct(e) {
  564. let dataIndex = e.target.dataset.index;
  565. this.$emit(
  566. "onUpdateProdAttr",
  567. this.currentIndex,
  568. dataIndex,
  569. e.target.value
  570. );
  571. },
  572. handleInput(e) {
  573. let dataIndex = e.target.dataset.index;
  574. this.$emit("onUpdateAttr", this.currentIndex, dataIndex, e.target.value);
  575. },
  576. onEditorReady(editor) {},
  577. onInputText(e) {
  578. this.$emit("onUpdate", this.currentIndex, e);
  579. },
  580. /* onFocus(e) {
  581. // var range = e.editor.getSelection().getRanges()[0];
  582. // range.collapse( true );
  583. // range.setStartAt( e.editor.editable(), CKEDITOR.POSITION_AFTER_START );
  584. // e.editor.insertText("AAA");
  585. },
  586. onBlur(e) {
  587. //// console.log(' onBlur e',e);
  588. // this.$emit("onUploadAttr",this.dataIndex,this.dataAttr);
  589. }, */
  590. },
  591. };
  592. </script>
  593. <style lang="scss">
  594. @import "./index.scss";
  595. .template-textarea {
  596. position: relative;
  597. }
  598. .overlay {
  599. position: absolute;
  600. top: 0;
  601. left: 0;
  602. width: 100%;
  603. height: 100%;
  604. background: rgba(0, 0, 0, 0.5); // Semi-transparent background
  605. display: flex;
  606. align-items: center;
  607. justify-content: center;
  608. z-index: 10; // Ensure it is above other content
  609. }
  610. .full-width-progress {
  611. width: 80%; // Adjust as needed
  612. color: inherit; // Ensure the color remains unchanged
  613. }
  614. </style>