feat: 增加AI生成页面功能。
This commit is contained in:
@@ -118,7 +118,7 @@ func (autoApi *AutoCodeApi) LLMAuto(c *gin.Context) {
|
|||||||
return
|
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(
|
res, err := request.HttpRequest(
|
||||||
path,
|
path,
|
||||||
"POST",
|
"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) => {
|
export const addFunc = (data) => {
|
||||||
return service({
|
return service({
|
||||||
url: '/autoCode/addFunc',
|
url: '/autoCode/addFunc',
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
"/src/view/example/breakpoint/breakpoint.vue": "BreakPoint",
|
"/src/view/example/breakpoint/breakpoint.vue": "BreakPoint",
|
||||||
"/src/view/example/customer/customer.vue": "Customer",
|
"/src/view/example/customer/customer.vue": "Customer",
|
||||||
"/src/view/example/index.vue": "Example",
|
"/src/view/example/index.vue": "Example",
|
||||||
|
"/src/view/example/upload/scanUpload.vue": "scanUpload",
|
||||||
"/src/view/example/upload/upload.vue": "Upload",
|
"/src/view/example/upload/upload.vue": "Upload",
|
||||||
"/src/view/init/index.vue": "Init",
|
"/src/view/init/index.vue": "Init",
|
||||||
"/src/view/layout/aside/asideComponent/asyncSubmenu.vue": "AsyncSubmenu",
|
"/src/view/layout/aside/asideComponent/asyncSubmenu.vue": "AsyncSubmenu",
|
||||||
@@ -53,6 +54,7 @@
|
|||||||
"/src/view/superAdmin/user/user.vue": "User",
|
"/src/view/superAdmin/user/user.vue": "User",
|
||||||
"/src/view/system/state.vue": "State",
|
"/src/view/system/state.vue": "State",
|
||||||
"/src/view/systemTools/autoCode/component/fieldDialog.vue": "FieldDialog",
|
"/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/component/previewCodeDialog.vue": "PreviewCodeDialog",
|
||||||
"/src/view/systemTools/autoCode/index.vue": "AutoCode",
|
"/src/view/systemTools/autoCode/index.vue": "AutoCode",
|
||||||
"/src/view/systemTools/autoCode/picture.vue": "Picture",
|
"/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"
|
href="https://www.bilibili.com/video/BV1kv4y1g7nT?p=3"
|
||||||
title="此功能为开发环境使用,不建议发布到生产,具体使用效果请点我观看。"
|
title="此功能为开发环境使用,不建议发布到生产,具体使用效果请点我观看。"
|
||||||
/>
|
/>
|
||||||
<div v-if="!isAdd" class="gva-search-box">
|
<div class="gva-search-box">
|
||||||
<div class="text-lg mb-2 text-gray-600">
|
<div class="text-lg mb-2 text-gray-600">
|
||||||
使用AI创建<a
|
使用AI创建<a
|
||||||
class="text-blue-600 text-sm ml-4"
|
class="text-blue-600 text-sm ml-4"
|
||||||
@@ -17,39 +17,13 @@
|
|||||||
<el-input
|
<el-input
|
||||||
v-model="prompt"
|
v-model="prompt"
|
||||||
:maxlength="2000"
|
: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"
|
:rows="5"
|
||||||
resize="none"
|
resize="none"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
@blur="handleBlur"
|
@blur="handleBlur"
|
||||||
@focus="handleFocus"
|
@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">
|
<div class="flex absolute right-2 bottom-2">
|
||||||
<el-tooltip effect="light">
|
<el-tooltip effect="light">
|
||||||
<template #content>
|
<template #content>
|
||||||
@@ -63,7 +37,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-button
|
<el-button
|
||||||
:disabled="form.onlyTemplate"
|
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="llmAutoFunc()"
|
@click="llmAutoFunc()"
|
||||||
>
|
>
|
||||||
@@ -81,14 +54,11 @@
|
|||||||
<el-empty :image-size="200"/>
|
<el-empty :image-size="200"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="outPut">
|
<div v-if="outPut">
|
||||||
<div
|
<div v-for="(snippet, index) in htmlFromLLM" :key="index" class="mb-6 p-4 border">
|
||||||
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>
|
<el-button type="primary" :icon="Upload" class="px-2 py-1" @click="copySnippet(snippet)" plain>复制</el-button>
|
||||||
<!-- 用 v-html 渲染该片段 -->
|
<div class="mt-2">
|
||||||
<div class="mt-2" v-html="snippet"></div>
|
<iframe-renderer :html="snippet" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -97,25 +67,23 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
getDB,
|
eye, createWeb
|
||||||
getMeta,
|
|
||||||
getPackageApi,
|
|
||||||
llmAuto, eye
|
|
||||||
} from '@/api/autoCode'
|
} from '@/api/autoCode'
|
||||||
import {getDict} from '@/utils/dictionary'
|
import {ref} from 'vue'
|
||||||
import {ref, watch, nextTick} from 'vue'
|
|
||||||
import {useRoute, useRouter} from 'vue-router'
|
|
||||||
import {ElMessage, ElMessageBox} from 'element-plus'
|
|
||||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'Picture'
|
||||||
|
})
|
||||||
|
|
||||||
|
import IframeRenderer from '@/view/systemTools/autoCode/component/iframeRenderer.vue'
|
||||||
|
|
||||||
const handleFocus = () => {
|
const handleFocus = () => {
|
||||||
document.addEventListener('keydown', handleKeydown);
|
document.addEventListener('keydown', handleKeydown);
|
||||||
document.addEventListener('paste', handlePaste);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleBlur = () => {
|
const handleBlur = () => {
|
||||||
document.removeEventListener('keydown', handleKeydown);
|
document.removeEventListener('keydown', handleKeydown);
|
||||||
document.removeEventListener('paste', handlePaste);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleKeydown = (event) => {
|
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 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)
|
const outPut = ref(false)
|
||||||
// 容纳llm返回的html
|
// 容纳llm返回的html
|
||||||
const htmlFromLLM = ref([])
|
const htmlFromLLM = ref([])
|
||||||
|
|
||||||
const llmAutoFunc = async (flag) => {
|
const llmAutoFunc = async () => {
|
||||||
const res = await llmAuto({
|
const res = await createWeb({web: prompt.value, command: 'createWeb'})
|
||||||
prompt: flag ? '结构体名称为' + form.value.structName : prompt.value
|
|
||||||
})
|
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
outPut.value = true
|
outPut.value = true
|
||||||
// 使用mock数据模拟大模型返回值
|
// 使用mock数据模拟大模型返回值
|
||||||
const mock1 = `<div class="flex flex-col min-h-screen bg-white">
|
htmlFromLLM.value.push(res.data)
|
||||||
<!-- 顶部导航栏 -->
|
|
||||||
<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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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>
|
</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',
|
darkMode: 'class',
|
||||||
plugins: []
|
plugins: []
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user