会员个人中心页面初步完成
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const CSS_DIR = path.join(__dirname, '../common/style/memberInfo/pages');
|
||||
const STYLE_PREFIX = '@/common/style/memberInfo/pages';
|
||||
|
||||
const PAGES = {
|
||||
booking: {
|
||||
cssFile: 'booking-pixso.css',
|
||||
pageCssFile: 'booking-page.css',
|
||||
rootClass: 'Pixso-frame-2_964',
|
||||
backFrameClass: 'Pixso-frame-2_969',
|
||||
extraData: `,
|
||||
activeTab: 'ongoing'`,
|
||||
extraMethods: `,
|
||||
switchTab(tab) {
|
||||
this.activeTab = tab
|
||||
},
|
||||
cancelBooking() {
|
||||
uni.showModal({
|
||||
title: '取消预约',
|
||||
content: '确定要取消该预约吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showToast({ title: '已取消', icon: 'success' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}`
|
||||
},
|
||||
memberCard: {
|
||||
cssFile: 'member-card-pixso.css',
|
||||
pageCssFile: 'member-card-page.css',
|
||||
rootClass: 'Pixso-frame-2_877',
|
||||
backFrameClass: 'Pixso-frame-2_882',
|
||||
extraData: `,
|
||||
activeFilter: 'all'`,
|
||||
extraMethods: `,
|
||||
switchFilter(filter) {
|
||||
this.activeFilter = filter
|
||||
},
|
||||
renewCard() {
|
||||
uni.showToast({ title: '续费功能开发中', icon: 'none' })
|
||||
}`
|
||||
},
|
||||
userInfo: {
|
||||
cssFile: 'user-info-pixso.css',
|
||||
pageCssFile: 'user-info-page.css',
|
||||
rootClass: 'Pixso-frame-2_791',
|
||||
backFrameClass: 'Pixso-frame-2_796',
|
||||
navWrapperClass: 'stroke-wrapper-2_795',
|
||||
navContentClass: 'frame-content-2_795',
|
||||
navTitleClass: 'Pixso-paragraph-2_799',
|
||||
saveFrameClass: 'Pixso-frame-2_800',
|
||||
extraData: `,
|
||||
name: '张小芳',
|
||||
phone: '138****6789',
|
||||
gender: 'female',
|
||||
birthday: '1995年06月15日',
|
||||
height: '165',
|
||||
weight: '63.5',
|
||||
fitnessGoals: ['增肌']`,
|
||||
extraMethods: `,
|
||||
handleSave() {
|
||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
||||
},
|
||||
changeAvatar() {
|
||||
uni.showToast({ title: '更换头像功能开发中', icon: 'none' })
|
||||
},
|
||||
rebindPhone() {
|
||||
uni.showToast({ title: '换绑功能开发中', icon: 'none' })
|
||||
},
|
||||
selectGender(gender) {
|
||||
this.gender = gender
|
||||
},
|
||||
toggleGoal(goal) {
|
||||
const index = this.fitnessGoals.indexOf(goal)
|
||||
if (index >= 0) {
|
||||
this.fitnessGoals.splice(index, 1)
|
||||
} else {
|
||||
this.fitnessGoals.push(goal)
|
||||
}
|
||||
},
|
||||
isGoalSelected(goal) {
|
||||
return this.fitnessGoals.includes(goal)
|
||||
}`
|
||||
}
|
||||
};
|
||||
|
||||
function alignCss(css) {
|
||||
let result = css.replace(/@import\s+['"]\.\/base\.css['"];\s*/g, '');
|
||||
const replacements = [
|
||||
['url(@/assets/images/', 'url(@/static/images/'],
|
||||
['box-shadow: 0px 8px 16px 0px rgba(255, 107, 53, 0.25098039215686274)', 'box-shadow: var(--shadow-orange-glow)'],
|
||||
['background-color: rgba(255, 255, 255, 1)', 'background-color: var(--bg-white)'],
|
||||
['color: rgba(255, 255, 255, 1)', 'color: var(--text-inverse)'],
|
||||
['border-color: rgba(255, 255, 255, 1)', 'border-color: var(--bg-white)'],
|
||||
['rgba(11, 43, 75, 1)', 'var(--primary-dark)'],
|
||||
['rgba(26, 74, 111, 1)', 'var(--primary-deep)'],
|
||||
['rgba(255, 107, 53, 1)', 'var(--accent-orange)'],
|
||||
['rgba(255, 140, 90, 1)', 'var(--accent-orange-light)'],
|
||||
['rgba(249, 250, 254, 1)', 'var(--bg-light)'],
|
||||
['rgba(30, 42, 58, 1)', 'var(--text-dark)'],
|
||||
['rgba(94, 111, 141, 1)', 'var(--text-muted)'],
|
||||
['rgba(138, 153, 180, 1)', 'var(--text-light)'],
|
||||
['rgba(233, 237, 242, 1)', 'var(--border-light)'],
|
||||
['rgba(46, 204, 113, 1)', 'var(--success-green)'],
|
||||
['rgba(231, 76, 60, 1)', 'var(--error-red)'],
|
||||
['height: 900px', 'height: auto;\n min-height: 100%'],
|
||||
['height: 2300px', 'height: auto;\n min-height: 100%'],
|
||||
['height: 1901px', 'height: auto']
|
||||
];
|
||||
for (const [from, to] of replacements) {
|
||||
result = result.split(from).join(to);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function convertTemplate(template, config) {
|
||||
let result = template.replace(/\r\n/g, '\n');
|
||||
|
||||
result = result.replace(/<div\b/g, '<view');
|
||||
result = result.replace(/<\/div>/g, '</view>');
|
||||
result = result.replace(/<p\b/g, '<text');
|
||||
result = result.replace(/<\/p>/g, '</text>');
|
||||
result = result.replace(/\s+id="[^"]*"/g, '');
|
||||
result = result.replace(/\{\{\s*"([^"]*?)"\s*\}\}/gs, '$1');
|
||||
result = result.replace(/\{\{\s*'([^']*?)'\s*\}\}/gs, '$1');
|
||||
result = result.replace(
|
||||
'<view class="scroll-container">',
|
||||
'<view class="scroll-container theme-light">'
|
||||
);
|
||||
result = result.replace(/>\s*9:41\s*</g, '>{{ statusBarTime }}<');
|
||||
|
||||
if (config.backFrameClass) {
|
||||
result = result.replace(
|
||||
new RegExp(`class="${config.backFrameClass}"`, 'g'),
|
||||
`class="${config.backFrameClass} nav-back" @tap.stop="goBack"`
|
||||
);
|
||||
}
|
||||
|
||||
if (config.navTitleClass) {
|
||||
result = result.replace(
|
||||
new RegExp(`class="${config.navTitleClass}"`, 'g'),
|
||||
`class="${config.navTitleClass} nav-title"`
|
||||
);
|
||||
}
|
||||
|
||||
if (config.saveFrameClass) {
|
||||
result = result.replace(
|
||||
new RegExp(`class="${config.saveFrameClass}"`, 'g'),
|
||||
`class="${config.saveFrameClass}" @tap="handleSave"`
|
||||
);
|
||||
}
|
||||
|
||||
if (config.bindings) {
|
||||
for (const { from, to } of config.bindings) {
|
||||
result = result.replace(from, to);
|
||||
}
|
||||
}
|
||||
|
||||
return result.trim();
|
||||
}
|
||||
|
||||
function buildPageCss(config) {
|
||||
return `@import './sub-page-base.css';
|
||||
|
||||
.${config.rootClass} {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
function convertPage(pageName, config) {
|
||||
const vuePath = path.join(__dirname, `../pages/memberInfo/${pageName}.vue`);
|
||||
const cssPath = path.join(CSS_DIR, config.cssFile);
|
||||
const pageCssPath = path.join(CSS_DIR, config.pageCssFile);
|
||||
|
||||
const content = fs.readFileSync(vuePath, 'utf8').replace(/\r\n/g, '\n');
|
||||
const templateMatch = content.match(/<template>([\s\S]*?)<\/template>/);
|
||||
const styleMatch = content.match(/<style>([\s\S]*?)<\/style>/);
|
||||
|
||||
if (!templateMatch || !styleMatch) {
|
||||
throw new Error(`Could not parse ${pageName}.vue`);
|
||||
}
|
||||
|
||||
const template = convertTemplate(templateMatch[1], config);
|
||||
const css = alignCss(styleMatch[1].trim());
|
||||
const pageCss = buildPageCss(config);
|
||||
|
||||
const extraMethods = (config.extraMethods || '').replace(/^\s*,\s*/, '');
|
||||
const extraDataBlock = (config.extraData || '').replace(/^\s*,\s*/, '');
|
||||
const dataBody = pageName === 'userInfo'
|
||||
? '...userInfoMock'
|
||||
: extraDataBlock;
|
||||
|
||||
const vueOutput = `<template>
|
||||
${template}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { statusBarTimeMixin, subPageMixin } from '@/common/memberInfo/mixins.js'${pageName === 'userInfo' ? "\nimport { userInfoMock } from '@/common/memberInfo/mockData.js'" : ''}
|
||||
|
||||
export default {
|
||||
mixins: [statusBarTimeMixin, subPageMixin],
|
||||
data() {
|
||||
return {
|
||||
${dataBody}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
${extraMethods}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import '${STYLE_PREFIX}/${config.pageCssFile}';
|
||||
@import '${STYLE_PREFIX}/${config.cssFile}';
|
||||
</style>
|
||||
`;
|
||||
|
||||
fs.writeFileSync(cssPath, css + '\n', 'utf8');
|
||||
fs.writeFileSync(pageCssPath, pageCss, 'utf8');
|
||||
fs.writeFileSync(vuePath, vueOutput, 'utf8');
|
||||
console.log(`converted ${pageName}.vue -> ${config.cssFile}`);
|
||||
}
|
||||
|
||||
const target = process.argv[2];
|
||||
const entries = target
|
||||
? [[target, PAGES[target]]].filter(([, config]) => config)
|
||||
: Object.entries(PAGES);
|
||||
|
||||
for (const [pageName, config] of entries) {
|
||||
convertPage(pageName, config);
|
||||
}
|
||||
|
||||
console.log('done');
|
||||
@@ -0,0 +1,293 @@
|
||||
/**
|
||||
|
||||
* 微信小程序组件/页面 WXSS 不支持本地 background-image,
|
||||
|
||||
* 将 /static/images/ 背景图改为 <image> 标签,并清理 CSS。
|
||||
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
const path = require('path');
|
||||
|
||||
|
||||
|
||||
const root = path.join(__dirname, '..');
|
||||
|
||||
|
||||
|
||||
const COMPONENT_MAP = {
|
||||
|
||||
MemberInfoStatusBar: 'member-info-status-bar.css',
|
||||
|
||||
MemberInfoHeader: 'member-info-header.css',
|
||||
|
||||
MemberInfoMemberCard: 'member-info-member-card.css',
|
||||
|
||||
MemberInfoQuickActions: 'member-info-quick-actions.css',
|
||||
|
||||
MemberInfoBookingList: 'member-info-booking-list.css',
|
||||
|
||||
MemberInfoCheckInList: 'member-info-check-in-list.css',
|
||||
|
||||
MemberInfoBodyReport: 'member-info-body-report.css',
|
||||
|
||||
MemberInfoCouponPoints: 'member-info-coupon-points.css',
|
||||
|
||||
MemberInfoReferral: 'member-info-referral.css',
|
||||
|
||||
MemberInfoSettings: 'member-info-settings.css',
|
||||
|
||||
MemberInfoLogout: 'member-info-logout.css'
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
const PAGE_MAP = {
|
||||
|
||||
booking: ['booking-pixso.css'],
|
||||
|
||||
memberCard: ['member-card-pixso.css'],
|
||||
|
||||
userInfo: ['user-info-pixso.css']
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
function extractBgImages(css) {
|
||||
|
||||
const map = new Map();
|
||||
|
||||
const ruleRe = /^\.([a-zA-Z0-9_-]+)\s*\{([^}]*)\}/gm;
|
||||
|
||||
let match;
|
||||
|
||||
while ((match = ruleRe.exec(css)) !== null) {
|
||||
|
||||
const className = match[1];
|
||||
|
||||
const body = match[2];
|
||||
|
||||
const urlMatch = body.match(/background-image:\s*url\(\/static\/images\/([^)]+)\)/);
|
||||
|
||||
if (urlMatch) {
|
||||
|
||||
map.set(className, `/static/images/${urlMatch[1]}`);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return map;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function ensureImageDisplay(css, className) {
|
||||
|
||||
const ruleRe = new RegExp(`(\\.${className.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*\\{)([^}]*)(\\})`);
|
||||
|
||||
return css.replace(ruleRe, (full, head, body, tail) => {
|
||||
|
||||
if (/display:\s*block/.test(body)) return full;
|
||||
|
||||
return `${head}${body.trim()}\n display: block;\n${tail}`;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function getMode(className) {
|
||||
|
||||
if (/avatar|banner|photo|card-preview|AC\d/i.test(className)) return 'aspectFill';
|
||||
|
||||
return 'aspectFit';
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function replaceViewWithImage(template, className, src) {
|
||||
|
||||
const mode = getMode(className);
|
||||
|
||||
const imageTag = `<image class="${className}" src="${src}" mode="${mode}" />`;
|
||||
|
||||
const escaped = className.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
|
||||
|
||||
|
||||
const patterns = [
|
||||
|
||||
new RegExp(`<view\\s+class="${escaped}"\\s*></view>`, 'g'),
|
||||
|
||||
new RegExp(`<view\\s+class="${escaped}"\\s*/>`, 'g'),
|
||||
|
||||
new RegExp(`<view\\s+class="${escaped}"\\s*>\\s*</view>`, 'g'),
|
||||
|
||||
new RegExp(`<view\\s+\\n\\s*class="${escaped}"\\s*\\n\\s*></view>`, 'g'),
|
||||
|
||||
new RegExp(`<view\\s+\\n\\s*class="${escaped}"\\s*\\n\\s*/>`, 'g'),
|
||||
|
||||
new RegExp(`<view\\s+[^>]*class="${escaped}"[^>]*>\\s*</view>`, 'g')
|
||||
|
||||
];
|
||||
|
||||
|
||||
|
||||
let result = template;
|
||||
|
||||
for (const re of patterns) {
|
||||
|
||||
result = result.replace(re, imageTag);
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function stripAllLocalBgFromCss(css) {
|
||||
|
||||
let next = css;
|
||||
|
||||
next = next.replace(/background-image:\s*url\(\/static\/images\/[^)]+\);/g, '');
|
||||
|
||||
next = next.replace(/\n\s*background-size:\s*100%\s*100%;/g, '');
|
||||
|
||||
next = next.replace(/\n\s*background-repeat:\s*no-repeat;/g, '');
|
||||
|
||||
return next;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function convertPair(vuePath, cssPath) {
|
||||
|
||||
if (!fs.existsSync(vuePath) || !fs.existsSync(cssPath)) return;
|
||||
|
||||
|
||||
|
||||
let css = fs.readFileSync(cssPath, 'utf8');
|
||||
|
||||
const hadLocalBg = /background-image:\s*url\(\/static\/images\//.test(css);
|
||||
|
||||
if (!hadLocalBg) return;
|
||||
|
||||
|
||||
|
||||
const bgMap = extractBgImages(css);
|
||||
|
||||
let template = fs.readFileSync(vuePath, 'utf8');
|
||||
|
||||
const templateMatch = template.match(/<template>([\s\S]*?)<\/template>/);
|
||||
|
||||
if (!templateMatch) {
|
||||
|
||||
css = stripAllLocalBgFromCss(css);
|
||||
|
||||
fs.writeFileSync(cssPath, css, 'utf8');
|
||||
|
||||
console.log('css-only (no template)', path.relative(root, cssPath));
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
let inner = templateMatch[1];
|
||||
|
||||
let changed = false;
|
||||
|
||||
|
||||
|
||||
for (const [className, src] of bgMap.entries()) {
|
||||
|
||||
const before = inner;
|
||||
|
||||
inner = replaceViewWithImage(inner, className, src);
|
||||
|
||||
if (inner !== before) {
|
||||
|
||||
changed = true;
|
||||
|
||||
css = ensureImageDisplay(css, className);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
css = stripAllLocalBgFromCss(css);
|
||||
|
||||
|
||||
|
||||
if (changed) {
|
||||
|
||||
template = template.replace(templateMatch[1], inner);
|
||||
|
||||
fs.writeFileSync(vuePath, template, 'utf8');
|
||||
|
||||
}
|
||||
|
||||
fs.writeFileSync(cssPath, css, 'utf8');
|
||||
|
||||
console.log(
|
||||
|
||||
changed ? 'converted' : 'css-only',
|
||||
|
||||
path.relative(root, vuePath),
|
||||
|
||||
`(${bgMap.size} rules stripped)`
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
for (const [name, cssFile] of Object.entries(COMPONENT_MAP)) {
|
||||
|
||||
convertPair(
|
||||
|
||||
path.join(root, 'components/memberInfo', `${name}.vue`),
|
||||
|
||||
path.join(root, 'common/style/memberInfo', cssFile)
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
for (const [page, cssFiles] of Object.entries(PAGE_MAP)) {
|
||||
|
||||
for (const cssFile of cssFiles) {
|
||||
|
||||
convertPair(
|
||||
|
||||
path.join(root, 'pages/memberInfo', `${page}.vue`),
|
||||
|
||||
path.join(root, 'common/style/memberInfo/pages', cssFile)
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
console.log('done');
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const dir = path.join(__dirname, '../components/memberInfo');
|
||||
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.vue'));
|
||||
|
||||
for (const file of files) {
|
||||
let content = fs.readFileSync(path.join(dir, file), 'utf8');
|
||||
content = content.replace(
|
||||
/export default \{\n\s+options:/,
|
||||
'export default {\n options:'
|
||||
);
|
||||
fs.writeFileSync(path.join(dir, file), content, 'utf8');
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const dir = path.join(__dirname, '../components/memberInfo');
|
||||
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.vue'));
|
||||
|
||||
const optionsBlock = ` options: {
|
||||
virtualHost: true,
|
||||
styleIsolation: 'apply-shared'
|
||||
},`;
|
||||
|
||||
for (const file of files) {
|
||||
let content = fs.readFileSync(path.join(dir, file), 'utf8');
|
||||
content = content.replace(/\r\n/g, '\n');
|
||||
content = content.replace(/\n<style>[\s\S]*?<\/style>\n?/g, '\n');
|
||||
|
||||
if (content.includes('options:')) {
|
||||
content = content.replace(/options:\s*\{[\s\S]*?\},?/m, optionsBlock);
|
||||
} else {
|
||||
content = content.replace(/export default \{\n/, `export default {\n${optionsBlock}\n`);
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.join(dir, file), content.endsWith('\n') ? content : content + '\n', 'utf8');
|
||||
console.log('updated', file);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const root = path.join(__dirname, '..');
|
||||
const styleDir = path.join(root, 'common/style/memberInfo');
|
||||
const pagesDir = path.join(styleDir, 'pages');
|
||||
const componentsDir = path.join(root, 'components/memberInfo');
|
||||
|
||||
const cssMap = {
|
||||
MemberInfoStatusBar: 'member-info-status-bar.css',
|
||||
MemberInfoHeader: 'member-info-header.css',
|
||||
MemberInfoMemberCard: 'member-info-member-card.css',
|
||||
MemberInfoQuickActions: 'member-info-quick-actions.css',
|
||||
MemberInfoBookingList: 'member-info-booking-list.css',
|
||||
MemberInfoCheckInList: 'member-info-check-in-list.css',
|
||||
MemberInfoBodyReport: 'member-info-body-report.css',
|
||||
MemberInfoCouponPoints: 'member-info-coupon-points.css',
|
||||
MemberInfoReferral: 'member-info-referral.css',
|
||||
MemberInfoSettings: 'member-info-settings.css',
|
||||
MemberInfoLogout: 'member-info-logout.css'
|
||||
};
|
||||
|
||||
function stripCssImports(filePath) {
|
||||
let css = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
||||
const next = css.replace(/^@import\s+[^;]+;\s*\n/gm, '');
|
||||
if (next !== css) {
|
||||
fs.writeFileSync(filePath, next.replace(/^\n+/, ''), 'utf8');
|
||||
console.log('stripped imports:', path.relative(root, filePath));
|
||||
}
|
||||
}
|
||||
|
||||
// 纯样式文件:去掉嵌套 @import(小程序只认 vue 里第一层 @import)
|
||||
for (const name of fs.readdirSync(styleDir)) {
|
||||
if (!name.endsWith('.css') || name === 'member-info-all.css') continue;
|
||||
stripCssImports(path.join(styleDir, name));
|
||||
}
|
||||
for (const name of fs.readdirSync(pagesDir)) {
|
||||
if (!name.endsWith('.css')) continue;
|
||||
stripCssImports(path.join(pagesDir, name));
|
||||
}
|
||||
|
||||
const componentStyleBlock = (cssFile) => `<style>
|
||||
@import '@/common/style/base.css';
|
||||
@import '@/common/style/memberInfo/member-info-component-reset.css';
|
||||
@import '@/common/style/memberInfo/${cssFile}';
|
||||
</style>
|
||||
`;
|
||||
|
||||
for (const [name, cssFile] of Object.entries(cssMap)) {
|
||||
const filePath = path.join(componentsDir, `${name}.vue`);
|
||||
let content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
||||
content = content.replace(/\n<style>[\s\S]*?<\/style>\n?/g, '\n');
|
||||
content = content.trimEnd() + '\n' + componentStyleBlock(cssFile);
|
||||
fs.writeFileSync(filePath, content, 'utf8');
|
||||
console.log('updated component:', name);
|
||||
}
|
||||
|
||||
const memberInfoVue = path.join(root, 'pages/memberInfo/memberInfo.vue');
|
||||
let memberInfoContent = fs.readFileSync(memberInfoVue, 'utf8').replace(/\r\n/g, '\n');
|
||||
memberInfoContent = memberInfoContent.replace(
|
||||
/<style>[\s\S]*?<\/style>/,
|
||||
`<style>
|
||||
@import '@/common/style/base.css';
|
||||
@import '@/common/style/memberInfo/member-info-page.css';
|
||||
</style>`
|
||||
);
|
||||
fs.writeFileSync(memberInfoVue, memberInfoContent, 'utf8');
|
||||
console.log('updated memberInfo.vue');
|
||||
|
||||
const subPages = {
|
||||
booking: ['booking-page.css', 'booking-pixso.css'],
|
||||
memberCard: ['member-card-page.css', 'member-card-pixso.css'],
|
||||
userInfo: ['user-info-page.css', 'user-info-pixso.css']
|
||||
};
|
||||
|
||||
for (const [page, files] of Object.entries(subPages)) {
|
||||
const vuePath = path.join(root, `pages/memberInfo/${page}.vue`);
|
||||
let content = fs.readFileSync(vuePath, 'utf8').replace(/\r\n/g, '\n');
|
||||
const imports = [
|
||||
"@import '@/common/style/base.css';",
|
||||
"@import '@/common/style/memberInfo/pages/page-reset.css';",
|
||||
"@import '@/common/style/memberInfo/pages/sub-page-base.css';",
|
||||
...files.map((f) => `@import '@/common/style/memberInfo/pages/${f}';`)
|
||||
].join('\n');
|
||||
content = content.replace(/<style>[\s\S]*?<\/style>/, `<style>\n${imports}\n</style>`);
|
||||
fs.writeFileSync(vuePath, content, 'utf8');
|
||||
console.log('updated', `${page}.vue`);
|
||||
}
|
||||
|
||||
console.log('done');
|
||||
@@ -0,0 +1,27 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const styleDir = path.join(__dirname, '../common/style');
|
||||
|
||||
function walk(dir) {
|
||||
for (const name of fs.readdirSync(dir)) {
|
||||
const full = path.join(dir, name);
|
||||
if (fs.statSync(full).isDirectory()) {
|
||||
walk(full);
|
||||
continue;
|
||||
}
|
||||
if (!name.endsWith('.css')) continue;
|
||||
let css = fs.readFileSync(full, 'utf8');
|
||||
const next = css
|
||||
.replace(/url\(@\/static\/images\//g, 'url(/static/images/')
|
||||
.replace(/url\("@\/static\/images\//g, 'url("/static/images/')
|
||||
.replace(/url\('@\/static\/images\//g, "url('/static/images/");
|
||||
if (next !== css) {
|
||||
fs.writeFileSync(full, next, 'utf8');
|
||||
console.log('fixed', path.relative(styleDir, full));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
walk(styleDir);
|
||||
console.log('done');
|
||||
@@ -0,0 +1,77 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const styleDir = path.join(__dirname, '../common/style/memberInfo');
|
||||
|
||||
/** 硬编码 px → 统一字号变量(从大到小替换,避免误匹配) */
|
||||
const fontSizePxMap = [
|
||||
['32px', 'var(--font-size-5xl)'],
|
||||
['28px', 'var(--font-size-4xl)'],
|
||||
['24px', 'var(--font-size-3xl)'],
|
||||
['22px', 'var(--font-size-2xl)'],
|
||||
['20px', 'var(--font-size-xl)'],
|
||||
['18px', 'var(--font-size-lg)'],
|
||||
['17px', 'var(--font-size-md)'],
|
||||
['16px', 'var(--font-size-md)'],
|
||||
['15px', 'var(--font-size-base)'],
|
||||
['14px', 'var(--font-size-base)'],
|
||||
['13px', 'var(--font-size-base)'],
|
||||
['12px', 'var(--font-size-sm)'],
|
||||
['11px', 'var(--font-size-xs)'],
|
||||
['10px', 'var(--font-size-xs)']
|
||||
];
|
||||
|
||||
function walk(dir) {
|
||||
for (const name of fs.readdirSync(dir)) {
|
||||
const full = path.join(dir, name);
|
||||
if (fs.statSync(full).isDirectory()) {
|
||||
walk(full);
|
||||
continue;
|
||||
}
|
||||
if (!name.endsWith('.css')) continue;
|
||||
normalize(full);
|
||||
}
|
||||
}
|
||||
|
||||
function normalize(filePath) {
|
||||
let css = fs.readFileSync(filePath, 'utf8');
|
||||
let next = css;
|
||||
|
||||
const fontFamilyMap = [
|
||||
['"Noto Sans SC-Bold"', 'var(--font-family)'],
|
||||
['"Noto Sans SC-Medium"', 'var(--font-family)'],
|
||||
['"Noto Sans SC-Regular"', 'var(--font-family)'],
|
||||
['"Noto Sans SC-SemiBold"', 'var(--font-family)'],
|
||||
['"Inter-Semi Bold"', 'var(--font-family)'],
|
||||
['"Inter-Bold"', 'var(--font-family)'],
|
||||
['"Inter-Regular"', 'var(--font-family)']
|
||||
];
|
||||
for (const [from, to] of fontFamilyMap) {
|
||||
next = next.split(`font-family: ${from}`).join(`font-family: ${to}`);
|
||||
}
|
||||
|
||||
const weightMap = [
|
||||
['font-weight: Bold', 'font-weight: 700'],
|
||||
['font-weight: Medium', 'font-weight: 500'],
|
||||
['font-weight: Regular', 'font-weight: 400'],
|
||||
['font-weight: SemiBold', 'font-weight: 600'],
|
||||
['font-weight: Semi Bold', 'font-weight: 600']
|
||||
];
|
||||
for (const [from, to] of weightMap) {
|
||||
next = next.split(from).join(to);
|
||||
}
|
||||
|
||||
next = next.replace(/white-space:\s*pre;/g, 'white-space: nowrap;');
|
||||
|
||||
for (const [px, token] of fontSizePxMap) {
|
||||
next = next.replace(new RegExp(`font-size:\\s*${px.replace('.', '\\.')};`, 'g'), `font-size: ${token};`);
|
||||
}
|
||||
|
||||
if (next !== css) {
|
||||
fs.writeFileSync(filePath, next, 'utf8');
|
||||
console.log('normalized', path.relative(styleDir, filePath));
|
||||
}
|
||||
}
|
||||
|
||||
walk(styleDir);
|
||||
console.log('done');
|
||||
@@ -0,0 +1,61 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const componentsDir = path.join(__dirname, '../components/memberInfo');
|
||||
|
||||
const cssMap = {
|
||||
MemberInfoStatusBar: 'member-info-status-bar.css',
|
||||
MemberInfoHeader: 'member-info-header.css',
|
||||
MemberInfoMemberCard: 'member-info-member-card.css',
|
||||
MemberInfoQuickActions: 'member-info-quick-actions.css',
|
||||
MemberInfoBookingList: 'member-info-booking-list.css',
|
||||
MemberInfoCheckInList: 'member-info-check-in-list.css',
|
||||
MemberInfoBodyReport: 'member-info-body-report.css',
|
||||
MemberInfoCouponPoints: 'member-info-coupon-points.css',
|
||||
MemberInfoReferral: 'member-info-referral.css',
|
||||
MemberInfoSettings: 'member-info-settings.css',
|
||||
MemberInfoLogout: 'member-info-logout.css'
|
||||
};
|
||||
|
||||
for (const [name, cssFile] of Object.entries(cssMap)) {
|
||||
const filePath = path.join(componentsDir, `${name}.vue`);
|
||||
let content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
||||
|
||||
content = content.replace(/\n<style>[\s\S]*?<\/style>\n?/g, '\n');
|
||||
|
||||
const styleBlock = `\n<style>
|
||||
@import '@/common/style/base.css';
|
||||
@import '@/common/style/memberInfo/member-info-component-reset.css';
|
||||
@import '@/common/style/memberInfo/${cssFile}';
|
||||
</style>\n`;
|
||||
|
||||
if (!content.trimEnd().endsWith('</script>')) {
|
||||
console.warn('skip unexpected format:', name);
|
||||
continue;
|
||||
}
|
||||
|
||||
content = content.trimEnd() + styleBlock;
|
||||
fs.writeFileSync(filePath, content, 'utf8');
|
||||
console.log('restored styles:', name);
|
||||
}
|
||||
|
||||
const pageFiles = [
|
||||
path.join(__dirname, '../pages/memberInfo/memberInfo.vue'),
|
||||
path.join(__dirname, '../pages/memberInfo/userInfo.vue')
|
||||
];
|
||||
|
||||
for (const pagePath of pageFiles) {
|
||||
let content = fs.readFileSync(pagePath, 'utf8');
|
||||
content = content.replace(
|
||||
"@import '@/common/style/member-info-all.css';",
|
||||
"@import '@/common/style/memberInfo/member-info-page.css';"
|
||||
);
|
||||
content = content.replace(
|
||||
"@import '@/common/style/member-info-page.css';",
|
||||
"@import '@/common/style/memberInfo/member-info-page.css';"
|
||||
);
|
||||
fs.writeFileSync(pagePath, content, 'utf8');
|
||||
console.log('updated page:', path.basename(pagePath));
|
||||
}
|
||||
|
||||
console.log('done');
|
||||
@@ -0,0 +1,19 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const root = path.join(__dirname, '..');
|
||||
const componentsDir = path.join(root, 'components/memberInfo');
|
||||
|
||||
const componentFiles = fs.readdirSync(componentsDir).filter((name) => name.endsWith('.vue'));
|
||||
|
||||
for (const file of componentFiles) {
|
||||
const filePath = path.join(componentsDir, file);
|
||||
let content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
||||
const next = content.replace(/\n<style>[\s\S]*?<\/style>\n?/g, '\n');
|
||||
if (next !== content) {
|
||||
fs.writeFileSync(filePath, next.trimEnd() + '\n', 'utf8');
|
||||
console.log('removed style block:', file);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('done');
|
||||
Reference in New Issue
Block a user