fix(admin): support 4-segment version in domain layer & make changelog optional
- VersionName value object now accepts x.y.z.w format - changelog field is now optional in upload version DTO - profile page: ensure expired section has full width 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4307d1eb91
commit
586dfda8f7
|
|
@ -27,9 +27,10 @@ export class UploadVersionDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
buildNumber?: string
|
buildNumber?: string
|
||||||
|
|
||||||
@ApiProperty({ description: '更新日志' })
|
@ApiPropertyOptional({ description: '更新日志', default: '' })
|
||||||
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
changelog: string
|
changelog?: string
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: '是否强制更新', default: false })
|
@ApiPropertyOptional({ description: '是否强制更新', default: false })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,21 @@ import { DomainException } from '../../shared/exceptions/domain.exception';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 版本名称值对象
|
* 版本名称值对象
|
||||||
* 格式: major.minor.patch (e.g., 1.0.0, 2.3.1)
|
* 格式: major.minor.patch 或 major.minor.patch.build (e.g., 1.0.0, 1.0.0.4)
|
||||||
*/
|
*/
|
||||||
export class VersionName {
|
export class VersionName {
|
||||||
private readonly _value: string;
|
private readonly _value: string;
|
||||||
private readonly _major: number;
|
private readonly _major: number;
|
||||||
private readonly _minor: number;
|
private readonly _minor: number;
|
||||||
private readonly _patch: number;
|
private readonly _patch: number;
|
||||||
|
private readonly _build?: number;
|
||||||
|
|
||||||
private constructor(value: string, major: number, minor: number, patch: number) {
|
private constructor(value: string, major: number, minor: number, patch: number, build?: number) {
|
||||||
this._value = value;
|
this._value = value;
|
||||||
this._major = major;
|
this._major = major;
|
||||||
this._minor = minor;
|
this._minor = minor;
|
||||||
this._patch = patch;
|
this._patch = patch;
|
||||||
|
this._build = build;
|
||||||
}
|
}
|
||||||
|
|
||||||
get value(): string {
|
get value(): string {
|
||||||
|
|
@ -33,24 +35,30 @@ export class VersionName {
|
||||||
return this._patch;
|
return this._patch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get build(): number | undefined {
|
||||||
|
return this._build;
|
||||||
|
}
|
||||||
|
|
||||||
static create(value: string): VersionName {
|
static create(value: string): VersionName {
|
||||||
if (!value || value.trim() === '') {
|
if (!value || value.trim() === '') {
|
||||||
throw new DomainException('VersionName cannot be empty');
|
throw new DomainException('VersionName cannot be empty');
|
||||||
}
|
}
|
||||||
|
|
||||||
const trimmed = value.trim();
|
const trimmed = value.trim();
|
||||||
const versionPattern = /^(\d+)\.(\d+)\.(\d+)$/;
|
// 支持 x.y.z 或 x.y.z.w 格式
|
||||||
|
const versionPattern = /^(\d+)\.(\d+)\.(\d+)(?:\.(\d+))?$/;
|
||||||
const match = trimmed.match(versionPattern);
|
const match = trimmed.match(versionPattern);
|
||||||
|
|
||||||
if (!match) {
|
if (!match) {
|
||||||
throw new DomainException('VersionName must follow semantic versioning format (e.g., 1.0.0)');
|
throw new DomainException('VersionName must follow semantic versioning format (e.g., 1.0.0 or 1.0.0.4)');
|
||||||
}
|
}
|
||||||
|
|
||||||
const major = parseInt(match[1], 10);
|
const major = parseInt(match[1], 10);
|
||||||
const minor = parseInt(match[2], 10);
|
const minor = parseInt(match[2], 10);
|
||||||
const patch = parseInt(match[3], 10);
|
const patch = parseInt(match[3], 10);
|
||||||
|
const build = match[4] ? parseInt(match[4], 10) : undefined;
|
||||||
|
|
||||||
return new VersionName(trimmed, major, minor, patch);
|
return new VersionName(trimmed, major, minor, patch, build);
|
||||||
}
|
}
|
||||||
|
|
||||||
equals(other: VersionName): boolean {
|
equals(other: VersionName): boolean {
|
||||||
|
|
|
||||||
|
|
@ -1005,17 +1005,19 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
|
|
||||||
/// 构建已过期区域
|
/// 构建已过期区域
|
||||||
Widget _buildExpiredSection() {
|
Widget _buildExpiredSection() {
|
||||||
return Container(
|
return SizedBox(
|
||||||
padding: const EdgeInsets.all(16),
|
width: double.infinity,
|
||||||
decoration: BoxDecoration(
|
child: Container(
|
||||||
color: const Color(0xFFFFF5E6),
|
padding: const EdgeInsets.all(16),
|
||||||
borderRadius: BorderRadius.circular(8),
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
color: const Color(0xFFFFF5E6),
|
||||||
color: const Color(0x33D4AF37),
|
borderRadius: BorderRadius.circular(8),
|
||||||
width: 1,
|
border: Border.all(
|
||||||
|
color: const Color(0x33D4AF37),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
child: Column(
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
const Text(
|
||||||
|
|
@ -1083,6 +1085,7 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue