123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790 |
- <template>
- <div class="editor">
- <draggable
- v-model="comList"
- @add="onAdd"
- @update="onDragEnd"
- @end="onDragComplete"
- group="itxst"
- animation="300"
- :style="draggableStyle"
- handle=".drag-handle"
- :scroll="true"
- :scrollSensitivity="100"
- :scrollSpeed="20"
- >
- <transition-group style="display: block; min-height: 100vh">
- <template v-for="(it, index) in comList">
- <div
- :key="index"
- class="layers"
- :class="comIndex == index ? 'active-layer' : ''"
- >
- <!-- <div class="drag-handle" draggable>
- <img src="@/icons/svg/drag.svg" style="width: 20px;" icon-class="drag" />
- </div> -->
- <!-- :options="categoryList":props="props" -->
- <!-- <div style="padding: 10px 0">
- <el-form label-position="left" class="inline-form">
- <el-row :gutter="24" style="margin-left: 0; margin-right: 0">
- <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
- <el-form-item
- >
- <div class="btn-save">
- <el-tooltip
- v-if="type !== 'document'"
- class="item"
- effect="dark"
- content="保存模块"
- placement="top"
- >
- <el-button
- @click="onSaveTemplate(it)"
- icon="el-icon-document-add"
- circle
- >
- </el-button>
- </el-tooltip>
- <el-button
- circle
- v-if="it.isEdit !== 1"
- @click="onEdit(index, 1)"
- icon="el-icon-edit"
- >
- </el-button>
- <el-button
- circle
- @click="onEdit(index, 2)"
- icon="el-icon-view"
- v-else
- >
- </el-button>
- <el-tooltip
- class="item"
- effect="dark"
- content="删除"
- placement="top"
- >
- <el-button
- circle
- @click="onRemove(index)"
- icon="el-icon-delete"
- ></el-button
- >
- </el-tooltip>
- </div>
- </el-form-item>
- </el-col>
- </el-row>
- </el-form>
- </div> -->
- <div class="main" @click="onSetActive(index)">
- <component
- :is="it.type"
- :ref="`component_${index}`"
- :com="it"
- :coms="comList"
- :currentIndex="index"
- :isAdmin="isAdmin"
- :insertCmd="insertCmd"
- @onUpdate="onUpdate"
- @onUpdateAttr="onUpdateAttr"
- @onUpdata="onUpdata"
- @onUpdateProdAttr="onUpdateProdAttr"
- @updateComContent="handleComContentUpdate"
- :isEdit="it.isEdit"
- />
- </div>
- <!-- <div class="label">
- {{ it.name }}
- <div class="del">
- <svg-icon
- @click="onEdit(index, 1)"
- v-if="it.isEdit !== 1"
- icon-class="edit"
- style="color: #fff; margin-right: 10px"
- />
- <svg-icon
- @click="onEdit(index, 2)"
- v-else
- icon-class="view"
- style="color: #fff; margin-right: 10px"
- />
- <svg-icon
- @click="onRemove(index)"
- icon-class="delete"
- style="color: #fff; margin-left: 10px"
- />
- </div>
- </div> -->
- </div>
- </template>
- <template v-if="comList.length <= 0">
- <div class="editor-empty" :key="-1">
- <el-empty description="请拖放组件至当前页面" />
- </div>
- </template>
- </transition-group>
- </draggable>
- </div>
- </template>
- <script>
- import draggable from "vuedraggable";
- import Table from "./components/Table";
- import TextArea from "./components/TextArea";
- import Empty from "./components/Empty";
- import {
- getAllList,
- createTemplate,
- updateTemplate,
- treeCategory,
- } from "@/api/template";
- export default {
- name: "editor",
- emits: [
- "onSetActiveIndex",
- "onDelete",
- "onInsert",
- "onLoadArticle",
- "onRebuild",
- "onSetComs",
- ],
- components: {
- Table,
- TextArea,
- draggable,
- Empty,
- },
- props: {
- coms: {
- type: Array,
- default: null,
- },
- comIndex: {
- type: Number,
- default: 0,
- },
- templateCate: {
- type: Array,
- default: () => {
- return [];
- },
- },
- insertCmd: {
- type: Object,
- default: null,
- },
- isAdmin: {
- type: Number,
- default: 2,
- },
- },
- watch: {
- coms: {
- async handler(val) {
- // 如果正在保存,则跳过处理
- if (this.isSaving || !val) return;
- try {
- // 处理拖拽数据,确保属性正确转换
- const processedComs = val.map((item) => {
- // 首先创建基础对象
- const processedItem = {
- ...item,
- // 确保 attrs 是数组
- dcb_attrs: Array.isArray(item.attrs)
- ? item.attrs
- : JSON.parse(item.attrs || "[]"),
- // 同步最新内容,优先使用当前显示的内容
- content: item.content || item.dcb_nr || "",
- dcb_nr: item.content || item.dcb_nr || "",
- // 确保 type 使用 dcb_type
- type: item.dcb_type || item.type,
- // 确保 name 使用 dcb_name
- name: item.dcb_name || item.name,
- };
- // 确保 content 和 dcb_nr 同步
- if (processedItem.content !== processedItem.dcb_nr) {
- processedItem.dcb_nr = processedItem.content;
- }
- return processedItem;
- });
- console.log("editor_coms:",processedComs);
- this.comList = processedComs;
- this.$emit("onSetComs:", this.comList);
- } catch (error) {
- console.error('处理组件数据时出错:', error);
- }
- },
- immediate: true,
- deep: true,
- },
- comIndex: {
- handler(val) {
- if (val <= 0) return;
- this.comList = this.comList.map((it) => {
- it.isEdit = 2;
- it.selDisabled = true;
- it.valDisabled = true;
- if (
- typeof it.category_id === "string" &&
- it.category_id.includes(" ")
- ) {
- it.category_id = it.category_id.split(" ");
- } else if (!Array.isArray(it.category_id)) {
- // 如果不是数组,将其转换为包含单个元素的数组
- it.category_id = [it.category_id].filter(Boolean);
- }
- return it;
- });
- },
- immediate: true, //立即执行
- deep: true,
- },
- },
- data() {
- return {
- showEditor: false,
- operation: "",
- id: 0,
- comList: [],
- categoryList: [],
- props: {
- value: "id",
- label: "name",
- children: "children",
- checkStrictly: true,
- },
- saveAs: false,
- draggableStyle: {
- width: "100%",
- overflowY: "auto",
- },
- type: "",
- isSaving: false, // 添加保存状态标志
- };
- },
- mounted() {
- console.log(this.$store.state);
- this.initCategoryList();
- this.type = this.$route.query.type;
- this.templateId = this.$route.query.templateId;
- this.$nextTick(() => {
- this.updateDirectoryNumbers(); // 初始化目录序号
- });
- },
- methods: {
- /* 拖拽 修改目录序号*/
- onDragComplete() {
- console.log("拖动结束");
- this.$nextTick(() => {
- this.updateDirectoryNumbers();
- this.$emit("onRebuild", this.comList);
- });
- },
- updateDirectoryNumbers() {
- let levelCounters = {};
- this.comList.forEach((module, moduleIndex) => {
- if (module.attrs) {
- module.attrs.forEach((attr) => {
- if (attr.type === "Directory") {
- const newNumber = this.generateNumberedContent(
- moduleIndex,
- attr.level,
- levelCounters
- );
- // 只更新 number 属性,完全不触及 content
- if (!attr.hasOwnProperty("number")) {
- this.$set(attr, "number", newNumber);
- } else {
- attr.number = newNumber;
- }
- }
- });
- }
- });
- },
- generateNumberedContent(moduleIndex, level, levelCounters) {
- return this.generateLevelPrefix(moduleIndex, level, levelCounters);
- },
- generateLevelPrefix(moduleIndex, level, levelCounters) {
- let prefix = [];
- for (let i = 1; i <= level; i++) {
- if (i === 1) {
- prefix.push(moduleIndex + 1);
- } else {
- if (!levelCounters[i]) {
- levelCounters[i] = 1;
- } else {
- levelCounters[i]++;
- }
- prefix.push(levelCounters[i]);
- }
- }
- return prefix.join(".");
- },
- carefulCopy(items) {
- return items.map((item) => {
- const newItem = { ...item };
- // 深拷贝 attrs 数组
- if (Array.isArray(newItem.attrs)) {
- newItem.attrs = newItem.attrs.map((attr) => ({ ...attr }));
- }
- // 深拷贝 content,如果它是一个对象或数组
- if (typeof newItem.content === "object" && newItem.content !== null) {
- newItem.content = JSON.parse(JSON.stringify(newItem.content));
- }
- // 处理特殊的 type,如 Table
- if (newItem.type === "Table") {
- newItem.tableHeader = [...newItem.tableHeader];
- newItem.tableData = newItem.tableData.map((row) => ({ ...row }));
- }
- // 保留其他属性的引用
- return newItem;
- });
- },
- handleComContentUpdate(index, updatedCom) {
- // 更新本地的 coms 数组
- this.$set(this.coms, index, updatedCom);
- // 触发父组件的更新
- this.$emit("onRebuild", this.coms);
- },
- showCategoryName(item) {
- return (
- this.type == "module" &&
- /* this.$store.state.user.roleInfo.id == 1 && */
- !item.valDisabled
- );
- },
- enableName(item) {
- item.valDisabled = false;
- this.$forceUpdate();
- },
- showCategorySelect(item) {
- return (
- this.type === "module" &&
- /* this.$store.state.user.roleInfo.id === 1 && */
- !item.selDisabled
- );
- },
- /* 选择分类 */
- enableSelect(item) {
- item.selDisabled = true;
- console.log(item);
- this.$forceUpdate();
- },
- /* 获取分类名称 */
- getCategory(item) {
- const findCategory = (list, id) => {
- for (const el of list) {
- if (el.id === id) {
- return el.name;
- }
- if (el.children) {
- const name = findCategory(el.children, id);
- if (name) {
- return name;
- }
- }
- }
- return null;
- };
- return findCategory(this.categoryList, item) || "请选择分类";
- /* return (
- this.categoryList.find((el) => el.id === item)?.name || "请选择分类"
- ); */
- },
- onSaveAs(e) {
- this.saveAs = true;
- this.onSaveTemplate(e);
- },
- /* 保存模块 */
- onSaveTemplate(e) {
- let _this = this;
- // 创建一个新对象来存储过滤后的数据
- let data = {};
- // 遍历原始对象,只保留 dcb_ 开头的属性
- Object.keys(e).forEach((key) => {
- if (key.startsWith("dcb_")) {
- data[key] = e[key];
- }
- });
- // 特殊字段映射
- data.dcb_nr = e.content;
- data.dcb_attrs = JSON.stringify(e.attrs);
- data.dcb_type = e.type;
- data.dcb_name = e.name; // 如果需要保存名称
- // 处理分类ID
- if (Array.isArray(e.category_id)) {
- data.category_id = e.category_id[e.category_id.length - 1];
- } else {
- data.category_id = e.category_id;
- }
- data.status = 5;
- // 根据是否存在 dcb_id 决定创建还是更新
- if (e.dcb_id == undefined || this.saveAs) {
- createTemplate(data).then((res) => {
- if (res.code != 200) return;
- e.id = res.data;
- _this.$alert("模块信息保存成功");
- _this.$emit("onRefresh");
- this.saveAs = false;
- e.selDisabled = false;
- e.valDisabled = false;
- this.$forceUpdate();
- });
- } else {
- updateTemplate(data).then((res) => {
- if (res.code != 200) return;
- _this.$alert("模块信息更新成功");
- _this.$emit("onRefresh");
- e.selDisabled = false;
- e.valDisabled = false;
- this.$forceUpdate();
- });
- }
- },
- /* 获取模块分类 */
- //获取模板分类信息
- async initCategoryList() {
- let _this = this;
- let res = await treeCategory(); //await getAllList();
- console.log(res);
- this.categoryList =
- res.data; /* //this.processDataForCascader(res.data); */
- },
- processDataForCascader(data) {
- console.log("Raw data:", data); // 调试原始数据
- const idMap = new Map();
- const rootItems = [];
- // First pass: create a map of all items
- data.forEach((item) => {
- const processedItem = { ...item, children: [] };
- idMap.set(item.id, processedItem);
- });
- // Second pass: build the tree structure
- data.forEach((item) => {
- if (item.parent_id === "0" || item.parent_id === 0) {
- rootItems.push(idMap.get(item.id));
- } else {
- const parent = idMap.get(parseInt(item.parent_id));
- if (parent) {
- parent.children.push(idMap.get(item.id));
- } else {
- console.warn(
- `Parent with id ${item.parent_id} not found for item:`,
- item
- );
- rootItems.push(idMap.get(item.id)); // 如果找不到父项,作为根项添加
- }
- }
- });
- console.log("Processed data:", rootItems); // 调试处理后的数据
- return rootItems;
- },
- onChangeCategory(item, value) {
- const selectedId = value[value.length - 1];
- item.category_id = selectedId;
- // You can now use the selected ID as needed
- },
- //更新产品属性
- onUpdateProdAttr(comIndex, attrIndex, data) {
- this.comList[comIndex].attrs[attrIndex].content = data;
- let attrName = this.comList[comIndex].attrs[attrIndex].attrs.name;
- let attrType = this.comList[comIndex].attrs[attrIndex].attrs.type;
- for (var i = 0; i < this.comList.length; i++) {
- for (var j = 0; j < this.comList[i].attrs.length; j++) {
- if (this.comList[i].attrs[j].type == "ProductAttr") {
- if (
- this.comList[i].attrs[j].attrs.name == attrName &&
- this.comList[i].attrs[j].attrs.type == attrType
- ) {
- this.comList[i].attrs[j].content = data;
- }
- }
- }
- }
- this.$emit("onRebuild", this.comList);
- },
- onUpdateAttr(comIndex, attrIndex, data) {
- console.log(comIndex, attrIndex, data);
- this.comList[comIndex].attrs[attrIndex].content = data;
- let dataType = this.comList[comIndex].attrs[attrIndex].type;
- let dataId = this.comList[comIndex].attrs[attrIndex].data;
- for (var i = 0; i < this.comList.length; i++) {
- for (var j = 0; j < this.comList[i].attrs.length; j++) {
- if (
- this.comList[i].attrs[j].type == dataType &&
- this.comList[i].attrs[j].id == dataId
- ) {
- this.comList[i].attrs[j].content = data;
- }
- }
- }
- this.$emit("onRebuild", this.comList);
- },
- /* 修改单个值 */
- onUpdata(comIndex, attrIndex, attrId, data, type) {
- let _this = this;
- let com = _this.coms[comIndex];
- if (type === "variableNull") {
- // 只更新特定的 variableNull 属性
- if (
- com &&
- com.attrs &&
- com.attrs[attrIndex] &&
- com.attrs[attrIndex].id === attrId
- ) {
- this.$set(com.attrs[attrIndex], "content", data);
- }
- } else {
- // 原有的逻辑,更新所有相同类型的属性
- let dataType = com.attrs[attrIndex].type;
- for (var i = 0; i < _this.coms.length; i++) {
- for (var j = 0; j < _this.coms[i].attrs.length; j++) {
- if (
- _this.coms[i].attrs[j].type == dataType &&
- _this.coms[i].attrs[j].id == attrId
- ) {
- _this.coms[i].attrs[j].content = data;
- }
- }
- }
- }
- _this.$emit("onRebuild", _this.coms);
- },
- onRemove(index) {
- this.$confirm("此操作将删除该模块, 是否继续?", "提示", {
- confirmButtonText: "确定",
- cancelButtonText: "取消",
- type: "warning",
- })
- .then(() => {
- this.$emit("onDelete", index);
- })
- .catch(() => {
- this.$message({
- type: "info",
- message: "已取消删除",
- });
- });
- },
- async onEdit(e, state) {
- try {
- // 如果是切换到预览模式
- if (state === 2) {
- this.isSaving = true;
-
- // 获取组件引用并添加详细日志
- const componentRef = this.$refs[`component_${e}`];
- console.log("componentRef:", componentRef);
-
- // 检查组件引用是否正确
- if (!componentRef || !componentRef[0]) {
- console.error(`组件引用获取失败: component_${e}`);
- throw new Error('组件引用不存在');
- }
- // 检查 save 方法是否存在
- if (typeof componentRef[0].save !== 'function') {
- console.error('组件没有 save 方法:', componentRef[0]);
- throw new Error('组件 save 方法不存在');
- }
- try {
- // 1. 等待组件内容保存
- console.log('开始保存组件内容...');
- await componentRef[0].save();
- console.log('组件内容保存完成');
-
- // 2. 等待父组件的更新完成
- console.log('开始更新父组件...');
- await new Promise(resolve => {
- this.$nextTick(async () => {
- await this.$emit("onRebuild", this.comList);
- resolve();
- });
- });
- console.log('父组件更新完成');
-
- // 3. 更新编辑状态
- console.log('更新编辑状态...');
- this.$set(this.comList[e], 'isEdit', 2);
-
- // 4. 确保视图更新完成
- await this.$nextTick();
- console.log('视图更新完成');
-
- } catch (error) {
- console.error('保存过程中出错:', error);
- this.$message.error('保存失败:' + error.message);
- throw error;
- }
- } else {
- // 编辑模式的处理...
- console.log('切换到编辑模式...');
- this.comList = this.comList.map((it, index) => {
- it.isEdit = 2;
- if (index === e && state === 1) {
- this.filterAttrs(it);
- }
- return it;
- });
- if (state === 1) {
- this.comList[e].isEdit = 1;
- this.onSetActive(e);
- }
- }
- // 触发重建
- await this.$emit("onRebuild", this.comList);
- console.log('重建完成');
- } catch (error) {
- console.error('切换模式失败:', error);
- this.$message.error('切换模式失败:' + error.message);
- } finally {
- this.isSaving = false;
- console.log('操作完成,isSaving 重置为 false');
- }
- },
- // 新增 filterAttrs 方法
- filterAttrs(item) {
- // 从 content 中提取所有 {{}} 包裹的 ID
- const contentIds = (item.dcb_nr.match(/{{([^}]+)}}/g) || []).map(
- (match) => match.slice(2, -2).trim()
- );
- // 过滤 attrs,只保留在 content 中出现的 ID
- item.attrs = item.attrs.filter((attr) => contentIds.includes(attr.id));
- },
- onAdd(e) {
- e.preventDefault();
- e.stopPropagation();
- this.$emit("onRebuild", this.comList);
- },
- onDragEnd(e) {
- e.preventDefault();
- e.stopPropagation();
- this.$emit("onRebuild", this.comList);
- },
- onUpdate(index, content) {
- let _this = this;
- this.coms[index].content = content;
- // 触发父组件的更新
- this.$emit("onRebuild", this.coms);
- },
- //激活当前层
- onSetActive(e) {
- this.$emit("onSetActiveIndex", e);
- },
- // 添加一个辅助方法来验证组件引用
- validateComponentRef(ref, index) {
- if (!ref) {
- throw new Error(`组件引用为空 (index: ${index})`);
- }
- if (!Array.isArray(ref)) {
- throw new Error(`组件引用不是数组 (index: ${index})`);
- }
- if (!ref[0]) {
- throw new Error(`组件引用数组为空 (index: ${index})`);
- }
- if (!ref[0].save) {
- throw new Error(`组件没有 save 方法 (index: ${index})`);
- }
- }
- },
- };
- </script>
- <style lang="scss">
- @import "./editor.scss";
- .layers {
- // margin-left: 26px;
- .drag-handle {
- position: absolute;
- top: 0;
- left: -27px;
- cursor: move;
- padding: 0 5px;
- display: flex;
- align-items: center;
- /* background-color: #f0f0f0; */
- &:hover {
- /* background-color: #e0e0e0; */
- }
- }
- .content-wrapper {
- flex-grow: 1;
- display: flex;
- flex-direction: column;
- }
- .main {
- flex-grow: 1;
- }
- }
- .inline-form {
- display: block;
- width: 100%;
- /* flex-wrap: wrap;
- gap: 10px; */
- }
- .inline-form .el-form-item {
- display: flex;
- align-items: center;
- margin-bottom: 0;
- flex: 1;
- min-width: 300px;
- }
- .inline-form .el-form-item__label {
- flex-shrink: 0;
- width: auto;
- padding-right: 12px;
- }
- .inline-form .el-form-item__content {
- flex-grow: 1;
- margin-left: 0 !important;
- }
- .input-item {
- width: 200px;
- }
- </style>
|