feat: 增加AI生成页面功能。
This commit is contained in:
@@ -118,7 +118,7 @@ func (autoApi *AutoCodeApi) LLMAuto(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
path := strings.ReplaceAll(global.GVA_CONFIG.AutoCode.AiPath, "{FUNC}", fmt.Sprintf("api/chat/%s", llm["mode"]))
|
||||
path := strings.ReplaceAll(global.GVA_CONFIG.AutoCode.AiPath, "{FUNC}", fmt.Sprintf("chat/%s", llm["mode"]))
|
||||
res, err := request.HttpRequest(
|
||||
path,
|
||||
"POST",
|
||||
|
@@ -173,6 +173,17 @@ export const eye = (data) => {
|
||||
}
|
||||
|
||||
|
||||
export const createWeb = (data) => {
|
||||
return service({
|
||||
url: '/autoCode/llmAuto',
|
||||
method: 'post',
|
||||
data: { ...data, mode: 'painter' },
|
||||
timeout: 1000 * 60 * 10
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const addFunc = (data) => {
|
||||
return service({
|
||||
url: '/autoCode/addFunc',
|
||||
|
@@ -16,6 +16,7 @@
|
||||
"/src/view/example/breakpoint/breakpoint.vue": "BreakPoint",
|
||||
"/src/view/example/customer/customer.vue": "Customer",
|
||||
"/src/view/example/index.vue": "Example",
|
||||
"/src/view/example/upload/scanUpload.vue": "scanUpload",
|
||||
"/src/view/example/upload/upload.vue": "Upload",
|
||||
"/src/view/init/index.vue": "Init",
|
||||
"/src/view/layout/aside/asideComponent/asyncSubmenu.vue": "AsyncSubmenu",
|
||||
@@ -53,6 +54,7 @@
|
||||
"/src/view/superAdmin/user/user.vue": "User",
|
||||
"/src/view/system/state.vue": "State",
|
||||
"/src/view/systemTools/autoCode/component/fieldDialog.vue": "FieldDialog",
|
||||
"/src/view/systemTools/autoCode/component/iframeRenderer.vue": "IframeRenderer",
|
||||
"/src/view/systemTools/autoCode/component/previewCodeDialog.vue": "PreviewCodeDialog",
|
||||
"/src/view/systemTools/autoCode/index.vue": "AutoCode",
|
||||
"/src/view/systemTools/autoCode/picture.vue": "Picture",
|
||||
|
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<iframe
|
||||
ref="iframe"
|
||||
class="w-full border-0"
|
||||
:style="{ height: iframeHeight + 'px' }"
|
||||
></iframe>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
html: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const iframe = ref(null)
|
||||
const iframeHeight = ref(400) // Default height
|
||||
|
||||
const renderContent = () => {
|
||||
if (!iframe.value) return
|
||||
|
||||
const doc = iframe.value.contentDocument || iframe.value.contentWindow.document
|
||||
|
||||
const htmlTemplate = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- Production version of Vue -->
|
||||
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"><\/script>
|
||||
<!-- Element Plus -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css" />
|
||||
<script src="https://unpkg.com/element-plus/dist/index.full.min.js"><\/script>
|
||||
<!-- Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"><\/script>
|
||||
<style>
|
||||
body { margin: 0; padding: 10px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
// Initialize Vue app
|
||||
const app = Vue.createApp({
|
||||
template: \`${props.html.replace(/`/g, '\\`')}\`,
|
||||
data() {
|
||||
return {}
|
||||
}
|
||||
});
|
||||
|
||||
// Use Element Plus
|
||||
app.use(ElementPlus);
|
||||
|
||||
// Mount the application
|
||||
app.mount('#app');
|
||||
|
||||
// Adjust iframe height
|
||||
setTimeout(() => {
|
||||
const height = document.body.scrollHeight;
|
||||
window.parent.postMessage({
|
||||
type: 'resize',
|
||||
height: height
|
||||
}, '*');
|
||||
}, 100);
|
||||
<\/script>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
doc.open()
|
||||
doc.write(htmlTemplate)
|
||||
doc.close()
|
||||
}
|
||||
|
||||
// Listen for height updates from iframe
|
||||
onMounted(() => {
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.data && event.data.type === 'resize') {
|
||||
iframeHeight.value = event.data.height + 20 // Add padding
|
||||
}
|
||||
})
|
||||
|
||||
// Render with slight delay to ensure iframe is ready
|
||||
setTimeout(() => {
|
||||
renderContent()
|
||||
}, 50)
|
||||
})
|
||||
|
||||
// Re-render when HTML changes
|
||||
watch(() => props.html, () => {
|
||||
setTimeout(() => {
|
||||
renderContent()
|
||||
}, 50)
|
||||
})
|
||||
</script>
|
@@ -4,7 +4,7 @@
|
||||
href="https://www.bilibili.com/video/BV1kv4y1g7nT?p=3"
|
||||
title="此功能为开发环境使用,不建议发布到生产,具体使用效果请点我观看。"
|
||||
/>
|
||||
<div v-if="!isAdd" class="gva-search-box">
|
||||
<div class="gva-search-box">
|
||||
<div class="text-lg mb-2 text-gray-600">
|
||||
使用AI创建<a
|
||||
class="text-blue-600 text-sm ml-4"
|
||||
@@ -17,39 +17,13 @@
|
||||
<el-input
|
||||
v-model="prompt"
|
||||
:maxlength="2000"
|
||||
:placeholder="`现已完全免费\n试试复制一张图片然后按下ctrl+v或者commend+v\n试试描述你的表,让AI帮你完成。\n此功能需要到插件市场个人中心获取自己的AI-Path,把AI-Path填入config.yaml下的autocode-->ai-path,重启项目即可使用。\n按下 Ctrl+Enter 或 Cmd+Enter 直接生成`"
|
||||
:placeholder="`请大体描述您希望生成的页面`"
|
||||
:rows="5"
|
||||
resize="none"
|
||||
type="textarea"
|
||||
@blur="handleBlur"
|
||||
@focus="handleFocus"
|
||||
/>
|
||||
|
||||
<div class="flex absolute right-28 bottom-2">
|
||||
<el-tooltip effect="light">
|
||||
<template #content>
|
||||
<div>
|
||||
【完全免费】前往<a
|
||||
class="text-blue-600"
|
||||
href="https://plugin.gin-vue-admin.com/#/layout/userInfo/center"
|
||||
target="_blank"
|
||||
>插件市场个人中心</a
|
||||
>申请AIPath,填入config.yaml的ai-path属性即可使用。
|
||||
</div>
|
||||
</template>
|
||||
<el-button
|
||||
:disabled="form.onlyTemplate"
|
||||
type="primary"
|
||||
@click="eyeFunc()"
|
||||
>
|
||||
<el-icon size="18">
|
||||
<ai-gva/>
|
||||
</el-icon>
|
||||
识图
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="flex absolute right-2 bottom-2">
|
||||
<el-tooltip effect="light">
|
||||
<template #content>
|
||||
@@ -63,7 +37,6 @@
|
||||
</div>
|
||||
</template>
|
||||
<el-button
|
||||
:disabled="form.onlyTemplate"
|
||||
type="primary"
|
||||
@click="llmAutoFunc()"
|
||||
>
|
||||
@@ -81,14 +54,11 @@
|
||||
<el-empty :image-size="200"/>
|
||||
</div>
|
||||
<div v-if="outPut">
|
||||
<div
|
||||
v-for="(snippet, index) in htmlFromLLM"
|
||||
:key="index"
|
||||
class="mb-6 p-4 border"
|
||||
>
|
||||
<div v-for="(snippet, index) in htmlFromLLM" :key="index" class="mb-6 p-4 border">
|
||||
<el-button type="primary" :icon="Upload" class="px-2 py-1" @click="copySnippet(snippet)" plain>复制</el-button>
|
||||
<!-- 用 v-html 渲染该片段 -->
|
||||
<div class="mt-2" v-html="snippet"></div>
|
||||
<div class="mt-2">
|
||||
<iframe-renderer :html="snippet" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -97,25 +67,23 @@
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
getDB,
|
||||
getMeta,
|
||||
getPackageApi,
|
||||
llmAuto, eye
|
||||
eye, createWeb
|
||||
} from '@/api/autoCode'
|
||||
import {getDict} from '@/utils/dictionary'
|
||||
import {ref, watch, nextTick} from 'vue'
|
||||
import {useRoute, useRouter} from 'vue-router'
|
||||
import {ElMessage, ElMessageBox} from 'element-plus'
|
||||
import {ref} from 'vue'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'Picture'
|
||||
})
|
||||
|
||||
import IframeRenderer from '@/view/systemTools/autoCode/component/iframeRenderer.vue'
|
||||
|
||||
const handleFocus = () => {
|
||||
document.addEventListener('keydown', handleKeydown);
|
||||
document.addEventListener('paste', handlePaste);
|
||||
}
|
||||
|
||||
const handleBlur = () => {
|
||||
document.removeEventListener('keydown', handleKeydown);
|
||||
document.removeEventListener('paste', handlePaste);
|
||||
}
|
||||
|
||||
const handleKeydown = (event) => {
|
||||
@@ -124,402 +92,22 @@ const handleKeydown = (event) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handlePaste = (event) => {
|
||||
const items = event.clipboardData.items;
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].type.indexOf('image') !== -1) {
|
||||
const file = items[i].getAsFile();
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (e) => {
|
||||
const base64String = e.target.result;
|
||||
const res = await eye({picture: base64String, command: 'eye'})
|
||||
if (res.code === 0) {
|
||||
prompt.value = res.data
|
||||
llmAutoFunc()
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const prompt = ref('')
|
||||
|
||||
const eyeFunc = async () => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = 'image/*';
|
||||
|
||||
input.onchange = (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (e) => {
|
||||
const base64String = e.target.result;
|
||||
|
||||
const res = await eye({picture: base64String, command: 'eye'})
|
||||
if (res.code === 0) {
|
||||
prompt.value = res.data
|
||||
llmAutoFunc()
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
|
||||
input.click();
|
||||
}
|
||||
|
||||
// 判断是否返回的标志
|
||||
const outPut = ref(false)
|
||||
// 容纳llm返回的html
|
||||
const htmlFromLLM = ref([])
|
||||
|
||||
const llmAutoFunc = async (flag) => {
|
||||
const res = await llmAuto({
|
||||
prompt: flag ? '结构体名称为' + form.value.structName : prompt.value
|
||||
})
|
||||
const llmAutoFunc = async () => {
|
||||
const res = await createWeb({web: prompt.value, command: 'createWeb'})
|
||||
if (res.code === 0) {
|
||||
outPut.value = true
|
||||
// 使用mock数据模拟大模型返回值
|
||||
const mock1 = `<div class="flex flex-col min-h-screen bg-white">
|
||||
<!-- 顶部导航栏 -->
|
||||
<header class="bg-white shadow-md">
|
||||
<div class="container mx-auto px-6 py-4 flex items-center justify-between">
|
||||
<div class="text-2xl font-bold text-blue-600">LOGO</div>
|
||||
<nav class="flex space-x-6">
|
||||
<a href="#" class="text-black hover:text-blue-600 transition duration-300">首页</a>
|
||||
<a href="#" class="text-black hover:text-blue-600 transition duration-300">关于我们</a>
|
||||
<a href="#" class="text-black hover:text-blue-600 transition duration-300">服务</a>
|
||||
<a href="#" class="text-black hover:text-blue-600 transition duration-300">联系我们</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 中间内容区域 -->
|
||||
<main class="flex-grow container mx-auto px-6 py-8 flex">
|
||||
<!-- 左侧边栏 -->
|
||||
<aside class="w-1/4 bg-gray-50 p-6 rounded-lg shadow-sm">
|
||||
<nav class="space-y-3">
|
||||
<a href="#" class="block text-black hover:text-blue-600 transition duration-300">导航1</a>
|
||||
<a href="#" class="block text-black hover:text-blue-600 transition duration-300">导航2</a>
|
||||
<a href="#" class="block text-black hover:text-blue-600 transition duration-300">导航3</a>
|
||||
<a href="#" class="block text-black hover:text-blue-600 transition duration-300">导航4</a>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- 右侧主要内容区 -->
|
||||
<div class="w-3/4 ml-8">
|
||||
<!-- 轮播图 -->
|
||||
<div class="bg-gray-200 h-64 mb-8 rounded-lg shadow-sm flex items-center justify-center text-gray-600">
|
||||
轮播图
|
||||
</div>
|
||||
|
||||
<!-- 新闻列表 -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-bold text-black mb-4">最新新闻</h2>
|
||||
<ul class="space-y-4">
|
||||
<li class="p-4 bg-gray-50 rounded-lg shadow-sm hover:shadow-md transition duration-300">
|
||||
<a href="#" class="text-black hover:text-blue-600 transition duration-300">新闻标题1</a>
|
||||
<p class="text-sm text-gray-600 mt-1">新闻简介内容...</p>
|
||||
</li>
|
||||
<li class="p-4 bg-gray-50 rounded-lg shadow-sm hover:shadow-md transition duration-300">
|
||||
<a href="#" class="text-black hover:text-blue-600 transition duration-300">新闻标题2</a>
|
||||
<p class="text-sm text-gray-600 mt-1">新闻简介内容...</p>
|
||||
</li>
|
||||
<li class="p-4 bg-gray-50 rounded-lg shadow-sm hover:shadow-md transition duration-300">
|
||||
<a href="#" class="text-black hover:text-blue-600 transition duration-300">新闻标题3</a>
|
||||
<p class="text-sm text-gray-600 mt-1">新闻简介内容...</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 广告位 -->
|
||||
<div class="bg-gray-200 h-32 rounded-lg shadow-sm flex items-center justify-center text-gray-600">
|
||||
广告位
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 底部 -->
|
||||
<footer class="bg-gray-800 text-white py-6">
|
||||
<div class="container mx-auto px-6 text-center">
|
||||
<p class="text-sm">© 2023 公司名称. 保留所有权利.</p>
|
||||
<p class="text-sm mt-2">联系我们: info@example.com</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>`
|
||||
const mock2 = ` <div class="flex flex-col min-h-screen bg-[#e9f0fc]">
|
||||
<!-- 上布局 -->
|
||||
<div class="flex flex-1">
|
||||
<!-- 左侧登录表单区域 -->
|
||||
<div class="w-1/2 p-12 flex items-center justify-center">
|
||||
<div class="w-full max-w-md bg-white p-8 rounded-xl shadow-2xl">
|
||||
<h2 class="text-3xl font-bold text-[#2c3e50] mb-8 text-center">登录</h2>
|
||||
<form class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-[#2c3e50] mb-2">用户名</label>
|
||||
<input type="text" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#20a0ff] transition duration-300" placeholder="请输入用户名" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-[#2c3e50] mb-2">密码</label>
|
||||
<input type="password" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#20a0ff] transition duration-300" placeholder="请输入密码" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-[#2c3e50] mb-2">验证码</label>
|
||||
<div class="flex space-x-4">
|
||||
<input type="text" class="w-3/4 px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#20a0ff] transition duration-300" placeholder="请输入验证码" />
|
||||
<button class="w-1/4 px-4 py-3 bg-[#20a0ff] text-white rounded-lg hover:bg-[#1d8fe0] transition duration-300">获取验证码</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="w-full px-4 py-3 bg-[#20a0ff] text-white rounded-lg hover:bg-[#1d8fe0] transition duration-300">登录</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧功能介绍区 -->
|
||||
<div class="w-1/2 p-12 flex items-center justify-center">
|
||||
<div class="relative w-full max-w-2xl">
|
||||
<!-- 复古电视机样式 -->
|
||||
<div class="relative bg-gray-800 rounded-2xl p-8 shadow-2xl">
|
||||
<div class="absolute inset-0 bg-black rounded-2xl opacity-50"></div>
|
||||
<div class="relative z-10">
|
||||
<!-- 电视屏幕 -->
|
||||
<div class="bg-gray-700 rounded-lg p-6">
|
||||
<div class="bg-gray-900 rounded-lg p-6">
|
||||
<!-- SVG图形和动画内容 -->
|
||||
<svg class="w-full h-64" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="50" cy="50" r="45" fill="#20a0ff" />
|
||||
<text x="50%" y="50%" text-anchor="middle" fill="#ffffff" font-size="24" dy=".3em">TV</text>
|
||||
<!-- 模拟电视内容 -->
|
||||
<animateTransform attributeName="transform" type="rotate" from="0 50 50" to="360 50 50" dur="10s" repeatCount="indefinite" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中部空白区域 -->
|
||||
<div class="flex-1 bg-[#e9f0fc] p-12">
|
||||
<!-- 广告或其他内容 -->
|
||||
<div class="bg-white p-8 rounded-xl shadow-2xl text-center">
|
||||
<h3 class="text-2xl font-bold text-[#2c3e50] mb-4">广告位</h3>
|
||||
<p class="text-gray-600">这里可以放置广告或其他内容。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部 -->
|
||||
<footer class="bg-[#2c3e50] text-white py-6">
|
||||
<div class="container mx-auto px-6 text-center">
|
||||
<p class="text-sm">© 2023 公司名称. 保留所有权利.</p>
|
||||
<p class="text-sm mt-2">联系我们: info@example.com</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>`
|
||||
htmlFromLLM.value.push(mock1, mock2)
|
||||
console.log(htmlFromLLM.value);
|
||||
}
|
||||
}
|
||||
|
||||
// 复制方法:把某个字符串写进剪贴板
|
||||
const copySnippet = (htmlString) => {
|
||||
navigator.clipboard.writeText(htmlString)
|
||||
.then(() => {
|
||||
ElMessage({
|
||||
message: '复制成功',
|
||||
type: 'success',
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
ElMessage({
|
||||
message: '复制失败',
|
||||
type: 'warning',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const isAdd = ref(false)
|
||||
|
||||
|
||||
defineOptions({
|
||||
name: 'AutoCode'
|
||||
})
|
||||
const gormModelList = ['id', 'created_at', 'updated_at', 'deleted_at']
|
||||
|
||||
const dataModelList = ['created_by', 'updated_by', 'deleted_by']
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const dbform = ref({
|
||||
businessDB: '',
|
||||
dbName: '',
|
||||
tableName: ''
|
||||
})
|
||||
const fdMap = ref({})
|
||||
const form = ref({
|
||||
structName: '',
|
||||
tableName: '',
|
||||
packageName: '',
|
||||
package: '',
|
||||
abbreviation: '',
|
||||
description: '',
|
||||
businessDB: '',
|
||||
autoCreateApiToSql: true,
|
||||
autoCreateMenuToSql: true,
|
||||
autoCreateBtnAuth: false,
|
||||
autoMigrate: true,
|
||||
gvaModel: true,
|
||||
autoCreateResource: false,
|
||||
onlyTemplate: false,
|
||||
isTree: false,
|
||||
treeJson: "",
|
||||
fields: []
|
||||
})
|
||||
const rules = ref({
|
||||
structName: [
|
||||
{required: true, message: '请输入结构体名称', trigger: 'blur'}
|
||||
],
|
||||
abbreviation: [
|
||||
{required: true, message: '请输入结构体简称', trigger: 'blur'}
|
||||
],
|
||||
description: [
|
||||
{required: true, message: '请输入结构体描述', trigger: 'blur'}
|
||||
],
|
||||
packageName: [
|
||||
{
|
||||
required: true,
|
||||
message: '文件名称:sysXxxxXxxx',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
package: [{required: true, message: '请选择package', trigger: 'blur'}]
|
||||
})
|
||||
|
||||
|
||||
const dbList = ref([])
|
||||
const dbOptions = ref([])
|
||||
|
||||
const getDbFunc = async () => {
|
||||
dbform.value.dbName = ''
|
||||
dbform.value.tableName = ''
|
||||
const res = await getDB({businessDB: dbform.value.businessDB})
|
||||
if (res.code === 0) {
|
||||
dbOptions.value = res.data.dbs
|
||||
dbList.value = res.data.dbList
|
||||
}
|
||||
}
|
||||
|
||||
const setFdMap = async () => {
|
||||
const fdTypes = ['string', 'int', 'bool', 'float64', 'time.Time']
|
||||
fdTypes.forEach(async (fdtype) => {
|
||||
const res = await getDict(fdtype)
|
||||
res &&
|
||||
res.forEach((item) => {
|
||||
fdMap.value[item.label] = fdtype
|
||||
})
|
||||
})
|
||||
}
|
||||
const getAutoCodeJson = async (id) => {
|
||||
const res = await getMeta({id: Number(id)})
|
||||
if (res.code === 0) {
|
||||
const add = route.query.isAdd
|
||||
isAdd.value = add
|
||||
form.value = JSON.parse(res.data.meta)
|
||||
if (isAdd.value) {
|
||||
form.value.fields.forEach((item) => {
|
||||
item.disabled = true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const pkgs = ref([])
|
||||
const getPkgs = async () => {
|
||||
const res = await getPackageApi()
|
||||
if (res.code === 0) {
|
||||
pkgs.value = res.data.pkgs
|
||||
htmlFromLLM.value.push(res.data)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const init = () => {
|
||||
getDbFunc()
|
||||
setFdMap()
|
||||
getPkgs()
|
||||
const id = route.params.id
|
||||
if (id) {
|
||||
getAutoCodeJson(id)
|
||||
}
|
||||
}
|
||||
init()
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
() => {
|
||||
if (route.name === 'autoCodeEdit') {
|
||||
init()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const getCatch = () => {
|
||||
const data = window.sessionStorage.getItem('autoCode')
|
||||
if (data) {
|
||||
form.value = JSON.parse(data)
|
||||
}
|
||||
}
|
||||
|
||||
const clearCatch = async () => {
|
||||
form.value = {
|
||||
structName: '',
|
||||
tableName: '',
|
||||
packageName: '',
|
||||
package: '',
|
||||
abbreviation: '',
|
||||
description: '',
|
||||
businessDB: '',
|
||||
autoCreateApiToSql: true,
|
||||
autoCreateMenuToSql: true,
|
||||
autoCreateBtnAuth: false,
|
||||
autoMigrate: true,
|
||||
gvaModel: true,
|
||||
autoCreateResource: false,
|
||||
onlyTemplate: false,
|
||||
isTree: false,
|
||||
treeJson: "",
|
||||
fields: []
|
||||
}
|
||||
await nextTick()
|
||||
window.sessionStorage.removeItem('autoCode')
|
||||
}
|
||||
|
||||
getCatch()
|
||||
|
||||
|
||||
watch(
|
||||
() => form.value.onlyTemplate,
|
||||
(val) => {
|
||||
if (val) {
|
||||
ElMessageBox.confirm(
|
||||
'使用基础模板将不会生成任何结构体和CURD,仅仅配置enter等属性方便自行开发非CURD逻辑',
|
||||
'注意',
|
||||
{
|
||||
confirmButtonText: '继续',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
form.value.fields = []
|
||||
})
|
||||
.catch(() => {
|
||||
form.value.onlyTemplate = false
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
@@ -18,108 +18,6 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
},
|
||||
safelist: [
|
||||
/*
|
||||
1) 常见的自定义颜色写法,如 bg-[#xxxxxx]、text-[#xxxxxx]、border-[#xxxxxx]
|
||||
如果 LLM/接口频繁返回各种 [#RRGGBB] 形式,这个 pattern 可以保留它们的CSS。
|
||||
*/
|
||||
{ pattern: /^bg-\[.*\]$/ },
|
||||
{ pattern: /^text-\[.*\]$/ },
|
||||
{ pattern: /^border-\[.*\]$/ },
|
||||
|
||||
/*
|
||||
2) Tailwind 默认调色板里的常见前缀
|
||||
下面以 (red|green|blue|gray|indigo|...) 为例,你可根据自己项目加减。
|
||||
同时包括不同深度 (50,100,200,...,900),也可再详细拆分。
|
||||
*/
|
||||
{
|
||||
pattern: /^(bg|text|border)-(red|green|blue|gray|indigo|yellow|purple|pink|cyan|teal|orange|amber|lime|emerald|fuchsia|rose|sky|violet|stone|neutral)-(50|100|200|300|400|500|600|700|800|900)$/
|
||||
},
|
||||
|
||||
/*
|
||||
3) 大小相关(padding、margin等):
|
||||
m-*, p-* 以及更精细的 mt-*, mb-*...
|
||||
匹配数字、1/2、px、等常见写法(也包括 m-auto)。
|
||||
你可以根据自己需求加减。
|
||||
*/
|
||||
{ pattern: /^m-[0-9]+$/ },
|
||||
{ pattern: /^mx-[0-9]+$/ },
|
||||
{ pattern: /^my-[0-9]+$/ },
|
||||
{ pattern: /^mt-[0-9]+$/ },
|
||||
{ pattern: /^mr-[0-9]+$/ },
|
||||
{ pattern: /^mb-[0-9]+$/ },
|
||||
{ pattern: /^ml-[0-9]+$/ },
|
||||
{ pattern: /^m-(auto|px)$/ },
|
||||
|
||||
{ pattern: /^p-[0-9]+$/ },
|
||||
{ pattern: /^px-[0-9]+$/ },
|
||||
{ pattern: /^py-[0-9]+$/ },
|
||||
{ pattern: /^pt-[0-9]+$/ },
|
||||
{ pattern: /^pr-[0-9]+$/ },
|
||||
{ pattern: /^pb-[0-9]+$/ },
|
||||
{ pattern: /^pl-[0-9]+$/ },
|
||||
{ pattern: /^p-(auto|px)$/ },
|
||||
|
||||
/*
|
||||
4) 宽高相关: w-*, h-*,以及自定义的 w-[300px]、h-[50vh] 等。
|
||||
*/
|
||||
{ pattern: /^w-.*$/ },
|
||||
{ pattern: /^h-.*$/ },
|
||||
|
||||
/*
|
||||
5) 文本尺寸/排版
|
||||
常见如 text-sm, text-lg, text-xl, text-2xl... 也可加正则覆盖 text-[数字]xl
|
||||
*/
|
||||
{ pattern: /^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl)$/ },
|
||||
{ pattern: /^text-\d+xl$/ },
|
||||
|
||||
/*
|
||||
6) Flex 相关
|
||||
*/
|
||||
"flex",
|
||||
{ pattern: /^flex-(row|col|wrap|nowrap|row-reverse|col-reverse)$/ },
|
||||
{ pattern: /^items-(start|end|center|baseline|stretch)$/ },
|
||||
{ pattern: /^justify-(start|end|center|between|around|evenly)$/ },
|
||||
{ pattern: /^content-(start|end|center|between|around|evenly)$/ },
|
||||
|
||||
/*
|
||||
7) 边框 & 圆角 & 阴影
|
||||
*/
|
||||
{ pattern: /^rounded(-(none|sm|md|lg|xl|2xl|3xl|full))?$/ },
|
||||
{ pattern: /^rounded-[trbl]{1,2}(-(none|sm|md|lg|xl|2xl|3xl|full))?$/ }, // 形如 rounded-t-lg、rounded-bl-md
|
||||
{ pattern: /^shadow(-(sm|md|lg|xl|2xl|inner|none))?$/ },
|
||||
{ pattern: /^border(-(0|2|4|8))?$/ },
|
||||
{ pattern: /^border-[trbl]{1,2}(-(0|2|4|8))?$/ },
|
||||
|
||||
/*
|
||||
8) 文本对齐 & 显示模式
|
||||
*/
|
||||
{ pattern: /^text-(left|center|right|justify)$/ },
|
||||
{ pattern: /^(block|inline|inline-block|inline-flex|hidden)$/ },
|
||||
|
||||
/*
|
||||
9) 状态变体(如 hover:, focus:, active: 等)
|
||||
允许 hover:bg-red-500、focus:border-blue-500 等
|
||||
这里用 .+ 去捕获前缀后所有东西,不过要小心可能会保留过多无用CSS
|
||||
*/
|
||||
{ pattern: /^hover:.+$/ },
|
||||
{ pattern: /^focus:.+$/ },
|
||||
{ pattern: /^active:.+$/ },
|
||||
|
||||
/*
|
||||
10) 你自己项目中最常出现的其他 patterns (可自行添加):
|
||||
- z-[数字]
|
||||
- absolute / relative / fixed / sticky
|
||||
- top-[数字] / left-[数字]
|
||||
- grid / gap-[数字]
|
||||
- ...
|
||||
*/
|
||||
// { pattern: /^z-\d+$/ },
|
||||
// "absolute", "relative", "fixed", "sticky",
|
||||
// { pattern: /^top-\[.*\]$/ },
|
||||
// { pattern: /^left-\[.*\]$/ },
|
||||
// "grid", { pattern: /^gap-\d+$/ },
|
||||
],
|
||||
darkMode: 'class',
|
||||
plugins: []
|
||||
}
|
||||
|
Reference in New Issue
Block a user