add web and server

feature/koa-web
guofei 2025-01-23 00:01:37 +08:00
parent d42944fd4e
commit 393dd32407
22 changed files with 2216 additions and 105 deletions

23
.gitignore vendored
View File

@ -2,3 +2,26 @@ node_modules
dist
.zip
web-mobile
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -9,6 +9,7 @@
"dev": "concurrently \"pnpm dev:frontend\" \"pnpm dev:backend\""
},
"devDependencies": {
"@types/node": "^22.10.7",
"concurrently": "^7.6.0"
}
}

View File

@ -0,0 +1,4 @@
# 生产环境配置
VITE_APP_ENV = 'production'
VITE_APP_BASE_API = '/dev-api'

View File

@ -0,0 +1,4 @@
# 生产环境配置
VITE_APP_ENV = 'production'
VITE_APP_BASE_API = '/prod-api'

View File

@ -0,0 +1,10 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
}

View File

@ -0,0 +1,22 @@
/* eslint-disable */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
ElButton: typeof import('element-plus/es')['ElButton']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElInput: typeof import('element-plus/es')['ElInput']
ElRow: typeof import('element-plus/es')['ElRow']
ElUpload: typeof import('element-plus/es')['ElUpload']
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
PackageSelector: typeof import('./src/components/PackageSelector.vue')['default']
}
}

View File

@ -9,12 +9,21 @@
"preview": "vite preview"
},
"dependencies": {
"ali-oss": "^6.22.0",
"axios": "^1.7.9",
"dayjs": "^1.11.13",
"element-plus": "^2.9.3",
"vue": "^3.5.13"
},
"devDependencies": {
"@types/ali-oss": "^6.16.11",
"@unocss/preset-wind": "^65.4.3",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/tsconfig": "^0.7.0",
"typescript": "~5.6.2",
"unocss": "^65.4.3",
"unplugin-auto-import": "^19.0.0",
"unplugin-vue-components": "^28.0.0",
"vite": "^6.0.5",
"vue-tsc": "^2.2.0"
}

View File

@ -1,30 +1,10 @@
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
import PackageSelector from "./components/PackageSelector.vue";
</script>
<template>
<div>
<a href="https://vite.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
<PackageSelector />
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

View File

@ -0,0 +1,8 @@
import request from "@/utils/request";
import OSS from 'ali-oss';
// 定义一键生成的 API 请求
export const generatePackage = (data: any) => {
return request.post("/generate", data);
};

View File

@ -0,0 +1,132 @@
<template>
<div>
<el-form :model="form" label-width="140px">
<el-form-item label="Zip渠道包">
<el-checkbox-group v-model="form.zipChannels">
<el-checkbox v-for="channel in zipChannel" :key="channel" :label="channel">{{ channel }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="HTML渠道包">
<el-checkbox-group v-model="form.htmlChannels">
<el-checkbox v-for="channel in htmlChannel" :key="channel" :label="channel">{{ channel }}</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="前缀名">
<el-input v-model="form.outputPrefix" placeholder="请输入前缀名称"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="后缀名">
<el-input v-model="form.outputSuffix" placeholder="请输入后缀名称"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="Android商店URL">
<el-input v-model="form.androidUrl" placeholder="请输入Android商店的URL"></el-input>
</el-form-item>
<el-form-item label="iOS商店URL">
<el-input v-model="form.iosUrl" placeholder="请输入iOS商店的URL"></el-input>
</el-form-item>
<el-form-item label="上传Zip包">
<el-upload action="" :before-upload="beforeUpload" :show-file-list="false" :http-request="customUpload" drag>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<div class="el-upload__tip" slot="tip">只能上传zip文件且不超过10MB</div>
</el-upload>
<el-input class="mt-2" v-model="form.ossUrl" placeholder="zip地址地址" readonly></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm"></el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { generatePackage } from "@/api";
import { ElMessage } from "element-plus";
import { Oss } from "@/utils/oss";
import dayjs from "dayjs";
const zipChannel = ["facebook", "google", "tiktok", "vungle", "liftoff"];
const htmlChannel = ["applovin", "unity", "appier", "ironsource", "mintegral", "moloco"];
const form = ref({
zipChannels: [],
htmlChannels: [],
outputPrefix: "",
outputSuffix: "",
androidUrl: "",
iosUrl: "",
ossUrl: "",
});
const beforeUpload = (file: File) => {
const isZip = file.type === "application/zip" || file.type === "application/x-zip-compressed";
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isZip) {
ElMessage.error("上传文件必须是 zip 格式!");
return false;
}
if (!isLt10M) {
ElMessage.error("上传文件大小不能超过 10MB!");
return false;
}
return true;
};
const customUpload = async (options: any) => {
const { file, onSuccess, onError } = options;
try {
const formattedDate = dayjs().format("YYYYMMDDHHmm");
const newFileName = `${formattedDate}_${encodeURIComponent(file.name)}`;
const result = await Oss.put(newFileName, file);
form.value.ossUrl = result.url;
ElMessage.success("文件上传成功!");
onSuccess(result, file);
} catch (error) {
ElMessage.error("上传失败,请重试!");
onError(error);
}
};
const submitForm = async () => {
try {
if (form.value.zipChannels.length + form.value.htmlChannels.length === 0) {
ElMessage.error("请先选择一个渠道!");
return;
}
if (!form.value.ossUrl) {
ElMessage.error("请先上传cocos zip包!");
return;
}
const response = await generatePackage({
zipChannels: form.value.zipChannels,
htmlChannels: form.value.htmlChannels,
outputPrefix: form.value.outputPrefix,
outputSuffix: form.value.outputSuffix,
androidUrl: form.value.androidUrl,
iosUrl: form.value.iosUrl,
});
console.log("Response:", response);
} catch (error) {
console.error("Error generating package:", error);
}
};
</script>
<style scoped>
/* Add any custom styles here */
</style>

View File

@ -1,5 +1,9 @@
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import { createApp } from "vue";
import App from "./App.vue";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import "virtual:uno.css";
createApp(App).mount('#app')
const app = createApp(App);
app.use(ElementPlus);
app.mount("#app");

View File

@ -0,0 +1,9 @@
import OSS from "ali-oss";
export const Oss = new OSS({
region: "oss-cn-beijing",
accessKeyId: "LTAI5tEday8PJNaMTz5mp8g4",
accessKeySecret: "ck84eTxx4aSTjornlYrCy8RkurCHfc",
bucket: "cocos-build-channel",
secure: true,
});

View File

@ -0,0 +1,30 @@
import axios from "axios";
// 创建 axios 实例
const request = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API, // 使用环境变量
timeout: 5000, // 请求超时时间
});
// 请求拦截器
request.interceptors.request.use(
(config) => {
// 可以在这里添加请求头等配置
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
request.interceptors.response.use(
(response) => {
return response.data;
},
(error) => {
return Promise.reject(error);
}
);
export default request;

View File

@ -1,14 +0,0 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}

View File

@ -1,7 +1,19 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"types": ["element-plus/global", "node", "vite/client"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"exclude": ["node_modules", "dist"]
}

View File

@ -1,24 +0,0 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

View File

@ -0,0 +1,7 @@
import { defineConfig, presetWind } from "unocss";
export default defineConfig({
presets: [
presetWind(),
],
});

View File

@ -1,7 +1,40 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
import UnoCSS from "unocss/vite";
// https://vite.dev/config/
export default defineConfig({
plugins: [vue()],
})
export default defineConfig(({ command, mode }) => {
return {
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
UnoCSS(),
],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
server: {
proxy: {
"/dev-api": {
target: "http://localhost:3000", // 代理到 Koa 服务器
changeOrigin: true,
rewrite: (path) => path.replace(/^\/dev-api/, ""), // 确保路径正确
},
},
},
define: {
"process.env.NODE_ENV": JSON.stringify(mode),
},
};
});

View File

@ -12,6 +12,8 @@
"dependencies": {
"archiver": "^7.0.1",
"brotli": "^1.3.3",
"koa": "^2.15.3"
"koa": "^2.15.3",
"koa-bodyparser": "^4.4.1",
"koa-router": "^13.0.1"
}
}

View File

@ -1,12 +1,21 @@
import Koa from "koa";
import Router from "koa-router";
import bodyParser from "koa-bodyparser";
const app = new Koa();
const router = new Router();
// 中间件示例
app.use(async (ctx) => {
ctx.body = "Hello, Koa!";
app.use(bodyParser());
router.post("/generate", async (ctx) => {
const { zipChannels, htmlChannels, outputPrefix, outputSuffix, ossUrl, androidUrl, iosUrl } = ctx.request.body;
ctx.body = { message: ossUrl };
});
app.use(router.routes()).use(router.allowedMethods());
// 监听端口
const PORT = 3000;
app.listen(PORT, () => {

14
panda.config.ts 100644
View File

@ -0,0 +1,14 @@
import { defineConfig } from '@pandacss/dev'
export default defineConfig({
// 你的 Panda CSS 配置
theme: {
extend: {
colors: {
primary: '#1e90ff',
secondary: '#ff6347',
},
},
},
// 其他配置选项
})

File diff suppressed because it is too large Load Diff