This commit is contained in:
telangpu
2026-04-27 16:33:26 +08:00
parent c48009648e
commit 2fd1a741cf
437 changed files with 42017 additions and 0 deletions

5
0000_gb_points_temp/.env Normal file
View File

@@ -0,0 +1,5 @@
# 平台本地运行端口号
VITE_PORT = 8848
# 是否隐藏首页 隐藏 true 不隐藏 false 勿删除VITE_HIDE_HOME只需在.env文件配置
VITE_HIDE_HOME = false

View 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"

View 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"

View 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
View 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?

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}

View 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
```
```

View 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不再需要独立的多语言配置

View 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

View 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'
}
})

View 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!')
})
})

View File

@@ -0,0 +1,10 @@
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": ["./**/*", "../support/**/*"],
"compilerOptions": {
"isolatedModules": false,
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress"]
}
}

View 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"
}

View 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 {}

View 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
View 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;
}

View 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];
// 重新构建 HTMLCSS 链接 + 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💡 提示: 刷新浏览器测试页面,所有资源应该正常加载');

View File

@@ -0,0 +1 @@
FAIL

View 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

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 B

Some files were not shown because too many files have changed in this diff Show More