feat(v3.4.14): mini-user store UX, brand config, client settings
Store list/detail package flow, configurable WeChat mini brand assets and customer service, shop H5 home refresh. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,4 +1,15 @@
|
||||
import type { SystemConfigFieldMeta, SystemConfigGroupMeta } from '@dukang/shared-types';
|
||||
import {
|
||||
BRAND_LOGO_MARK_URL,
|
||||
BRAND_LOGO_OSS_BASE,
|
||||
BRAND_LOGO_URL,
|
||||
BRAND_LOGO_WIDE_URL,
|
||||
CUSTOMER_SERVICE_PHONE,
|
||||
DEFAULT_USER_H5_URL,
|
||||
MINI_USER_STATIC_OSS_BASE,
|
||||
MOCK_SMS_FIXED_CODE,
|
||||
QUALIFICATION_DISCLOSURE_URL,
|
||||
} from '@dukang/shared-types';
|
||||
|
||||
/** 运行环境、安全与鉴权仅保留在 .env,不在 HQ 系统设置中维护 */
|
||||
export const SYSTEM_CONFIG_GROUPS: SystemConfigGroupMeta[] = [
|
||||
@@ -122,9 +133,86 @@ export const SYSTEM_CONFIG_FIELDS: SystemConfigFieldMeta[] = [
|
||||
group: G.wechat_mini,
|
||||
type: 'string',
|
||||
requiresRestart: false,
|
||||
placeholder: '3.4.13',
|
||||
placeholder: '3.4.14',
|
||||
description: 'semver 格式;客户端低于此版本时提示更新',
|
||||
},
|
||||
{
|
||||
key: 'USER_H5_URL',
|
||||
label: 'C 端 H5 落地页',
|
||||
group: G.wechat_mini,
|
||||
type: 'string',
|
||||
requiresRestart: false,
|
||||
placeholder: 'https://user.example.com/user',
|
||||
description: '推广码二维码 / 未配置时的默认落地页前缀(无末尾斜杠)',
|
||||
},
|
||||
{
|
||||
key: 'BRAND_LOGO_OSS_BASE',
|
||||
label: '品牌 Logo OSS 根路径',
|
||||
group: G.wechat_mini,
|
||||
type: 'string',
|
||||
requiresRestart: false,
|
||||
placeholder: 'https://xxx.oss-cn-beijing.aliyuncs.com/logo/',
|
||||
description: '仅作说明/备份;下方三张 Logo 请直接配置完整 URL',
|
||||
},
|
||||
{
|
||||
key: 'BRAND_LOGO_URL',
|
||||
label: '方形 Logo',
|
||||
group: G.wechat_mini,
|
||||
type: 'image',
|
||||
requiresRestart: false,
|
||||
description: '分享卡片默认图、首页等品牌展示',
|
||||
},
|
||||
{
|
||||
key: 'BRAND_LOGO_WIDE_URL',
|
||||
label: '长方形 Logo',
|
||||
group: G.wechat_mini,
|
||||
type: 'image',
|
||||
requiresRestart: false,
|
||||
description: '含文字,登录页等场景',
|
||||
},
|
||||
{
|
||||
key: 'BRAND_LOGO_MARK_URL',
|
||||
label: '图标 Logo',
|
||||
group: G.wechat_mini,
|
||||
type: 'image',
|
||||
requiresRestart: false,
|
||||
description: '默认头像(未微信授权时)',
|
||||
},
|
||||
{
|
||||
key: 'MINI_USER_STATIC_OSS_BASE',
|
||||
label: '小程序静态资源 OSS 根路径',
|
||||
group: G.wechat_mini,
|
||||
type: 'string',
|
||||
requiresRestart: false,
|
||||
placeholder: 'https://xxx.oss-cn-beijing.aliyuncs.com/static/mini-user/',
|
||||
description: '资质公示等静态资源根路径;完整 URL 优先用下方「资质公示图」',
|
||||
},
|
||||
{
|
||||
key: 'QUALIFICATION_DISCLOSURE_URL',
|
||||
label: '资质公示长图',
|
||||
group: G.wechat_mini,
|
||||
type: 'image',
|
||||
requiresRestart: false,
|
||||
description: '「我的」页资质公示大图',
|
||||
},
|
||||
{
|
||||
key: 'CUSTOMER_SERVICE_PHONE',
|
||||
label: '总部客服电话',
|
||||
group: G.wechat_mini,
|
||||
type: 'string',
|
||||
requiresRestart: false,
|
||||
placeholder: '13203801799',
|
||||
description: 'C 端联系客服拨号号码',
|
||||
},
|
||||
{
|
||||
key: 'MOCK_SMS_FIXED_CODE',
|
||||
label: 'Mock 短信固定验证码',
|
||||
group: G.wechat_mini,
|
||||
type: 'string',
|
||||
requiresRestart: false,
|
||||
placeholder: '999888',
|
||||
description: '仅 MOCK_SMS 开启时生效;留空则用默认 999888',
|
||||
},
|
||||
|
||||
{ key: 'OSS_ACCESS_KEY_ID', label: 'OSS AccessKey ID', group: G.oss, type: 'password', secret: true, requiresRestart: true },
|
||||
{ key: 'OSS_ACCESS_KEY_SECRET', label: 'OSS AccessKey Secret', group: G.oss, type: 'password', secret: true, requiresRestart: true },
|
||||
@@ -137,7 +225,6 @@ export const SYSTEM_CONFIG_FIELDS: SystemConfigFieldMeta[] = [
|
||||
{ key: 'OSS_UPLOAD_EXPIRE_SECONDS', label: '直传凭证有效期(秒)', group: G.oss, type: 'number', requiresRestart: false },
|
||||
{ key: 'OSS_MAX_UPLOAD_BYTES', label: '单文件上限(字节)', group: G.oss, type: 'number', requiresRestart: false },
|
||||
|
||||
{ key: 'USER_H5_URL', label: 'C 端 H5 落地页', group: G.app, type: 'string', requiresRestart: false },
|
||||
{
|
||||
key: 'TENCENT_LBS_KEY',
|
||||
label: '腾讯位置服务 Key',
|
||||
@@ -247,6 +334,23 @@ export const SYSTEM_CONFIG_RETIRED_KEYS = [
|
||||
|
||||
export const SYSTEM_CONFIG_KEY_SET = new Set(SYSTEM_CONFIG_FIELDS.map((f) => f.key));
|
||||
|
||||
/** 表单空值时展示 / 启动补种的默认值(与 shared-types 常量对齐) */
|
||||
export const SYSTEM_CONFIG_DEFAULTS: Record<string, string> = {
|
||||
USER_H5_URL: DEFAULT_USER_H5_URL.replace(/\/$/, ''),
|
||||
BRAND_LOGO_OSS_BASE: BRAND_LOGO_OSS_BASE,
|
||||
BRAND_LOGO_URL: BRAND_LOGO_URL,
|
||||
BRAND_LOGO_WIDE_URL: BRAND_LOGO_WIDE_URL,
|
||||
BRAND_LOGO_MARK_URL: BRAND_LOGO_MARK_URL,
|
||||
MINI_USER_STATIC_OSS_BASE: MINI_USER_STATIC_OSS_BASE,
|
||||
QUALIFICATION_DISCLOSURE_URL: QUALIFICATION_DISCLOSURE_URL,
|
||||
CUSTOMER_SERVICE_PHONE: CUSTOMER_SERVICE_PHONE,
|
||||
MOCK_SMS_FIXED_CODE: MOCK_SMS_FIXED_CODE,
|
||||
};
|
||||
|
||||
export function getSystemConfigField(key: string): SystemConfigFieldMeta | undefined {
|
||||
return SYSTEM_CONFIG_FIELDS.find((f) => f.key === key);
|
||||
}
|
||||
|
||||
export function getSystemConfigDefault(key: string): string {
|
||||
return SYSTEM_CONFIG_DEFAULTS[key] ?? '';
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
import { loadAppConfig, parseMiniHomeBanners, serializeMiniHomeBanners } from '@dukang/shared-types';
|
||||
import { PrismaService } from '../prisma/prisma.module';
|
||||
import {
|
||||
SYSTEM_CONFIG_DEFAULTS,
|
||||
SYSTEM_CONFIG_FIELDS,
|
||||
SYSTEM_CONFIG_GROUPS,
|
||||
SYSTEM_CONFIG_KEY_SET,
|
||||
@@ -42,6 +43,7 @@ export class SystemConfigService implements OnModuleInit {
|
||||
try {
|
||||
await this.purgeRetiredKeys();
|
||||
await this.seedMissingFromProcessEnv();
|
||||
await this.seedMissingDefaults();
|
||||
const rows = await this.prisma.systemConfig.findMany();
|
||||
applyEnvOverlay(Object.fromEntries(rows.map((r) => [r.configKey, r.value])));
|
||||
this.lastUpdatedAt = rows.reduce<Date | null>(
|
||||
@@ -82,7 +84,7 @@ export class SystemConfigService implements OnModuleInit {
|
||||
for (const field of fields) {
|
||||
const fromDb = dbMap.get(field.key);
|
||||
const fromEnv = process.env[field.key];
|
||||
const raw = fromDb ?? fromEnv ?? '';
|
||||
const raw = (fromDb ?? fromEnv ?? SYSTEM_CONFIG_DEFAULTS[field.key] ?? '').trim();
|
||||
if (field.secret) {
|
||||
if (raw) configuredSecrets.push(field.key);
|
||||
values[field.key] = '';
|
||||
@@ -227,6 +229,26 @@ export class SystemConfigService implements OnModuleInit {
|
||||
}
|
||||
}
|
||||
|
||||
/** 将代码默认值写入空缺配置,便于 HQ 表单可见、可改 */
|
||||
private async seedMissingDefaults() {
|
||||
const overlay: Record<string, string> = {};
|
||||
for (const field of SYSTEM_CONFIG_FIELDS) {
|
||||
const defaultVal = SYSTEM_CONFIG_DEFAULTS[field.key];
|
||||
if (!defaultVal?.trim()) continue;
|
||||
const existing = await this.prisma.systemConfig.findUnique({ where: { configKey: field.key } });
|
||||
if (existing?.value?.trim()) continue;
|
||||
if (process.env[field.key]?.trim()) continue;
|
||||
const value = this.normalizeByMeta(field, defaultVal);
|
||||
await this.prisma.systemConfig.upsert({
|
||||
where: { configKey: field.key },
|
||||
create: { configKey: field.key, value },
|
||||
update: { value },
|
||||
});
|
||||
overlay[field.key] = value;
|
||||
}
|
||||
if (Object.keys(overlay).length) applyEnvOverlay(overlay);
|
||||
}
|
||||
|
||||
private normalizeByMeta(meta: { key?: string; type: string }, raw: string): string {
|
||||
if (meta.type === 'boolean') {
|
||||
return raw === 'true' || raw === '1' ? 'true' : 'false';
|
||||
|
||||
Reference in New Issue
Block a user