Fix H5 all-in-one asset paths

This commit is contained in:
Chopper711
2026-06-17 16:45:36 +08:00
parent a19f039da4
commit e4e6bd272c
11 changed files with 185 additions and 17 deletions

View File

@@ -10,6 +10,8 @@
想快速体验完整商城不需要分别部署后端、PC、商家端、运营端、IM、H5、MySQL 和 Redis。安装 Docker Desktop 后,直接执行下面脚本,按提示输入 IP/域名、端口、密码和数据目录即可启动单镜像单容器环境。
数据库口径:常规部署和项目技术栈仍以 MySQL 为准All-In-One 镜像内置 MariaDB 只是单容器本地体验中的 MySQL 协议兼容实现,不代表生产部署或常规部署已经切换为 MariaDB。
macOS / Linux:
```bash
@@ -26,7 +28,7 @@ Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\install-lilishop.ps1
```
默认镜像:`ccr.ccs.tencentyun.com/lilishop/lilishop-all-in-one:lite`。启动后可访问买家 PC、商家端、运营端、IM Web、uniapp H5后端 API。更多说明见 [docker/all-in-one 文档](https://gitee.com/beijing_hongye_huicheng/docker/tree/allinone-lite-mysql-redis/all-in-one)。
默认镜像:`ccr.ccs.tencentyun.com/lilishop/lilishop-all-in-one:lite`。启动后可访问买家 PC、商家端、运营端、IM Web、uniapp H5,统一后端 All-In-One 的同域 `/api/` 入口代理。更多说明见 [docker/all-in-one 文档](https://gitee.com/beijing_hongye_huicheng/docker/tree/allinone-lite-mysql-redis/all-in-one)。
LILISHOP 是基于 Spring Boot / Spring Cloud / Vue / Uniapp 开发的 Java 开源商城系统,支持 B2B2C 多商户商城、小程序商城、微服务商城、直播电商、分销返佣、秒杀活动、Docker 私有化部署。
@@ -80,7 +82,7 @@ Lilishop 由 4 个独立仓库组成,均同步托管于 GitHub 与 Gitee
- **全端覆盖**: 一套代码库支持PC、H5、小程序、APP降低开发和维护成本。
- **商家入驻**: 支持多商家入驻,构建平台化电商生态。
- **分布式架构**: 后端API服务化支持独立部署和弹性伸缩
- **All-In-One 后端**: 当前分支推荐使用 `lilishop-all` 统一后端入口,降低本地体验和私有化部署门槛
- **前后端分离**: 清晰的职责划分,便于团队协作和独立开发。
- **容器化支持**: 提供Docker镜像和docker-compose配置实现一键部署。
- **功能完善**: 涵盖客户、订单、商品、促销、店铺、运营、统计等完整电商业务模块。

View File

@@ -7,15 +7,15 @@ const localImBase = process.env.VUE_APP_IM_API_BASE_URL || localApiBase;
// 开发环境优先读取一键启动脚本生成的 .env.development.local。
const dev = {
im: localImBase || "https://im-api.pickmall.cn",
common: localApiBase || "https://common-api.pickmall.cn",
buyer: localApiBase || "https://buyer-api.pickmall.cn",
im: localImBase || "/api",
common: localApiBase || "/api",
buyer: localApiBase || "/api",
};
// 生产环境保持原有线上默认值,构建时允许通过环境变量显式覆盖。
// 生产环境默认走 All-In-One 同域入口,构建时允许通过环境变量显式覆盖。
const prod = {
im: localImBase || "https://im-api.pickmall.cn",
common: localApiBase || "https://common-api.pickmall.cn",
buyer: localApiBase || "https://buyer-api.pickmall.cn",
im: localImBase || "/api",
common: localApiBase || "/api",
buyer: localApiBase || "/api",
};
//默认生产环境

View File

@@ -2,6 +2,19 @@ const name = "lilishop"; //全局商城name
const schemeName = "lilishop"; //唤醒app需要的schemeName
const wapUrl = process.env.VUE_APP_WAP_URL || "https://m-b2b2c.pickmall.cn";
const loginCaptchaBypass = process.env.VUE_APP_LOGIN_CAPTCHA_BYPASS === "true";
const normalizeWsUrl = (url) => {
// H5 可使用同域相对路径,运行时按当前页面协议补齐 WebSocket 地址。
if (!url || /^wss?:\/\//.test(url)) {
return url || "";
}
// #ifdef H5
if (typeof window !== "undefined" && url.startsWith("/")) {
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
return `${protocol}//${window.location.host}${url}`;
}
// #endif
return url;
};
export default {
name: name,
@@ -16,7 +29,7 @@ export default {
customerServiceMobile: "13161366885", //客服电话
customerServiceEmail: "lili@lili.com", //客服邮箱
imWebSrc: process.env.VUE_APP_IM_WEB_URL || "https://im.pickmall.cn", //IM地址
baseWsUrl: process.env.VUE_APP_IM_WS_URL || "wss://im-api.pickmall.cn/lili/webSocket", // IM WS 地址
baseWsUrl: normalizeWsUrl(process.env.VUE_APP_IM_WS_URL || "/lili/webSocket"), // IM WS 地址
loginCaptchaBypass,
enableGetClipboard: false, //是否启用粘贴板获取 scanAuthNavigation 中的链接,如果匹配则会跳转到对应页面
enableMiniBarStartUpApp: true, //是否在h5中右侧浮空按钮点击启动app

View File

@@ -321,7 +321,7 @@ page {
.star {
width: 30rpx;
height: 30rpx;
background: url("/static/star.png");
background: url("../../static/star.png");
background-size: 100%;
}
}

View File

@@ -1,5 +1,5 @@
.group-wrapper {
background: url("/static/exchange.png");
background: url("../../../../static/exchange.png");
background-size: cover;
}
.u-group-row {

View File

@@ -119,7 +119,7 @@
<style lang="scss" scoped>
.header-wraper {
background: url('/static/bg.png');
background: url('../../static/bg.png');
height: 200rpx;
display: flex;
align-items: center;

View File

@@ -35,7 +35,7 @@ export default {
.user-point {
padding: 0 20rpx;
height: 300rpx;
background: url("/static/point-bg.png") no-repeat;
background: url("../../../static/point-bg.png") no-repeat;
background-size: 100%;
}
.point {

View File

@@ -168,7 +168,7 @@
}
.header-wraper {
background: url('/static/bg.png');
background: url('../../static/bg.png');
height: 200rpx;
display: flex;
align-items: center;

View File

@@ -0,0 +1,10 @@
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import test from 'node:test';
const source = readFileSync(new URL('./my.vue', import.meta.url), 'utf8');
test('member center header background resolves under the H5 base path', () => {
assert.doesNotMatch(source, /background-image:\s*url\(["']\/static\/img\/main-bg\.png["']\)/);
assert.match(source, /background-image:\s*url\(["']\.\.\/\.\.\/\.\.\/static\/img\/main-bg\.png["']\)/);
});

View File

@@ -174,7 +174,7 @@ body {
background-size: cover;
border-bottom-left-radius: 30rpx;
border-bottom-right-radius: 30rpx;
background-image: url("/static/img/main-bg.png");
background-image: url("../../../static/img/main-bg.png");
background-position: bottom;
background-repeat: no-repeat;
color: #ffffff;

View File

@@ -0,0 +1,143 @@
import assert from 'node:assert/strict';
import { readdirSync, readFileSync, statSync } from 'node:fs';
import path from 'node:path';
import test from 'node:test';
import valueParser from 'postcss-value-parser';
import vueTemplateCompiler from 'vue-template-compiler';
const roots = ['pages', 'components'];
const extraFiles = ['App.vue', 'uni.scss'];
const styleExtensions = new Set(['.css', '.scss']);
function listFiles(dir) {
const result = [];
for (const entry of readdirSync(dir)) {
const fullPath = path.join(dir, entry);
const stat = statSync(fullPath);
if (stat.isDirectory()) {
result.push(...listFiles(fullPath));
} else {
result.push(fullPath);
}
}
return result;
}
function stripCssCommentsPreserveOffsets(source) {
let output = '';
let quote = null;
for (let i = 0; i < source.length; i += 1) {
const char = source[i];
const next = source[i + 1];
if (quote) {
output += char;
if (char === '\\') {
i += 1;
output += source[i] || '';
} else if (char === quote) {
quote = null;
}
continue;
}
if (char === '"' || char === "'") {
quote = char;
output += char;
continue;
}
if (char === '/' && next === '*') {
output += ' ';
i += 2;
while (i < source.length && !(source[i] === '*' && source[i + 1] === '/')) {
output += source[i] === '\n' ? '\n' : ' ';
i += 1;
}
output += ' ';
i += 1;
continue;
}
if (char === '/' && next === '/') {
output += ' ';
i += 2;
while (i < source.length && source[i] !== '\n') {
output += ' ';
i += 1;
}
if (source[i] === '\n') {
output += '\n';
}
continue;
}
output += char;
}
return output;
}
function lineForOffset(source, offset) {
return source.slice(0, offset).split('\n').length;
}
function collectRootStaticStyleUrls(filePath, styleSource, sourceOffset = 0, originalSource = styleSource) {
const stripped = stripCssCommentsPreserveOffsets(styleSource);
const findings = [];
valueParser(stripped).walk((node) => {
if (node.type !== 'function' || node.value.toLowerCase() !== 'url') {
return;
}
const url = valueParser.stringify(node.nodes).trim().replace(/^['"]|['"]$/g, '');
if (!url.startsWith('/static/')) {
return;
}
findings.push({
filePath,
line: lineForOffset(originalSource, sourceOffset + node.sourceIndex),
url,
});
});
return findings;
}
function styleSourcesForFile(filePath) {
const source = readFileSync(filePath, 'utf8');
if (filePath.endsWith('.vue')) {
const descriptor = vueTemplateCompiler.parseComponent(source);
return descriptor.styles.map((style) => ({
source: style.content,
sourceOffset: style.start,
originalSource: source,
}));
}
return [{ source, sourceOffset: 0, originalSource: source }];
}
test('style url() references do not use H5-root /static paths', () => {
const files = [
...roots.flatMap((root) => listFiles(root)),
...extraFiles,
].filter((filePath) => filePath.endsWith('.vue') || styleExtensions.has(path.extname(filePath)));
const findings = files.flatMap((filePath) =>
styleSourcesForFile(filePath).flatMap(({ source, sourceOffset, originalSource }) =>
collectRootStaticStyleUrls(filePath, source, sourceOffset, originalSource)
)
);
assert.deepEqual(
findings,
[],
`style url() must use relative static paths so H5 /h5 deployments do not request site-root assets:\n${
findings.map((item) => `${item.filePath}:${item.line} ${item.url}`).join('\n')
}`
);
});