11.OpenCode 生成 Vue3 前端「私文简搜」完整实操记录 + Docker 部署全过程
前面已完成API的代码开发,现在结合OpenCode完成「私文简搜」Vue3前端开发。
一. 前端项目开发
1.1 通过OpenCode创建项目
在OpenCode中发指令(前提是打开项目不变)
分析前面代码后,在ivy-simple-api目录中创建 frontend前端项目,vue3 有登录,查询,类似百度一样的页面,调用查询函数,显示列表。首先显示的是百度一样的搜索页面,只有登录后才能进行查询,登录页面也要生成。
任务拆分分步执行,进度效果正常。
代码生成任务执行完成。
1.2 VS Code 打开代码本地调试
打开代码后执行npm i安装依赖,过程中出现多类问题逐一修复。
1.2.1 PowerShell 执行策略报错
修复命令:
Set-ExecutionPolicy RemoteSigned
1.2.2 安装项目依赖
npm i
1.2.3 启动项目报变量重名错误
npm run dev
完整报错日志:
Failed to scan for dependencies from entries:
E:/ivy/ivy-simple-api/frontend/index.html
X [ERROR] The symbol "getSmsCode" has already been declared
script:E:/ivy/ivy-simple-api/frontend/src/views/Login.vue?id=0:17:15:
17 │ async function getSmsCode() {
╵ ~~~~~~~~~~
The symbol "getSmsCode" was originally declared here:
script:E:/ivy/ivy-simple-api/frontend/src/views/Login.vue?id=0:4:9:
4 │ import { getSmsCode, smsVerify } from '../api'
╵ ~~~~~~~~~~
at failureErrorWithLog (E:\ivy\ivy-simple-api\frontend\node_modules\esbuild\lib\main.js:1472:15)
at E:\ivy\ivy-simple-api\frontend\node_modules\esbuild\lib\main.js:945:25
at runOnEndCallbacks (E:\ivy\ivy-simple-api\frontend\node_modules\esbuild\lib\main.js:1315:45)
at buildResponseToResult (E:\ivy\ivy-simple-api\frontend\node_modules\esbuild\lib\main.js:943:7)
at E:\ivy\ivy-simple-api\frontend\node_modules\esbuild\lib\main.js:955:9
at new Promise (<anonymous>)
at requestCallbacks.on-end (E:\ivy\ivy-simple-api\frontend\node_modules\esbuild\lib\main.js:954:54)
at handleRequest (E:\ivy\ivy-simple-api\frontend\node_modules\esbuild\lib\main.js:647:17)
at handleIncomingPacket (E:\ivy\ivy-simple-api\frontend\node_modules\esbuild\lib\main.js:672:7)
at Socket.readFromStdout (E:\ivy\ivy-simple-api\frontend\node_modules\esbuild\lib\main.js:600:7)
本地访问地址:[http://localhost:3000/]
1.2.4 页面基础信息调整
OpenCode 优化指令:
项目中文名:私文简搜 英文名:DocSolo ,将ivy Search替换为中文项目名 时间改为 yyyy-MM-dd HH:mm:ss
调整后主页面展示效果:
搜索结果列表页面:
1.2.5 新增登录缓存、文档详情功能
OpenCode 指令:
保存登录信息,并显示详细页面
补充优化需求:
还有要改进的地方,手机号要加掩码,中间***,退出登录后不要进行login页面,而是回到主页面,前面还有一个功能没完成,搜索后的内容,点击进入详细页面。页面可以从目录文件获取。还有,debug模式去掉,如果code不为空,就直接显示,还有登录页面记录上次的手机号
同步配套修改后端代码:
1.2.6 登录交互 Bug 修复
在OpenCode中输入
退出后,再单击登录无法进入登录页面,但要搜索时,没有登录是可以跳入登录页面的。登录页面还是没有记录上次登录帐号
补充问题:
还是单击登录,无法跳入登录页面
修复完成后,登录跳转、手机号本地缓存功能正常。
1.2.7 文档详情页面异常问题
路由报错问题:
查看详情页面显示的是
http://localhost:3000/detail/null,导致错误
文档渲染缺陷:
页面没有按md格式显示,还有图片也要正确显示,还有高亮也要在正文中显示
详情页面基础效果:
新增需求:
还有个小问题,详情 做为新页面
1.2.8 文档图片加载异常修复
图片无法正常展示截图:
路径替换方案输入OpenCode
图片路径可以替换成 192.168..../md-doces/111/22/xxx/images/具体的图片.png
修复后效果:Markdown 渲染、关键词高亮、内嵌图片全部正常展示
1.3 前端 API 代理统一改造
1.3.1 原始多代理配置(维护繁琐)
原始 vite.config.js 代码中api配置三个地方,要修改成一个
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
port: 3000,
proxy: {
'/auth': {
target: 'http://192.168.55.140:8101',
changeOrigin: true
},
'/md': {
target: 'http://192.168.55.140:8101',
changeOrigin: true
},
'/md-docs': {
target: 'http://192.168.55.140:8101',
changeOrigin: true
}
}
}
})
分析代码,同时修改frontend 所有访问的接口都是/api,转发时去掉/api,这样配置就只用配置一个地方。现在搞三个地方,访问修改有点麻烦
1.3.2 接口与 Vite 配置改造完成
改造后接口请求代码:
export function getSmsCode(mobile) {
return api.post('/api/auth/user/getSmsCode', { mobile })
}
export function smsVerify(mobile, code, verify) {
return api.post('/api/auth/user/smsVerify', { mobile, code, verify })
}
export function search(keywords, page = 1, pageSize = 10) {
return api.get('/api/md/search', { params: { keywords, page, pageSize } })
}
export function getDocDetail(id) {
return api.get('/api/md/detail', { params: { id } })
}
改造后单 /api 代理 vite 配置:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://192.168.55.140:8101',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})
1.3.3 改造后本地启动验证
二. 前端 Docker 容器化部署
部署思路:基础 Dockerfile 打包,正式环境使用数据卷挂载配置与静态资源,迭代无需重新构建镜像;适配 Jenkins 自动化打包流程。
2.1 前端目录新增部署文件
新增 Dockerfile、nginx.conf,用于线上代理转发配置参考
在frontend中添加Dockerfile文件,还有nginx配置文件也放在里面,不一定现在就用,主要用于指导nginx配置。/api映射的
2.2 Dockerfile 调整
初始多阶段构建脚本:
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
不知道为什么要配置两个,我想通过jenkins来部署,就不要前面这个了。并且nginx不使用alpine版本,并能适配 Jenkins打包。
去要使用jenkins进行打包,所以前面一个不用,直接使用FROM nginx ,并增加维护者信息
#维护者信息
#MAINTAINER 黄卫华 <wayhua@126.com>
修改配置文件复制路径:
COPY nginx.conf /etc/nginx/nginx.conf
这里也埋了一个bug。
2.3 Nginx 代理配置片段
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://192.168.55.140:8101/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /md-docs/ {
proxy_pass http://192.168.55.140:8101/md-docs/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
看到这个代码,是不是vite.config.js 中也要配置md-docs
看到nginx这个代码,是不是vite.config.js 中也要配置md-docs
2.4 最终 vite.config.js(本地 + 静态资源双代理)
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://192.168.55.140:8101',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
'/md-docs': {
target: 'http://192.168.55.140:8101',
changeOrigin: true
}
}
}
})
2.5 docker-compose 编排部署
2.5.1 前端打包编译
npm run build
2.5.2 部署目录结构
在 api 目录新建 ui 文件夹,存放 dist(编译后的文件夹)、nginx.conf、Dockerfile
2.5.3 docker-compose.yml ui 服务配置
在前面的docker-compose.yml中添加ui
version: '3'
services:
ui:
build:
context: ./ui
container_name: md-ui
ports:
- "8102:80"
environment:
- TZ=Asia/Shanghai
restart: unless-stopped
volumes:
- ./ui/nginx.conf:/etc/nginx/nginx.conf
- ./ui/dist:/usr/share/nginx/html/dist
2.5.4 启动容器脚本
./autorun.sh
2.5.5 容器查看与报错说明
查看容器命令:docker ps
报错原因:nginx.conf 仅片段,缺少完整 http 外层配置。
2.5.6 完整可用 nginx.conf
worker_processes auto;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://192.168.55.140:8101/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /md-docs/ {
proxy_pass http://192.168.55.140:8101/md-docs/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
2.5.7 重新部署验证
执行重启命令:
docker-compose stop
docker-compose rm
./autorun.sh
docker ps
线上访问地址:[http://192.168.55.140:8102/]
登录、搜索、文档详情全部功能正常,前端容器部署完成。
三. 小结
3.1 接口代码聚合在一起
OpenCode 生成完整 Vue3 前端代码,包含登录、文档检索、详情渲染全套业务逻辑,但代码结构存在聚合问题,全部接口与请求拦截器写在同一文件:
import axios from 'axios'
import { getToken, clearAuth } from '../utils/auth'
import router from '../router'
const api = axios.create({
baseURL: '',
timeout: 15000
})
api.interceptors.request.use(config => {
const token = getToken()
if (token) {
config.headers['Authorization'] = token
}
return config
})
api.interceptors.response.use(
res => res.data,
err => {
if (err.response && err.response.status === 401) {
clearAuth()
router.push('/')
}
return Promise.reject(err)
}
)
export function getSmsCode(mobile) {
return api.post('/auth/user/getSmsCode', { mobile })
}
export function smsVerify(mobile, code, verify) {
return api.post('/auth/user/smsVerify', { mobile, code, verify })
}
export function search(keywords, page = 1, pageSize = 10) {
return api.get('/md/search', { params: { keywords, page, pageSize } })
}
export function getDocDetail(id) {
return api.get('/md/detail', { params: { id } })
}
当然,这也不是什么大问题,各人写代码的习惯不同而已。我的开发习惯会将接口拆分目录、请求工具抽离至 utils,该规范不作强制要求。如果告诉OpenCode也是可以改过来的。
3.2 多代理问题
改造前多代理 vite 配置参考:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
port: 3000,
proxy: {
'/auth': {
target: 'http://localhost:8101',
changeOrigin: true
},
'/md': {
target: 'http://localhost:8101',
changeOrigin: true
},
'/md-docs': {
target: 'http://localhost:8101',
changeOrigin: true
}
}
}
})
优化方案:统一 /api 前缀代理,减少多处配置维护成本。
3.3 OpenCode问题
OpenCode 生成代码存在较多逻辑缺陷,包括命名冲突、路由判空缺失、图片路径未适配、登录缓存逻辑不完善,需要具备前端基础的人员调试修复,这时要提高代码质量就需要有一个架构比较好的程序员来指导。
3.4 拓展使用场景
除 Web 前端外,已使用 OpenCode 生成 Android 原生、鸿蒙端代码,因本地环境未调试完成无相关截图记录。
3.5 落地经验
先统一内部基础开发框架,再使用 OpenCode 生成业务页面,兼顾开发效率与项目代码规范。