feat quickly

This commit is contained in:
2026-01-12 00:44:57 +08:00
parent 43ca340d54
commit 1d5bc97cdd
30 changed files with 2664 additions and 140 deletions

View File

@@ -10,7 +10,8 @@
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"element-plus": "^2.13.1",
"vue": "^3.2.37"
"vue": "^3.2.37",
"vue-router": "^4.6.4"
},
"devDependencies": {
"@babel/types": "^7.18.10",
@@ -276,6 +277,12 @@
"@vue/shared": "3.5.26"
}
},
"node_modules/@vue/devtools-api": {
"version": "6.6.4",
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
"license": "MIT"
},
"node_modules/@vue/language-core": {
"version": "1.8.27",
"resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-1.8.27.tgz",
@@ -1264,6 +1271,21 @@
}
}
},
"node_modules/vue-router": {
"version": "4.6.4",
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz",
"integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
"license": "MIT",
"dependencies": {
"@vue/devtools-api": "^6.6.4"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"vue": "^3.5.0"
}
},
"node_modules/vue-template-compiler": {
"version": "2.7.16",
"resolved": "https://registry.npmmirror.com/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz",

View File

@@ -11,7 +11,8 @@
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"element-plus": "^2.13.1",
"vue": "^3.2.37"
"vue": "^3.2.37",
"vue-router": "^4.6.4"
},
"devDependencies": {
"@babel/types": "^7.18.10",

View File

@@ -1 +1 @@
b777c503b422e1e0e3f70b49d2291cdc
27251dcea746309433392e41407ed6c9

View File

@@ -1,18 +1,88 @@
<script lang="ts" setup>
import HelloWorld from './components/HelloWorld.vue'
import {ref} from 'vue'
import {useRoute, useRouter} from 'vue-router'
const router = useRouter()
const route = useRoute()
const activeMenu = ref('home')
const menuItems = [
{index: 'home', path: '/home', title: '主页'},
{index: 'settings', path: '/settings', title: '设置'},
{index: 'swagger', path: '/swagger', title: 'Swagger'},
{index: 'mysql-model', path: '/mysql-model', title: 'MySQL Model'},
{index: 'update-common', path: '/update-common', title: '更新 Common'}
]
function handleMenuSelect(index: string) {
activeMenu.value = index
const menuItem = menuItems.find(item => item.index === index)
if (menuItem) {
router.push(menuItem.path)
}
}
router.afterEach((to) => {
const menuItem = menuItems.find(item => item.path === to.path)
if (menuItem) {
activeMenu.value = menuItem.index
}
})
</script>
<template>
<el-container class="app-container">
<el-header>
<div class="header-content">
<img src="./assets/images/logo-universal.png" alt="Wails logo" class="logo"/>
<h1>Quickly 金牌助手</h1>
<el-aside width="200px" class="app-aside">
<div class="logo-container">
<img src="./assets/images/logo-universal.png" alt="logo" class="logo"/>
<span class="app-title">Quickly</span>
</div>
</el-header>
<el-main>
<HelloWorld/>
</el-main>
<el-menu
:default-active="activeMenu"
class="el-menu-vertical"
@select="handleMenuSelect"
>
<el-menu-item index="home">
<el-icon>
<House/>
</el-icon>
<span>主页</span>
</el-menu-item>
<el-menu-item index="settings">
<el-icon>
<Setting/>
</el-icon>
<span>设置</span>
</el-menu-item>
<el-menu-item index="swagger">
<el-icon>
<Document/>
</el-icon>
<span>Swagger</span>
</el-menu-item>
<el-menu-item index="mysql-model">
<el-icon>
<DataLine/>
</el-icon>
<span>MySQL Model</span>
</el-menu-item>
<el-menu-item index="update-common">
<el-icon>
<Refresh/>
</el-icon>
<span>更新 Common</span>
</el-menu-item>
</el-menu>
</el-aside>
<el-container class="main-container">
<el-header class="app-header">
<div class="header-title">{{ route.meta.title || 'Quickly 金牌助手' }}</div>
</el-header>
<el-main class="app-main">
<router-view/>
</el-main>
</el-container>
</el-container>
</template>
@@ -21,33 +91,74 @@ import HelloWorld from './components/HelloWorld.vue'
height: 100vh;
}
.el-header {
.app-aside {
background-color: #1b2636;
color: #fff;
display: flex;
align-items: center;
padding: 0 20px;
flex-direction: column;
}
.header-content {
.logo-container {
display: flex;
align-items: center;
gap: 15px;
padding: 20px;
gap: 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.logo {
width: 40px;
height: 40px;
width: 32px;
height: 32px;
}
.header-content h1 {
margin: 0;
font-size: 20px;
.app-title {
color: #fff;
font-size: 18px;
font-weight: 600;
}
.el-menu-vertical {
border: none;
background-color: transparent;
flex: 1;
}
.el-menu-vertical .el-menu-item {
color: rgba(255, 255, 255, 0.7);
}
.el-menu-vertical .el-menu-item:hover {
background-color: rgba(255, 255, 255, 0.1);
color: #fff;
}
.el-menu-vertical .el-menu-item.is-active {
background-color: #409eff;
color: #fff;
}
.main-container {
display: flex;
flex-direction: column;
}
.app-header {
background-color: #fff;
border-bottom: 1px solid #e4e7ed;
display: flex;
align-items: center;
padding: 0 20px;
height: 60px;
}
.header-title {
font-size: 18px;
font-weight: 500;
color: #303133;
}
.el-main {
.app-main {
background-color: #f5f7fa;
padding: 20px;
padding: 0;
overflow: auto;
}
</style>

View File

@@ -1,70 +0,0 @@
<script lang="ts" setup>
import {reactive} from 'vue'
import {Greet} from '../../wailsjs/go/main/App'
const data = reactive({
name: "",
resultText: "Please enter your name below 👇",
})
function greet() {
Greet(data.name).then(result => {
data.resultText = result
})
}
</script>
<template>
<el-card class="greeting-card">
<template #header>
<div class="card-header">
<span>Greeting Demo</span>
</div>
</template>
<div class="result">
<el-text type="primary" size="large">{{ data.resultText }}</el-text>
</div>
<div class="input-box">
<el-input
v-model="data.name"
placeholder="Please enter your name"
clearable
class="input"
/>
<el-button type="primary" @click="greet">Greet</el-button>
</div>
</el-card>
</template>
<style scoped>
.greeting-card {
max-width: 500px;
margin: 40px auto;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
}
.result {
text-align: center;
margin: 30px 0;
min-height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.input-box {
display: flex;
gap: 10px;
}
.input {
flex: 1;
}
</style>

View File

@@ -1,9 +1,17 @@
import {createApp} from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import App from './App.vue'
import router from './router'
import './style.css'
const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(ElementPlus)
app.use(router)
app.mount('#app')

View File

@@ -0,0 +1,50 @@
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import Home from '../views/Home.vue'
import Settings from '../views/Settings.vue'
import Swagger from '../views/Swagger.vue'
import MySQLModel from '../views/MySQLModel.vue'
import UpdateCommon from '../views/UpdateCommon.vue'
const routes: RouteRecordRaw[] = [
{
path: '/',
redirect: '/home'
},
{
path: '/home',
name: 'Home',
component: Home,
meta: { title: '主页' }
},
{
path: '/settings',
name: 'Settings',
component: Settings,
meta: { title: '设置' }
},
{
path: '/swagger',
name: 'Swagger',
component: Swagger,
meta: { title: 'Swagger' }
},
{
path: '/mysql-model',
name: 'MySQLModel',
component: MySQLModel,
meta: { title: 'MySQL Model' }
},
{
path: '/update-common',
name: 'UpdateCommon',
component: UpdateCommon,
meta: { title: '更新 Common' }
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router

View File

@@ -0,0 +1,143 @@
<script lang="ts" setup>
import {reactive} from 'vue'
const features = reactive([
{
title: '更新 Common',
description: '批量更新多个项目中 Common 库的版本,支持自定义 Go Module Name自动执行 go get 和 go mod tidy 命令。',
icon: '🔄',
path: '/update-common'
},
{
title: 'MySQL Model',
description: '执行 gen.ps1 脚本生成 model 和 query 文件,自动替换 import 路径,支持多数据库配置管理。',
icon: '🗄️',
path: '/mysql-model'
},
{
title: 'Swagger',
description: '配置 Swagger 目录,自动扫描所有 .swagger.json 文件,启动本地 HTTP 服务查看 API 文档。',
icon: '📚',
path: '/swagger'
},
{
title: '设置',
description: '自定义主题、语言、通知等应用设置,管理全局配置。',
icon: '⚙️',
path: '/settings'
}
])
</script>
<template>
<div class="home-container">
<div class="welcome-section">
<h1 class="welcome-title">欢迎使用 Quickly</h1>
<p class="welcome-subtitle">高效开发工具提升工作效率</p>
</div>
<div class="features-section">
<h2 class="section-title">功能介绍</h2>
<div class="features-grid">
<el-card v-for="(feature, index) in features" :key="index" class="feature-card">
<div class="feature-icon">{{ feature.icon }}</div>
<h3 class="feature-title">{{ feature.title }}</h3>
<p class="feature-description">{{ feature.description }}</p>
</el-card>
</div>
</div>
</div>
</template>
<style scoped>
.home-container {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.welcome-section {
text-align: center;
margin: 40px 0;
}
.welcome-title {
font-size: 48px;
font-weight: 700;
margin: 0 0 10px 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.welcome-subtitle {
font-size: 18px;
color: #666;
margin: 0;
}
.features-section {
margin: 40px 0;
}
.section-title {
font-size: 28px;
font-weight: 600;
color: #333;
margin: 0 0 30px 0;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
}
.feature-card {
text-align: center;
transition: transform 0.3s, box-shadow 0.3s;
cursor: pointer;
padding: 30px 20px;
}
.feature-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.feature-icon {
font-size: 64px;
margin-bottom: 20px;
}
.feature-title {
font-size: 22px;
font-weight: 600;
margin: 0 0 15px 0;
color: #333;
}
.feature-description {
font-size: 14px;
color: #666;
line-height: 1.6;
margin: 0;
}
.info-card {
margin: 40px 0;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
}
.info-content {
padding: 20px 0;
}
</style>

View File

@@ -0,0 +1,431 @@
<script lang="ts" setup>
import {onMounted, reactive, ref} from 'vue'
import {ElMessage} from 'element-plus'
import {
AddDatabaseConfig,
CheckGenPs1Exists,
ExecuteGenPs1,
GetSettings,
ReadGoModModule,
RemoveDatabaseConfig,
SaveSettings,
SelectDirectory,
SelectFile,
UpdateDatabaseConfig
} from '../../wailsjs/go/backend/App'
interface DatabaseConfig {
name: string
targetPath: string
modelPackagePath: string
}
interface AppSettings {
theme: string
language: string
notifications: boolean
autoStart: boolean
mysqlModelPath: string
defaultQueryPackagePath: string
databases: DatabaseConfig[]
}
const settings = reactive<AppSettings>({
theme: 'light',
language: 'zh-CN',
notifications: true,
autoStart: false,
mysqlModelPath: '',
defaultQueryPackagePath: '',
databases: []
})
const dbForm = reactive<DatabaseConfig>({
name: '',
targetPath: '',
modelPackagePath: ''
})
const scriptForm = reactive({
selectedDbName: '',
output: ''
})
const isExecuting = ref(false)
const dialogVisible = ref(false)
const editingIndex = ref(-1)
onMounted(async () => {
try {
const loadedSettings = await GetSettings() as any
settings.theme = loadedSettings.theme
settings.language = loadedSettings.language
settings.notifications = loadedSettings.notifications
settings.autoStart = loadedSettings.autoStart
settings.mysqlModelPath = loadedSettings.mysqlModelPath
settings.defaultQueryPackagePath = loadedSettings.defaultQueryPackagePath || ''
if (loadedSettings.databases) {
settings.databases = loadedSettings.databases
}
} catch (error) {
console.error('Failed to load settings:', error)
}
})
async function validatePath(path: string): Promise<boolean> {
if (!path) {
return false
}
try {
const exists = await CheckGenPs1Exists(path)
if (!exists) {
ElMessage.warning('所选文件不是有效的 gen.ps1 文件')
}
return exists
} catch (error) {
console.error('Failed to check gen.ps1:', error)
ElMessage.error('检查文件时出错')
return false
}
}
async function selectMysqlModelPath() {
try {
const path = await SelectFile('选择 gen.ps1 文件', settings.mysqlModelPath, '*.ps1')
if (path) {
const isValid = await validatePath(path)
if (isValid) {
settings.mysqlModelPath = path
try {
const modulePath = await ReadGoModModule(path)
if (modulePath) {
settings.defaultQueryPackagePath = modulePath
ElMessage.success(`已自动读取 go.mod 中的 module: ${modulePath}`)
}
} catch (error) {
console.error('Failed to read go.mod:', error)
}
}
}
} catch (error) {
console.error('Failed to select file:', error)
}
}
async function handlePathChange() {
if (settings.mysqlModelPath) {
await validatePath(settings.mysqlModelPath)
}
}
async function saveSettings() {
if (!settings.mysqlModelPath) {
ElMessage.warning('请先设置 MySQL Model 路径')
return
}
const isValid = await validatePath(settings.mysqlModelPath)
if (!isValid) {
return
}
try {
const currentSettings = await GetSettings() as any
currentSettings.mysqlModelPath = settings.mysqlModelPath
currentSettings.defaultQueryPackagePath = settings.defaultQueryPackagePath
currentSettings.databases = settings.databases
await SaveSettings(currentSettings)
ElMessage.success('设置保存成功')
} catch (error) {
console.error('Failed to save settings:', error)
ElMessage.error('设置保存失败')
}
}
async function selectTargetPath() {
try {
const path = await SelectDirectory('选择生成文件目标路径', dbForm.targetPath)
if (path) {
dbForm.targetPath = path
}
} catch (error) {
console.error('Failed to select directory:', error)
}
}
function openAddDialog() {
editingIndex.value = -1
dbForm.name = ''
dbForm.targetPath = ''
dbForm.modelPackagePath = ''
dialogVisible.value = true
}
function openEditDialog(index: number) {
editingIndex.value = index
const db = settings.databases[index]
dbForm.name = db.name
dbForm.targetPath = db.targetPath
dbForm.modelPackagePath = db.modelPackagePath || ''
dialogVisible.value = true
}
async function handleAddDb() {
if (!dbForm.name) {
ElMessage.warning('请输入数据库名称')
return
}
if (!dbForm.targetPath) {
ElMessage.warning('请选择目标路径')
return
}
try {
if (editingIndex.value === -1) {
await AddDatabaseConfig(dbForm.name, dbForm.targetPath, dbForm.modelPackagePath)
ElMessage.success('添加成功')
} else {
await UpdateDatabaseConfig(settings.databases[editingIndex.value].name, dbForm.name, dbForm.targetPath, dbForm.modelPackagePath)
ElMessage.success('更新成功')
}
dialogVisible.value = false
const loadedSettings = await GetSettings() as any
settings.theme = loadedSettings.theme
settings.language = loadedSettings.language
settings.notifications = loadedSettings.notifications
settings.autoStart = loadedSettings.autoStart
settings.mysqlModelPath = loadedSettings.mysqlModelPath
settings.defaultQueryPackagePath = loadedSettings.defaultQueryPackagePath || ''
if (loadedSettings.databases) {
settings.databases = loadedSettings.databases
}
} catch (error) {
console.error('Failed to save database config:', error)
ElMessage.error('保存失败')
}
}
async function handleDeleteDb(index: number) {
const db = settings.databases[index]
try {
await RemoveDatabaseConfig(db.name)
ElMessage.success('删除成功')
const loadedSettings = await GetSettings() as any
settings.theme = loadedSettings.theme
settings.language = loadedSettings.language
settings.notifications = loadedSettings.notifications
settings.autoStart = loadedSettings.autoStart
settings.mysqlModelPath = loadedSettings.mysqlModelPath
if (loadedSettings.databases) {
settings.databases = loadedSettings.databases
}
} catch (error) {
console.error('Failed to delete database config:', error)
ElMessage.error('删除失败')
}
}
async function executeScript() {
if (!settings.mysqlModelPath) {
ElMessage.warning('请先设置 MySQL Model 路径')
return
}
if (!scriptForm.selectedDbName) {
ElMessage.warning('请选择要执行的数据库')
return
}
const isValid = await validatePath(settings.mysqlModelPath)
if (!isValid) {
return
}
const selectedDb = settings.databases.find(db => db.name === scriptForm.selectedDbName)
if (!selectedDb) {
ElMessage.warning('未找到数据库配置')
return
}
isExecuting.value = true
scriptForm.output = '正在执行脚本...'
try {
const output = await ExecuteGenPs1(settings.mysqlModelPath, scriptForm.selectedDbName, selectedDb.targetPath, selectedDb.modelPackagePath || '')
scriptForm.output = output + '\n\n文件已复制到目标路径: ' + selectedDb.targetPath
ElMessage.success('脚本执行成功')
} catch (error) {
console.error('Failed to execute script:', error)
scriptForm.output = `执行失败: ${error}`
ElMessage.error('脚本执行失败')
} finally {
isExecuting.value = false
}
}
</script>
<template>
<div class="mysql-model-container">
<el-card class="script-card">
<template #header>
<div class="card-header">
<span>生成 model&query 文件</span>
</div>
</template>
<el-form label-width="140px" class="script-form">
<el-form-item label="选择数据库">
<el-select
v-model="scriptForm.selectedDbName"
placeholder="请选择要执行的数据库"
clearable
>
<el-option
v-for="db in settings.databases"
:key="db.name"
:label="db.name"
:value="db.name"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="executeScript"
:loading="isExecuting"
>
执行脚本
</el-button>
</el-form-item>
<el-form-item label="执行输出">
<el-input
v-model="scriptForm.output"
type="textarea"
:rows="10"
placeholder="脚本执行结果将显示在这里"
readonly
/>
</el-form-item>
</el-form>
</el-card>
<el-card class="mysql-model-card">
<template #header>
<div class="card-header">
<span>MySQL Model 设置</span>
</div>
</template>
<el-form label-width="140px" class="settings-form">
<el-form-item label="gen.ps1 路径">
<el-input
v-model="settings.mysqlModelPath"
placeholder="请输入或选择 gen.ps1 文件路径"
clearable
@change="handlePathChange"
>
<template #append>
<el-button @click="selectMysqlModelPath">选择文件</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item label="Query包名">
<el-input
v-model="settings.defaultQueryPackagePath"
placeholder="请输入默认 Query 包名例如git.hlsq.asia/mmorpg/service-user/internal/dao/model"
clearable
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveSettings">保存设置</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="database-card">
<template #header>
<div class="card-header">
<span>数据库配置管理</span>
<el-button type="primary" size="small" @click="openAddDialog">添加配置</el-button>
</div>
</template>
<el-table :data="settings.databases" style="width: 100%">
<el-table-column prop="name" label="数据库名称"/>
<el-table-column prop="targetPath" label="目标路径" show-overflow-tooltip/>
<el-table-column prop="modelPackagePath" label="Query包名" show-overflow-tooltip/>
<el-table-column label="操作" width="200">
<template #default="scope">
<el-button size="small" @click="openEditDialog(scope.$index)">编辑</el-button>
<el-button size="small" type="danger" @click="handleDeleteDb(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog
v-model="dialogVisible"
:title="editingIndex === -1 ? '添加数据库配置' : '编辑数据库配置'"
width="500px"
>
<el-form label-width="120px">
<el-form-item label="数据库名称">
<el-input
v-model="dbForm.name"
placeholder="请输入数据库名称例如user_db"
clearable
/>
</el-form-item>
<el-form-item label="目标路径">
<el-input
v-model="dbForm.targetPath"
placeholder="请输入或选择生成文件的目标路径"
clearable
>
<template #append>
<el-button @click="selectTargetPath">选择文件夹</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item label="Query包名">
<el-input
v-model="dbForm.modelPackagePath"
placeholder="请输入package name例如git.hlsq.asia/mmorpg/service-user/internal/dao/model"
clearable
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleAddDb">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<style scoped>
.mysql-model-container {
padding: 20px;
}
.mysql-model-card,
.database-card,
.script-card {
max-width: 900px;
margin: 20px auto;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
}
.settings-form,
.script-form {
padding: 20px 0;
}
</style>

View File

@@ -0,0 +1,114 @@
<script lang="ts" setup>
import {onMounted, reactive} from 'vue'
import {ElMessage} from 'element-plus'
import {GetSettings, SaveSettings} from '../../wailsjs/go/backend/App'
interface DatabaseConfig {
name: string
targetPath: string
modelPackagePath: string
}
interface AppSettings {
theme: string
language: string
notifications: boolean
autoStart: boolean
databases?: DatabaseConfig[]
}
const settings = reactive<AppSettings>({
theme: 'light',
language: 'zh-CN',
notifications: true,
autoStart: false
})
onMounted(async () => {
try {
const loadedSettings = await GetSettings() as any
settings.theme = loadedSettings.theme
settings.language = loadedSettings.language
settings.notifications = loadedSettings.notifications
settings.autoStart = loadedSettings.autoStart
if (loadedSettings.databases) {
settings.databases = loadedSettings.databases
}
} catch (error) {
console.error('Failed to load settings:', error)
}
})
async function saveSettings() {
try {
const currentSettings = await GetSettings() as any
currentSettings.theme = settings.theme
currentSettings.language = settings.language
currentSettings.notifications = settings.notifications
currentSettings.autoStart = settings.autoStart
await SaveSettings(currentSettings)
ElMessage.success('设置保存成功')
} catch (error) {
console.error('Failed to save settings:', error)
ElMessage.error('设置保存失败')
}
}
</script>
<template>
<div class="settings-container">
<el-card class="settings-card">
<template #header>
<div class="card-header">
<span>设置</span>
</div>
</template>
<el-form label-width="120px" class="settings-form">
<el-form-item label="主题">
<el-select v-model="settings.theme" placeholder="选择主题">
<el-option label="浅色" value="light"/>
<el-option label="深色" value="dark"/>
<el-option label="跟随系统" value="auto"/>
</el-select>
</el-form-item>
<el-form-item label="语言">
<el-select v-model="settings.language" placeholder="选择语言">
<el-option label="简体中文" value="zh-CN"/>
<el-option label="English" value="en-US"/>
</el-select>
</el-form-item>
<el-form-item label="通知">
<el-switch v-model="settings.notifications"/>
</el-form-item>
<el-form-item label="开机自启">
<el-switch v-model="settings.autoStart"/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveSettings">保存设置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<style scoped>
.settings-container {
padding: 20px;
}
.settings-card {
max-width: 600px;
margin: 20px auto;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
}
.settings-form {
padding: 20px 0;
}
</style>

View File

@@ -0,0 +1,349 @@
<script lang="ts" setup>
import {onMounted, reactive, ref} from 'vue'
import {ElMessage} from 'element-plus'
import {
GetSettings,
GetSwaggerFiles,
GetSwaggerServerURL,
IsSwaggerServerRunning,
ReadSwaggerFile,
SaveSettings,
SelectDirectory,
StartSwaggerServer,
StopSwaggerServer
} from '../../wailsjs/go/backend/App'
interface SwaggerFile {
name: string
path: string
size: number
modifiedTime: string
}
interface AppSettings {
theme: string
language: string
notifications: boolean
autoStart: boolean
swaggerDir: string
}
const settings = reactive<AppSettings>({
theme: 'light',
language: 'zh-CN',
notifications: true,
autoStart: false,
swaggerDir: ''
})
const swaggerFiles = ref<SwaggerFile[]>([])
const selectedFile = ref<SwaggerFile | null>(null)
const selectedSwaggerFile = ref<SwaggerFile | null>(null)
const fileContent = ref('')
const isLoading = ref(false)
const serverURL = ref('')
const isServerRunning = ref(false)
async function selectSwaggerDir() {
try {
const path = await SelectDirectory('选择 Swagger 目录', settings.swaggerDir)
if (path) {
settings.swaggerDir = path
await loadSwaggerFiles()
await saveSettings()
}
} catch (error) {
console.error('Failed to select directory:', error)
}
}
async function loadSwaggerFiles() {
if (!settings.swaggerDir) {
return
}
isLoading.value = true
try {
const files = await GetSwaggerFiles(settings.swaggerDir)
swaggerFiles.value = files
} catch (error) {
console.error('Failed to load swagger files:', error)
ElMessage.error('加载 Swagger 文件失败')
} finally {
isLoading.value = false
}
}
async function saveSettings() {
try {
const currentSettings = await GetSettings() as any
currentSettings.swaggerDir = settings.swaggerDir
await SaveSettings(currentSettings)
ElMessage.success('设置保存成功')
} catch (error) {
console.error('Failed to save settings:', error)
ElMessage.error('设置保存失败')
}
}
async function viewFile(file: SwaggerFile) {
selectedFile.value = file
isLoading.value = true
try {
const content = await ReadSwaggerFile(file.path)
fileContent.value = content
} catch (error) {
console.error('Failed to read swagger file:', error)
ElMessage.error('读取文件失败')
} finally {
isLoading.value = false
}
}
function formatFileSize(bytes: number): string {
if (bytes < 1024) {
return bytes + ' B'
} else if (bytes < 1024 * 1024) {
return (bytes / 1024).toFixed(2) + ' KB'
} else {
return (bytes / (1024 * 1024)).toFixed(2) + ' MB'
}
}
async function startSwaggerServer() {
if (!settings.swaggerDir) {
ElMessage.warning('请先配置 Swagger 目录')
return
}
try {
const url = await StartSwaggerServer(settings.swaggerDir)
serverURL.value = url
isServerRunning.value = true
if (swaggerFiles.value.length > 0) {
selectedSwaggerFile.value = swaggerFiles.value[0]
}
ElMessage.success(`Swagger 服务已启动: ${url}`)
} catch (error) {
console.error('Failed to start swagger server:', error)
ElMessage.error('启动 Swagger 服务失败')
}
}
async function stopSwaggerServer() {
try {
await StopSwaggerServer()
serverURL.value = ''
isServerRunning.value = false
ElMessage.success('Swagger 服务已停止')
} catch (error) {
console.error('Failed to stop swagger server:', error)
ElMessage.error('停止 Swagger 服务失败')
}
}
async function checkServerStatus() {
try {
const running = await IsSwaggerServerRunning()
isServerRunning.value = running
if (running) {
const url = await GetSwaggerServerURL()
serverURL.value = url
}
} catch (error) {
console.error('Failed to check server status:', error)
}
}
function openSwaggerURL() {
if (serverURL.value) {
window.open(serverURL.value, '_blank')
}
}
function updateServerURL() {
if (serverURL.value && selectedSwaggerFile.value) {
const baseUrl = serverURL.value.split('?')[0]
serverURL.value = `${baseUrl}?file=${encodeURIComponent(selectedSwaggerFile.value.name)}`
}
}
onMounted(async () => {
try {
const loadedSettings = await GetSettings() as any
settings.theme = loadedSettings.theme
settings.language = loadedSettings.language
settings.notifications = loadedSettings.notifications
settings.autoStart = loadedSettings.autoStart
settings.swaggerDir = loadedSettings.swaggerDir || ''
if (settings.swaggerDir) {
await loadSwaggerFiles()
}
await checkServerStatus()
} catch (error) {
console.error('Failed to load settings:', error)
}
})
</script>
<template>
<div class="swagger-container">
<el-card class="settings-card">
<template #header>
<div class="card-header">
<span>Swagger 设置</span>
</div>
</template>
<el-form label-width="140px" class="settings-form">
<el-form-item label="Swagger 目录">
<el-input
v-model="settings.swaggerDir"
placeholder="请输入或选择 Swagger 文件目录"
clearable
>
<template #append>
<el-button @click="selectSwaggerDir">选择文件夹</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item label="Swagger 服务">
<div class="server-controls">
<el-button
v-if="!isServerRunning"
type="primary"
@click="startSwaggerServer"
>
启动服务
</el-button>
<el-button
v-else
type="danger"
@click="stopSwaggerServer"
>
停止服务
</el-button>
<el-select
v-if="isServerRunning && swaggerFiles.length > 1"
v-model="selectedSwaggerFile"
placeholder="选择 Swagger 文件"
class="file-select"
@change="updateServerURL"
>
<el-option
v-for="file in swaggerFiles"
:key="file.name"
:label="file.name"
:value="file"
/>
</el-select>
<el-input
v-if="serverURL"
v-model="serverURL"
readonly
class="server-url-input"
>
<template #append>
<el-button @click="openSwaggerURL">打开</el-button>
</template>
</el-input>
</div>
</el-form-item>
</el-form>
</el-card>
<el-card class="files-card">
<template #header>
<div class="card-header">
<span>Swagger 文件列表</span>
<el-button type="primary" size="small" @click="loadSwaggerFiles" :loading="isLoading">刷新</el-button>
</div>
</template>
<el-table :data="swaggerFiles" style="width: 100%" v-loading="isLoading">
<el-table-column prop="name" label="文件名" show-overflow-tooltip/>
<el-table-column prop="size" label="大小" width="120">
<template #default="scope">
{{ formatFileSize(scope.row.size) }}
</template>
</el-table-column>
<el-table-column prop="modifiedTime" label="修改时间" width="180"/>
<el-table-column label="操作" width="150">
<template #default="scope">
<el-button size="small" @click="viewFile(scope.row)">查看</el-button>
</template>
</el-table-column>
</el-table>
<el-empty v-if="swaggerFiles.length === 0 && !isLoading" description="暂无 Swagger 文件"/>
</el-card>
<el-card class="content-card" v-if="selectedFile">
<template #header>
<div class="card-header">
<span>{{ selectedFile.name }}</span>
<el-button size="small" @click="selectedFile = null">关闭</el-button>
</div>
</template>
<div class="file-content" v-loading="isLoading">
<pre>{{ fileContent }}</pre>
</div>
</el-card>
</div>
</template>
<style scoped>
.swagger-container {
padding: 20px;
}
.settings-card {
margin-bottom: 20px;
}
.files-card {
margin-bottom: 20px;
}
.content-card {
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
}
.settings-form {
margin-top: 10px;
}
.server-controls {
display: flex;
flex-direction: column;
gap: 10px;
}
.server-url-input {
width: 100%;
}
.file-select {
width: 100%;
}
.file-content {
max-height: 600px;
overflow-y: auto;
}
.file-content pre {
margin: 0;
padding: 15px;
background-color: #f5f5f5;
border-radius: 4px;
font-size: 13px;
line-height: 1.5;
white-space: pre-wrap;
word-wrap: break-word;
}
</style>

View File

@@ -0,0 +1,327 @@
<script lang="ts" setup>
import {onMounted, reactive, ref} from 'vue'
import {ElMessage} from 'element-plus'
import {
SelectDirectory,
GetSettings,
AddProjectConfig,
RemoveProjectConfig,
UpdateProjectConfig,
UpdateServiceCommon,
SaveSettings
} from '../../wailsjs/go/backend/App'
interface ProjectConfig {
name: string
path: string
}
interface AppSettings {
theme: string
language: string
notifications: boolean
autoStart: boolean
modelBasePath: string
databases?: any[]
projects?: ProjectConfig[]
}
const settings = reactive<AppSettings>({
theme: 'light',
language: 'zh-CN',
notifications: true,
autoStart: false,
modelBasePath: '',
projects: []
})
const projectForm = reactive<ProjectConfig>({
name: '',
path: ''
})
const updateForm = reactive({
commitId: '',
selectedProjects: [] as string[]
})
const isUpdating = ref(false)
const output = ref('')
const projectDialogVisible = ref(false)
const editingProjectIndex = ref(-1)
onMounted(async () => {
try {
const loadedSettings = await GetSettings() as any
settings.theme = loadedSettings.theme
settings.language = loadedSettings.language
settings.notifications = loadedSettings.notifications
settings.autoStart = loadedSettings.autoStart
settings.modelBasePath = loadedSettings.modelBasePath || ''
if (loadedSettings.projects) {
settings.projects = loadedSettings.projects
}
} catch (error) {
console.error('Failed to load settings:', error)
}
})
async function selectProjectPath() {
try {
const path = await SelectDirectory('选择项目路径', projectForm.path)
if (path) {
projectForm.path = path
const pathParts = path.replace(/\\/g, '/').split('/')
projectForm.name = pathParts[pathParts.length - 1] || ''
}
} catch (error) {
console.error('Failed to select directory:', error)
}
}
function openAddProjectDialog() {
editingProjectIndex.value = -1
projectForm.name = ''
projectForm.path = ''
projectDialogVisible.value = true
}
function openEditProjectDialog(index: number) {
editingProjectIndex.value = index
const project = settings.projects![index]
projectForm.name = project.name
projectForm.path = project.path
projectDialogVisible.value = true
}
async function handleAddProject() {
if (!projectForm.name) {
ElMessage.warning('请输入项目名称')
return
}
if (!projectForm.path) {
ElMessage.warning('请选择项目路径')
return
}
try {
if (editingProjectIndex.value === -1) {
await AddProjectConfig(projectForm.name, projectForm.path)
ElMessage.success('添加成功')
} else {
await UpdateProjectConfig(settings.projects![editingProjectIndex.value].name, projectForm.name, projectForm.path)
ElMessage.success('更新成功')
}
projectDialogVisible.value = false
const loadedSettings = await GetSettings() as any
settings.theme = loadedSettings.theme
settings.language = loadedSettings.language
settings.notifications = loadedSettings.notifications
settings.autoStart = loadedSettings.autoStart
settings.modelBasePath = loadedSettings.modelBasePath || ''
if (loadedSettings.projects) {
settings.projects = loadedSettings.projects
}
} catch (error) {
console.error('Failed to save project config:', error)
ElMessage.error('保存失败')
}
}
async function handleDeleteProject(index: number) {
const project = settings.projects![index]
try {
await RemoveProjectConfig(project.name)
ElMessage.success('删除成功')
const loadedSettings = await GetSettings() as any
settings.theme = loadedSettings.theme
settings.language = loadedSettings.language
settings.notifications = loadedSettings.notifications
settings.autoStart = loadedSettings.autoStart
settings.modelBasePath = loadedSettings.modelBasePath || ''
if (loadedSettings.projects) {
settings.projects = loadedSettings.projects
}
} catch (error) {
console.error('Failed to delete project config:', error)
ElMessage.error('删除失败')
}
}
async function updateCommonVersion() {
if (!updateForm.commitId) {
ElMessage.warning('请输入 Commit ID')
return
}
if (updateForm.selectedProjects.length === 0) {
ElMessage.warning('请至少选择一个项目')
return
}
isUpdating.value = true
output.value = '正在更新 Common 版本...\n'
try {
const result = await UpdateServiceCommon(updateForm.commitId, updateForm.selectedProjects)
output.value = result
} catch (error) {
console.error('Failed to update common version:', error)
output.value = `更新失败: ${error}`
} finally {
isUpdating.value = false
}
}
async function saveSettings() {
try {
const currentSettings = await GetSettings() as any
currentSettings.modelBasePath = settings.modelBasePath
currentSettings.projects = settings.projects
await SaveSettings(currentSettings)
ElMessage.success('设置保存成功')
} catch (error) {
console.error('Failed to save settings:', error)
ElMessage.error('设置保存失败')
}
}
</script>
<template>
<div class="update-common-container">
<el-card class="update-version-card">
<template #header>
<div class="card-header">
<span>更新 Common 版本</span>
</div>
</template>
<el-form label-width="140px" class="settings-form">
<el-form-item label="Commit ID">
<el-input
v-model="updateForm.commitId"
placeholder="请输入 Commit ID"
clearable
/>
</el-form-item>
<el-form-item label="选择项目">
<el-checkbox-group v-model="updateForm.selectedProjects">
<el-checkbox
v-for="project in settings.projects"
:key="project.name"
:label="project.name"
>
{{ project.name }}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="updateCommonVersion"
:loading="isUpdating"
>
更新 Common 版本
</el-button>
</el-form-item>
<el-form-item label="执行输出">
<el-input
v-model="output"
type="textarea"
:rows="10"
placeholder="更新结果将显示在这里"
readonly
/>
</el-form-item>
</el-form>
</el-card>
<el-card class="project-card">
<template #header>
<div class="card-header">
<span>项目配置管理</span>
<el-button type="primary" size="small" @click="openAddProjectDialog">添加配置</el-button>
</div>
</template>
<el-form label-width="140px" class="settings-form">
<el-form-item label="Go Module Name">
<el-input
v-model="settings.modelBasePath"
placeholder="请输入 Go Module Name例如git.hlsq.asia/mmorpg/service-common"
clearable
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveSettings">保存设置</el-button>
</el-form-item>
</el-form>
<el-table :data="settings.projects" style="width: 100%">
<el-table-column prop="name" label="项目名称"/>
<el-table-column prop="path" label="项目路径" show-overflow-tooltip/>
<el-table-column label="操作" width="200">
<template #default="scope">
<el-button size="small" @click="openEditProjectDialog(scope.$index)">编辑</el-button>
<el-button size="small" type="danger" @click="handleDeleteProject(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog
v-model="projectDialogVisible"
:title="editingProjectIndex === -1 ? '添加项目配置' : '编辑项目配置'"
width="500px"
>
<el-form label-width="120px">
<el-form-item label="项目名称">
<el-input
v-model="projectForm.name"
placeholder="自动从路径提取,也可手动修改"
clearable
/>
</el-form-item>
<el-form-item label="项目路径">
<el-input
v-model="projectForm.path"
placeholder="请输入或选择项目路径(需包含 go.mod 文件)"
clearable
>
<template #append>
<el-button @click="selectProjectPath">选择文件夹</el-button>
</template>
</el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="projectDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleAddProject">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<style scoped>
.update-common-container {
padding: 20px;
}
.update-common-card,
.project-card,
.update-version-card {
max-width: 900px;
margin: 20px auto;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
}
.settings-form {
padding: 20px 0;
}
</style>

View File

@@ -4,7 +4,7 @@
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"strict": false,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,

View File

@@ -0,0 +1,45 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {backend} from '../models';
export function AddDatabaseConfig(arg1:string,arg2:string,arg3:string):Promise<void>;
export function AddProjectConfig(arg1:string,arg2:string):Promise<void>;
export function CheckGenPs1Exists(arg1:string):Promise<boolean>;
export function ExecuteGenPs1(arg1:string,arg2:string,arg3:string,arg4:string):Promise<string>;
export function GetSettings():Promise<backend.Settings>;
export function GetSwaggerFiles(arg1:string):Promise<Array<backend.SwaggerFile>>;
export function GetSwaggerServerURL():Promise<string>;
export function Greet(arg1:string):Promise<string>;
export function IsSwaggerServerRunning():Promise<boolean>;
export function ReadGoModModule(arg1:string):Promise<string>;
export function ReadSwaggerFile(arg1:string):Promise<string>;
export function RemoveDatabaseConfig(arg1:string):Promise<void>;
export function RemoveProjectConfig(arg1:string):Promise<void>;
export function SaveSettings(arg1:backend.Settings):Promise<void>;
export function SelectDirectory(arg1:string,arg2:string):Promise<string>;
export function SelectFile(arg1:string,arg2:string,arg3:string):Promise<string>;
export function StartSwaggerServer(arg1:string):Promise<string>;
export function StopSwaggerServer():Promise<void>;
export function UpdateDatabaseConfig(arg1:string,arg2:string,arg3:string,arg4:string):Promise<void>;
export function UpdateProjectConfig(arg1:string,arg2:string,arg3:string):Promise<void>;
export function UpdateServiceCommon(arg1:string,arg2:Array<string>):Promise<string>;

View File

@@ -0,0 +1,87 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function AddDatabaseConfig(arg1, arg2, arg3) {
return window['go']['backend']['App']['AddDatabaseConfig'](arg1, arg2, arg3);
}
export function AddProjectConfig(arg1, arg2) {
return window['go']['backend']['App']['AddProjectConfig'](arg1, arg2);
}
export function CheckGenPs1Exists(arg1) {
return window['go']['backend']['App']['CheckGenPs1Exists'](arg1);
}
export function ExecuteGenPs1(arg1, arg2, arg3, arg4) {
return window['go']['backend']['App']['ExecuteGenPs1'](arg1, arg2, arg3, arg4);
}
export function GetSettings() {
return window['go']['backend']['App']['GetSettings']();
}
export function GetSwaggerFiles(arg1) {
return window['go']['backend']['App']['GetSwaggerFiles'](arg1);
}
export function GetSwaggerServerURL() {
return window['go']['backend']['App']['GetSwaggerServerURL']();
}
export function Greet(arg1) {
return window['go']['backend']['App']['Greet'](arg1);
}
export function IsSwaggerServerRunning() {
return window['go']['backend']['App']['IsSwaggerServerRunning']();
}
export function ReadGoModModule(arg1) {
return window['go']['backend']['App']['ReadGoModModule'](arg1);
}
export function ReadSwaggerFile(arg1) {
return window['go']['backend']['App']['ReadSwaggerFile'](arg1);
}
export function RemoveDatabaseConfig(arg1) {
return window['go']['backend']['App']['RemoveDatabaseConfig'](arg1);
}
export function RemoveProjectConfig(arg1) {
return window['go']['backend']['App']['RemoveProjectConfig'](arg1);
}
export function SaveSettings(arg1) {
return window['go']['backend']['App']['SaveSettings'](arg1);
}
export function SelectDirectory(arg1, arg2) {
return window['go']['backend']['App']['SelectDirectory'](arg1, arg2);
}
export function SelectFile(arg1, arg2, arg3) {
return window['go']['backend']['App']['SelectFile'](arg1, arg2, arg3);
}
export function StartSwaggerServer(arg1) {
return window['go']['backend']['App']['StartSwaggerServer'](arg1);
}
export function StopSwaggerServer() {
return window['go']['backend']['App']['StopSwaggerServer']();
}
export function UpdateDatabaseConfig(arg1, arg2, arg3, arg4) {
return window['go']['backend']['App']['UpdateDatabaseConfig'](arg1, arg2, arg3, arg4);
}
export function UpdateProjectConfig(arg1, arg2, arg3) {
return window['go']['backend']['App']['UpdateProjectConfig'](arg1, arg2, arg3);
}
export function UpdateServiceCommon(arg1, arg2) {
return window['go']['backend']['App']['UpdateServiceCommon'](arg1, arg2);
}

View File

@@ -1,4 +0,0 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function Greet(arg1:string):Promise<string>;

View File

@@ -1,7 +0,0 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function Greet(arg1) {
return window['go']['main']['App']['Greet'](arg1);
}

View File

@@ -0,0 +1,101 @@
export namespace backend {
export class DatabaseConfig {
name: string;
targetPath: string;
modelPackagePath: string;
static createFrom(source: any = {}) {
return new DatabaseConfig(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.name = source["name"];
this.targetPath = source["targetPath"];
this.modelPackagePath = source["modelPackagePath"];
}
}
export class ProjectConfig {
name: string;
path: string;
static createFrom(source: any = {}) {
return new ProjectConfig(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.name = source["name"];
this.path = source["path"];
}
}
export class Settings {
theme: string;
language: string;
notifications: boolean;
autoStart: boolean;
mysqlModelPath: string;
defaultQueryPackagePath: string;
modelBasePath: string;
swaggerDir: string;
databases: DatabaseConfig[];
projects: ProjectConfig[];
static createFrom(source: any = {}) {
return new Settings(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.theme = source["theme"];
this.language = source["language"];
this.notifications = source["notifications"];
this.autoStart = source["autoStart"];
this.mysqlModelPath = source["mysqlModelPath"];
this.defaultQueryPackagePath = source["defaultQueryPackagePath"];
this.modelBasePath = source["modelBasePath"];
this.swaggerDir = source["swaggerDir"];
this.databases = this.convertValues(source["databases"], DatabaseConfig);
this.projects = this.convertValues(source["projects"], ProjectConfig);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class SwaggerFile {
name: string;
path: string;
size: number;
modifiedTime: string;
static createFrom(source: any = {}) {
return new SwaggerFile(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.name = source["name"];
this.path = source["path"];
this.size = source["size"];
this.modifiedTime = source["modifiedTime"];
}
}
}