pcEdit.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. <template>
  2. <div class="pcEdit-pages">
  3. <el-header class="flex items-center h-[50px] bg-primary px-[20px]">
  4. <div class="text-white cursor-pointer flex items-center" @click="goBack">
  5. <el-icon size="14">
  6. <ArrowLeft />
  7. </el-icon>
  8. <span class="pl-[5px] text-[14px]">返回</span>
  9. </div>
  10. <div class="text-white ml-[10px] mr-[20px] flex items-center">
  11. <span class="mr-[5px] text-[rgba(255,255,255,.5)]">|</span>
  12. <span class="mr-[5px] text-[14px]">正在装修:{{ diyStore.name || '页面名字' }}</span>
  13. </div>
  14. <div class="flex-1"></div>
  15. <el-button @click="preview()">保存并预览</el-button>
  16. <el-button @click="save()">保存</el-button>
  17. <!-- :loading="loading" -->
  18. </el-header>
  19. <div class="full-container flex flex-row flex-1 bg-page">
  20. <!-- w-[192px] -->
  21. <div class="component-list">
  22. <!-- 组件列表区域 -->
  23. <el-collapse v-model="activeNames" @change="handleChange">
  24. <el-collapse-item v-for="(item, key) in collapse" :key="key" :title="item.name" :name="key">
  25. <ul class="flex flex-row flex-wrap">
  26. <li
  27. v-for="(compItem, compKey) in item.list"
  28. :key="compKey"
  29. class="w-2/4 text-center cursor-pointer h-[65px]"
  30. :title="compItem.name"
  31. @click="diyStore.addComponent(compItem, compKey)"
  32. >
  33. <icon v-if="compItem.icon" :name="compItem.icon" size="20px" class="inline-block mt-[3px]" />
  34. <icon v-else name="iconfont iconkaifazujian" size="20px" class="inline-block mt-[3px]" />
  35. <span class="block text-[12px] truncate">{{ compItem.name }}</span>
  36. </li>
  37. </ul>
  38. </el-collapse-item>
  39. </el-collapse>
  40. </div>
  41. <div class="preview-wrap">
  42. <div class="preview-box">
  43. <!-- 组件编辑区域 -->
  44. <div class="preview-pages shadow-lg" :style="{ backgroundColor: diyStore.backgroundColor }">
  45. <draggable v-model="diyStore.componentList" item-key="itemKey" class="drag-area" :move="onDragMove">
  46. <template #item="{ element, index }">
  47. <div @click="diyStore.onComponent(element, index)" class="component-bos">
  48. <div class="component-box" :style="{ borderWidth: diyStore.currentIndex == index ? '2px' : '0px' }"></div>
  49. <component :is="element.components" :key="element.itemKey" :index="index"></component>
  50. </div>
  51. </template>
  52. </draggable>
  53. </div>
  54. <div class="quick-action text-center w-[42px] rounded shadow-md">
  55. <el-tooltip effect="light" content="上移" placement="right">
  56. <icon name="iconfont iconjiantoushang" size="20px" class="block cursor-pointer leading-[40px]" @click="diyStore.moveUpComponent" />
  57. </el-tooltip>
  58. <el-tooltip effect="light" content="下移" placement="right">
  59. <icon name="iconfont iconjiantouxia" size="20px" class="block cursor-pointer leading-[40px]" @click="diyStore.moveDownComponent" />
  60. </el-tooltip>
  61. <!-- <el-tooltip effect="light" content="复制" placement="right">
  62. <icon name="iconfont iconcopy-line" size="20px" class="block cursor-pointer leading-[40px]" @click="diyStore.copyComponent" />
  63. </el-tooltip> -->
  64. <el-tooltip effect="light" content="删除" placement="right">
  65. <icon name="iconfont icondelete-line" size="20px" class="block cursor-pointer leading-[40px]" @click="diyStore.delComponent" />
  66. </el-tooltip>
  67. <el-tooltip effect="light" content="页面设置" placement="right">
  68. <icon name="iconfont iconloader-line" size="20px" class="block cursor-pointer leading-[40px]" @click="diyStore.resetComponent" />
  69. </el-tooltip>
  70. </div>
  71. </div>
  72. </div>
  73. <!-- 编辑组件属性区域 -->
  74. <!-- w-[400px] -->
  75. <div class="edit-attribute-wrap">
  76. <!-- 编辑组件属性区域 -->
  77. <el-scrollbar>
  78. <el-card class="box-card" shadow="never">
  79. <template #header>
  80. <div class="card-header flex justify-between items-center">
  81. <span class="title flex-1">{{ diyStore.currentIndex == -99 ? '页面设置' : diyStore.editComponent.name }}</span>
  82. <div class="tab-wrap flex rounded-[50px] bg-gray-100 text-[14px]">
  83. <span
  84. class="cursor-pointer rounded-[50px] py-[5px] px-[15px]"
  85. :class="{ 'bg-primary text-white': diyStore.editTab == 'content' }"
  86. @click="diyStore.editTab = 'content'"
  87. >内容</span
  88. >
  89. <span
  90. class="cursor-pointer rounded-[50px] py-[5px] px-[15px]"
  91. :class="{ 'bg-primary text-white': diyStore.editTab == 'style' }"
  92. @click="diyStore.editTab = 'style'"
  93. >样式</span
  94. >
  95. </div>
  96. </div>
  97. </template>
  98. <div class="edit-component-wrap">
  99. <component
  100. v-if="diyStore.currentKey && diyStore.currentIndex != -99"
  101. :is="diyStore.editComponent.edit"
  102. :key="diyStore.currentIndex"
  103. :value="diyStore.componentList[diyStore.currentIndex]"
  104. >
  105. <template #style>
  106. <div class="edit-attr-item-wrap">
  107. <h3 class="mb-[10px]">组件样式</h3>
  108. <el-form label-width="90px" class="px-[10px]">
  109. <template v-if="diyStore.editComponent.ignore.indexOf('pageBgColor') == -1">
  110. <el-form-item label="底部背景">
  111. <el-color-picker v-model="diyStore.editComponent.pageStartBgColor" :predefine="diyStore.predefineColors" />
  112. <icon name="iconfont iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]" />
  113. <el-color-picker v-model="diyStore.editComponent.pageEndBgColor" :predefine="diyStore.predefineColors" />
  114. </el-form-item>
  115. <div class="text-sm text-gray-400 ml-[90px] mb-[10px]">底部背景包含边距和圆角</div>
  116. </template>
  117. <el-form-item label="渐变角度" v-if="diyStore.editComponent.ignore.indexOf('pageBgColor') == -1">
  118. <el-radio-group v-model="diyStore.editComponent.pageGradientAngle">
  119. <el-radio value="to bottom">从上到下</el-radio>
  120. <el-radio value="to right">从左到右</el-radio>
  121. </el-radio-group>
  122. </el-form-item>
  123. <el-form-item label="组件背景色" v-if="diyStore.editComponent.ignore.indexOf('componentBgColor') == -1">
  124. <el-color-picker v-model="diyStore.editComponent.componentStartBgColor" :predefine="diyStore.predefineColors" />
  125. <icon name="iconfont iconmap-connect" size="20px" class="block !text-gray-400 mx-[5px]" />
  126. <el-color-picker v-model="diyStore.editComponent.componentEndBgColor" :predefine="diyStore.predefineColors" />
  127. </el-form-item>
  128. <el-form-item label="渐变角度" v-if="diyStore.editComponent.ignore.indexOf('componentBgColor') == -1">
  129. <el-radio-group v-model="diyStore.editComponent.componentGradientAngle">
  130. <el-radio value="to bottom">从上到下</el-radio>
  131. <el-radio value="to right">从左到右</el-radio>
  132. </el-radio-group>
  133. </el-form-item>
  134. <el-form-item label="上边距" v-if="diyStore.editComponent.ignore.indexOf('marginTop') == -1">
  135. <el-slider v-model="diyStore.editComponent.padding.top" show-input size="small" :min="0" class="ml-[10px] diy-nav-slider" />
  136. </el-form-item>
  137. <el-form-item label="下边距" v-if="diyStore.editComponent.ignore.indexOf('marginBottom') == -1">
  138. <el-slider
  139. v-model="diyStore.editComponent.padding.bottom"
  140. show-input
  141. size="small"
  142. class="ml-[10px] diy-nav-slider"
  143. :min="0"
  144. />
  145. </el-form-item>
  146. <el-form-item label="左右边距" v-if="diyStore.editComponent.ignore.indexOf('marginBoth') == -1">
  147. <el-slider v-model="diyStore.editComponent.padding.both" show-input size="small" class="ml-[10px] diy-nav-slider" />
  148. </el-form-item>
  149. <el-form-item label="上圆角" v-if="diyStore.editComponent.ignore.indexOf('topRounded') == -1">
  150. <el-slider v-model="diyStore.editComponent.topRounded" show-input size="small" class="ml-[10px] diy-nav-slider" :max="100" />
  151. </el-form-item>
  152. <el-form-item label="下圆角" v-if="diyStore.editComponent.ignore.indexOf('bottomRounded') == -1">
  153. <el-slider
  154. v-model="diyStore.editComponent.bottomRounded"
  155. show-input
  156. size="small"
  157. class="ml-[10px] diy-nav-slider"
  158. :max="100"
  159. />
  160. </el-form-item>
  161. <el-form-item label="上间距">
  162. <el-slider v-model="diyStore.editComponent.margin.top" show-input size="small" :min="0" class="ml-[10px] diy-nav-slider" />
  163. </el-form-item>
  164. <el-form-item label="下间距">
  165. <el-slider v-model="diyStore.editComponent.margin.bottom" show-input size="small" class="ml-[10px] diy-nav-slider" :min="0" />
  166. </el-form-item>
  167. </el-form>
  168. </div>
  169. </template>
  170. </component>
  171. <pagesEdit v-if="diyStore.currentIndex == -99"></pagesEdit>
  172. </div>
  173. </el-card>
  174. </el-scrollbar>
  175. <!-- <div v-for="(item, index) in diyStore.componentList" :key="index">
  176. <component v-if="item.itemKey == diyStore.currentKey" :is="item.edit" :value="item"> </component>
  177. </div> -->
  178. </div>
  179. </div>
  180. </div>
  181. </template>
  182. <script setup name="Index" lang="ts">
  183. import { pcAddDiy, pcEditDiy, pcLookDiy } from '@/api/diy/index';
  184. import icon from '@/components/icon/index.vue';
  185. import draggable from 'vuedraggable';
  186. import { getComponentGroups } from '@/views/diy/pccomponents/index';
  187. import usePcdiyStore from '@/store/modules/pcdiy';
  188. import pagesEdit from '@/views/diy/pccomponents/edit/pages-edit.vue';
  189. // const pagesEditRef = shallowRef(pagesEdit);
  190. const diyStore = usePcdiyStore();
  191. const route = useRoute();
  192. const query = route.query;
  193. const loading = ref<any>(false);
  194. const id = ref<any>(null);
  195. const activeNames = ref<any>([0]);
  196. const collapse = ref<any>([]);
  197. const siteId = ref<any>(null);
  198. const clientId = ref<any>(null);
  199. onMounted(() => {
  200. if (query.id) {
  201. id.value = query.id;
  202. } else {
  203. id.value = null;
  204. diyStore.type = query.type;
  205. diyStore.name = query.title;
  206. diyStore.backgroundColor = '#f2f2f2';
  207. diyStore.hoverColor = '#E7000B';
  208. if (query.siteId) siteId.value = query.siteId;
  209. if (query.clientId) {
  210. clientId.value = query.clientId;
  211. diyStore.clientId = query.clientId;
  212. }
  213. }
  214. loadComponents();
  215. });
  216. // 加载组件
  217. const loadComponents = async () => {
  218. collapse.value = getComponentGroups();
  219. if (id.value) {
  220. pcLookDiy(id.value).then((res) => {
  221. if (res.code == 200) {
  222. diyStore.name = res.data.name;
  223. diyStore.type = res.data.type;
  224. diyStore.backgroundColor = res.data.backgroundColor ? res.data.backgroundColor : '#f2f2f2';
  225. diyStore.hoverColor = res.data.hoverColor ? res.data.hoverColor : '#E7000B';
  226. diyStore.currentIndex = -99;
  227. if (res.data.clientId) diyStore.clientId = res.data.clientId;
  228. const datas = JSON.parse(res.data.property);
  229. collapse.value.forEach((item1: any) => {
  230. datas.forEach((item2: any) => {
  231. if (item1.id == item2.fid) {
  232. item1.list.forEach((item3: any) => {
  233. if (item2.id == item3.id) {
  234. item2.components = item3.components;
  235. item2.edit = item3.edit;
  236. }
  237. });
  238. }
  239. });
  240. });
  241. diyStore.componentList = datas;
  242. }
  243. });
  244. }
  245. };
  246. const onDragMove = (evt: any) => {
  247. const { willInsertAfter } = evt;
  248. // 获取被拖动元素的数据 (注意:不同版本 vuedraggable 获取数据的方式可能略有不同,通常是 dragged.context.element)
  249. const draggedElement = evt.draggedContext.element;
  250. // console.log(draggedElement, '456465');
  251. // 获取目标位置元素的数据
  252. const relatedElement = evt.relatedContext.element;
  253. // 规则 1: 如果正在拖动的是 ID 为 1 的头部组件 -> 禁止拖动
  254. if (draggedElement.id === 1) {
  255. return false;
  256. }
  257. // 规则 2: 如果试图将组件移动到 ID 为 1 的头部组件 之前 (即插队到第 0 位)
  258. // willInsertAfter: true 表示要插在 related 后面,false 表示插在前面
  259. if (relatedElement.id === 1 && !willInsertAfter) {
  260. return false; // 阻止插队到头部前面
  261. }
  262. // 允许其他所有移动
  263. return true;
  264. };
  265. const handleChange = (val: string[]) => {};
  266. // 返回上一页
  267. const goBack = () => {
  268. let url = 'platformDiyIndex'; //默认到平台微页面 防止404
  269. if (diyStore.type == '1') {
  270. //平台微页面
  271. url = 'platformDiyIndex';
  272. } else if (diyStore.type == '2') {
  273. //工业品微页面
  274. url = 'industryDiyIndex';
  275. } else if (diyStore.type == '3') {
  276. //福礼微页面
  277. url = 'fuliDiyIndex';
  278. } else if (diyStore.type == '5') {
  279. //pc微页面
  280. url = 'pcDiyIndex';
  281. }
  282. location.href = `${location.origin}/diy/` + url;
  283. };
  284. // 预览
  285. const preview = () => {
  286. save(1);
  287. };
  288. // 保存
  289. const save = (type?: number) => {
  290. const datas: any = {
  291. name: diyStore.name,
  292. siteId: siteId.value,
  293. clientId: query.clientId,
  294. type: diyStore.type,
  295. remark: '',
  296. previewPicUrls: '',
  297. hoverColor: diyStore.hoverColor,
  298. backgroundColor: diyStore.backgroundColor,
  299. property: JSON.stringify(diyStore.componentList),
  300. isHome: 1
  301. };
  302. loading.value = true;
  303. const api = id.value ? pcEditDiy : pcAddDiy;
  304. if (id.value) {
  305. datas.id = id.value;
  306. }
  307. api(datas)
  308. .then((res: any) => {
  309. loading.value = false;
  310. if (res.code == 200) {
  311. ElMessage({
  312. message: '保存成功~',
  313. type: 'success'
  314. });
  315. if (type == 1) {
  316. window.open('/diy/pcdiy?id=' + res.data.id);
  317. }
  318. }
  319. })
  320. .catch(() => {});
  321. };
  322. </script>
  323. <style lang="scss" scoped>
  324. .pcEdit-pages {
  325. // min-height: calc(100vh - 84px);
  326. min-width: 2000px;
  327. height: calc(100vh);
  328. .full-container {
  329. height: calc(100vh - 50px);
  330. background-color: #f2f2f2;
  331. .component-list {
  332. height: 100%;
  333. background-color: #ffffff;
  334. padding: 0 10px;
  335. min-width: 190px;
  336. width: 190px;
  337. }
  338. .component-list ul li {
  339. &:not(.disabled):hover {
  340. color: var(--el-color-primary);
  341. background: var(--el-color-primary-light-9);
  342. }
  343. }
  344. .preview-wrap {
  345. flex: 1;
  346. display: flex;
  347. justify-content: center;
  348. .preview-box {
  349. width: 1350px;
  350. position: relative;
  351. }
  352. .preview-pages {
  353. // margin: 30px auto;
  354. width: 1300px;
  355. background: var(--el-bg-color-page);
  356. overflow-y: auto;
  357. // height: calc(130vh - 194px);
  358. // zoom: 0.7;
  359. // height: calc(100vh - 194px);
  360. height: 100%;
  361. padding-top: 15px;
  362. /* 为了兼容某些情况,可能还需要配合 display */
  363. display: inline-block;
  364. padding-bottom: 30px;
  365. .component-bos {
  366. position: relative;
  367. .component-box {
  368. position: absolute;
  369. width: 100%;
  370. height: 100%;
  371. top: 0;
  372. left: 0;
  373. border: 2px solid var(--el-color-primary);
  374. z-index: 2;
  375. cursor: move;
  376. }
  377. }
  378. }
  379. .quick-action {
  380. background: var(--el-bg-color);
  381. position: absolute;
  382. top: 20px;
  383. right: 0px;
  384. }
  385. }
  386. //编辑组件属性区域
  387. .edit-attribute-wrap {
  388. width: 400px;
  389. min-width: 400px;
  390. background: var(--el-bg-color);
  391. }
  392. .edit-attribute-wrap .box-card {
  393. border: none;
  394. }
  395. .edit-attr-item-wrap {
  396. border-top: 2px solid var(--el-color-info-light-8);
  397. padding-top: 20px;
  398. &:first-of-type {
  399. border-top: none;
  400. padding-top: 0;
  401. }
  402. }
  403. }
  404. :deep(.el-header) {
  405. height: 50px;
  406. }
  407. }
  408. </style>