grapes.init.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. const panelsConfig = {
  2. stylePrefix: 'pn-', // 面板的样式前缀
  3. defaults: [
  4. {
  5. id: 'commands',
  6. buttons: [{}],
  7. },
  8. {
  9. id: 'options',
  10. buttons: [
  11. {
  12. active: false,
  13. id: 'sw-visibility',
  14. className: 'fa fa-square-o',
  15. command: 'core:component-outline',
  16. context: 'sw-visibility',
  17. attributes: { title: 'View components' },
  18. },
  19. {
  20. id: 'preview',
  21. className: 'fa fa-eye',
  22. command: 'preview',
  23. context: 'preview',
  24. attributes: { title: 'Preview' },
  25. },
  26. {
  27. id: 'fullscreen',
  28. className: 'fa fa-arrows-alt',
  29. command: 'fullscreen',
  30. context: 'fullscreen',
  31. attributes: { title: 'Fullscreen' },
  32. },
  33. // {
  34. // id: 'export-template',
  35. // className: 'fa fa-code',
  36. // command: 'export-template',
  37. // attributes: { title: 'View code' },
  38. // },
  39. ],
  40. },
  41. {
  42. id: 'views',
  43. buttons: [
  44. // {
  45. // id: 'open-sm',
  46. // className: 'fa fa-paint-brush',
  47. // command: 'open-sm',
  48. // active: false,
  49. // togglable: false,
  50. // attributes: { title: 'Open Style Manager' },
  51. // },
  52. {
  53. id: 'open-tm',
  54. className: 'fa fa-cog',
  55. command: 'open-tm',
  56. active: true,
  57. togglable: true,
  58. attributes: { title: 'Settings' },
  59. },
  60. // {
  61. // id: 'open-layers',
  62. // className: 'fa fa-bars',
  63. // command: 'open-layers',
  64. // togglable: false,
  65. // attributes: { title: 'Open Layer Manager' },
  66. // },
  67. // {
  68. // id: 'open-blocks',
  69. // className: 'fa fa-th-large',
  70. // command: 'open-blocks',
  71. // togglable: false,
  72. // attributes: { title: 'Open Blocks' },
  73. // },
  74. ],
  75. },
  76. ],
  77. };
  78. // 定义自定义组件类型
  79. const myNewComponentTypes = (editor) => {
  80. //连接属性
  81. // editor.Components.addType('a', {
  82. // isComponent: (el) => el.tagName === 'A',
  83. // model: {
  84. // defaults: {
  85. // traits: [
  86. // {
  87. // type: 'text', // Type of the trait
  88. // name: 'href', // (required) The name of the attribute/property to use on component
  89. // label: 'URL', // The label you will see in Settings
  90. // }
  91. // ],
  92. // },
  93. // },
  94. // });
  95. //图片属性
  96. editor.Components.addType('img', {
  97. isComponent: (el) => el.tagName === 'IMG',
  98. model: {
  99. defaults: {
  100. traits: [
  101. {
  102. type: 'text', // Type of the trait
  103. name: 'title', // (required) The name of the attribute/property to use on component
  104. label: 'title', // The label you will see in Settings
  105. },
  106. {
  107. type: 'text', // Type of the trait
  108. name: 'width', // (required) The name of the attribute/property to use on component
  109. label: 'width', // The label you will see in Settings
  110. },
  111. {
  112. type: 'text', // Type of the trait
  113. name: 'height', // (required) The name of the attribute/property to use on component
  114. label: 'height', // The label you will see in Settings
  115. },
  116. ],
  117. },
  118. },
  119. });
  120. };
  121. const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
  122. // 创建 GrapesJS 编辑器实例
  123. const editor = grapesjs.init({
  124. container: '#gjs', // 指定编辑器容器的 DOM 元素
  125. fromElement: true, // 从 DOM 元素中加载初始内容
  126. height: '100%', // 设置编辑器的高度
  127. width: '100%', // 设置宽度自适应
  128. storageManager: false,
  129. panels: panelsConfig,
  130. plugins: [myNewComponentTypes],
  131. assetManager: {
  132. upload: '/dist/visual-editor/upload',
  133. uploadName: '_file_',
  134. multiUpload:false,
  135. headers: {
  136. 'X-CSRF-TOKEN': csrfToken,
  137. },
  138. },
  139. });
  140. const am = editor.AssetManager;
  141. // 锁定整个画布
  142. editor.getWrapper().set({ locked: true });
  143. // 遍历所有组件并解锁具有edit属性的组件
  144. const edit_classes = ['mtb_edit']; // 定义需要解锁的组件类名
  145. editor.getComponents().each(component => {
  146. component.onAll(component => {
  147. //解销CLASS
  148. let html = component.toHTML();
  149. const classes = component.getClasses();
  150. // 判断是否有交集
  151. const hasIntersection = classes.some(item => {
  152. // 如果是数组,则递归检查子数组
  153. if (Array.isArray(item)) {
  154. return item.some(subItem => edit_classes.includes(subItem));
  155. }
  156. // 如果是字符串,直接检查是否在 edit_classes 中
  157. return edit_classes.includes(item);
  158. });
  159. // 如果有交集,解锁组件
  160. if (hasIntersection || component.get('attributes').mtb_edit == 'true') {
  161. component.set({ locked: false });
  162. setBorder(component);
  163. }
  164. if (component.get('attributes').mtb_edit == 'false') {
  165. component.set({ locked: true });
  166. }
  167. if (component.get('attributes').mtb_edit_this == 'true') {
  168. component.set({ locked: false });
  169. setBorder(component);
  170. //关闭子组件
  171. component.forEachChild(child => {
  172. child.set({ locked: true });
  173. })
  174. }
  175. if (component.get('attributes').mtb_edit_this == 'false') {
  176. component.set({ locked: true });
  177. }
  178. })
  179. });
  180. function setBorder(component) {
  181. if (component.attributes.tagName == 'a') {
  182. component.setStyle({
  183. border: '3px dotted #f08300'
  184. });
  185. } else {
  186. component.setStyle({
  187. border: '1px dotted #3b97e3'
  188. });
  189. }
  190. }
  191. // 监听组件选择事件
  192. editor.on('component:selected', (comp) => {
  193. //去掉选中组件后的按钮
  194. const selectedComponent = editor.getSelected();
  195. const defaultToolbar = selectedComponent.get('toolbar');
  196. selectedComponent.set({
  197. // toolbar: [ ...defaultToolbar, { attributes: {class: commandIcon}, command: commandToAdd}]
  198. toolbar: [ ]
  199. });
  200. //如果是连接与图片,显示右边的属性面板
  201. // let tags = ['a', 'img'];
  202. // const hasScheduleAttribute = selectedComponent.attributes;
  203. // if (tags.includes(selectedComponent.attributes.tagName)) {
  204. // //editor.runCommand('open-tm');
  205. // } else {
  206. // }
  207. // 双击图片时弹出图片管理器
  208. if (selectedComponent.attributes.tagName == 'img') {
  209. //双击图片时弹出图片管理器
  210. const el = selectedComponent.getEl();
  211. // 添加双击事件监听器
  212. el.addEventListener('dblclick', () => {
  213. am.open({
  214. types: ['image'], // This is the default option
  215. // Without select, nothing will happen on asset selection
  216. select(asset, complete) {
  217. const selected = editor.getSelected();
  218. if (selected) {
  219. selected.addAttributes({ src: asset.getSrc() });
  220. // The default AssetManager UI will trigger `select(asset, false)`
  221. // on asset click and `select(asset, true)` on double-click
  222. complete && am.close();
  223. }
  224. },
  225. });
  226. });
  227. }
  228. //其他
  229. });
  230. var open_tm_show = true;
  231. var cogIcons = document.querySelectorAll('.fa-cog');
  232. cogIcons.forEach(function(icon) {
  233. icon.addEventListener('click', function() {
  234. // 在这里执行你想要的操作
  235. if (open_tm_show) {
  236. open_tm_show = false;
  237. } else {
  238. open_tm_show = true;
  239. }
  240. leftPanelDisplay(open_tm_show);
  241. });
  242. });
  243. function leftPanelDisplay(isShow = true) {
  244. if (isShow) {
  245. // canvasBg = document.querySelector('.gjs-cv-canvas');
  246. // canvasBg.classList.remove('gjs-cv-canvas-bg');
  247. let elements1 = document.querySelectorAll('.gjs-pn-views-container');
  248. elements1.forEach(function(element) {
  249. element.style.display = 'block'; // 或者 'flex',取决于你希望如何显示这些元素
  250. });
  251. let elements2 = document.querySelectorAll('.gjs-pn-views');
  252. elements2.forEach(function(element) {
  253. element.style = 'border_bottom: 2px solid var(--gjs-main-dark-color)';
  254. });
  255. } else {
  256. // canvasBg = document.querySelector('.gjs-cv-canvas');
  257. // canvasBg.classList.add('gjs-cv-canvas-bg');
  258. let elements1 = document.querySelectorAll('.gjs-pn-views-container');
  259. elements1.forEach(function(element) {
  260. element.style.display = 'none'; // 或者 'flex',取决于你希望如何显示这些元素
  261. });
  262. let elements2 = document.querySelectorAll('.gjs-pn-views');
  263. elements2.forEach(function(element) {
  264. element.style = 'border:none';
  265. });
  266. }
  267. }
  268. // 监听 load 事件
  269. editor.on('load', () => {
  270. // 在这里执行你的初始化逻辑
  271. // 获取目标元素
  272. const targetElement = document.querySelector('.fa-arrows-alt');
  273. // 创建下拉选择框
  274. const dropdown = document.createElement('select');
  275. dropdown.className = 'custom-select'; // 添加自定义样式类
  276. dropdown.id = 'dropdown'; // 添加 id 属性
  277. // 构建 option 标签
  278. var options = '';
  279. siteMenu.forEach(function (item) {
  280. if (mid == item.id) {
  281. options += `<option value="${item.id}" uri="${item.url}" selected>${item.title}</option>`;
  282. } else {
  283. options += `<option value="${item.id}" uri="${item.url}">${item.title}</option>`;
  284. }
  285. });
  286. // 将生成的 option 标签添加到 dropdown 中
  287. dropdown.innerHTML = options;
  288. // 将下拉选择框插入到目标元素之后
  289. targetElement.insertAdjacentElement('afterend', dropdown);
  290. dropdownel = document.getElementById('dropdown');
  291. // 监听 change 事件
  292. dropdownel.addEventListener('change', function () {
  293. // 获取选中的 option
  294. var selectedOption = dropdownel.options[dropdownel.selectedIndex];
  295. // 获取选中的 id 和 uri
  296. var id = selectedOption.value;
  297. var uri = selectedOption.getAttribute('uri');
  298. if (id > 0) {
  299. // 生成跳转的 URL
  300. var redirectUrl = `?mid=${id}&uri=${encodeURIComponent(uri)}`;
  301. // 跳转到生成的 URL
  302. window.location.href = redirectUrl;
  303. }
  304. });
  305. //发布按钮
  306. customSelect = document.querySelector('.custom-select');
  307. // 创建 sendButton 按钮
  308. const sendButton = document.createElement('button');
  309. sendButton.textContent = 'publish'; // 按钮文本
  310. sendButton.classList.add('send-button');
  311. sendButton.classList.add('layui-btn');
  312. sendButton.classList.add('layui-btn-radius');
  313. sendButton.classList.add('layui-btn-radius');
  314. sendButton.style = 'margin-left:10px;height:25px;line-height:25px;padding:0 10px;vertical-align:unset;'; // 添加样式(可选)
  315. customSelect.insertAdjacentElement('afterend', sendButton);
  316. });
  317. //更新时调用接口保存数据
  318. editor.on('update', () => {
  319. const html = editor.getHtml();
  320. // 你可以在这里添加自定义逻辑
  321. //ajax提交数据
  322. $.ajax({
  323. url: '/dist/visual-editor/preview-save',
  324. type: 'POST',
  325. headers: {
  326. 'X-CSRF-TOKEN': csrfToken,
  327. },
  328. data: { // 发送数据
  329. html: html
  330. },
  331. success: function (res) {
  332. if (res.status != 1) {
  333. alert('save error');
  334. }
  335. },
  336. error: function (err) {
  337. alert(err);
  338. }
  339. });
  340. });
  341. // 上传图片时触发
  342. var layer = layui.layer;
  343. var loadIndex = null;
  344. editor.on('asset:upload:start', () => {
  345. loadIndex = layer.load(0, {shade: [0.5, '#000']});
  346. });
  347. // The upload is ended (completed or not)
  348. editor.on('asset:upload:end', () => {
  349. layer.close(loadIndex);
  350. });