editor.vue 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  1. <template>
  2. <div class="editor">
  3. <draggable
  4. v-model="comList"
  5. @add="onAdd"
  6. @update="onDragEnd"
  7. @end="onDragComplete"
  8. group="itxst"
  9. animation="300"
  10. :style="draggableStyle"
  11. handle=".drag-handle"
  12. :scroll="true"
  13. :scrollSensitivity="100"
  14. :scrollSpeed="20"
  15. >
  16. <transition-group style="display: block; min-height: 100vh">
  17. <template v-for="(it, index) in comList">
  18. <div
  19. :key="index"
  20. class="layers"
  21. :class="comIndex == index ? 'active-layer' : ''"
  22. >
  23. <!-- <div class="drag-handle" draggable>
  24. <img src="@/icons/svg/drag.svg" style="width: 20px;" icon-class="drag" />
  25. </div> -->
  26. <!-- :options="categoryList":props="props" -->
  27. <!-- <div style="padding: 10px 0">
  28. <el-form label-position="left" class="inline-form">
  29. <el-row :gutter="24" style="margin-left: 0; margin-right: 0">
  30. <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
  31. <el-form-item
  32. >
  33. <div class="btn-save">
  34. <el-tooltip
  35. v-if="type !== 'document'"
  36. class="item"
  37. effect="dark"
  38. content="保存模块"
  39. placement="top"
  40. >
  41. <el-button
  42. @click="onSaveTemplate(it)"
  43. icon="el-icon-document-add"
  44. circle
  45. >
  46. </el-button>
  47. </el-tooltip>
  48. <el-button
  49. circle
  50. v-if="it.isEdit !== 1"
  51. @click="onEdit(index, 1)"
  52. icon="el-icon-edit"
  53. >
  54. </el-button>
  55. <el-button
  56. circle
  57. @click="onEdit(index, 2)"
  58. icon="el-icon-view"
  59. v-else
  60. >
  61. </el-button>
  62. <el-tooltip
  63. class="item"
  64. effect="dark"
  65. content="删除"
  66. placement="top"
  67. >
  68. <el-button
  69. circle
  70. @click="onRemove(index)"
  71. icon="el-icon-delete"
  72. ></el-button
  73. >
  74. </el-tooltip>
  75. </div>
  76. </el-form-item>
  77. </el-col>
  78. </el-row>
  79. </el-form>
  80. </div> -->
  81. <div class="main" @click="onSetActive(index)">
  82. <component
  83. :is="it.type"
  84. :ref="`component_${index}`"
  85. :com="it"
  86. :coms="comList"
  87. :currentIndex="index"
  88. :isAdmin="isAdmin"
  89. :insertCmd="insertCmd"
  90. @onUpdate="onUpdate"
  91. @onUpdateAttr="onUpdateAttr"
  92. @onUpdata="onUpdata"
  93. @onUpdateProdAttr="onUpdateProdAttr"
  94. @updateComContent="handleComContentUpdate"
  95. :isEdit="it.isEdit"
  96. />
  97. </div>
  98. <!-- <div class="label">
  99. {{ it.name }}
  100. <div class="del">
  101. <svg-icon
  102. @click="onEdit(index, 1)"
  103. v-if="it.isEdit !== 1"
  104. icon-class="edit"
  105. style="color: #fff; margin-right: 10px"
  106. />
  107. <svg-icon
  108. @click="onEdit(index, 2)"
  109. v-else
  110. icon-class="view"
  111. style="color: #fff; margin-right: 10px"
  112. />
  113. <svg-icon
  114. @click="onRemove(index)"
  115. icon-class="delete"
  116. style="color: #fff; margin-left: 10px"
  117. />
  118. </div>
  119. </div> -->
  120. </div>
  121. </template>
  122. <template v-if="comList.length <= 0">
  123. <div class="editor-empty" :key="-1">
  124. <el-empty description="请拖放组件至当前页面" />
  125. </div>
  126. </template>
  127. </transition-group>
  128. </draggable>
  129. </div>
  130. </template>
  131. <script>
  132. import draggable from "vuedraggable";
  133. import Table from "./components/Table";
  134. import TextArea from "./components/TextArea";
  135. import Empty from "./components/Empty";
  136. import {
  137. getAllList,
  138. createTemplate,
  139. updateTemplate,
  140. treeCategory,
  141. } from "@/api/template";
  142. export default {
  143. name: "editor",
  144. emits: [
  145. "onSetActiveIndex",
  146. "onDelete",
  147. "onInsert",
  148. "onLoadArticle",
  149. "onRebuild",
  150. "onSetComs",
  151. ],
  152. components: {
  153. Table,
  154. TextArea,
  155. draggable,
  156. Empty,
  157. },
  158. props: {
  159. coms: {
  160. type: Array,
  161. default: null,
  162. },
  163. comIndex: {
  164. type: Number,
  165. default: 0,
  166. },
  167. templateCate: {
  168. type: Array,
  169. default: () => {
  170. return [];
  171. },
  172. },
  173. insertCmd: {
  174. type: Object,
  175. default: null,
  176. },
  177. isAdmin: {
  178. type: Number,
  179. default: 2,
  180. },
  181. },
  182. watch: {
  183. coms: {
  184. handler(val) {
  185. // 如果正在保存或没有值,则跳过处理
  186. if (this.isSaving || !val || !Array.isArray(val)) return;
  187. try {
  188. // 检查数组长度变化或内容更新
  189. const needsUpdate =
  190. val.length !== this.comList.length || // 检查长度变化
  191. val.some((item, index) => {
  192. const currentCom = this.comList[index];
  193. if (!currentCom) return true;
  194. // 比较关键属性是否发生变化
  195. return (
  196. item.content !== currentCom.content ||
  197. item.dcb_nr !== currentCom.dcb_nr ||
  198. JSON.stringify(item.attrs) !== JSON.stringify(currentCom.attrs) ||
  199. JSON.stringify(item.dcb_attrs) !== JSON.stringify(currentCom.dcb_attrs) ||
  200. item.isEdit !== currentCom.isEdit ||
  201. item !== currentCom
  202. );
  203. });
  204. // 如果不需要更新,直接返回
  205. if (!needsUpdate) return;
  206. // 处理数据,确保属性正确转换
  207. const processedComs = val.map((item) => ({
  208. ...item,
  209. attrs: Array.isArray(item.attrs) ? item.attrs : [],
  210. dcb_attrs: Array.isArray(item.dcb_attrs)
  211. ? item.dcb_attrs
  212. : (typeof item.dcb_attrs === 'string'
  213. ? JSON.parse(item.dcb_attrs || '[]')
  214. : []),
  215. content: item.content || item.dcb_nr || '',
  216. dcb_nr: item.content || item.dcb_nr || '',
  217. type: item.dcb_type || item.type,
  218. name: item.dcb_name || item.name,
  219. }));
  220. // 使用 Vue.set 更新整个数组
  221. this.$set(this, 'comList', processedComs);
  222. // 触发父组件更新
  223. this.$emit("onSetComs", this.comList);
  224. // 如果长度减少,可能需要更新其他相关状态
  225. if (val.length < this.comList.length) {
  226. this.$nextTick(() => {
  227. // 更新目录序号
  228. this.updateDirectoryNumbers();
  229. // 触发重建
  230. this.$emit("onRebuild", this.comList);
  231. });
  232. }
  233. } catch (error) {
  234. console.error('处理组件数据时出错:', error);
  235. if (!Array.isArray(this.comList)) {
  236. this.$set(this, 'comList', Array.isArray(val) ? val : []);
  237. }
  238. }
  239. },
  240. immediate: true,
  241. deep: true,
  242. },
  243. comIndex: {
  244. handler(val) {
  245. if (val <= 0) return;
  246. this.comList = this.comList.map((it) => {
  247. it.isEdit = 2;
  248. it.selDisabled = true;
  249. it.valDisabled = true;
  250. if (
  251. typeof it.category_id === "string" &&
  252. it.category_id.includes(" ")
  253. ) {
  254. it.category_id = it.category_id.split(" ");
  255. } else if (!Array.isArray(it.category_id)) {
  256. // 如果不是数组,将其转换为包含单个元素的数组
  257. it.category_id = [it.category_id].filter(Boolean);
  258. }
  259. return it;
  260. });
  261. },
  262. immediate: true, //立即执行
  263. deep: true,
  264. },
  265. },
  266. data() {
  267. return {
  268. showEditor: false,
  269. operation: "",
  270. id: 0,
  271. comList: [],
  272. categoryList: [],
  273. props: {
  274. value: "id",
  275. label: "name",
  276. children: "children",
  277. checkStrictly: true,
  278. },
  279. saveAs: false,
  280. draggableStyle: {
  281. width: "100%",
  282. overflowY: "auto",
  283. },
  284. type: "",
  285. isSaving: false, // 添加保存状态标志
  286. };
  287. },
  288. mounted() {
  289. console.log(this.$store.state);
  290. this.initCategoryList();
  291. this.type = this.$route.query.type;
  292. this.templateId = this.$route.query.templateId;
  293. this.$nextTick(() => {
  294. this.updateDirectoryNumbers(); // 初始化目录序号
  295. });
  296. },
  297. methods: {
  298. /* 拖拽 修改目录序号*/
  299. onDragComplete() {
  300. console.log("拖动结束");
  301. this.$nextTick(() => {
  302. this.updateDirectoryNumbers();
  303. this.$emit("onRebuild", this.comList);
  304. });
  305. },
  306. updateDirectoryNumbers() {
  307. let levelCounters = {};
  308. this.comList.forEach((module, moduleIndex) => {
  309. if (module.attrs) {
  310. module.attrs.forEach((attr) => {
  311. if (attr.type === "Directory") {
  312. const newNumber = this.generateNumberedContent(
  313. moduleIndex,
  314. attr.level,
  315. levelCounters
  316. );
  317. // 只更新 number 属性,完全不触及 content
  318. if (!attr.hasOwnProperty("number")) {
  319. this.$set(attr, "number", newNumber);
  320. } else {
  321. attr.number = newNumber;
  322. }
  323. }
  324. });
  325. }
  326. });
  327. },
  328. generateNumberedContent(moduleIndex, level, levelCounters) {
  329. return this.generateLevelPrefix(moduleIndex, level, levelCounters);
  330. },
  331. generateLevelPrefix(moduleIndex, level, levelCounters) {
  332. let prefix = [];
  333. for (let i = 1; i <= level; i++) {
  334. if (i === 1) {
  335. prefix.push(moduleIndex + 1);
  336. } else {
  337. if (!levelCounters[i]) {
  338. levelCounters[i] = 1;
  339. } else {
  340. levelCounters[i]++;
  341. }
  342. prefix.push(levelCounters[i]);
  343. }
  344. }
  345. return prefix.join(".");
  346. },
  347. carefulCopy(items) {
  348. return items.map((item) => {
  349. const newItem = { ...item };
  350. // 深拷贝 attrs 数组
  351. if (Array.isArray(newItem.attrs)) {
  352. newItem.attrs = newItem.attrs.map((attr) => ({ ...attr }));
  353. }
  354. // 深拷贝 content,如果它是一个对象或数组
  355. if (typeof newItem.content === "object" && newItem.content !== null) {
  356. newItem.content = JSON.parse(JSON.stringify(newItem.content));
  357. }
  358. // 处理特殊的 type,如 Table
  359. if (newItem.type === "Table") {
  360. newItem.tableHeader = [...newItem.tableHeader];
  361. newItem.tableData = newItem.tableData.map((row) => ({ ...row }));
  362. }
  363. // 保留其他属性的引用
  364. return newItem;
  365. });
  366. },
  367. handleComContentUpdate(index, updatedCom) {
  368. // 更新本地的 coms 数组
  369. this.$set(this.coms, index, updatedCom);
  370. // 触发父组件的更新
  371. this.$emit("onRebuild", this.coms);
  372. },
  373. showCategoryName(item) {
  374. return (
  375. this.type == "module" &&
  376. /* this.$store.state.user.roleInfo.id == 1 && */
  377. !item.valDisabled
  378. );
  379. },
  380. enableName(item) {
  381. item.valDisabled = false;
  382. this.$forceUpdate();
  383. },
  384. showCategorySelect(item) {
  385. return (
  386. this.type === "module" &&
  387. /* this.$store.state.user.roleInfo.id === 1 && */
  388. !item.selDisabled
  389. );
  390. },
  391. /* 选择分类 */
  392. enableSelect(item) {
  393. item.selDisabled = true;
  394. console.log(item);
  395. this.$forceUpdate();
  396. },
  397. /* 获取分类名称 */
  398. getCategory(item) {
  399. const findCategory = (list, id) => {
  400. for (const el of list) {
  401. if (el.id === id) {
  402. return el.name;
  403. }
  404. if (el.children) {
  405. const name = findCategory(el.children, id);
  406. if (name) {
  407. return name;
  408. }
  409. }
  410. }
  411. return null;
  412. };
  413. return findCategory(this.categoryList, item) || "请选择分类";
  414. /* return (
  415. this.categoryList.find((el) => el.id === item)?.name || "请选择分类"
  416. ); */
  417. },
  418. onSaveAs(e) {
  419. this.saveAs = true;
  420. this.onSaveTemplate(e);
  421. },
  422. /* 保存模块 */
  423. onSaveTemplate(e) {
  424. let _this = this;
  425. // 创建一个新对象来存储过滤后的数据
  426. let data = {};
  427. // 遍历原始对象,只保留 dcb_ 开头的属性
  428. Object.keys(e).forEach((key) => {
  429. if (key.startsWith("dcb_")) {
  430. data[key] = e[key];
  431. }
  432. });
  433. // 特殊字段映射
  434. data.dcb_nr = e.content;
  435. data.dcb_attrs = JSON.stringify(e.attrs);
  436. data.dcb_type = e.type;
  437. data.dcb_name = e.name; // 如果需要保存名称
  438. // 处理分类ID
  439. if (Array.isArray(e.category_id)) {
  440. data.category_id = e.category_id[e.category_id.length - 1];
  441. } else {
  442. data.category_id = e.category_id;
  443. }
  444. data.status = 5;
  445. // 根据是否存在 dcb_id 决定创建还是更新
  446. if (e.dcb_id == undefined || this.saveAs) {
  447. createTemplate(data).then((res) => {
  448. if (res.code != 200) return;
  449. e.id = res.data;
  450. _this.$alert("模块信息保存成功");
  451. _this.$emit("onRefresh");
  452. this.saveAs = false;
  453. e.selDisabled = false;
  454. e.valDisabled = false;
  455. this.$forceUpdate();
  456. });
  457. } else {
  458. updateTemplate(data).then((res) => {
  459. if (res.code != 200) return;
  460. _this.$alert("模块信息更新成功");
  461. _this.$emit("onRefresh");
  462. e.selDisabled = false;
  463. e.valDisabled = false;
  464. this.$forceUpdate();
  465. });
  466. }
  467. },
  468. /* 获取模块分类 */
  469. //获取模板分类信息
  470. async initCategoryList() {
  471. let _this = this;
  472. let res = await treeCategory(); //await getAllList();
  473. console.log(res);
  474. this.categoryList =
  475. res.data; /* //this.processDataForCascader(res.data); */
  476. },
  477. processDataForCascader(data) {
  478. console.log("Raw data:", data); // 调试原始数据
  479. const idMap = new Map();
  480. const rootItems = [];
  481. // First pass: create a map of all items
  482. data.forEach((item) => {
  483. const processedItem = { ...item, children: [] };
  484. idMap.set(item.id, processedItem);
  485. });
  486. // Second pass: build the tree structure
  487. data.forEach((item) => {
  488. if (item.parent_id === "0" || item.parent_id === 0) {
  489. rootItems.push(idMap.get(item.id));
  490. } else {
  491. const parent = idMap.get(parseInt(item.parent_id));
  492. if (parent) {
  493. parent.children.push(idMap.get(item.id));
  494. } else {
  495. console.warn(
  496. `Parent with id ${item.parent_id} not found for item:`,
  497. item
  498. );
  499. rootItems.push(idMap.get(item.id)); // 如果找不到父项,作为根项添加
  500. }
  501. }
  502. });
  503. console.log("Processed data:", rootItems); // 调试处理后的数据
  504. return rootItems;
  505. },
  506. onChangeCategory(item, value) {
  507. const selectedId = value[value.length - 1];
  508. item.category_id = selectedId;
  509. // You can now use the selected ID as needed
  510. },
  511. //更新产品属性
  512. onUpdateProdAttr(comIndex, attrIndex, data) {
  513. this.comList[comIndex].attrs[attrIndex].content = data;
  514. let attrName = this.comList[comIndex].attrs[attrIndex].attrs.name;
  515. let attrType = this.comList[comIndex].attrs[attrIndex].attrs.type;
  516. for (var i = 0; i < this.comList.length; i++) {
  517. for (var j = 0; j < this.comList[i].attrs.length; j++) {
  518. if (this.comList[i].attrs[j].type == "ProductAttr") {
  519. if (
  520. this.comList[i].attrs[j].attrs.name == attrName &&
  521. this.comList[i].attrs[j].attrs.type == attrType
  522. ) {
  523. this.comList[i].attrs[j].content = data;
  524. }
  525. }
  526. }
  527. }
  528. this.$emit("onRebuild", this.comList);
  529. },
  530. onUpdateAttr(comIndex, attrIndex, data) {
  531. console.log(comIndex, attrIndex, data);
  532. this.comList[comIndex].attrs[attrIndex].content = data;
  533. let dataType = this.comList[comIndex].attrs[attrIndex].type;
  534. let dataId = this.comList[comIndex].attrs[attrIndex].data;
  535. for (var i = 0; i < this.comList.length; i++) {
  536. for (var j = 0; j < this.comList[i].attrs.length; j++) {
  537. if (
  538. this.comList[i].attrs[j].type == dataType &&
  539. this.comList[i].attrs[j].id == dataId
  540. ) {
  541. this.comList[i].attrs[j].content = data;
  542. }
  543. }
  544. }
  545. this.$emit("onRebuild", this.comList);
  546. },
  547. /* 修改单个值 */
  548. onUpdata(comIndex, attrIndex, attrId, data, type) {
  549. let _this = this;
  550. let com = _this.coms[comIndex];
  551. if (type === "variableNull") {
  552. // 只更新特定的 variableNull 属性
  553. if (
  554. com &&
  555. com.attrs &&
  556. com.attrs[attrIndex] &&
  557. com.attrs[attrIndex].id === attrId
  558. ) {
  559. this.$set(com.attrs[attrIndex], "content", data);
  560. }
  561. } else {
  562. // 原有的逻辑,更新所有相同类型的属性
  563. let dataType = com.attrs[attrIndex].type;
  564. for (var i = 0; i < _this.coms.length; i++) {
  565. for (var j = 0; j < _this.coms[i].attrs.length; j++) {
  566. if (
  567. _this.coms[i].attrs[j].type == dataType &&
  568. _this.coms[i].attrs[j].id == attrId
  569. ) {
  570. _this.coms[i].attrs[j].content = data;
  571. }
  572. }
  573. }
  574. }
  575. _this.$emit("onRebuild", _this.coms);
  576. },
  577. onRemove(index) {
  578. this.$confirm("此操作将删除该模块, 是否继续?", "提示", {
  579. confirmButtonText: "确定",
  580. cancelButtonText: "取消",
  581. type: "warning",
  582. })
  583. .then(() => {
  584. this.$emit("onDelete", index);
  585. })
  586. .catch(() => {
  587. this.$message({
  588. type: "info",
  589. message: "已取消删除",
  590. });
  591. });
  592. },
  593. async onEdit(e, state) {
  594. try {
  595. // 如果是切换到预览模式
  596. if (state === 2) {
  597. this.isSaving = true;
  598. // 获取组件引用并添加详细日志
  599. const componentRef = this.$refs[`component_${e}`];
  600. console.log("componentRef:", componentRef);
  601. // 检查组件引用是否正确
  602. if (!componentRef || !componentRef[0]) {
  603. console.error(`组件引用获取失败: component_${e}`);
  604. throw new Error('组件引用不存在');
  605. }
  606. // 检查 save 方法是否存在
  607. if (typeof componentRef[0].save !== 'function') {
  608. console.error('组件没有 save 方法:', componentRef[0]);
  609. throw new Error('组件 save 方法不存在');
  610. }
  611. try {
  612. // 1. 等待组件内容保存
  613. console.log('开始保存组件内容...');
  614. await componentRef[0].save();
  615. console.log('组件内容保存完成');
  616. // 2. 等待父组件的更新完成
  617. console.log('开始更新父组件...');
  618. await new Promise(resolve => {
  619. this.$nextTick(async () => {
  620. await this.$emit("onRebuild", this.comList);
  621. resolve();
  622. });
  623. });
  624. console.log('父组件更新完成');
  625. // 3. 更新编辑状态
  626. console.log('更新编辑状态...');
  627. this.$set(this.comList[e], 'isEdit', 2);
  628. // 4. 确保视图更新完成
  629. await this.$nextTick();
  630. console.log('视图更新完成');
  631. } catch (error) {
  632. console.error('保存过程中出错:', error);
  633. this.$message.error('保存失败:' + error.message);
  634. throw error;
  635. }
  636. } else {
  637. // 编辑模式的处理...
  638. console.log('切换到编辑模式...');
  639. this.comList = this.comList.map((it, index) => {
  640. it.isEdit = 2;
  641. if (index === e && state === 1) {
  642. this.filterAttrs(it);
  643. }
  644. return it;
  645. });
  646. if (state === 1) {
  647. this.comList[e].isEdit = 1;
  648. this.onSetActive(e);
  649. }
  650. }
  651. // 触发重建
  652. await this.$emit("onRebuild", this.comList);
  653. console.log('重建完成');
  654. } catch (error) {
  655. console.error('切换模式失败:', error);
  656. this.$message.error('切换模式失败:' + error.message);
  657. } finally {
  658. this.isSaving = false;
  659. console.log('操作完成,isSaving 重置为 false');
  660. }
  661. },
  662. // 新增 filterAttrs 方法
  663. filterAttrs(item) {
  664. if (!item || !item.dcb_nr) return;
  665. // 从 content 中提取所有 {{}} 包裹的 ID
  666. const contentIds = (item.dcb_nr.match(/{{([^}]+)}}/g) || []).map(
  667. (match) => match.slice(2, -2).trim()
  668. );
  669. // 过滤 attrs,只保留在 content 中出现的 ID
  670. if (Array.isArray(item.attrs)) {
  671. item.attrs = item.attrs.filter((attr) => contentIds.includes(attr.id));
  672. }
  673. },
  674. onAdd(e) {
  675. e.preventDefault();
  676. e.stopPropagation();
  677. // 确保拖拽的组件数据格式正确
  678. const processedList = this.comList.map(item => {
  679. return {
  680. ...item,
  681. // 确保 attrs 和 dcb_attrs 都是数组
  682. attrs: Array.isArray(item.attrs) ? item.attrs : [],
  683. dcb_attrs: Array.isArray(item.dcb_attrs)
  684. ? item.dcb_attrs
  685. : (typeof item.dcb_attrs === 'string'
  686. ? JSON.parse(item.dcb_attrs || '[]')
  687. : []),
  688. // 确保其他必要属性存在
  689. content: item.content || item.dcb_nr || '',
  690. dcb_nr: item.content || item.dcb_nr || '',
  691. type: item.dcb_type || item.type,
  692. name: item.dcb_name || item.name,
  693. isEdit: item.isEdit || 2
  694. };
  695. });
  696. // 更新组件列表
  697. this.$set(this, 'comList', processedList);
  698. // 触发父组件更新
  699. this.$emit("onRebuild", this.comList);
  700. },
  701. onDragEnd(e) {
  702. e.preventDefault();
  703. e.stopPropagation();
  704. this.$emit("onRebuild", this.comList);
  705. },
  706. onUpdate(index, content) {
  707. // 使用实例方法中的 debounce
  708. const debouncedUpdate = this.debounce((index, content) => {
  709. if (!this.coms || !Array.isArray(this.coms)) {
  710. console.warn('coms is not an array');
  711. return;
  712. }
  713. if (this.coms[index]) {
  714. // 使用 Vue.set 确保响应式更新
  715. this.$set(this.coms[index], 'content', content);
  716. this.$set(this.coms[index], 'dcb_nr', content);
  717. // 触发父组件的更新
  718. this.$emit("onRebuild", this.coms);
  719. }
  720. }, 300);
  721. // 调用防抖函数
  722. debouncedUpdate(index, content);
  723. },
  724. validateComponentRef(ref, index) {
  725. if (!ref) {
  726. throw new Error(`组件引用为空 (index: ${index})`);
  727. }
  728. if (!Array.isArray(ref)) {
  729. throw new Error(`组件引用不是数组 (index: ${index})`);
  730. }
  731. if (!ref[0]) {
  732. throw new Error(`组件引用数组为空 (index: ${index})`);
  733. }
  734. if (!ref[0].save) {
  735. throw new Error(`组件没有 save 方法 (index: ${index})`);
  736. }
  737. },
  738. // 添加防抖函数
  739. debounce(fn, delay = 300) {
  740. let timer = null;
  741. return function(...args) {
  742. if (timer) clearTimeout(timer);
  743. timer = setTimeout(() => {
  744. fn.apply(this, args);
  745. }, delay);
  746. };
  747. },
  748. // 添加回之前删除的 onSetActive 方法
  749. onSetActive(e) {
  750. this.$emit("onSetActiveIndex", e);
  751. }
  752. },
  753. };
  754. </script>
  755. <style lang="scss">
  756. @import "./editor.scss";
  757. .layers {
  758. // margin-left: 26px;
  759. .drag-handle {
  760. position: absolute;
  761. top: 0;
  762. left: -27px;
  763. cursor: move;
  764. padding: 0 5px;
  765. display: flex;
  766. align-items: center;
  767. /* background-color: #f0f0f0; */
  768. &:hover {
  769. /* background-color: #e0e0e0; */
  770. }
  771. }
  772. .content-wrapper {
  773. flex-grow: 1;
  774. display: flex;
  775. flex-direction: column;
  776. }
  777. .main {
  778. flex-grow: 1;
  779. }
  780. }
  781. .inline-form {
  782. display: block;
  783. width: 100%;
  784. /* flex-wrap: wrap;
  785. gap: 10px; */
  786. }
  787. .inline-form .el-form-item {
  788. display: flex;
  789. align-items: center;
  790. margin-bottom: 0;
  791. flex: 1;
  792. min-width: 300px;
  793. }
  794. .inline-form .el-form-item__label {
  795. flex-shrink: 0;
  796. width: auto;
  797. padding-right: 12px;
  798. }
  799. .inline-form .el-form-item__content {
  800. flex-grow: 1;
  801. margin-left: 0 !important;
  802. }
  803. .input-item {
  804. width: 200px;
  805. }
  806. </style>