Commit de1573a9 authored by liaozan's avatar liaozan 🏀

feat: rewrite with vue3

parent fe260566
VITE_APP_BASE_URL= 'http://localhost:8080'
\ No newline at end of file
VITE_APP_BASE_URL= 'https://start.develop.schbrain.com'
\ No newline at end of file
# schbrain-archetype-initializer
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
\ No newline at end of file
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<link href="/favicon.ico" rel="icon"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>Schbrain Code Initializer</title>
</head>
<body>
<div id="app"></div>
<script src="/src/main.ts" type="module"></script>
</body>
</html>
\ No newline at end of file
{ {
"name": "schbrain-archetype-initializer-ui", "name": "initializer-ui",
"version": "0.1.0", "version": "0.0.0",
"private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "dev": "vite --mode development",
"build": "vue-cli-service build ", "build": "vite build --mode production"
"lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"@vitejs/plugin-vue": "^2.3.1",
"ant-design-vue": "^3.1.1",
"axios": "^0.26.1", "axios": "^0.26.1",
"element-plus": "^2.1.8", "consola": "^2.15.3",
"vite-plugin-style-import": "^1.4.1",
"vue": "^3.2.31" "vue": "^3.2.31"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.17.9", "@vue/compiler-sfc": "^3.2.31",
"@vue/cli-plugin-babel": "~5.0.4", "less": "^4.1.2",
"@vue/cli-service": "^5.0.4" "sass": "^1.50.0",
}, "unplugin-vue-components": "^0.18.5",
"browserslist": [ "vite": "^2.9.1"
"> 1%", }
"last 2 versions",
"not dead",
"not ie 11"
]
} }
\ No newline at end of file
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<meta content="width=device-width,initial-scale=1.0" name="viewport">
<link href="<%= BASE_URL %>favicon.ico" rel="icon">
<title>Schbrain Code Initializer</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
\ No newline at end of file
<template> <template>
<Starter/>
<div id="form" class="form">
<p class="form-title">项目初始代码生成</p>
<div class="form-div">
<label class="form-label" for="input-group">GroupId</label>
<input id="input-group" v-model="form.groupId" class="form-input" type="text">
</div>
<div class="form-div">
<label class="form-label" for="input-artifact">ArtifactId</label>
<input id="input-artifact" v-model="form.artifactId" class="form-input" type="text">
</div>
<div class="form-div">
<label class="form-label" for="input-version">Version</label>
<input id="input-version" v-model="form.version" class="form-input" type="text">
</div>
<div class="form-div">
<label class="form-label" for="input-packageName">PackageName</label>
<input id="input-packageName" v-model="form.packageName" class="form-input" placeholder="默认为 GroupId" type="text">
</div>
<input class="form-submit" type="button" value="生成" @click="submit">
<input :disabled="this.id === null" class="form-submit" type="button" value="预览" @click="preview">
<input :disabled="this.id === null" class="form-submit" type="button" value="下载" @click="download">
<el-dialog v-model="dialogVisible" title="预览" width="30%">
<el-tree :data="treeData" :props="defaultProps" @node-click="handleNodeClick"/>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="dialogVisible = false">关闭</el-button>
</span>
</template>
</el-dialog>
</div>
</template> </template>
<script> <script lang="ts" setup>
import axios from './util/request' import Starter from "./components/starter.vue";</script>
import {ElMessageBox} from 'element-plus' \ No newline at end of file
export default {
name: "BackendStarter",
data() {
return {
id: null,
dialogVisible: false,
fileContentDialogVisible: false,
fileContent: '',
defaultProps: {
children: 'children',
label: 'fileName',
},
treeData: [],
form: {
groupId: '',
artifactId: '',
version: '1.0.0-SNAPSHOT',
packageName: '',
}
}
},
methods: {
submit: function () {
axios.post('/archetype/generate', this.form).then(res => {
this.id = res.data.result
}, error => {
ElMessageBox.alert(error.data.message)
})
},
preview: function () {
axios.get(`/archetype/preview/${this.id}`, this.form).then(res => {
this.treeData = res.data.result
this.dialogVisible = true
}, error => {
ElMessageBox.alert(error.data.message)
})
},
download: function () {
axios.get(`/archetype/download/${this.id}`, {
responseType: 'blob'
}).then(res => {
const {headers, data} = res
const fileName = headers['content-disposition'].replace(/\w+;\sfilename="(.*)"/, '$1')
this.downloadGeneratedProject(data, fileName);
}, error => {
const reader = new FileReader();
reader.readAsText(error.data, 'utf-8');
reader.onload = function () {
ElMessageBox.alert(JSON.parse(reader.result).message, '提示', {confirmButtonText: '确定'})
}
})
},
handleNodeClick: function (data) {
const {file, fileContent} = data
if (!file) {
return
}
this.fileContentDialogVisible = true
this.fileContent = fileContent
},
downloadGeneratedProject: function (data, fileName) {
const blob = new Blob([data])
const link = document.createElement('a');
link.style.display = "none";
link.href = URL.createObjectURL(blob);
link.download = fileName;
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(link.href);
document.body.removeChild(link);
}
}
}
</script>
<style scoped>
.form {
text-align: center;
margin: 80px auto;
max-width: 500px;
background: #FFF;
padding: 30px;
box-shadow: rgba(187, 187, 187, 1) 0 0 20px -1px;
-webkit-box-shadow: rgba(187, 187, 187, 1) 0 0 20px -1px;
font: 14px Arial, Helvetica, sans-serif;
border-radius: 10px;
-webkit-border-radius: 10px;
}
.form-title {
font-size: 20px;
margin-top: 0;
}
.form-div {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.form-label {
width: 150px;
}
.form-input {
color: #555;
width: 100%;
padding: 3px 10px;
margin-top: 10px;
margin-right: 6px;
margin-bottom: 10px;
border: 1px solid #9a9191;
background: transparent;
height: 25px;
line-height: 15px;
outline: 0;
border-radius: 10px;
-webkit-border-radius: 10px;
}
.form-submit {
margin: 10px 5px 0 5px;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0.05, #ff5bb0), color-stop(1, #ef027d));
border-radius: 9px;
-webkit-border-radius: 9px;
border: 1px solid #ee1eb5;
display: inline-block;
color: #ffffff;
font-size: 15px;
font-weight: bold;
font-style: normal;
height: 35px;
line-height: 30px;
width: 60px;
text-decoration: none;
text-shadow: 1px 1px 0 #c70067;
cursor: pointer;
}
.form-submit:disabled {
background: -webkit-gradient(linear, left top, left bottom, color-stop(0.05, #000000), color-stop(1, #000000));
border: 1px solid #000000;
text-shadow: 1px 1px 0 #000000;
cursor: not-allowed;
}
</style>
\ No newline at end of file
<template>
<div class="start-wrapper">
<a-spin :delay="500" :spinning="loading">
<h3 style="margin-bottom: 20px">项目初始代码生成</h3>
<a-form ref="downloadForm" :labelCol="{span: 6}" :model="formState" :rules="formRules" :wrapperCol="{span: 18}" class="form-wrapper">
<a-form-item has-feedback label="GroupId" name="groupId">
<a-input v-model:value="formState.groupId"/>
</a-form-item>
<a-form-item has-feedback label="ArtifactId" name="artifactId">
<a-input v-model:value="formState.artifactId"/>
</a-form-item>
<a-form-item has-feedback label="Version" name="version">
<a-input v-model:value="formState.version"/>
</a-form-item>
<a-form-item has-feedback label="PackageName" name="packageName">
<a-input v-model:value="formState.packageName" placeholder="默认为GroupId"/>
</a-form-item>
</a-form>
<div class="button-group">
<a-button class="generic-btn" type="primary" @click="onGenerate">生成</a-button>
<a-button v-if="id > 0" class="generic-btn" @click="onPreview">预览</a-button>
<a-button v-if="id > 0" class="generic-btn" @click="onDownload(id)">下载</a-button>
</div>
</a-spin>
<a-modal v-model:visible="visible" :footer="null" title="预览">
<a-tree :tree-data="treeData"/>
</a-modal>
</div>
</template>
<script lang="ts" setup>
import {reactive, ref} from 'vue'
import type {TreeProps} from 'ant-design-vue';
import {FormInstance, message} from "ant-design-vue";
import request from "../utils/request";
import BaseResponse from "../models/BaseResponse";
interface FormState {
groupId: string,
artifactId: string,
version: string,
packageName: string
}
interface FileNode {
fileName: string,
fileContent: string | null,
children: FileNode[],
isFile: boolean
}
const fileNodeList = ref<FileNode[]>([])
const treeData = ref<TreeProps['treeData']>([])
const id = ref(-1)
const visible = ref(false)
const loading = ref(false)
const downloadForm = ref<FormInstance>();
const formState = reactive<FormState>({
groupId: '',
artifactId: '',
version: '1.0.0-SNAPSHOT',
packageName: ''
})
const formRules = {
groupId: [{required: true, message: '请输入groupId', trigger: 'change'}],
artifactId: [{required: true, message: '请输入artifactId', trigger: 'change'}],
}
const onGenerate = async () => {
let result = await downloadForm.value?.validate()
if (result) {
if (!result.packageNama) {
result.packageNama = result.groupId
}
loading.value = true
try {
let data: BaseResponse = await request.post('/archetype/generate', result)
if (data.code === 0) {
id.value = data.result
message.success('生成成功!')
loading.value = false
}
} catch (e) {
loading.value = false
}
}
}
const onPreview = async () => {
fileNodeList.value = await fetchFileList(id.value)
treeData.value = createTreeData(fileNodeList.value)
visible.value = true
}
const onDownload = async (id: number) => {
try {
loading.value = true
let res = await request.get(`/archetype/download/${id}`, {responseType: 'blob'})
const {headers, data} = res
const fileName = headers['content-disposition'].replace(/\w+;\sfilename="(.*)"/, '$1')
downloadGeneratedProject(data, fileName)
} catch (e) {
console.log('err:', e)
loading.value = false
}
}
const downloadGeneratedProject = (data: BlobPart, fileName: string) => {
const blob = new Blob([data])
const link = document.createElement('a')
link.style.display = "none"
link.href = URL.createObjectURL(blob)
link.download = fileName
document.body.appendChild(link)
link.click()
URL.revokeObjectURL(link.href)
document.body.removeChild(link)
loading.value = false
}
const fetchFileList = async (id: number) => {
let data: BaseResponse = await request.get(`/archetype/preview/${id}`)
if (data.code === 0) {
return data.result
}
}
const createTreeData = (fileNodeList: FileNode[]): any => {
if (!fileNodeList) {
return []
}
return fileNodeList.map((item: FileNode) => {
// 非叶子结点
if (item.children && item.children.length > 0) {
Object.assign(item, {
key: item.fileContent! + Math.random(),
children: createTreeData(item.children),
title: item.fileName,
selectable: false,
isLeaf: false
})
return item
} else {
return {
key: item.fileContent! + Math.random(),
title: item.fileName,
children: [],
selectable: false,
isLeaf: true
}
}
})
}
</script>
<style lang="scss" scoped>
.start-wrapper {
width: 500px;
min-height: 300px;
border-radius: 8px;
box-shadow: 0 0 20px gray;
margin: 100px auto;
padding: 50px;
.button-group {
display: flex;
justify-content: center;
align-items: center;
.generic-btn {
margin-left: 10px;
}
}
:deep(.ant-input), :deep(.ant-btn) {
border-radius: 6px;
}
}
</style>
\ No newline at end of file
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
\ No newline at end of file
import {createApp} from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')
\ No newline at end of file
import {createApp} from 'vue'
import App from './App.vue'
import './index.css'
createApp(App).mount('#app')
\ No newline at end of file
import {AxiosResponse} from "axios";
export default interface BaseResponse<T = any> extends AxiosResponse {
code?: number
action?: number
message?: string
uuid?: string
result?: T
}
\ No newline at end of file
declare module '*.vue' {
import type {DefineComponent} from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}
\ No newline at end of file
/**
* 全局loading效果:合并多次loading请求,避免重复请求
* 当调用一次showLoading,则次数+1;当次数为0时,则显示loading
* 当调用一次hideLoading,则次数-1; 当次数为0时,则结束loading
*/
import {ElLoading} from 'element-plus';
// 定义一个请求次数的变量,用来记录当前页面总共请求的次数
let loadingRequestCount = 0;
// 初始化loading
let loadingInstance;
// 编写一个显示loading的函数 并且记录请求次数 ++
const showLoading = () => {
if (loadingRequestCount === 0) {
loadingInstance = ElLoading.service({});
}
loadingRequestCount++
}
// 编写一个隐藏loading的函数,并且记录请求次数 --
const hideLoading = () => {
if (loadingRequestCount <= 0) return
loadingRequestCount--
if (loadingRequestCount === 0) {
loadingInstance.close();
}
}
export {
showLoading,
hideLoading
}
\ No newline at end of file
import axios from 'axios';
import {hideLoading, showLoading} from './loading'
const envDict = {
'development': 'http://localhost:8080',
'production': 'https://start.develop.schbrain.com'
}
axios.defaults.baseURL = envDict[process.env.NODE_ENV]
axios.defaults.timeout = 10000
const service = axios.create()
service.interceptors.request.use(config => {
showLoading()
return config
})
service.interceptors.response.use(response => {
hideLoading()
if (response.headers['content-type'] === 'application/json') {
if (response.data.code !== 0) {
return Promise.reject(response);
}
}
return response
}, error => {
hideLoading()
return Promise.reject(error)
})
export default service
\ No newline at end of file
import type {AxiosResponse} from "axios";
import axios from "axios";
import {message} from "ant-design-vue";
// @ts-ignore
axios.defaults.baseURL = import.meta.env.VITE_APP_BASE_URL
axios.interceptors.response.use((config: AxiosResponse) => {
const {data} = config
if (config.headers['content-type'] === 'application/json') {
switch (data.code) {
case 0:
return Promise.resolve(data)
default:
message.error(data.message)
return Promise.reject(data)
}
} else {
return Promise.resolve(config)
}
})
export default axios
\ No newline at end of file
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": [
"esnext",
"dom"
],
"types": [
"vite/client"
]
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
]
}
\ No newline at end of file
import Components from 'unplugin-vue-components/vite'
import {AntDesignVueResolver} from 'unplugin-vue-components/resolvers'
import styleImport, {AndDesignVueResolve, AntdResolve} from 'vite-plugin-style-import'
import {defineConfig} from 'vite'
// @ts-ignore
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue(),
// antd自动按需引入
Components({
resolvers: [
AntDesignVueResolver(),
]
}),
// @ts-ignore
styleImport({
resolves: [
AndDesignVueResolve(),
AntdResolve()
],
// 自定义规则
libs: [
{
libraryName: 'ant-design-vue',
esModule: true,
resolveStyle: (name: any) => {
return `ant-design-vue/es/${name}/style/index`
}
}
]
})
],
// 引用使用less的库要配置一下
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true
}
}
},
// 让terminal显示network地址
server: {
host: '0.0.0.0'
}
})
\ No newline at end of file
const {defineConfig} = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
productionSourceMap: false
})
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment