之前寫過一篇文章 我理想中的低代碼開發(fā)工具的形態(tài),已經(jīng)吐槽了各種封裝 xxxForm,xxxTable 的行為,這里就不啰嗦了。今天再來看看我的工具達(dá)到了什么程度。
多圖預(yù)警。。。
以管理后臺(tái)一個(gè)列表頁為例
選擇對(duì)應(yīng)的模板
截圖查詢區(qū)域,使用 OCR 初始化查詢表單的配置
截圖表頭,使用 OCR 初始化 table 的配置
使用 ChatGPT 翻譯中文字段
生成代碼
效果
目前我們沒有寫一行代碼,就已經(jīng)達(dá)到了如下的效果
下面是一部分生成的代碼
import { reactive, ref } from 'vue'
import { IFetchTableListResult } from './api'
interface ITableListItem {
/**
* 決算單狀態(tài)
*/
settlementStatus: string
/**
* 主合同編號(hào)
*/
mainContractNumber: string
/**
* 客戶名稱
*/
customerName: string
/**
* 客戶手機(jī)號(hào)
*/
customerPhone: string
/**
* 房屋地址
*/
houseAddress: string
/**
* 工程管理
*/
projectManagement: string
/**
* 接口返回的數(shù)據(jù),新增字段不需要改 ITableListItem 直接從這里取
*/
apiResult: IFetchTableListResult['result']['records'][0]
}
interface IFormData {
/**
* 決算單狀態(tài)
*/
settlementStatus?: string
/**
* 主合同編號(hào)
*/
mainContractNumber?: string
/**
* 客戶名稱
*/
customerName?: string
/**
* 客戶手機(jī)號(hào)
*/
customerPhone?: string
/**
* 工程管理
*/
projectManagement?: string
}
interface IOptionItem {
label: string
value: string
}
interface IOptions {
settlementStatus: IOptionItem[]
}
const defaultOptions: IOptions = {
settlementStatus: [],
}
export const defaultFormData: IFormData = {
settlementStatus: undefined,
mainContractNumber: undefined,
customerName: undefined,
customerPhone: undefined,
projectManagement: undefined,
}
export const useModel = () => {
const filterForm = reactive<IFormData>({ ...defaultFormData })
const options = reactive<IOptions>({ ...defaultOptions })
const tableList = ref<(ITableListItem & { _?: unknown })[]>([])
const pagination = reactive<{
page: number
pageSize: number
total: number
}>({
page: 1,
pageSize: 10,
total: 0,
})
const loading = reactive<{ list: boolean }>({
list: false,
})
return {
filterForm,
options,
tableList,
pagination,
loading,
}
}
export type Model = ReturnType<typeof useModel>
這就是用模板生成的好處,有規(guī)范,隨時(shí)可以改,而封裝 xxxForm,xxxTable 就是一個(gè)黑盒。
原理
下面大致說一下原理
首先是寫好一個(gè)個(gè)模版,vscode 插件讀取指定目錄下模版顯示到界面上
每個(gè)模版下可能包含如下內(nèi)容:
選擇模版后,進(jìn)入動(dòng)態(tài)表單配置界面
動(dòng)態(tài)表單是讀取 config/schema.json 里的內(nèi)容進(jìn)行動(dòng)態(tài)渲染的,目前支持 amis、form-render、formily
配置表單是為了生成 JSON 數(shù)據(jù),然后根據(jù) JSON 數(shù)據(jù)生成代碼。所以最終還是無法避免的使用私有的 DSL ,但是生成后的代碼是沒有私有 DSL 的痕跡的。生成代碼本質(zhì)是 JSON + EJS 模版引擎編譯 src 目錄下的 ejs 文件。
為了加快表單的配置,可以自定義腳本進(jìn)行操作
這部分內(nèi)容是讀取 config/preview.json 內(nèi)容進(jìn)行顯示的
選擇對(duì)應(yīng)的腳本方法后,插件會(huì)動(dòng)態(tài)加載 script/index.js 腳本,并執(zhí)行里面對(duì)應(yīng)的方法
以 initColumnsFromImage 方法為例,這個(gè)方法是讀取剪貼板里的圖片,然后使用百度 OCR 解析出文本,再使用文本初始化表單
initColumnsFromImage: async (lowcodeContext) => {
context.lowcodeContext = lowcodeContext;
const res = await main.handleInitColumnsFromImage();
return res;
},
export async function handleInitColumnsFromImage() {
const { lowcodeContext } = context;
if (!lowcodeContext?.clipboardImage) {
window.showInformationMessage('剪貼板里沒有截圖');
return lowcodeContext?.model;
}
const ocrRes = await generalBasic({ image: lowcodeContext!.clipboardImage! });
env.clipboard.writeText(ocrRes.words_result.map((s) => s.words).join('\r\n'));
window.showInformationMessage('內(nèi)容已經(jīng)復(fù)制到剪貼板');
const columns = ocrRes.words_result.map((s) => ({
slot: false,
title: s.words,
dataIndex: s.words,
key: s.words,
}));
return { ...lowcodeContext.model, columns };
}
反正就是可以根據(jù)自己的需求定義各種各樣的腳本。比如使用 ChatGPT 翻譯 JSON 里的指定字段,可以看我的上一篇文章 TypeChat、JSONSchemaChat實(shí)戰(zhàn) - 讓ChatGPT更聽你的話
再比如要實(shí)現(xiàn)把中文翻譯成英文,然后英文使用駝峰語法,這樣就可以將中文轉(zhuǎn)成英文代碼變量,下面是實(shí)現(xiàn)的效果
選擇對(duì)應(yīng)的命令菜單后 vscode 插件會(huì)加載對(duì)應(yīng)模版里的腳本,然后執(zhí)行里面的 onSelect 方法。
main.ts 代碼如下
import { env, window, Range } from 'vscode';
import { context } from './context';
export async function bootstrap() {
const clipboardText = await env.clipboard.readText();
const { selection, document } = window.activeTextEditor!;
const selectText = document.getText(selection).trim();
let content = await context.lowcodeContext!.createChatCompletion({
messages: [
{
role: 'system',
content: `你是一個(gè)翻譯家,你的目標(biāo)是把中文翻譯成英文單詞,請(qǐng)翻譯時(shí)使用駝峰格式,小寫字母開頭,不要帶翻譯腔,而是要翻譯得自然、流暢和地道,使用優(yōu)美和高雅的表達(dá)方式。請(qǐng)翻譯下面用戶輸入的內(nèi)容`,
},
{
role: 'user',
content: selectText || clipboardText,
},
],
});
content = content.charAt(0).toLowerCase() + content.slice(1);
window.activeTextEditor?.edit((editBuilder) => {
if (window.activeTextEditor?.selection.isEmpty) {
editBuilder.insert(window.activeTextEditor.selection.start, content);
} else {
editBuilder.replace(
new Range(
window.activeTextEditor!.selection.start,
window.activeTextEditor!.selection.end,
),
content,
);
}
});
}
使用了 ChatGPT。
再來看看,之前生成管理后臺(tái) CURD 頁面的時(shí)候,連 mock 也一起生成了,主要邏輯放在了 complete 方法里,這是插件的一個(gè)生命周期函數(shù)。
因?yàn)?mock 服務(wù)在另一個(gè)項(xiàng)目里,所以需要跨目錄去生成代碼,這里我在 mock 服務(wù)里加了個(gè)接口返回 mock 項(xiàng)目所在的目錄
.get(`/mockProjectPath`, async (ctx, next) => {
ctx.body = {
status: 200,
msg: '',
result: __dirname,
};
})
生成代碼的時(shí)候請(qǐng)求這個(gè)接口,就知道往哪個(gè)目錄生成代碼了
const mockProjectPathRes = await axios
.get('http://localhost:3001/mockProjectPath', { timeout: 1000 })
.catch(() => {
window.showInformationMessage(
'獲取 mock 項(xiàng)目路徑失敗,跳過更新 mock 服務(wù)',
);
});
if (mockProjectPathRes?.data.result) {
const projectName = workspace.rootPath
?.replace(/\\/g, '/')
.split('/')
.pop();
const mockRouteFile = path.join(
mockProjectPathRes.data.result,
`${projectName}.js`,
);
let mockFileContent = `
import KoaRouter from 'koa-router';
import proxy from '../middleware/Proxy';
import { delay } from '../lib/util';
const Mock = require('mockjs');
const { Random } = Mock;
const router = new KoaRouter();
router{{mockScript}}
module.exports = router;
`;
if (fs.existsSync(mockRouteFile)) {
mockFileContent = fs.readFileSync(mockRouteFile).toString().toString();
const index = mockFileContent.lastIndexOf(')') + 1;
mockFileContent = `${mockFileContent.substring(
0,
index,
)}{{mockScript}}\n${mockFileContent.substring(index)}`;
}
mockFileContent = mockFileContent.replace(/{{mockScript}}/g, mockScript);
fs.writeFileSync(mockRouteFile, mockFileContent);
try {
execa.sync('node', [
path.join(
mockProjectPathRes.data.result
.replace(/\\/g, '/')
.replace('/src/routes', ''),
'/node_modules/eslint/bin/eslint.js',
),
mockRouteFile,
'--resolve-plugins-relative-to',
mockProjectPathRes.data.result
.replace(/\\/g, '/')
.replace('/src/routes', ''),
'--fix',
]);
} catch (err) {
console.log(err);
}
mock 項(xiàng)目也可以通過 vscode 插件快速創(chuàng)建和使用
插件源碼 https://github.com/lowcoding/lowcode-vscode文章來源:http://www.zghlxwxcb.cn/news/detail-781453.html
上面展示的模版都放在了 https://github.com/lowcode-scaffold/lowcode-materials 倉庫里,照著 README 步驟做就可以使用了。文章來源地址http://www.zghlxwxcb.cn/news/detail-781453.html
到了這里,關(guān)于還在封裝 xxxForm,xxxTable 殘害你的同事?試試這個(gè)工具的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!