瀏覽代碼

refactor: 从 Vue CLI 5 迁移到 Vite 5
- 替换构建工具:vue-cli-service → vite
- 环境变量前缀:VUE_APP_ → VITE_
- 配置文件:删除 babel.config.js/vue.config.js,新增 vite.config.js
- 入口文件:public/index.html → index.html
- SVG 图标:svg-sprite-loader → vite-plugin-svg-icons
- 依赖更新:移除 Babel 相关,添加 Vite 生态依赖

CareyWong 1 月之前
父節點
當前提交
4c1d17c2ba
共有 19 個文件被更改,包括 390 次插入888 次删除
  1. 10 10
      .env
  2. 2 1
      .eslintrc.js
  3. 16 8
      .github/workflows/build.yml
  4. 124 0
      AGENTS.md
  5. 25 8
      README.md
  6. 0 14
      babel.config.js
  7. 28 0
      index.html
  8. 13 11
      package.json
  9. 0 28
      public/index.html
  10. 1 1
      src/composables/useSubscriptionForm.js
  11. 8 8
      src/config/constants.js
  12. 2 5
      src/icons/index.js
  13. 4 4
      src/main.js
  14. 1 0
      src/plugins/element-ui.js
  15. 1 1
      src/router/index.js
  16. 6 2
      src/views/Subconverter.vue
  17. 22 0
      vite.config.js
  18. 0 26
      vue.config.js
  19. 127 761
      yarn.lock

+ 10 - 10
.env

@@ -1,22 +1,22 @@
-VUE_APP_PROJECT = "https://github.com/CareyWang/sub-web"
+VITE_PROJECT = "https://github.com/CareyWang/sub-web"
 
-VUE_APP_BOT_LINK = "https://t.me/subconverter_discuss"
+VITE_BOT_LINK = "https://t.me/subconverter_discuss"
 
-VUE_APP_BACKEND_RELEASE = "https://github.com/tindy2013/subconverter/actions"
+VITE_BACKEND_RELEASE = "https://github.com/tindy2013/subconverter/actions"
 
-VUE_APP_SUBCONVERTER_REMOTE_CONFIG = "https://raw.githubusercontent.com/tindy2013/subconverter/master/base/config/example_external_config.ini"
+VITE_SUBCONVERTER_REMOTE_CONFIG = "https://raw.githubusercontent.com/tindy2013/subconverter/master/base/config/example_external_config.ini"
 
-VUE_APP_SUBCONVERTER_DOC_ADVANCED = "https://github.com/tindy2013/subconverter/blob/master/README-cn.md#%E8%BF%9B%E9%98%B6%E9%93%BE%E6%8E%A5"
+VITE_SUBCONVERTER_DOC_ADVANCED = "https://github.com/tindy2013/subconverter/blob/master/README-cn.md#%E8%BF%9B%E9%98%B6%E9%93%BE%E6%8E%A5"
 
 # API 后端
-VUE_APP_SUBCONVERTER_DEFAULT_BACKEND = "https://api.wcc.best"
+VITE_SUBCONVERTER_DEFAULT_BACKEND = "https://api.wcc.best"
 
 # 短链接后端
-VUE_APP_MYURLS_API = "https://suosuo.de/short"
+VITE_MYURLS_API = "https://suosuo.de/short"
 
 # 文本托管后端
-VUE_APP_CONFIG_UPLOAD_API = "https://oss.wcc.best/upload"
+VITE_CONFIG_UPLOAD_API = "https://oss.wcc.best/upload"
 
 # 页面配置
-VUE_APP_USE_STORAGE = true 
-VUE_APP_CACHE_TTL = 86400
+VITE_USE_STORAGE = true 
+VITE_CACHE_TTL = 86400

+ 2 - 1
.eslintrc.js

@@ -14,6 +14,7 @@ module.exports = {
     'vue/multi-word-component-names': 'off'
   },
   parserOptions: {
-    parser: '@babel/eslint-parser'
+    parser: '@babel/eslint-parser',
+    requireConfigFile: false
   }
 }

+ 16 - 8
.github/workflows/build.yml

@@ -3,6 +3,8 @@ name: Build
 on:
   push:
     branches: [ master, dev ]
+  pull_request:
+    branches: [ master, dev ]
 
 jobs:
 
@@ -15,20 +17,26 @@ jobs:
         node-version: [22.x]
 
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v4
 
     - name: Use Node.js ${{ matrix.node-version }}
-      uses: actions/setup-node@v1
+      uses: actions/setup-node@v4
       with:
         node-version: ${{ matrix.node-version }}
+        cache: 'yarn'
+
+    - name: Install dependencies
+      run: yarn install --frozen-lockfile
+
+    - name: Run linter
+      run: yarn lint
 
-    - name: Get dependencies and build
-      run: |
-        yarn install
-        yarn build
+    - name: Build
+      run: yarn build
 
-    - name: Upload
+    - name: Upload build artifacts
       uses: actions/upload-artifact@v4
       with:
-        name: dist
+        name: dist-${{ matrix.node-version }}
         path: dist/
+        retention-days: 7

+ 124 - 0
AGENTS.md

@@ -0,0 +1,124 @@
+# AGENTS GUIDE
+
+This repository is a Vue 2 + Vite SPA with Element UI. Keep changes small, follow existing patterns, and avoid refactors during fixes.
+
+## Quick Facts
+- Framework: Vue 2.6 (Options API)
+- Build tool: Vite 5
+- UI: Element UI 2
+- Router: Vue Router 3
+- Node: 22.x (see package.json)
+- No automated tests currently
+
+## Commands (local)
+- Install: `yarn install`
+- Dev server: `yarn dev`
+- Build: `yarn build`
+- Preview build: `yarn preview`
+- Lint: `yarn lint`
+
+## Single-Test / Focused Runs
+- There is no test runner configured (no Jest/Vitest scripts).
+- If you add tests later, document the single-test command here.
+
+## CI / Workflows
+- Build on push to `master` and `dev`: `.github/workflows/build.yml` runs `yarn install` + `yarn build`.
+- Docker build+push on `master`: `.github/workflows/docker-build-push.yml`.
+
+## Repository Layout
+- `src/main.js`: app bootstrap, plugin setup, Vue mount
+- `src/router/index.js`: router (history mode)
+- `src/views/Subconverter.vue`: main screen
+- `src/components/`: UI components
+- `src/composables/`: shared logic
+- `src/services/`: API adapters
+- `src/utils/`: utility helpers
+- `src/config/`: env-backed constants and option lists
+- `src/icons/svg`: SVG sprite inputs
+- `services/`: docker compose stack
+
+## Code Style (Observed + ESLint)
+- Indentation: 2 spaces
+- Quotes: single quotes are preferred in most files
+- Semicolons: not used (`semi: 0`)
+- Vue component names: single-word allowed (`vue/multi-word-component-names: off`)
+- Console/debugger: forbidden in production (`no-console`, `no-debugger`)
+
+## Imports & Modules
+- Use ES modules (`import`/`export`).
+- Prefer absolute alias `@` for `src/` (see `vite.config.js`).
+- Keep import groups readable: core libs, local config/utils, services, components.
+- Dynamic import only used for route lazy-load.
+
+## Vue Patterns
+- Options API is used across components.
+- Component file structure: `<template>`, `<script>`, `<style>`.
+- Keep reactive state in `data()`; derived state in `computed`.
+- Use composables from `src/composables/` for shared logic.
+
+## Services & Error Handling
+- Service classes in `src/services/*Service.js` handle API calls.
+- Prefer explicit error messages with `Error` instances.
+- UI errors are surfaced with `this.$message.*` or `this.$notify`.
+- Silent failures are acceptable only when UX demands it (e.g., backend version fetch).
+
+## Validation
+- Use validators from `src/utils/validators.js` for user-facing checks.
+- Return `{ valid, message }` or booleans; avoid throwing for validation flow.
+
+## Environment Variables
+- Use `import.meta.env` with `VITE_` prefix.
+- Key constants live in `src/config/constants.js`.
+- Do not commit local env files (`.env.local`, `.env.*.local`).
+
+## Icons
+- SVG sprites via `vite-plugin-svg-icons` and `src/icons/svg`.
+- Use `<svg-icon icon-class="name" />`.
+
+## Storage
+- Local storage helpers live in `src/utils/storage.js`.
+- TTL-based caching is already implemented; reuse it.
+
+## Linting
+- ESLint config: `.eslintrc.js`.
+- If you add new rules, keep consistent with current minimal setup.
+
+## Formatting
+- No Prettier config present; rely on ESLint and existing style.
+- Keep formatting consistent with nearby code.
+
+## Git Hygiene
+- Do not commit `dist/`, `node_modules/`, `.env.local`, `.env.*.local`.
+- Avoid adding generated files.
+
+## Frontend Safety
+- Avoid adding inline styles unless already used in nearby code.
+- Prefer Element UI components and existing patterns.
+- Keep UX messages consistent with existing language (mostly Chinese UI strings).
+
+## Performance Notes
+- Do not introduce heavy deps; prefer existing utilities.
+- Keep network calls centralized in `src/services`.
+
+## Example Patterns
+- Router lazy-load: `component: () => import('../views/Subconverter.vue')`
+- Service class: `export class BackendService { static async ... }`
+- Composable: `export function useSubscription() { return { ... } }`
+
+## Cursor / Copilot Rules
+- No Cursor rules found (`.cursor/rules`, `.cursorrules` absent).
+- No Copilot instructions found (`.github/copilot-instructions.md` absent).
+
+## If You Add Tests Later
+- Document the runner and single-test command here.
+- Keep test files near the logic or under a dedicated `tests/` folder.
+
+## Suggested Manual Checks
+- `yarn lint`
+- `yarn build`
+- Run dev server and smoke the main screen
+
+## Notes for Agents
+- Follow existing patterns and minimize scope.
+- No large refactors unless explicitly requested.
+- Avoid introducing TypeScript or new tooling without approval.

+ 25 - 8
README.md

@@ -44,10 +44,10 @@ cd sub-web
 yarn install
 
 # 启动开发服务器
-yarn serve
+yarn dev
 ```
 
-访问 <http://localhost:8080/> 查看应用。
+访问 <http://localhost:5173/> 查看应用。
 
 ## 📦 环境要求
 
@@ -86,23 +86,40 @@ yarn install
 
 ```env
 # Subconverter 后端地址
-VUE_APP_SUBCONVERTER_DEFAULT_BACKEND=https://api.wcc.best
+VITE_SUBCONVERTER_DEFAULT_BACKEND=https://api.wcc.best
 
-# 其他配置
-VUE_APP_PROJECT=https://github.com/CareyWang/sub-web
-VUE_APP_BOT_LINK=https://t.me/subconverter_discuss
+# 项目与社区链接
+VITE_PROJECT=https://github.com/CareyWang/sub-web
+VITE_BOT_LINK=https://t.me/subconverter_discuss
+
+# 可选:远程配置与说明文档
+VITE_SUBCONVERTER_REMOTE_CONFIG=
+VITE_SUBCONVERTER_DOC_ADVANCED=
+
+# 可选:后端版本标识
+VITE_BACKEND_RELEASE=
+
+# 可选:短链接与配置上传服务
+VITE_MYURLS_API=
+VITE_CONFIG_UPLOAD_API=
+
+# 可选:开启本地存储与缓存 TTL(秒)
+VITE_USE_STORAGE=true
+VITE_CACHE_TTL=86400
 ```
 
+如果部署在子路径(如 `/sub-web/`),通过 `BASE_URL` 指定路由基础路径。
+
 ## 🚀 使用
 
 ### 开发环境
 
 ```bash
 # 启动开发服务器
-yarn serve
+yarn dev
 ```
 
-访问 <http://localhost:8080/> 查看应用。
+访问 <http://localhost:5173/> 查看应用。
 
 ### 生产构建
 

+ 0 - 14
babel.config.js

@@ -1,14 +0,0 @@
-module.exports = {
-  presets: [
-    '@vue/cli-plugin-babel/preset'
-  ],
-  plugins: [
-    [
-      'component',
-      {
-        libraryName: 'element-ui',
-        styleLibraryName: 'theme-chalk'
-      }
-    ]
-  ]
-}

+ 28 - 0
index.html

@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <!-- Standard favicon -->
+    <link rel="icon" href="/favicons/favicon.ico">
+    <!-- Favicon for modern browsers -->
+    <link rel="icon" type="image/png" sizes="32x32" href="/favicons/favicon-32x32.png">
+    <link rel="icon" type="image/png" sizes="16x16" href="/favicons/favicon-16x16.png">
+    <!-- Apple Touch Icon -->
+    <link rel="apple-touch-icon" sizes="180x180" href="/favicons/apple-touch-icon.png">
+    <!-- Android Chrome icons -->
+    <link rel="icon" type="image/png" sizes="192x192" href="/favicons/android-chrome-192x192.png">
+    <link rel="icon" type="image/png" sizes="512x512" href="/favicons/android-chrome-512x512.png">
+    <!-- Web Manifest -->
+    <link rel="manifest" href="/favicons/site.webmanifest">
+    <title>sub-web</title>
+  </head>
+  <body>
+    <noscript>
+      <strong>We're sorry but sub-web doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+    </noscript>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>

+ 13 - 11
package.json

@@ -6,9 +6,10 @@
     "node": "22.x"
   },
   "scripts": {
-    "serve": "vue-cli-service serve",
-    "build": "vue-cli-service build",
-    "lint": "vue-cli-service lint"
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview",
+    "lint": "eslint --ext .js,.vue src"
   },
   "dependencies": {
     "axios": "^0.21.1",
@@ -19,16 +20,17 @@
     "vue-router": "^3.5.1"
   },
   "devDependencies": {
-    "@babel/core": "^7.26.0",
-    "@babel/eslint-parser": "^7.25.9",
-    "@vue/cli-plugin-babel": "5",
-    "@vue/cli-plugin-eslint": "5",
-    "@vue/cli-plugin-router": "5",
-    "@vue/cli-service": "5",
-    "babel-plugin-component": "^1.1.1",
+    "@babel/core": "^7.28.5",
+    "@babel/eslint-parser": "^7.28.5",
+    "@vitejs/plugin-vue2": "^2.3.1",
+    "consola": "^3.4.2",
     "eslint": "8.56.0",
     "eslint-plugin-vue": "9.17.0",
-    "svg-sprite-loader": "6.0.11",
+    "fast-glob": "^3.3.3",
+    "sass": "^1.69.0",
+    "vite": "^5.0.0",
+    "vite-plugin-style-import": "^2.0.0",
+    "vite-plugin-svg-icons": "^2.0.1",
     "vue-template-compiler": "^2.6.10"
   }
 }

+ 0 - 28
public/index.html

@@ -1,28 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <meta charset="utf-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <meta name="viewport" content="width=device-width,initial-scale=1.0">
-    <!-- Standard favicon -->
-    <link rel="icon" href="<%= BASE_URL %>favicons/favicon.ico">
-    <!-- Favicon for modern browsers -->
-    <link rel="icon" type="image/png" sizes="32x32" href="<%= BASE_URL %>favicons/favicon-32x32.png">
-    <link rel="icon" type="image/png" sizes="16x16" href="<%= BASE_URL %>favicons/favicon-16x16.png">
-    <!-- Apple Touch Icon -->
-    <link rel="apple-touch-icon" sizes="180x180" href="<%= BASE_URL %>favicons/apple-touch-icon.png">
-    <!-- Android Chrome icons -->
-    <link rel="icon" type="image/png" sizes="192x192" href="<%= BASE_URL %>favicons/android-chrome-192x192.png">
-    <link rel="icon" type="image/png" sizes="512x512" href="<%= BASE_URL %>favicons/android-chrome-512x512.png">
-    <!-- Web Manifest -->
-    <link rel="manifest" href="<%= BASE_URL %>favicons/site.webmanifest">
-    <title>sub-web</title>
-  </head>
-  <body>
-    <noscript>
-      <strong>We're sorry but sub-web 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>

+ 1 - 1
src/composables/useSubscriptionForm.js

@@ -70,7 +70,7 @@ export function addCustomParam(customParams) {
  */
 export function saveSubUrl(form) {
   if (form && form.sourceSubUrl !== '') {
-    const ttl = process.env.VUE_APP_CACHE_TTL || 3600;
+    const ttl = Number(import.meta.env.VITE_CACHE_TTL) || 3600;
     setLocalStorageItem('sourceSubUrl', form.sourceSubUrl, ttl);
   }
 }

+ 8 - 8
src/config/constants.js

@@ -1,13 +1,13 @@
 // 项目常量定义
 export const CONSTANTS = {
-  PROJECT: process.env.VUE_APP_PROJECT,
-  REMOTE_CONFIG_SAMPLE: process.env.VUE_APP_SUBCONVERTER_REMOTE_CONFIG,
-  DOC_ADVANCED: process.env.VUE_APP_SUBCONVERTER_DOC_ADVANCED,
-  BACKEND_RELEASE: process.env.VUE_APP_BACKEND_RELEASE,
-  DEFAULT_BACKEND: process.env.VUE_APP_SUBCONVERTER_DEFAULT_BACKEND + '/sub?',
-  SHORT_URL_API: process.env.VUE_APP_MYURLS_API,
-  CONFIG_UPLOAD_API: process.env.VUE_APP_CONFIG_UPLOAD_API,
-  BOT_LINK: process.env.VUE_APP_BOT_LINK,
+  PROJECT: import.meta.env.VITE_PROJECT,
+  REMOTE_CONFIG_SAMPLE: import.meta.env.VITE_SUBCONVERTER_REMOTE_CONFIG,
+  DOC_ADVANCED: import.meta.env.VITE_SUBCONVERTER_DOC_ADVANCED,
+  BACKEND_RELEASE: import.meta.env.VITE_BACKEND_RELEASE,
+  DEFAULT_BACKEND: import.meta.env.VITE_SUBCONVERTER_DEFAULT_BACKEND + '/sub?',
+  SHORT_URL_API: import.meta.env.VITE_MYURLS_API,
+  CONFIG_UPLOAD_API: import.meta.env.VITE_CONFIG_UPLOAD_API,
+  BOT_LINK: import.meta.env.VITE_BOT_LINK,
   DEFAULT_CLIENT_TYPE: 'clash',
   BUTTON_WIDTH: '140px',
   LARGE_BUTTON_WIDTH: '290px'

+ 2 - 5
src/icons/index.js

@@ -1,9 +1,6 @@
 import Vue from 'vue'
-import SvgIcon from '@/components/SvgIcon'// svg component
+import SvgIcon from '@/components/SvgIcon/index.vue'// svg component
+import 'virtual:svg-icons-register'
 
 // register globally
 Vue.component('svg-icon', SvgIcon)
-
-const req = require.context('./svg', false, /\.svg$/)
-const requireAll = requireContext => requireContext.keys().map(requireContext)
-requireAll(req)

+ 4 - 4
src/main.js

@@ -1,10 +1,10 @@
 import Vue from 'vue'
 import App from './App.vue'
 import router from './router'
-require(`@/plugins/element-ui`)
-require(`@/plugins/clipboard`)
-require(`@/plugins/axios`)
-require(`@/plugins/device`)
+import '@/plugins/element-ui'
+import '@/plugins/clipboard'
+import '@/plugins/axios'
+import '@/plugins/device'
 
 import '@/icons' // icon
 

+ 1 - 0
src/plugins/element-ui.js

@@ -1,4 +1,5 @@
 import Vue from 'vue'
+import 'element-ui/lib/theme-chalk/index.css'
 import {
   Autocomplete,
   Button,

+ 1 - 1
src/router/index.js

@@ -13,7 +13,7 @@ const routes = [
 
 const router = new VueRouter({
   mode: "history",
-  base: process.env.BASE_URL,
+  base: import.meta.env.BASE_URL,
   routes
 });
 

+ 6 - 2
src/views/Subconverter.vue

@@ -292,7 +292,7 @@ export default {
     this.isPC = this.$getOS().isPc;
 
     // 获取 url cache
-    if (process.env.VUE_APP_USE_STORAGE === 'true') {
+    if (import.meta.env.VITE_USE_STORAGE === 'true') {
       const cachedUrl = getLocalStorageItem('sourceSubUrl');
       if (cachedUrl) {
         this.form.sourceSubUrl = cachedUrl;
@@ -301,8 +301,12 @@ export default {
   },
   mounted() {
     this.form.clientType = CONSTANTS.DEFAULT_CLIENT_TYPE;
-    this.notify();
     this.getBackendVersion();
+    
+    // 延迟加载隐私提示,避免阻塞页面初始化
+    setTimeout(() => {
+      this.notify();
+    }, 1000);
   },
   methods: {
     onCopy() {

+ 22 - 0
vite.config.js

@@ -0,0 +1,22 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue2'
+import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
+import path from 'path'
+
+export default defineConfig({
+    plugins: [
+        vue(),
+        createSvgIconsPlugin({
+            iconDirs: [path.resolve(process.cwd(), 'src/icons/svg')],
+            symbolId: 'icon-[name]'
+        })
+    ],
+    resolve: {
+        alias: {
+            '@': path.resolve(__dirname, 'src')
+        }
+    },
+    server: {
+        host: '0.0.0.0'
+    }
+})

+ 0 - 26
vue.config.js

@@ -1,26 +0,0 @@
-const path = require('path')
-
-function resolve(dir) {
-  return path.join(__dirname, dir)
-}
-
-module.exports = {
-  chainWebpack: config => {
-    // set svg-sprite-loader
-    config.module
-      .rule('svg')
-      .exclude.add(resolve('src/icons'))
-      .end()
-    config.module
-      .rule('icons')
-      .test(/\.svg$/)
-      .include.add(resolve('src/icons'))
-      .end()
-      .use('svg-sprite-loader')
-      .loader('svg-sprite-loader')
-      .options({
-        symbolId: 'icon-[name]'
-      })
-      .end()
-  }
-};

文件差異過大導致無法顯示
+ 127 - 761
yarn.lock


部分文件因文件數量過多而無法顯示