update
5
0000_gb_points_temp/.env
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# 平台本地运行端口号
|
||||||
|
VITE_PORT = 8848
|
||||||
|
|
||||||
|
# 是否隐藏首页 隐藏 true 不隐藏 false (勿删除,VITE_HIDE_HOME只需在.env文件配置)
|
||||||
|
VITE_HIDE_HOME = false
|
||||||
11
0000_gb_points_temp/.env.development
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# 平台本地运行端口号
|
||||||
|
VITE_PORT = 8848
|
||||||
|
|
||||||
|
# 开发环境读取配置文件路径
|
||||||
|
VITE_PUBLIC_PATH = ./
|
||||||
|
|
||||||
|
# 网站前缀
|
||||||
|
VITE_BASE_URL = "vc1cccd1s325c.dagf7.top"
|
||||||
|
|
||||||
|
# 开发环境路由历史模式(Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数")
|
||||||
|
VITE_ROUTER_HISTORY = "hash"
|
||||||
16
0000_gb_points_temp/.env.production
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# 网站前缀
|
||||||
|
VITE_BASE_URL=/
|
||||||
|
|
||||||
|
# 线上环境平台打包路径
|
||||||
|
VITE_PUBLIC_PATH = ./
|
||||||
|
|
||||||
|
# 线上环境路由历史模式(Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数")
|
||||||
|
VITE_ROUTER_HISTORY = "hash"
|
||||||
|
|
||||||
|
# 是否在打包时使用cdn替换本地库 替换 true 不替换 false
|
||||||
|
VITE_CDN = false
|
||||||
|
|
||||||
|
# 是否启用gzip压缩或brotli压缩(分两种情况,删除原始文件和不删除原始文件)
|
||||||
|
# 压缩时不删除原始文件的配置:gzip、brotli、both(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认)
|
||||||
|
# 压缩时删除原始文件的配置:gzip-clear、brotli-clear、both-clear(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认)
|
||||||
|
VITE_COMPRESSION = "none"
|
||||||
25
0000_gb_points_temp/.eslintrc.cjs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
require('@rushstack/eslint-patch/modern-module-resolution')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
'extends': [
|
||||||
|
'plugin:vue/vue3-essential',
|
||||||
|
'eslint:recommended',
|
||||||
|
'@vue/eslint-config-typescript',
|
||||||
|
'@vue/eslint-config-prettier'
|
||||||
|
],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
'cypress/e2e/**/*.{cy,spec}.{js,ts,jsx,tsx}'
|
||||||
|
],
|
||||||
|
'extends': [
|
||||||
|
'plugin:cypress/recommended'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest'
|
||||||
|
}
|
||||||
|
}
|
||||||
28
0000_gb_points_temp/.gitignore
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
1
0000_gb_points_temp/.prettierrc.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
3
0000_gb_points_temp/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
||||||
|
}
|
||||||
69
0000_gb_points_temp/README.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# vue3-clean-architecture
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||||
|
|
||||||
|
## Type Support for `.vue` Imports in TS
|
||||||
|
|
||||||
|
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
|
||||||
|
|
||||||
|
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
|
||||||
|
|
||||||
|
1. Disable the built-in TypeScript Extension
|
||||||
|
1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
|
||||||
|
2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
|
||||||
|
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
|
||||||
|
|
||||||
|
## Customize configuration
|
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type-Check, Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Unit Tests with [Vitest](https://vitest.dev/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run test:unit
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run End-to-End Tests with [Cypress](https://www.cypress.io/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run test:e2e:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
This runs the end-to-end tests against the Vite development server.
|
||||||
|
It is much faster than the production build.
|
||||||
|
|
||||||
|
But it's still recommended to test the production build with `test:e2e` before deploying (e.g. in CI environments):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
npm run test:e2e
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lint with [ESLint](https://eslint.org/)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
```
|
||||||
80
0000_gb_points_temp/Theme1使用说明.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# Theme1 多语言使用说明
|
||||||
|
|
||||||
|
## 已完成的修改
|
||||||
|
|
||||||
|
### 1. 集成项目多语言系统
|
||||||
|
- ✅ 在 `src/locales/en/index.ts` 中添加了 Theme1 的英文文案
|
||||||
|
- ✅ 在 `src/locales/cn/index.ts` 中添加了 Theme1 的中文文案
|
||||||
|
- ✅ 在 `src/locales/es/index.ts` 中添加了 Theme1 的西班牙语文案
|
||||||
|
- ✅ Theme1 组件使用 `useI18n()` 自动获取当前语言
|
||||||
|
|
||||||
|
### 2. 样式优化
|
||||||
|
- ✅ 取消顶部背景渐变效果(改为纯色)
|
||||||
|
- ✅ 取消按钮渐变效果(改为纯色)
|
||||||
|
- ✅ 取消所有阴影效果
|
||||||
|
- ✅ 保留简洁的现代设计
|
||||||
|
|
||||||
|
### 3. 文案 Key 列表
|
||||||
|
```typescript
|
||||||
|
"Points Inquiry" // 主标题
|
||||||
|
"Enter your phone number to check available points" // 副标题
|
||||||
|
"Real-time" // 特性1
|
||||||
|
"Rich Rewards" // 特性2
|
||||||
|
"Secure" // 特性3
|
||||||
|
"Check My Points" // 查询卡片标题
|
||||||
|
"Please enter your phone number to query" // 查询卡片副标题
|
||||||
|
"Phone number" // 手机号标签
|
||||||
|
"Enter phone number" // 输入框占位符
|
||||||
|
"Query Now" // 查询按钮
|
||||||
|
"Your information will be securely encrypted and not used for other purposes" // 安全提示
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用方式
|
||||||
|
|
||||||
|
### 添加新语言
|
||||||
|
在 `src/locales/` 目录下创建新的语言文件,例如 `tr/index.ts`(土耳其语):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export default {
|
||||||
|
// ... 其他翻译 ...
|
||||||
|
|
||||||
|
// Theme1 texts
|
||||||
|
"Points Inquiry": "Puan Sorgulama",
|
||||||
|
"Enter your phone number to check available points": "Mevcut puanlarınızı kontrol etmek için telefon numaranızı girin",
|
||||||
|
"Real-time": "Gerçek Zamanlı",
|
||||||
|
"Rich Rewards": "Zengin Ödüller",
|
||||||
|
"Secure": "Güvenli",
|
||||||
|
"Check My Points": "Puanlarımı Kontrol Et",
|
||||||
|
"Please enter your phone number to query": "Lütfen sorgulamak için telefon numaranızı girin",
|
||||||
|
"Phone number": "Telefon Numarası",
|
||||||
|
"Enter phone number": "Telefon numarasını girin",
|
||||||
|
"Query Now": "Şimdi Sorgula",
|
||||||
|
"Your information will be securely encrypted and not used for other purposes": "Bilgileriniz güvenli bir şekilde şifrelenecek ve başka amaçlarla kullanılmayacaktır",
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 主题配置
|
||||||
|
```typescript
|
||||||
|
// 配置主题(在 goodsConfig 中)
|
||||||
|
goodsConfig.homeTheme = 1; // 使用 Theme1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 语言切换
|
||||||
|
项目的语言切换由 vue-i18n 自动管理,组件会自动响应语言更改。
|
||||||
|
|
||||||
|
## 技术细节
|
||||||
|
|
||||||
|
- 使用 `vue-i18n` 的 `useI18n()` 获取 `t()` 翻译函数
|
||||||
|
- 所有文案通过 `t("key")` 方式获取
|
||||||
|
- 颜色配置通过 props 传递(`colors`)
|
||||||
|
- 移除了渐变和阴影,使用纯色设计
|
||||||
|
- CSS 变量只保留 `--primary-color` 和 `--primary-light`
|
||||||
|
|
||||||
|
## 文件修改记录
|
||||||
|
|
||||||
|
1. **Theme1.vue**: 集成 useI18n,移除 texts prop,取消渐变和阴影
|
||||||
|
2. **PhoneView.vue**: 移除 texts 相关导入和传递
|
||||||
|
3. **locales/en/index.ts**: 添加英文文案
|
||||||
|
4. **locales/cn/index.ts**: 添加中文文案
|
||||||
|
5. **locales/es/index.ts**: 添加西班牙语文案
|
||||||
|
6. **删除**: theme-texts.ts(不再需要独立的多语言配置)
|
||||||
1
0000_gb_points_temp/cardloading.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" ?><svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style>.cls-1{fill:url(#linear-gradient);}.cls-2{fill:#a5a4a4;}.cls-3{fill:#333;}.cls-4{fill:#e6e6e6;}.cls-5{fill:gray;}.cls-6{fill:url(#linear-gradient-2);}.cls-7{fill:url(#linear-gradient-3);}.cls-8{fill:#fff;}</style><linearGradient gradientUnits="userSpaceOnUse" id="linear-gradient" x1="22.04" x2="22.04" y1="12.76" y2="39.8"><stop offset="0" stop-color="#e6e6e6"/><stop offset="1" stop-color="#bababa"/></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="linear-gradient-2" x1="35.54" x2="35.54" y1="11.27" y2="20.1"><stop offset="0" stop-color="#00bde8"/><stop offset="1" stop-color="#009dc1"/></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="linear-gradient-3" x1="35.54" x2="35.54" y1="12" y2="19.67"><stop offset="0" stop-color="#00cfff"/><stop offset="1" stop-color="#00afd6"/></linearGradient></defs><title/><g id="icons"><g data-name="Layer 3" id="Layer_3"><rect class="cls-1" height="26" rx="5" ry="5" width="35" x="4.54" y="12.81"/><path class="cls-2" d="M35.54,11.19a7.63,7.63,0,1,0,4,14.1V12.34A7.54,7.54,0,0,0,35.54,11.19Z"/><rect class="cls-3" height="4" width="35" x="4.54" y="19.81"/><rect class="cls-4" height="2" width="8" x="8.54" y="32.81"/><rect class="cls-4" height="2" width="6" x="19.54" y="32.81"/><rect class="cls-4" height="2" width="7" x="28.54" y="32.81"/><rect class="cls-5" height="2" width="8" x="8.54" y="31.81"/><rect class="cls-5" height="2" width="6" x="19.54" y="31.81"/><rect class="cls-5" height="2" width="7" x="28.54" y="31.81"/><path class="cls-6" d="M43.17,16.81a7.63,7.63,0,1,1-7.63-7.62A7.64,7.64,0,0,1,43.17,16.81Z"/><path class="cls-7" d="M35.54,23.44a6.63,6.63,0,1,1,6.63-6.63,6.63,6.63,0,0,1-6.63,6.63Z"/><path class="cls-8" d="M38,16.58V14.85a2.25,2.25,0,0,0-2.25-2.25h-.34a2.25,2.25,0,0,0-2.25,2.25v1.73H31.79V21H39.3V16.58Zm-1,0H34.12V14.85a1.25,1.25,0,0,1,1.25-1.25h.34A1.25,1.25,0,0,1,37,14.85Z"/></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
8
0000_gb_points_temp/cypress.config.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { defineConfig } from 'cypress'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
specPattern: 'cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}',
|
||||||
|
baseUrl: 'http://localhost:4173'
|
||||||
|
}
|
||||||
|
})
|
||||||
8
0000_gb_points_temp/cypress/e2e/example.cy.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// https://docs.cypress.io/api/introduction/api.html
|
||||||
|
|
||||||
|
describe('My First Test', () => {
|
||||||
|
it('visits the app root url', () => {
|
||||||
|
cy.visit('/')
|
||||||
|
cy.contains('h1', 'You did it!')
|
||||||
|
})
|
||||||
|
})
|
||||||
10
0000_gb_points_temp/cypress/e2e/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.web.json",
|
||||||
|
"include": ["./**/*", "../support/**/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"isolatedModules": false,
|
||||||
|
"target": "es5",
|
||||||
|
"lib": ["es5", "dom"],
|
||||||
|
"types": ["cypress"]
|
||||||
|
}
|
||||||
|
}
|
||||||
5
0000_gb_points_temp/cypress/fixtures/example.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "Using fixtures to represent data",
|
||||||
|
"email": "hello@cypress.io",
|
||||||
|
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||||
|
}
|
||||||
39
0000_gb_points_temp/cypress/support/commands.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/// <reference types="cypress" />
|
||||||
|
// ***********************************************
|
||||||
|
// This example commands.ts shows you how to
|
||||||
|
// create various custom commands and overwrite
|
||||||
|
// existing commands.
|
||||||
|
//
|
||||||
|
// For more comprehensive examples of custom
|
||||||
|
// commands please read more here:
|
||||||
|
// https://on.cypress.io/custom-commands
|
||||||
|
// ***********************************************
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a parent command --
|
||||||
|
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a child command --
|
||||||
|
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a dual command --
|
||||||
|
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This will overwrite an existing command --
|
||||||
|
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||||
|
//
|
||||||
|
// declare global {
|
||||||
|
// namespace Cypress {
|
||||||
|
// interface Chainable {
|
||||||
|
// login(email: string, password: string): Chainable<void>
|
||||||
|
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||||
|
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||||
|
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
export {}
|
||||||
20
0000_gb_points_temp/cypress/support/e2e.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// ***********************************************************
|
||||||
|
// This example support/index.js is processed and
|
||||||
|
// loaded automatically before your test files.
|
||||||
|
//
|
||||||
|
// This is a great place to put global configuration and
|
||||||
|
// behavior that modifies Cypress.
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off
|
||||||
|
// automatically serving support files with the
|
||||||
|
// 'supportFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/configuration
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// Import commands.js using ES2015 syntax:
|
||||||
|
import './commands'
|
||||||
|
|
||||||
|
// Alternatively you can use CommonJS syntax:
|
||||||
|
// require('./commands')
|
||||||
18
0000_gb_points_temp/env.d.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
VITE_BASE_URL: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asset module declarations for older @vue/tsconfig compatibility
|
||||||
|
declare module '*.svg' {
|
||||||
|
const src: string;
|
||||||
|
export default src;
|
||||||
|
}
|
||||||
|
declare module '*.jpg' {
|
||||||
|
const src: string;
|
||||||
|
export default src;
|
||||||
|
}
|
||||||
|
declare module '*.png' {
|
||||||
|
const src: string;
|
||||||
|
export default src;
|
||||||
|
}
|
||||||
453
0000_gb_points_temp/extract-resources.js
Normal file
@@ -0,0 +1,453 @@
|
|||||||
|
/**
|
||||||
|
* 静态资源提取脚本
|
||||||
|
* 从 header.html 和 footer.html 中提取 base64 图片、内联 CSS 和字体
|
||||||
|
*
|
||||||
|
* 功能:
|
||||||
|
* 1. 提取 base64 图片到 assets/images/
|
||||||
|
* 2. 提取所有内联 CSS 到 assets/css/
|
||||||
|
* 3. 提取字体文件到 assets/fonts/
|
||||||
|
* 4. 去除所有 meta 标签
|
||||||
|
* 5. 去除所有 script 标签
|
||||||
|
* 6. 只保留 body 内的内容
|
||||||
|
* 7. 把引入的 style 放到顶部
|
||||||
|
* 8. 给所有 HTML 属性值自动加上双引号
|
||||||
|
*
|
||||||
|
* 使用方法:
|
||||||
|
* - node extract-resources.js # 正常运行,从备份恢复
|
||||||
|
* - node extract-resources.js --keep # 保持当前文件,不从备份恢复
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// 检查命令行参数
|
||||||
|
const KEEP_CURRENT = process.argv.includes('--keep');
|
||||||
|
|
||||||
|
const PUBLIC_DIR = path.join(__dirname, 'public/pages');
|
||||||
|
const STATIC_DIR = path.join(__dirname, 'pages');
|
||||||
|
const FILES_TO_PROCESS = ['header.html', 'footer.html'];
|
||||||
|
// const FILES_TO_PROCESS = ['home.html', 'page2.html', 'page3.html', 'page4.html', 'page5.html'];
|
||||||
|
|
||||||
|
// 创建资源目录 (public)
|
||||||
|
const ASSETS_DIR = path.join(PUBLIC_DIR, 'assets');
|
||||||
|
const IMG_DIR = path.join(ASSETS_DIR, 'images');
|
||||||
|
const CSS_DIR = path.join(ASSETS_DIR, 'css');
|
||||||
|
const FONTS_DIR = path.join(ASSETS_DIR, 'fonts');
|
||||||
|
|
||||||
|
// 创建资源目录 (static)
|
||||||
|
const STATIC_ASSETS_DIR = path.join(STATIC_DIR, 'assets');
|
||||||
|
const STATIC_IMG_DIR = path.join(STATIC_ASSETS_DIR, 'images');
|
||||||
|
const STATIC_CSS_DIR = path.join(STATIC_ASSETS_DIR, 'css');
|
||||||
|
const STATIC_FONTS_DIR = path.join(STATIC_ASSETS_DIR, 'fonts');
|
||||||
|
|
||||||
|
// 清理旧资源文件的函数
|
||||||
|
function cleanDirectory(dir) {
|
||||||
|
if (fs.existsSync(dir)) {
|
||||||
|
const files = fs.readdirSync(dir);
|
||||||
|
files.forEach(file => {
|
||||||
|
const filePath = path.join(dir, file);
|
||||||
|
if (fs.statSync(filePath).isFile()) {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ASSETS_DIR, IMG_DIR, CSS_DIR, FONTS_DIR].forEach(dir => {
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 清理旧资源文件
|
||||||
|
console.log('🧹 清理旧资源文件...');
|
||||||
|
cleanDirectory(IMG_DIR);
|
||||||
|
cleanDirectory(CSS_DIR);
|
||||||
|
cleanDirectory(FONTS_DIR);
|
||||||
|
|
||||||
|
console.log('🚀 开始提取静态资源...\n');
|
||||||
|
console.log(`📂 工作目录: ${PUBLIC_DIR}\n`);
|
||||||
|
|
||||||
|
FILES_TO_PROCESS.forEach(filename => {
|
||||||
|
const filePath = path.join(PUBLIC_DIR, filename);
|
||||||
|
const backupPath = filePath + '.backup';
|
||||||
|
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
console.log(`⚠️ 文件不存在: ${filename}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建备份
|
||||||
|
if (!fs.existsSync(backupPath)) {
|
||||||
|
fs.copyFileSync(filePath, backupPath);
|
||||||
|
console.log(`📄 处理文件: ${filename} (已创建备份)`);
|
||||||
|
} else if (!KEEP_CURRENT) {
|
||||||
|
// 如果备份存在且未指定 --keep,从备份恢复
|
||||||
|
fs.copyFileSync(backupPath, filePath);
|
||||||
|
console.log(`📄 处理文件: ${filename} (从备份恢复)`);
|
||||||
|
} else {
|
||||||
|
console.log(`📄 处理文件: ${filename} (保持当前版本)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = fs.readFileSync(filePath, 'utf8');
|
||||||
|
const originalSize = content.length;
|
||||||
|
|
||||||
|
let imageCount = 0;
|
||||||
|
let cssCount = 0;
|
||||||
|
let fontCount = 0;
|
||||||
|
|
||||||
|
// 1. 提取 base64 图片
|
||||||
|
console.log(' 提取 base64 图片...');
|
||||||
|
content = content.replace(/url\s*\(\s*["']?(data:image\/([^;]+);base64,([^"')]+))["']?\s*\)/gi,
|
||||||
|
(match, dataUrl, imageType, base64Data) => {
|
||||||
|
imageCount++;
|
||||||
|
// 修复图片扩展名,处理 svg+xml 等情况
|
||||||
|
let ext = imageType.split('/').pop();
|
||||||
|
if (ext.includes('svg')) {
|
||||||
|
ext = 'svg';
|
||||||
|
} else if (ext.includes('+')) {
|
||||||
|
ext = ext.split('+')[0];
|
||||||
|
}
|
||||||
|
const imageName = `${filename.replace('.html', '')}_img_${imageCount}.${ext}`;
|
||||||
|
const imagePath = path.join(IMG_DIR, imageName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const buffer = Buffer.from(base64Data, 'base64');
|
||||||
|
fs.writeFileSync(imagePath, buffer);
|
||||||
|
return `url("/pages/assets/images/${imageName}")`;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(` ⚠️ 无法保存图片 ${imageName}:`, e.message);
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. 提取 img src 中的 base64 (有引号的)
|
||||||
|
content = content.replace(/<img([^>]*?)src\s*=\s*["'](data:image\/([^;]+);base64,([^"']+))["']([^>]*)>/gi,
|
||||||
|
(match, beforeAttrs, dataUrl, imageType, base64Data, afterAttrs) => {
|
||||||
|
imageCount++;
|
||||||
|
// 修复图片扩展名
|
||||||
|
let ext = imageType.split('/').pop();
|
||||||
|
if (ext.includes('svg')) {
|
||||||
|
ext = 'svg';
|
||||||
|
} else if (ext.includes('+')) {
|
||||||
|
ext = ext.split('+')[0];
|
||||||
|
}
|
||||||
|
const imageName = `${filename.replace('.html', '')}_inline_${imageCount}.${ext}`;
|
||||||
|
const imagePath = path.join(IMG_DIR, imageName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const buffer = Buffer.from(base64Data, 'base64');
|
||||||
|
fs.writeFileSync(imagePath, buffer);
|
||||||
|
// 确保属性间有正确的空格
|
||||||
|
const before = beforeAttrs ? ' ' + beforeAttrs.trim() : '';
|
||||||
|
const after = afterAttrs ? ' ' + afterAttrs.trim() : '';
|
||||||
|
return `<img${before} src="/pages/assets/images/${imageName}"${after}>`;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(` ⚠️ 无法保存图片 ${imageName}:`, e.message);
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. 提取 img src 中的 base64 (没有引号的,直到遇到空白字符或>)
|
||||||
|
content = content.replace(/<img([^>]*?)src\s*=\s*(data:image\/([^;\s>]+);base64,([^\s>]+))([^>]*)>/gi,
|
||||||
|
(match, beforeAttrs, dataUrl, imageType, base64Data, afterAttrs) => {
|
||||||
|
imageCount++;
|
||||||
|
// 修复图片扩展名
|
||||||
|
let ext = imageType.split('/').pop();
|
||||||
|
if (ext.includes('svg')) {
|
||||||
|
ext = 'svg';
|
||||||
|
} else if (ext.includes('+')) {
|
||||||
|
ext = ext.split('+')[0];
|
||||||
|
}
|
||||||
|
const imageName = `${filename.replace('.html', '')}_inline_${imageCount}.${ext}`;
|
||||||
|
const imagePath = path.join(IMG_DIR, imageName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const buffer = Buffer.from(base64Data, 'base64');
|
||||||
|
fs.writeFileSync(imagePath, buffer);
|
||||||
|
// 确保属性间有正确的空格
|
||||||
|
const before = beforeAttrs ? ' ' + beforeAttrs.trim() : '';
|
||||||
|
const after = afterAttrs ? ' ' + afterAttrs.trim() : '';
|
||||||
|
return `<img${before} src="/pages/assets/images/${imageName}"${after}>`;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(` ⚠️ 无法保存图片 ${imageName}:`, e.message);
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. 提取 CSS 变量中的 base64
|
||||||
|
content = content.replace(/--[^:]+:\s*url\s*\(\s*["']?(data:image\/([^;]+);base64,([^"')]+))["']?\s*\)/gi,
|
||||||
|
(match, dataUrl, imageType, base64Data) => {
|
||||||
|
imageCount++;
|
||||||
|
// 修复图片扩展名
|
||||||
|
let ext = imageType.split('/').pop();
|
||||||
|
if (ext.includes('svg')) {
|
||||||
|
ext = 'svg';
|
||||||
|
} else if (ext.includes('+')) {
|
||||||
|
ext = ext.split('+')[0];
|
||||||
|
}
|
||||||
|
const imageName = `${filename.replace('.html', '')}_var_${imageCount}.${ext}`;
|
||||||
|
const imagePath = path.join(IMG_DIR, imageName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const buffer = Buffer.from(base64Data, 'base64');
|
||||||
|
fs.writeFileSync(imagePath, buffer);
|
||||||
|
const varName = match.split(':')[0];
|
||||||
|
return `${varName}: url("/pages/assets/images/${imageName}")`;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(` ⚠️ 无法保存图片 ${imageName}:`, e.message);
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 5. 提取所有内联 CSS (style 标签)
|
||||||
|
console.log(' 提取内联 CSS...');
|
||||||
|
const cssLinks = []; // 用于收集所有 CSS 链接
|
||||||
|
const styleMatches = content.match(/<style[^>]*>([\s\S]*?)<\/style>/gi);
|
||||||
|
if (styleMatches && styleMatches.length > 0) {
|
||||||
|
styleMatches.forEach((styleTag, index) => {
|
||||||
|
const cssContent = styleTag.replace(/<\/?style[^>]*>/gi, '').trim();
|
||||||
|
// 提取所有 CSS,不管大小
|
||||||
|
if (cssContent.length > 0) {
|
||||||
|
cssCount++;
|
||||||
|
const cssName = `${filename.replace('.html', '')}_styles_${cssCount}.css`;
|
||||||
|
const cssPath = path.join(CSS_DIR, cssName);
|
||||||
|
fs.writeFileSync(cssPath, cssContent);
|
||||||
|
|
||||||
|
// 收集 CSS 链接,稍后会统一放到顶部
|
||||||
|
cssLinks.push(`<link rel="stylesheet" href="/pages/assets/css/${cssName}">`);
|
||||||
|
|
||||||
|
// 先删除原 style 标签
|
||||||
|
content = content.replace(styleTag, '');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 提取字体 (data:font)
|
||||||
|
console.log(' 提取字体文件...');
|
||||||
|
content = content.replace(/url\s*\(\s*["']?(data:font\/([^;]+);base64,([^"')]+))["']?\s*\)/gi,
|
||||||
|
(match, dataUrl, fontType, base64Data) => {
|
||||||
|
fontCount++;
|
||||||
|
const fontExt = fontType.includes('woff2') ? 'woff2' :
|
||||||
|
fontType.includes('woff') ? 'woff' :
|
||||||
|
fontType.includes('ttf') ? 'ttf' : 'font';
|
||||||
|
const fontName = `${filename.replace('.html', '')}_font_${fontCount}.${fontExt}`;
|
||||||
|
const fontPath = path.join(FONTS_DIR, fontName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const buffer = Buffer.from(base64Data, 'base64');
|
||||||
|
fs.writeFileSync(fontPath, buffer);
|
||||||
|
return `url("/pages/assets/fonts/${fontName}")`;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(` ⚠️ 无法保存字体 ${fontName}:`, e.message);
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 7. 提取 woff2 字体 (特殊处理)
|
||||||
|
content = content.replace(/url\s*\(\s*data:application\/font-woff2;charset=utf-8;base64,([^)]+)\)/gi,
|
||||||
|
(match, base64Data) => {
|
||||||
|
fontCount++;
|
||||||
|
const fontName = `${filename.replace('.html', '')}_font_${fontCount}.woff2`;
|
||||||
|
const fontPath = path.join(FONTS_DIR, fontName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const buffer = Buffer.from(base64Data, 'base64');
|
||||||
|
fs.writeFileSync(fontPath, buffer);
|
||||||
|
return `url("/pages/assets/fonts/${fontName}")`;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(` ⚠️ 无法保存字体 ${fontName}:`, e.message);
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 8. 去除 DOCTYPE 声明
|
||||||
|
console.log(' 去除 DOCTYPE 和 HTML 注释...');
|
||||||
|
let removedCount = 0;
|
||||||
|
if (content.match(/<!DOCTYPE[^>]*>/i)) {
|
||||||
|
content = content.replace(/<!DOCTYPE[^>]*>/gi, '');
|
||||||
|
removedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去除所有 HTML 注释(包括多行注释)
|
||||||
|
const commentCount = (content.match(/<!--[\s\S]*?-->/g) || []).length;
|
||||||
|
content = content.replace(/<!--[\s\S]*?-->/g, '');
|
||||||
|
removedCount += commentCount;
|
||||||
|
|
||||||
|
if (removedCount > 0) {
|
||||||
|
console.log(` - 已删除 DOCTYPE 和 ${commentCount} 个 HTML 注释`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. 去除所有 meta、title 和 link 标签
|
||||||
|
console.log(' 去除 meta、title 和 link 标签...');
|
||||||
|
const metaCount = (content.match(/<meta[^>]*>/gi) || []).length;
|
||||||
|
content = content.replace(/<meta[^>]*>/gi, '');
|
||||||
|
|
||||||
|
const titleCount = (content.match(/<title[^>]*>[\s\S]*?<\/title>/gi) || []).length;
|
||||||
|
content = content.replace(/<title[^>]*>[\s\S]*?<\/title>/gi, '');
|
||||||
|
|
||||||
|
// 去除 link 标签(canonical、icon、preload 等,但不包括我们生成的 stylesheet)
|
||||||
|
const linkCount = (content.match(/<link(?![^>]*rel=["']stylesheet["'])[^>]*>/gi) || []).length;
|
||||||
|
content = content.replace(/<link(?![^>]*rel=["']stylesheet["'])[^>]*>/gi, '');
|
||||||
|
|
||||||
|
if (metaCount > 0 || titleCount > 0 || linkCount > 0) {
|
||||||
|
console.log(` - 已删除 ${metaCount} 个 meta、${titleCount} 个 title 和 ${linkCount} 个 link 标签`);
|
||||||
|
} // 10. 去除所有 script 标签(包括内联和外部脚本)
|
||||||
|
console.log(' 去除 script 标签...');
|
||||||
|
const scriptCount = (content.match(/<script[^>]*>[\s\S]*?<\/script>/gi) || []).length;
|
||||||
|
content = content.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '');
|
||||||
|
if (scriptCount > 0) {
|
||||||
|
console.log(` - 已删除 ${scriptCount} 个 script 标签`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 11. 提取 body 内容并重组 HTML
|
||||||
|
console.log(' 重组 HTML 结构...');
|
||||||
|
const bodyMatch = content.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
||||||
|
if (bodyMatch) {
|
||||||
|
const bodyContent = bodyMatch[1];
|
||||||
|
|
||||||
|
// 重新构建 HTML:CSS 链接 + body 内容
|
||||||
|
let newContent = '';
|
||||||
|
|
||||||
|
// 将所有 CSS 链接放到顶部
|
||||||
|
if (cssLinks.length > 0) {
|
||||||
|
newContent = cssLinks.join('\n') + '\n\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加 body 内容
|
||||||
|
newContent += bodyContent;
|
||||||
|
|
||||||
|
content = newContent;
|
||||||
|
console.log(` - 已提取 body 内容并移除其他标签`);
|
||||||
|
} else {
|
||||||
|
// 如果找不到 body 标签,尝试去除 html、head、body 等标签
|
||||||
|
content = content.replace(/<\/?html[^>]*>/gi, '');
|
||||||
|
content = content.replace(/<head[^>]*>[\s\S]*?<\/head>/gi, '');
|
||||||
|
content = content.replace(/<\/?body[^>]*>/gi, '');
|
||||||
|
|
||||||
|
// 将 CSS 链接放到最前面
|
||||||
|
if (cssLinks.length > 0) {
|
||||||
|
content = cssLinks.join('\n') + '\n\n' + content;
|
||||||
|
}
|
||||||
|
console.log(` - 已去除 HTML 结构标签`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理可能遗留的 </head> 标签
|
||||||
|
content = content.replace(/<\/head>/gi, '');
|
||||||
|
|
||||||
|
// 清理多余的空行
|
||||||
|
content = content.replace(/\n\s*\n\s*\n/g, '\n\n');
|
||||||
|
|
||||||
|
// 11.5. 自动修复缺失的闭合标签(如 </li>)
|
||||||
|
console.log(' 自动修复缺失的闭合标签...');
|
||||||
|
let fixedTags = 0;
|
||||||
|
|
||||||
|
// 修复 <li> 标签:在遇到下一个 <li>、</ul>、</ol> 或其他块级标签前添加 </li>
|
||||||
|
// 使用 <li[\s>] 确保只匹配 <li 标签,不匹配 <link 等
|
||||||
|
content = content.replace(/<li([\s>][^>]*)>(?![\s\S]*?<\/li>)([\s\S]*?)(?=<li[\s>]|<\/ul|<\/ol)/g, (match, attrs, innerContent) => {
|
||||||
|
fixedTags++;
|
||||||
|
return `<li${attrs}>${innerContent}</li>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 修复最后一个 <li>(在 </ul> 或 </ol> 之前)
|
||||||
|
content = content.replace(/<li([\s>][^>]*)>([\s\S]*?)(?=<\/ul>|<\/ol>)/g, (match, attrs, innerContent) => {
|
||||||
|
// 检查是否已经有闭合标签
|
||||||
|
if (!innerContent.trim().endsWith('</li>') && !match.includes('</li>')) {
|
||||||
|
fixedTags++;
|
||||||
|
return `<li${attrs}>${innerContent}</li>`;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 修复 <p> 标签:在遇到下一个块级标签前添加 </p>
|
||||||
|
// 匹配 <p 开头的标签,查找到下一个块级标签(<p>、<div>、<section>等)之前
|
||||||
|
content = content.replace(/<p\s+([^>]*)>(?![\s\S]*?<\/p>)([\s\S]*?)(?=<(?:p\s|div|section|article|footer|header|main|aside|nav|ul|ol|li[\s>]|table|form|button|h[1-6]\s))/g, (match, attrs, innerContent) => {
|
||||||
|
// 确保不是已经闭合的
|
||||||
|
if (!innerContent.includes('</p>')) {
|
||||||
|
fixedTags++;
|
||||||
|
return `<p ${attrs}>${innerContent}</p>`;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fixedTags > 0) {
|
||||||
|
console.log(` - 已自动添加 ${fixedTags} 个缺失的闭合标签`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 12. 给所有HTML属性值加上引号
|
||||||
|
console.log(' 给HTML属性加上引号...');
|
||||||
|
|
||||||
|
// 首先,将所有跨行的标签内容合并到一行
|
||||||
|
// 处理开始标签跨行的情况(标签内有换行但还没遇到>)
|
||||||
|
// 使用更强大的正则,支持多次换行
|
||||||
|
let iterations = 0;
|
||||||
|
let prevLength = 0;
|
||||||
|
while (iterations < 10 && prevLength !== content.length) {
|
||||||
|
prevLength = content.length;
|
||||||
|
// 处理标签属性跨行
|
||||||
|
content = content.replace(/<([a-zA-Z][a-zA-Z0-9]*)\s+([^>]*?)\n\s*([^<]*?)>/gs, (match, tagName, beforeNewline, afterNewline) => {
|
||||||
|
return `<${tagName} ${beforeNewline} ${afterNewline}>`.replace(/\s+/g, ' ');
|
||||||
|
});
|
||||||
|
iterations++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 然后给属性加引号(单行情况)
|
||||||
|
// 匹配格式:属性名=值(值不以引号开头)
|
||||||
|
content = content.replace(/(<[^<>\/]+?\s+)([\w\-:]+)=([^"'\s>=][^\s>]*?)(\s|>)/g, (match, tagStart, attrName, attrValue, ending) => {
|
||||||
|
// 如果属性值为空,保持原样
|
||||||
|
if (!attrValue || attrValue === '') {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
// 给属性值加上双引号
|
||||||
|
return `${tagStart}${attrName}="${attrValue}"${ending}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 保存修改后的文件
|
||||||
|
const outputPath = path.join(PUBLIC_DIR, filename);
|
||||||
|
fs.writeFileSync(outputPath, content);
|
||||||
|
|
||||||
|
const newSize = content.length;
|
||||||
|
const reduction = ((originalSize - newSize) / originalSize * 100).toFixed(1);
|
||||||
|
|
||||||
|
console.log(` ✅ 完成:`);
|
||||||
|
console.log(` - 提取图片: ${imageCount} 个`);
|
||||||
|
console.log(` - 提取 CSS: ${cssCount} 个`);
|
||||||
|
console.log(` - 提取字体: ${fontCount} 个`);
|
||||||
|
console.log(` - 删除 meta: ${metaCount} 个`);
|
||||||
|
console.log(` - 删除 title: ${titleCount} 个`);
|
||||||
|
console.log(` - 删除 link: ${linkCount} 个`);
|
||||||
|
console.log(` - 删除 script: ${scriptCount} 个`);
|
||||||
|
console.log(` - 原始大小: ${(originalSize / 1024).toFixed(2)} KB`);
|
||||||
|
console.log(` - 新大小: ${(newSize / 1024).toFixed(2)} KB`);
|
||||||
|
console.log(` - 减少: ${reduction}%`);
|
||||||
|
|
||||||
|
// 验证生成的文件
|
||||||
|
if (imageCount > 0 || cssCount > 0 || fontCount > 0) {
|
||||||
|
console.log(` ℹ️ 提示: 请确保资源路径 /pages/assets/ 在服务器上可访问\n`);
|
||||||
|
} else {
|
||||||
|
console.log(` ℹ️ 未找到可提取的资源\n`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('✅ 资源提取完成!');
|
||||||
|
console.log('\n📁 资源文件位置:');
|
||||||
|
console.log(` - 图片: ${IMG_DIR}`);
|
||||||
|
console.log(` - CSS: ${CSS_DIR}`);
|
||||||
|
console.log(` - 字体: ${FONTS_DIR}`);
|
||||||
|
console.log('\n✨ 自动优化:');
|
||||||
|
console.log(' ✅ DOCTYPE 和 HTML 注释已删除');
|
||||||
|
console.log(' ✅ 所有 meta、title 和 link 标签已删除(保留生成的 stylesheet)');
|
||||||
|
console.log(' ✅ 所有 script 标签已删除');
|
||||||
|
console.log(' ✅ 所有 style 已提取到 CSS 文件');
|
||||||
|
console.log(' ✅ 只保留 body 内的内容');
|
||||||
|
console.log(' ✅ CSS 引用已放到顶部');
|
||||||
|
console.log(' ✅ 图片扩展名已修复(svg+xml → svg)');
|
||||||
|
console.log(' ✅ 使用绝对路径(/pages/assets/)');
|
||||||
|
console.log(' ✅ HTML 属性值已自动加上双引号');
|
||||||
|
console.log('\n💡 提示: 刷新浏览器测试页面,所有资源应该正常加载');
|
||||||
1
0000_gb_points_temp/extract.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
FAIL
|
||||||
24
0000_gb_points_temp/index.html
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
|
||||||
|
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/0000_gb_points_temp/fc.ico" type="image/x-icon" sizes="16x16">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</html>
|
||||||
8990
0000_gb_points_temp/package-lock.json
generated
Normal file
64
0000_gb_points_temp/package.json
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"name": "vue3-clean-architecture",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "run-p type-check build-only",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"test:unit": "vitest --environment jsdom --root src/",
|
||||||
|
"test:e2e": "start-server-and-test preview :4173 'cypress run --e2e'",
|
||||||
|
"test:e2e:dev": "start-server-and-test 'vite dev --port 4173' :4173 'cypress open --e2e'",
|
||||||
|
"build-only": "vite build",
|
||||||
|
"type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
|
||||||
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||||
|
"zip": "node zip.js",
|
||||||
|
"prod": "pnpm run build && pnpm run zip"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@headlessui/vue": "^1.7.23",
|
||||||
|
"@types/lodash": "^4.17.12",
|
||||||
|
"axios": "^1.7.7",
|
||||||
|
"bootstrap": "^5.3.3",
|
||||||
|
"install": "^0.13.0",
|
||||||
|
"jquery": "^3.7.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"mitt": "^3.0.1",
|
||||||
|
"pinia": "^2.2.2",
|
||||||
|
"socket.io-client": "^4.8.1",
|
||||||
|
"swiper": "^11.2.8",
|
||||||
|
"uuid": "^13.0.0",
|
||||||
|
"vue": "^3.2.45",
|
||||||
|
"vue-final-modal": "^4.5.5",
|
||||||
|
"vue-i18n": "^10.0.4",
|
||||||
|
"vue-router": "^4.1.6",
|
||||||
|
"vue-scrollto": "^2.20.0",
|
||||||
|
"ws": "^8.18.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rushstack/eslint-patch": "^1.1.4",
|
||||||
|
"@types/jquery": "^3.5.32",
|
||||||
|
"@types/jsdom": "^20.0.1",
|
||||||
|
"@types/node": "^18.11.12",
|
||||||
|
"@types/uuid": "^11.0.0",
|
||||||
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
|
"@vue/eslint-config-prettier": "^7.0.0",
|
||||||
|
"@vue/eslint-config-typescript": "^11.0.0",
|
||||||
|
"@vue/test-utils": "^2.2.6",
|
||||||
|
"@vue/tsconfig": "^0.1.3",
|
||||||
|
"archiver": "^7.0.1",
|
||||||
|
"cypress": "^12.0.2",
|
||||||
|
"eslint": "^8.22.0",
|
||||||
|
"eslint-plugin-cypress": "^2.12.1",
|
||||||
|
"eslint-plugin-vue": "^9.3.0",
|
||||||
|
"jsdom": "^20.0.3",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"prettier": "^2.7.1",
|
||||||
|
"start-server-and-test": "^1.15.2",
|
||||||
|
"typescript": "~4.7.4",
|
||||||
|
"vite": "^4.0.0",
|
||||||
|
"vitest": "^0.25.6",
|
||||||
|
"vue-tsc": "^1.0.12"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@10.6.2+sha512.47870716bea1572b53df34ad8647b42962bc790ce2bf4562ba0f643237d7302a3d6a8ecef9e4bdfc01d23af1969aa90485d4cebb0b9638fa5ef1daef656f6c1b"
|
||||||
|
}
|
||||||
5439
0000_gb_points_temp/pnpm-lock.yaml
generated
Normal file
BIN
0000_gb_points_temp/public/0000_en_points_porint5/fc.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
0000_gb_points_temp/public/img/1.jpg
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
0000_gb_points_temp/public/img/1.png
Normal file
|
After Width: | Height: | Size: 501 KiB |
BIN
0000_gb_points_temp/public/img/2.jpg
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
0000_gb_points_temp/public/img/3.jpg
Normal file
|
After Width: | Height: | Size: 89 KiB |
1
0000_gb_points_temp/public/img/default.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns='http://www.w3.org/2000/svg' width='750' height='500' fill='none' viewBox='0 0 27 18'><path fill='#E6E9EB' d='M0 3a3 3 0 0 1 3-3h21a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3z'/><path fill='#B9C4C9' d='M4 12h19v2H4z'/><rect width='4' height='4' x='4' y='4' fill='#fff' rx='1'/></svg>
|
||||||
|
After Width: | Height: | Size: 300 B |
BIN
0000_gb_points_temp/public/img/flag_png/ad.png
Normal file
|
After Width: | Height: | Size: 371 B |
BIN
0000_gb_points_temp/public/img/flag_png/ae.png
Normal file
|
After Width: | Height: | Size: 123 B |
BIN
0000_gb_points_temp/public/img/flag_png/af.png
Normal file
|
After Width: | Height: | Size: 655 B |
BIN
0000_gb_points_temp/public/img/flag_png/ag.png
Normal file
|
After Width: | Height: | Size: 466 B |
BIN
0000_gb_points_temp/public/img/flag_png/ai.png
Normal file
|
After Width: | Height: | Size: 449 B |
BIN
0000_gb_points_temp/public/img/flag_png/al.png
Normal file
|
After Width: | Height: | Size: 312 B |
BIN
0000_gb_points_temp/public/img/flag_png/am.png
Normal file
|
After Width: | Height: | Size: 114 B |
BIN
0000_gb_points_temp/public/img/flag_png/ao.png
Normal file
|
After Width: | Height: | Size: 331 B |
BIN
0000_gb_points_temp/public/img/flag_png/aq.png
Normal file
|
After Width: | Height: | Size: 290 B |
BIN
0000_gb_points_temp/public/img/flag_png/ar.png
Normal file
|
After Width: | Height: | Size: 214 B |
BIN
0000_gb_points_temp/public/img/flag_png/as.png
Normal file
|
After Width: | Height: | Size: 463 B |
BIN
0000_gb_points_temp/public/img/flag_png/at.png
Normal file
|
After Width: | Height: | Size: 93 B |
BIN
0000_gb_points_temp/public/img/flag_png/au.png
Normal file
|
After Width: | Height: | Size: 393 B |
BIN
0000_gb_points_temp/public/img/flag_png/aw.png
Normal file
|
After Width: | Height: | Size: 164 B |
BIN
0000_gb_points_temp/public/img/flag_png/ax.png
Normal file
|
After Width: | Height: | Size: 158 B |
BIN
0000_gb_points_temp/public/img/flag_png/az.png
Normal file
|
After Width: | Height: | Size: 169 B |
BIN
0000_gb_points_temp/public/img/flag_png/ba.png
Normal file
|
After Width: | Height: | Size: 246 B |
BIN
0000_gb_points_temp/public/img/flag_png/bb.png
Normal file
|
After Width: | Height: | Size: 247 B |
BIN
0000_gb_points_temp/public/img/flag_png/bd.png
Normal file
|
After Width: | Height: | Size: 197 B |
BIN
0000_gb_points_temp/public/img/flag_png/be.png
Normal file
|
After Width: | Height: | Size: 109 B |
BIN
0000_gb_points_temp/public/img/flag_png/bf.png
Normal file
|
After Width: | Height: | Size: 181 B |
BIN
0000_gb_points_temp/public/img/flag_png/bg.png
Normal file
|
After Width: | Height: | Size: 99 B |
BIN
0000_gb_points_temp/public/img/flag_png/bh.png
Normal file
|
After Width: | Height: | Size: 228 B |
BIN
0000_gb_points_temp/public/img/flag_png/bi.png
Normal file
|
After Width: | Height: | Size: 447 B |
BIN
0000_gb_points_temp/public/img/flag_png/bj.png
Normal file
|
After Width: | Height: | Size: 108 B |
BIN
0000_gb_points_temp/public/img/flag_png/bl.png
Normal file
|
After Width: | Height: | Size: 789 B |
BIN
0000_gb_points_temp/public/img/flag_png/bm.png
Normal file
|
After Width: | Height: | Size: 616 B |
BIN
0000_gb_points_temp/public/img/flag_png/bn.png
Normal file
|
After Width: | Height: | Size: 704 B |
BIN
0000_gb_points_temp/public/img/flag_png/bo.png
Normal file
|
After Width: | Height: | Size: 337 B |
BIN
0000_gb_points_temp/public/img/flag_png/bq.png
Normal file
|
After Width: | Height: | Size: 574 B |
BIN
0000_gb_points_temp/public/img/flag_png/br.png
Normal file
|
After Width: | Height: | Size: 452 B |
BIN
0000_gb_points_temp/public/img/flag_png/bs.png
Normal file
|
After Width: | Height: | Size: 198 B |
BIN
0000_gb_points_temp/public/img/flag_png/bt.png
Normal file
|
After Width: | Height: | Size: 609 B |
BIN
0000_gb_points_temp/public/img/flag_png/bv.png
Normal file
|
After Width: | Height: | Size: 164 B |
BIN
0000_gb_points_temp/public/img/flag_png/bw.png
Normal file
|
After Width: | Height: | Size: 119 B |
BIN
0000_gb_points_temp/public/img/flag_png/by.png
Normal file
|
After Width: | Height: | Size: 251 B |
BIN
0000_gb_points_temp/public/img/flag_png/bz.png
Normal file
|
After Width: | Height: | Size: 489 B |
BIN
0000_gb_points_temp/public/img/flag_png/ca.png
Normal file
|
After Width: | Height: | Size: 286 B |
BIN
0000_gb_points_temp/public/img/flag_png/cc.png
Normal file
|
After Width: | Height: | Size: 329 B |
BIN
0000_gb_points_temp/public/img/flag_png/cd.png
Normal file
|
After Width: | Height: | Size: 337 B |
BIN
0000_gb_points_temp/public/img/flag_png/cf.png
Normal file
|
After Width: | Height: | Size: 230 B |
BIN
0000_gb_points_temp/public/img/flag_png/cg.png
Normal file
|
After Width: | Height: | Size: 212 B |
BIN
0000_gb_points_temp/public/img/flag_png/ch.png
Normal file
|
After Width: | Height: | Size: 141 B |
BIN
0000_gb_points_temp/public/img/flag_png/ci.png
Normal file
|
After Width: | Height: | Size: 108 B |
BIN
0000_gb_points_temp/public/img/flag_png/ck.png
Normal file
|
After Width: | Height: | Size: 474 B |
BIN
0000_gb_points_temp/public/img/flag_png/cl.png
Normal file
|
After Width: | Height: | Size: 180 B |
BIN
0000_gb_points_temp/public/img/flag_png/cm.png
Normal file
|
After Width: | Height: | Size: 173 B |
BIN
0000_gb_points_temp/public/img/flag_png/cn.png
Normal file
|
After Width: | Height: | Size: 221 B |
BIN
0000_gb_points_temp/public/img/flag_png/co.png
Normal file
|
After Width: | Height: | Size: 114 B |
BIN
0000_gb_points_temp/public/img/flag_png/cr.png
Normal file
|
After Width: | Height: | Size: 225 B |
BIN
0000_gb_points_temp/public/img/flag_png/cu.png
Normal file
|
After Width: | Height: | Size: 272 B |
BIN
0000_gb_points_temp/public/img/flag_png/cv.png
Normal file
|
After Width: | Height: | Size: 175 B |
BIN
0000_gb_points_temp/public/img/flag_png/cw.png
Normal file
|
After Width: | Height: | Size: 199 B |
BIN
0000_gb_points_temp/public/img/flag_png/cx.png
Normal file
|
After Width: | Height: | Size: 387 B |
BIN
0000_gb_points_temp/public/img/flag_png/cy.png
Normal file
|
After Width: | Height: | Size: 355 B |
BIN
0000_gb_points_temp/public/img/flag_png/cz.png
Normal file
|
After Width: | Height: | Size: 269 B |
BIN
0000_gb_points_temp/public/img/flag_png/de.png
Normal file
|
After Width: | Height: | Size: 99 B |
BIN
0000_gb_points_temp/public/img/flag_png/dj.png
Normal file
|
After Width: | Height: | Size: 367 B |
BIN
0000_gb_points_temp/public/img/flag_png/dk.png
Normal file
|
After Width: | Height: | Size: 126 B |
BIN
0000_gb_points_temp/public/img/flag_png/dm.png
Normal file
|
After Width: | Height: | Size: 322 B |
BIN
0000_gb_points_temp/public/img/flag_png/do.png
Normal file
|
After Width: | Height: | Size: 264 B |
BIN
0000_gb_points_temp/public/img/flag_png/dz.png
Normal file
|
After Width: | Height: | Size: 291 B |
BIN
0000_gb_points_temp/public/img/flag_png/ec.png
Normal file
|
After Width: | Height: | Size: 500 B |
BIN
0000_gb_points_temp/public/img/flag_png/ee.png
Normal file
|
After Width: | Height: | Size: 114 B |
BIN
0000_gb_points_temp/public/img/flag_png/eg.png
Normal file
|
After Width: | Height: | Size: 182 B |
BIN
0000_gb_points_temp/public/img/flag_png/eh.png
Normal file
|
After Width: | Height: | Size: 292 B |
BIN
0000_gb_points_temp/public/img/flag_png/er.png
Normal file
|
After Width: | Height: | Size: 316 B |
BIN
0000_gb_points_temp/public/img/flag_png/es.png
Normal file
|
After Width: | Height: | Size: 381 B |
BIN
0000_gb_points_temp/public/img/flag_png/et.png
Normal file
|
After Width: | Height: | Size: 340 B |
BIN
0000_gb_points_temp/public/img/flag_png/eu.png
Normal file
|
After Width: | Height: | Size: 261 B |
BIN
0000_gb_points_temp/public/img/flag_png/fi.png
Normal file
|
After Width: | Height: | Size: 127 B |