將Vue 3集成到基于Webman Admin的后臺系統(tǒng)中,通過模板(layout)布局方式重構(gòu)了視圖,簡化頁面交互實(shí)現(xiàn),同時(shí)基于騰訊md編輯器完成了簡約cms并且可作為免費(fèi)的微信推文編輯器。
因?yàn)閭€(gè)人以后端工作為主,這些年頭經(jīng)歷的前端技術(shù)變革也不少。前后端分離 和 前后端不分離相關(guān)的項(xiàng)目都有經(jīng)歷?;氐絺€(gè)人項(xiàng)目中,我更喜歡從簡的方式。于是我將vue3以單文件方式引入到webman admin 后臺中,同時(shí)擴(kuò)展了webman view 的Raw
類(未使用第三方模板引擎)來稍微實(shí)現(xiàn)了模板布局渲染VueLayoutRaw
類,因?yàn)槲矣X得vue3來做一些交互比jquery操作dom更快,比如一些復(fù)雜的表單操作,如下圖。
如果有喜歡的朋友可以嘗試下,長期維護(hù)cms和模板功能,admin 基礎(chǔ)功能保持與webman admin 一致更新。
plugin/admin/view.php
的view_cache
為false,默認(rèn)關(guān)閉,推薦線上開啟opcache和緩存。tool.js
composer install -vvv
INSERT INTO `wa_rules`(`id`, `title`, `icon`, `key`, `pid`, `created_at`, `updated_at`, `href`, `type`, `weight`) VALUES (147, 'updateStatus', NULL, 'plugin\\admin\\app\\controller\\CmsArticleController@updateStatus', 140, '2024-12-30 21:55:32', '2024-12-30 21:55:32', NULL, 2, 0);
INSERT INTO `wa_rules`(`id`, `title`, `icon`, `key`, `pid`, `created_at`, `updated_at`, `href`, `type`, `weight`) VALUES (146, '注冊設(shè)置', NULL, 'plugin\\user\\app\\admin\\controller\\RegisterController', 7, '2024-12-30 11:40:16', '2024-12-30 11:40:16', '/app/user/admin/register', 1, 0);
INSERT INTO `wa_rules`(`id`, `title`, `icon`, `key`, `pid`, `created_at`, `updated_at`, `href`, `type`, `weight`) VALUES (145, '刪除', NULL, 'plugin\\admin\\app\\controller\\CmsArticleController@delete', 140, '2024-12-26 22:22:02', '2024-12-26 22:22:02', NULL, 2, 0);
INSERT INTO `wa_rules`(`id`, `title`, `icon`, `key`, `pid`, `created_at`, `updated_at`, `href`, `type`, `weight`) VALUES (144, '更新', NULL, 'plugin\\admin\\app\\controller\\CmsArticleController@update', 140, '2024-12-26 22:22:02', '2024-12-26 22:22:02', NULL, 2, 0);
INSERT INTO `wa_rules`(`id`, `title`, `icon`, `key`, `pid`, `created_at`, `updated_at`, `href`, `type`, `weight`) VALUES (143, '插入', NULL, 'plugin\\admin\\app\\controller\\CmsArticleController@insert', 140, '2024-12-26 22:22:02', '2024-12-26 22:22:02', NULL, 2, 0);
INSERT INTO `wa_rules`(`id`, `title`, `icon`, `key`, `pid`, `created_at`, `updated_at`, `href`, `type`, `weight`) VALUES (142, 'select', NULL, 'plugin\\admin\\app\\controller\\CmsArticleController@select', 140, '2024-12-26 22:22:02', '2024-12-26 22:22:02', NULL, 2, 0);
INSERT INTO `wa_rules`(`id`, `title`, `icon`, `key`, `pid`, `created_at`, `updated_at`, `href`, `type`, `weight`) VALUES (141, 'tree', NULL, 'plugin\\admin\\app\\controller\\CmsCatalogController@tree', 130, '2024-12-26 22:22:02', '2024-12-26 22:22:02', NULL, 2, 0);
INSERT INTO `wa_rules`(`id`, `title`, `icon`, `key`, `pid`, `created_at`, `updated_at`, `href`, `type`, `weight`) VALUES (140, '內(nèi)容管理', '', 'plugin\\admin\\app\\controller\\CmsArticleController', 114, '2024-12-23 15:47:23', '2024-12-23 15:47:23', '/app/admin/cms-article/index', 1, 0);
INSERT INTO `wa_rules`(`id`, `title`, `icon`, `key`, `pid`, `created_at`, `updated_at`, `href`, `type`, `weight`) VALUES (134, '刪除', NULL, 'plugin\\admin\\app\\controller\\CmsCatalogController@delete', 130, '2024-12-20 17:21:43', '2024-12-20 17:21:43', NULL, 2, 0);
INSERT INTO `wa_rules`(`id`, `title`, `icon`, `key`, `pid`, `created_at`, `updated_at`, `href`, `type`, `weight`) VALUES (133, 'select', NULL, 'plugin\\admin\\app\\controller\\CmsCatalogController@select', 130, '2024-12-20 17:21:43', '2024-12-20 18:39:55', NULL, 2, 0);
INSERT INTO `wa_rules`(`id`, `title`, `icon`, `key`, `pid`, `created_at`, `updated_at`, `href`, `type`, `weight`) VALUES (132, '更新', NULL, 'plugin\\admin\\app\\controller\\CmsCatalogController@update', 130, '2024-12-20 17:21:43', '2024-12-20 17:21:43', NULL, 2, 0);
INSERT INTO `wa_rules`(`id`, `title`, `icon`, `key`, `pid`, `created_at`, `updated_at`, `href`, `type`, `weight`) VALUES (131, '插入', NULL, 'plugin\\admin\\app\\controller\\CmsCatalogController@insert', 130, '2024-12-20 17:21:43', '2024-12-20 17:21:43', NULL, 2, 0);
INSERT INTO `wa_rules`(`id`, `title`, `icon`, `key`, `pid`, `created_at`, `updated_at`, `href`, `type`, `weight`) VALUES (130, '欄目管理', '', 'plugin\\admin\\app\\controller\\CmsCatalogController', 114, '2024-12-20 16:11:29', '2024-12-20 16:11:29', '/app/admin/cms-catalog/index', 1, 0);
INSERT INTO `wa_rules`(`id`, `title`, `icon`, `key`, `pid`, `created_at`, `updated_at`, `href`, `type`, `weight`) VALUES (114, 'CMS管理', 'layui-icon-chart-screen', 'cms', NULL, '2024-12-19 14:15:24', '2024-12-19 14:19:26', '', 0, 750);
vendor/webman/event/src/EventListCommand.php
execute未定義int返回類型,啟動會出錯(cuò),因?yàn)閣ebman admin 版本在0.6.3,它依賴webman event是1.0.4,這個(gè)版本有這個(gè)問題,不過webman event 1.0.5已經(jīng)沒問題。暫時(shí)處理方式:手動改vendor包代碼。cherry-markdown.zip
plugin/admin/public/component/cherry-markdown
打包為cherry-markdown,zip
,使用前請一定解壓到當(dāng)前目錄在用
style
標(biāo)簽就可以<link href="">
標(biāo)簽就可以,切記一定必須是帶有href
<script src="">
標(biāo)簽就可以,切記一定必須是帶有src
tool.tableRender
tool.tableRender
是列表核心的調(diào)用方法,用于渲染列表table。
列表 提供了 add、edit、remove、batchRemove等重要操作的回調(diào)函數(shù)覆蓋操作
plugin/admin/view.php
配置 view_form_use_vmodel
參數(shù),默認(rèn)為 false
,表示不使用v-model,使用默認(rèn)的form name。view_form_use_vmodel
為 true
時(shí),相關(guān)的表單項(xiàng)會加入v-model=form.{字段名}
,同時(shí)原來的dom操作獲取值會失效,下面說明一些解決方案(更多處理可以參考組件的相關(guān)文檔):// <input type="radio" name="status" lay-filter="status" v-for="m in statusItems" :value="m.key" :title="m.value" v-model="form.status" :checked="form.status == m.key" />
form.on('radio(status)', data => {
this.form.status = data.elem.value;
})
// <input type="checkbox" name="type" lay-filter="type-checkbox-filter" v-model="form.type" />
// 監(jiān)聽復(fù)選框 lay-filter="type-checkbox-filter"
form.on('checkbox(type-checkbox-filter)', (data) => {
var elem = data.elem;
var checked = elem.checked;
var value = elem.value === 'on' ? '0' : elem.value;
console.log('type:', value, this.form.type_list)
if (!checked) {
this.form.type_list = this.form.type_list.filter((item) => {
return item != value;
});
} else {
if (!this.form.type_list.includes(value)) {
this.form.type_list.push(value);
}
}
});
form.on('switch(cid)', function (data) {
that.form.cid = data.elem.checked ? 1 : 0
});
// <select name="catalog_code" lay-filter="catalog_code" v-model="form.catalog_code">...</select>
form.on('select(catalog_code)', (e) => {
this.form.catalog_code = e.value;
});
...
var edit = tinymce.render({
elem: "#content",
images_upload_url: "/app/admin/upload/image",
});
edit.on("blur", function(){
layui.$("#content").val(edit.getContent());
that.form.content = edit.getContent();
});
that.form.created_at = timeRange
layui.laydate.render({
elem: "#created_at",
range: ["#created_at-date-start", "#created_at-date-end"],
type: "datetime",
rangeLinked: true,
done: function(value, date, endDate){
const timeRange = value.split(" - ")
that.form.created_at = timeRange
that.refreshTable()
}
});
// vue data 定義 pid_xm_selector
this.pid_xm_selector = layui.xmSelect.render({
el: "#pid",
name: "pid",
initValue: initValue,
tips: "無",
toolbar: {show: true, list: ["CLEAR"]},
data: res.data,
value: "0",
model: {"icon":"hidden","label":{"type":"text"}},
clickClose: true,
radio: true,
tree: {show: true,"strict":false,"clickCheck":true,"clickExpand":false},
});
// 提交事件
form.on("submit(save)", data => {
const formData = $.extend(data.field, this.form);
if (this.pid_xm_selector) {
const selectValue = this.pid_xm_selector.getValue().shift();
formData.pid = selectValue.value;
}
// ...
})
重點(diǎn):that.form.cover = res.data.url
layui.use(["upload", "layer", "popup", "util"], function() {
let input = layui.$("#$id").prev();
input.prev().html(layui.util.escape(input.val()));
layui.$("#attachment-choose-$id").on("click", function() {
parent.layer.open({
type: 2,
title: "選擇附件",
content: "/app/admin/upload/attachment",
area: ["95%", "90%"],
success: function (layero, index) {
parent.layui.$("#layui-layer" + index).data("callback", function (data) {
input.val(data.url).prev().html(layui.util.escape(data.url));
// 更新
that.form.$id = data.url
});
}
});
});
layui.upload.render({
elem: "#cover",
url: "/app/admin/upload/image",
acceptMime: "image/gif,image/jpeg,image/jpg,image/png",
field: "__file__",
done: function (res) {
if (res.code > 0) return layui.layer.msg(res.msg);
that.form.cover = res.data.url
this.item.prev().val(res.data.url).prev().attr("src", res.data.url);
}
});
});
tool.js
tool.groupSeparator(num)
描述:
將數(shù)字添加千分位分隔符(例如,1234567.89
轉(zhuǎn)換為 1,234,567.89
)。
參數(shù):
num
:需要格式化的數(shù)字。返回值:
帶有千分位分隔符的數(shù)字字符串。
示例:
tool.groupSeparator(1234567.89); // "1,234,567.89"
tool.copy(text)
描述:
將指定的文本復(fù)制到剪貼板。
參數(shù):
text
:要復(fù)制的文本。返回值:
無
示例:
tool.copy("Hello, World!"); // 將 "Hello, World!" 復(fù)制到剪貼板
tool.getTopWin()
描述:
返回多層 iframe 結(jié)構(gòu)中的最頂層窗口對象。
返回值:
最頂層的窗口對象。
示例:
const topWindow = tool.getTopWin();
tool.navigateTo(url)
/ tool.redirectTo(url)
描述:
導(dǎo)航到指定的 URL,更新最頂層窗口的地址。
參數(shù):
url
:要導(dǎo)航到的 URL(可以是字符串或包含 url
屬性的對象)。返回值:
無
示例:
tool.navigateTo("https://www.example.com");
tool.openPage(options, call_ok)
描述:
在彈出窗口中(iframe)打開指定頁面,支持配置寬度、高度、標(biāo)題等。
參數(shù):
options
:包含頁面配置的對象(title
、width
、height
、url
、refresh
)。call_ok
:頁面關(guān)閉后的回調(diào)函數(shù)。返回值:
無
示例:
tool.openPage({
title: "新頁面",
url: "https://www.example.com",
width: 800,
height: 600
}, function () {
console.log("頁面已關(guān)閉!");
});
tool.closePage()
描述:
關(guān)閉當(dāng)前頁面或彈出框。
返回值:
false
示例:
tool.closePage();
tool.createTab(options)
描述:
創(chuàng)建一個(gè)新的標(biāo)簽頁,或者如果沒有標(biāo)簽頁功能,則在 iframe 中打開頁面。
參數(shù):
options
:包含標(biāo)簽頁配置的對象(id
、title
、url
)。返回值:
無
示例:
tool.createTab({
title: "新標(biāo)簽",
url: "https://www.example.com"
});
tool.openDrawer(options, call_ok)
描述:
打開一個(gè)抽屜(側(cè)邊欄),從指定的 URL 加載內(nèi)容。
參數(shù):
options
:包含抽屜配置的對象(title
、area
、offset
、url
、refresh
)。call_ok
:抽屜關(guān)閉后的回調(diào)函數(shù)。返回值:
無
示例:
tool.openDrawer({
title: "抽屜內(nèi)容",
url: "https://www.example.com"
});
tool.render()
描述:
渲染表單元素,確保最新的 UI 狀態(tài),包括 select
和其他輸入框。
返回值:
無
示例:
tool.render();
tool.tableRender(url, cols, actions, elemId = 'data-table', params = {})
描述:
初始化并渲染一個(gè)數(shù)據(jù)表格,支持分頁、排序和其他操作(編輯、刪除、批量操作)。
參數(shù):
url
:數(shù)據(jù)源的 URL。cols
:表格的列定義。actions
:包含操作的對象,如 add
、edit
、remove
、doRemove
等。elemId
:表格元素的 ID(默認(rèn)為 'data-table'
)。params
:表格渲染的額外配置。返回值:
無
示例:
tool.tableRender("/api/data", [
{ field: 'name', title: '姓名' },
{ field: 'age', title: '年齡' }
], {
add: { url: "/add" },
edit: { url: "/edit" },
remove: { url: "/remove" }
});
tool.sysPermissionCheck()
描述:
獲取并檢查當(dāng)前用戶的權(quán)限,根據(jù)權(quán)限顯示/隱藏相應(yīng)的元素。
返回值:
無
示例:
tool.sysPermissionCheck();
tool.request(method, url, data = {}, dataType = 'json')
描述:
封裝 AJAX 請求,使用 Promise 處理異步 API 調(diào)用。
參數(shù):
method
:HTTP 方法(例如,'GET'
、'POST'
)。url
:API 接口的 URL。data
:請求的 payload(可選)。dataType
:期望的響應(yīng)數(shù)據(jù)類型(默認(rèn)為 'json'
)。返回值:
返回一個(gè) Promise 對象,成功時(shí)解析響應(yīng)數(shù)據(jù)。
示例:
tool.request('POST', '/api/submit', { name: 'John' })
.then(response => console.log(response))
.catch(error => console.error(error));
tool.getUrlParam(name)
描述:
從當(dāng)前 URL 中提取指定參數(shù)的值。
參數(shù):
name
:URL 參數(shù)的名稱。返回值:
返回參數(shù)的值,如果未找到,則返回 null
。
示例:
const id = tool.getUrlParam('id'); // 獲取 'id' 參數(shù)的值
window.alert(msg)
描述:
使用 layui.layer.alert()
顯示自定義的提示消息。
window.confirm(message, btnArray, callback, type)
描述:
使用 layui.layer.confirm()
顯示自定義的確認(rèn)對話框。
window.toast(msg)
描述:
使用 layui.layer.msg()
顯示自定義的吐司消息。
本文檔作為 tool
對象功能的參考,提供了日期格式化、表格渲染、權(quán)限管理、AJAX 請求等功能的詳細(xì)說明,旨在幫助開發(fā)者在 Web 應(yīng)用中實(shí)現(xiàn)這些常用功能。
##聯(lián)系方式
wanzij177@163.com