Merge remote-tracking branch 'refs/remotes/origin/dev-281'

This commit is contained in:
piexlMax(奇淼
2025-04-09 09:51:04 +08:00
13 changed files with 339 additions and 86 deletions

View File

@@ -12,18 +12,31 @@ import (
// GormMysql 初始化Mysql数据库
// Author [piexlmax](https://github.com/piexlmax)
// Author [SliverHorn](https://github.com/SliverHorn)
// Author [ByteZhou-2018](https://github.com/ByteZhou-2018)
func GormMysql() *gorm.DB {
m := global.GVA_CONFIG.Mysql
return initMysqlDatabase(m)
}
// GormMysqlByConfig 通过传入配置初始化Mysql数据库
func GormMysqlByConfig(m config.Mysql) *gorm.DB {
return initMysqlDatabase(m)
}
// initMysqlDatabase 初始化Mysql数据库的辅助函数
func initMysqlDatabase(m config.Mysql) *gorm.DB {
if m.Dbname == "" {
return nil
}
mysqlConfig := mysql.Config{
DSN: m.Dsn(), // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
SkipInitializeWithVersion: false, // 根据版本自动配置
}
if db, err := gorm.Open(mysql.New(mysqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
return nil
panic(err)
} else {
db.InstanceSet("gorm:table_options", "ENGINE="+m.Engine)
sqlDB, _ := db.DB()
@@ -32,24 +45,3 @@ func GormMysql() *gorm.DB {
return db
}
}
// GormMysqlByConfig 初始化Mysql数据库用过传入配置
func GormMysqlByConfig(m config.Mysql) *gorm.DB {
if m.Dbname == "" {
return nil
}
mysqlConfig := mysql.Config{
DSN: m.Dsn(), // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
SkipInitializeWithVersion: false, // 根据版本自动配置
}
if db, err := gorm.Open(mysql.New(mysqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
panic(err)
} else {
db.InstanceSet("gorm:table_options", "ENGINE=InnoDB")
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(m.MaxIdleConns)
sqlDB.SetMaxOpenConns(m.MaxOpenConns)
return db
}
}

View File

@@ -15,32 +15,25 @@ import (
// 如果需要Oracle库 放开import里的注释 把下方 mysql.Config 改为 oracle.Config ; mysql.New 改为 oracle.New
func GormOracle() *gorm.DB {
m := global.GVA_CONFIG.Oracle
if m.Dbname == "" {
return nil
}
oracleConfig := mysql.Config{
DSN: m.Dsn(), // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
}
if db, err := gorm.Open(mysql.New(oracleConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
panic(err)
} else {
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(m.MaxIdleConns)
sqlDB.SetMaxOpenConns(m.MaxOpenConns)
return db
}
return initOracleDatabase(m)
}
// GormOracleByConfig 初始化Oracle数据库用过传入配置
func GormOracleByConfig(m config.Oracle) *gorm.DB {
return initOracleDatabase(m)
}
// initOracleDatabase 初始化Oracle数据库的辅助函数
func initOracleDatabase(m config.Oracle) *gorm.DB {
if m.Dbname == "" {
return nil
}
oracleConfig := mysql.Config{
DSN: m.Dsn(), // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
}
if db, err := gorm.Open(mysql.New(oracleConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
panic(err)
} else {

View File

@@ -13,25 +13,16 @@ import (
// Author [SliverHorn](https://github.com/SliverHorn)
func GormPgSql() *gorm.DB {
p := global.GVA_CONFIG.Pgsql
if p.Dbname == "" {
return nil
}
pgsqlConfig := postgres.Config{
DSN: p.Dsn(), // DSN data source name
PreferSimpleProtocol: false,
}
if db, err := gorm.Open(postgres.New(pgsqlConfig), internal.Gorm.Config(p.Prefix, p.Singular)); err != nil {
return nil
} else {
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(p.MaxIdleConns)
sqlDB.SetMaxOpenConns(p.MaxOpenConns)
return db
}
return initPgSqlDatabase(p)
}
// GormPgSqlByConfig 初始化 Postgresql 数据库 通过参数
// GormPgSqlByConfig 初始化 Postgresql 数据库 通过指定参数
func GormPgSqlByConfig(p config.Pgsql) *gorm.DB {
return initPgSqlDatabase(p)
}
// initPgSqlDatabase 初始化 Postgresql 数据库的辅助函数
func initPgSqlDatabase(p config.Pgsql) *gorm.DB {
if p.Dbname == "" {
return nil
}

View File

@@ -11,22 +11,16 @@ import (
// GormSqlite 初始化Sqlite数据库
func GormSqlite() *gorm.DB {
s := global.GVA_CONFIG.Sqlite
if s.Dbname == "" {
return nil
}
if db, err := gorm.Open(sqlite.Open(s.Dsn()), internal.Gorm.Config(s.Prefix, s.Singular)); err != nil {
panic(err)
} else {
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(s.MaxIdleConns)
sqlDB.SetMaxOpenConns(s.MaxOpenConns)
return db
}
return initSqliteDatabase(s)
}
// GormSqliteByConfig 初始化Sqlite数据库用过传入配置
func GormSqliteByConfig(s config.Sqlite) *gorm.DB {
return initSqliteDatabase(s)
}
// initSqliteDatabase 初始化Sqlite数据库辅助函数
func initSqliteDatabase(s config.Sqlite) *gorm.DB {
if s.Dbname == "" {
return nil
}

View File

@@ -2,10 +2,11 @@ package system
import (
"context"
model "github.com/flipped-aurora/gin-vue-admin/server/model/system"
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
"reflect"
"testing"
model "github.com/flipped-aurora/gin-vue-admin/server/model/system"
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
)
func Test_autoCodePackage_Create(t *testing.T) {
@@ -53,9 +54,10 @@ func Test_autoCodePackage_Create(t *testing.T) {
func Test_autoCodePackage_templates(t *testing.T) {
type args struct {
ctx context.Context
entity model.SysAutoCodePackage
info request.AutoCode
ctx context.Context
entity model.SysAutoCodePackage
info request.AutoCode
isPackage bool
}
tests := []struct {
name string
@@ -78,6 +80,7 @@ func Test_autoCodePackage_templates(t *testing.T) {
Abbreviation: "user",
HumpPackageName: "user",
},
isPackage: false,
},
wantErr: false,
},
@@ -85,7 +88,7 @@ func Test_autoCodePackage_templates(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &autoCodePackage{}
gotCode, gotEnter, gotCreates, err := s.templates(tt.args.ctx, tt.args.entity, tt.args.info)
gotCode, gotEnter, gotCreates, err := s.templates(tt.args.ctx, tt.args.entity, tt.args.info, tt.args.isPackage)
if (err != nil) != tt.wantErr {
t.Errorf("templates() error = %v, wantErr %v", err, tt.wantErr)
return

View File

@@ -82,6 +82,7 @@ func (i *initMenu) InitializeData(ctx context.Context) (next context.Context, er
{MenuLevel: 0, Hidden: false, ParentId: 15, Path: "exportTemplate", Name: "exportTemplate", Component: "view/systemTools/exportTemplate/exportTemplate.vue", Sort: 5, Meta: Meta{Title: "导出模板", Icon: "reading"}},
{MenuLevel: 0, Hidden: false, ParentId: 24, Path: "anInfo", Name: "anInfo", Component: "plugin/announcement/view/info.vue", Sort: 5, Meta: Meta{Title: "公告管理[示例]", Icon: "scaleToOriginal"}},
{MenuLevel: 0, Hidden: false, ParentId: 3, Path: "sysParams", Name: "sysParams", Component: "view/superAdmin/params/sysParams.vue", Sort: 7, Meta: Meta{Title: "参数管理", Icon: "compass"}},
{MenuLevel: 0, Hidden: false, ParentId: 15, Path: "picture", Name: "picture", Component: "view/systemTools/autoCode/picture.vue", Sort: 6, Meta: Meta{Title: "AI页面绘制", Icon: "picture-filled"}},
}
if err = db.Create(&entities).Error; err != nil {
return ctx, errors.Wrap(err, SysBaseMenu{}.TableName()+"表数据初始化失败!")

View File

@@ -5,7 +5,6 @@ import (
"time"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/mojocn/base64Captcha"
"go.uber.org/zap"
)
@@ -23,8 +22,10 @@ type RedisStore struct {
Context context.Context
}
func (rs *RedisStore) UseWithCtx(ctx context.Context) base64Captcha.Store {
rs.Context = ctx
func (rs *RedisStore) UseWithCtx(ctx context.Context) *RedisStore {
if ctx == nil {
rs.Context = ctx
}
return rs
}

View File

@@ -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',

View File

@@ -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,8 +54,10 @@
"/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",
"/src/view/systemTools/autoCodeAdmin/index.vue": "AutoCodeAdmin",
"/src/view/systemTools/autoPkg/autoPkg.vue": "AutoPkg",
"/src/view/systemTools/exportTemplate/exportTemplate.vue": "ExportTemplate",

View File

@@ -99,6 +99,27 @@ export const useAppStore = defineStore('app', () => {
config.transition_type = e
}
const baseCoinfg = {
weakness: false,
grey: false,
primaryColor: '#3b82f6',
showTabs: true,
darkMode: 'auto',
layout_side_width: 256,
layout_side_collapsed_width: 80,
layout_side_item_height: 48,
show_watermark: true,
side_mode: 'normal',
// 页面过渡动画配置
transition_type: 'slide'
}
const resetConfig = () => {
for (let baseCoinfgKey in baseCoinfg) {
config[baseCoinfgKey] = baseCoinfg[baseCoinfgKey]
}
}
// 监听色弱模式和灰色模式
watchEffect(() => {
document.documentElement.classList.toggle('html-weakenss', config.weakness)
@@ -128,6 +149,7 @@ export const useAppStore = defineStore('app', () => {
toggleConfigSideItemHeight,
toggleConfigWatermark,
toggleSideMode,
toggleTransition
toggleTransition,
resetConfig
}
})

View File

@@ -9,7 +9,7 @@
<template #header>
<div class="flex justify-between items-center">
<span class="text-lg">系统配置</span>
<el-button type="primary" @click="saveConfig">保存配置</el-button>
<el-button type="primary" @click="resetConfig">重置配置</el-button>
</div>
</template>
<div class="flex flex-col">
@@ -144,6 +144,8 @@
import { ElMessage } from 'element-plus'
import { setSelfSetting } from '@/api/user'
import Title from './title.vue'
import { watch } from 'vue';
const appStore = useAppStore()
const { config, device } = storeToRefs(appStore)
defineOptions({
@@ -185,24 +187,24 @@
]
const saveConfig = async () => {
/*const input = document.createElement("textarea");
input.value = JSON.stringify(config.value);
// 添加回车
input.value = input.value.replace(/,/g, ",\n");
document.body.appendChild(input);
input.select();
document.execCommand("copy");
document.body.removeChild(input);
ElMessage.success("复制成功, 请自行保存到本地文件中");*/
const res = await setSelfSetting(config.value)
console.log(config.value)
if (res.code === 0) {
localStorage.setItem('originSetting', JSON.stringify(config.value))
ElMessage.success('保存成功')
drawer.value = false
}
}
const customColor = ref('')
const resetConfig = () => {
appStore.resetConfig()
}
watch(config, async () => {
await saveConfig();
}, { deep: true });
</script>
<style lang="scss" scoped>

View File

@@ -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>

View File

@@ -0,0 +1,141 @@
<template>
<div>
<warning-bar
href="https://www.bilibili.com/video/BV1kv4y1g7nT?p=3"
title="此功能为开发环境使用,不建议发布到生产,具体使用效果请点我观看。"
/>
<div class="gva-search-box">
<div class="text-lg mb-2 text-gray-600">
使用AI创建<a
class="text-blue-600 text-sm ml-4"
href="https://plugin.gin-vue-admin.com/#/layout/userInfo/center"
target="_blank"
>获取AiPath</a
>
</div>
<div class="relative">
<el-input
v-model="prompt"
:maxlength="2000"
:placeholder="placeholder"
:rows="8"
resize="none"
type="textarea"
@blur="handleBlur"
@focus="handleFocus"
/>
<div class="flex absolute right-2 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
type="primary"
@click="llmAutoFunc()"
>
<el-icon size="18">
<ai-gva/>
</el-icon>
生成
</el-button>
</el-tooltip>
</div>
</div>
</div>
<div>
<div v-if="!outPut">
<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">
<el-button type="primary" :icon="Upload" class="px-2 py-1" @click="copySnippet(snippet)" plain>复制</el-button>
<div class="mt-2">
<iframe-renderer :html="snippet" />
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { createWeb
} from '@/api/autoCode'
import {ref} from 'vue'
import WarningBar from '@/components/warningBar/warningBar.vue'
import { ElMessage } from 'element-plus'
defineOptions({
name: 'Picture'
})
import IframeRenderer from '@/view/systemTools/autoCode/component/iframeRenderer.vue'
const handleFocus = () => {
document.addEventListener('keydown', handleKeydown);
}
const handleBlur = () => {
document.removeEventListener('keydown', handleKeydown);
}
const handleKeydown = (event) => {
if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
llmAutoFunc()
}
}
// 复制方法:把某个字符串写进剪贴板
const copySnippet = (htmlString) => {
navigator.clipboard.writeText(htmlString)
.then(() => {
ElMessage({
message: '复制成功',
type: 'success',
})
})
.catch(err => {
ElMessage({
message: '复制失败',
type: 'warning',
})
})
}
const prompt = ref('')
// 判断是否返回的标志
const outPut = ref(false)
// 容纳llm返回的html
const htmlFromLLM = ref([])
const llmAutoFunc = async () => {
const res = await createWeb({web: prompt.value, command: 'createWeb'})
if (res.code === 0) {
outPut.value = true
// 使用mock数据模拟大模型返回值
htmlFromLLM.value.push(res.data)
}
}
const placeholder = ref(`"✨ 请详细描述您想要的页面,例如:
• 页面用途(企业官网/电商页面/个人博客等)
• 需要包含的主要内容板块
• 偏好的风格(简约/科技感/温馨/专业等)
• 需要特别强调的元素
• 参考网站或配色建议
示例:'需要一个科技公司的产品介绍页包含banner轮播图、三栏功能特点展示、客户案例模块喜欢深蓝色调参考苹果官网的简洁风格'"`)
</script>