完成H5后端管理第一版。
parent
d6de752645
commit
d0aa47cae2
@ -1 +1,6 @@
|
||||
package price_default
|
||||
|
||||
type PriceDefaultRepository interface {
|
||||
Save(price *PriceDefault) error
|
||||
Get() (*PriceDefault, error)
|
||||
}
|
||||
|
@ -0,0 +1,32 @@
|
||||
package price_default
|
||||
|
||||
import (
|
||||
"cls-server/internal/domain/price_default"
|
||||
"cls-server/pkg/xorm_engine"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
type PriceDefaultRepositoryORM struct {
|
||||
engine *xorm_engine.Engine
|
||||
}
|
||||
|
||||
var _ price_default.PriceDefaultRepository = (*PriceDefaultRepositoryORM)(nil)
|
||||
|
||||
func NewPriceRepositoryORM(engine *xorm_engine.Engine) price_default.PriceDefaultRepository {
|
||||
return &PriceDefaultRepositoryORM{engine}
|
||||
}
|
||||
|
||||
func (p PriceDefaultRepositoryORM) Save(pd *price_default.PriceDefault) error {
|
||||
if pd.Id > 0 {
|
||||
_, err := p.engine.Update(pd)
|
||||
return err
|
||||
}
|
||||
_, err := p.engine.Insert(pd)
|
||||
return err
|
||||
}
|
||||
|
||||
func (p PriceDefaultRepositoryORM) Get() (*price_default.PriceDefault, error) {
|
||||
data := &price_default.PriceDefault{}
|
||||
_, err := p.engine.Where(builder.Eq{"id": 1}).Get(data)
|
||||
return data, err
|
||||
}
|
@ -1,11 +1,40 @@
|
||||
import { Article } from "./article";
|
||||
import { Column } from "./column";
|
||||
export interface Price {
|
||||
id: number;
|
||||
targetId: number;
|
||||
type: string;
|
||||
type: PriceType;
|
||||
amount: number;
|
||||
firstMontDiscount:number;
|
||||
oneMonthPrice: number;
|
||||
threeMonthsPrice: number;
|
||||
sixMonthsPrice: number;
|
||||
oneYearPrice: number;
|
||||
discount: number;
|
||||
}
|
||||
|
||||
export enum PriceType {
|
||||
TypeArticle = 1,
|
||||
TypeColumn = 2
|
||||
}
|
||||
|
||||
export interface PriceDefault {
|
||||
id: number;
|
||||
amount: number;
|
||||
firstMontDiscount:number;
|
||||
oneMonthPrice: number;
|
||||
threeMonthsPrice: number;
|
||||
sixMonthsPrice: number;
|
||||
oneYearPrice: number;
|
||||
discount: number;
|
||||
}
|
||||
export interface ArticlePrice {
|
||||
article: Article;
|
||||
price: Price;
|
||||
}
|
||||
|
||||
export interface ColumnPrice {
|
||||
column: Column;
|
||||
price: Price;
|
||||
}
|
||||
|
||||
|
@ -1,37 +1,34 @@
|
||||
<form *ngIf="validateForm" nz-form [formGroup]="validateForm">
|
||||
<form nz-form [formGroup]="validateForm" nzLayout="vertical">
|
||||
<nz-form-item>
|
||||
<nz-form-label [nzSpan]="6" >专栏名</nz-form-label>
|
||||
<nz-form-control [nzSpan]="14" >
|
||||
<input nz-input type="text" formControlName="name" [disabled]="true" />
|
||||
<nz-form-label>专栏名称</nz-form-label>
|
||||
<nz-form-control>
|
||||
<input nz-input formControlName="title" [disabled]="true" />
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<nz-form-item>
|
||||
<nz-form-label [nzSpan]="6" nzRequired>专栏描述</nz-form-label>
|
||||
<nz-form-control [nzSpan]="14" [nzErrorTip]="errorTpl1" nzHasFeedback>
|
||||
<input nz-input type="text" formControlName="brief"/>
|
||||
<ng-template #errorTpl1 let-control>
|
||||
<ng-container *ngIf="control.hasError('required')">
|
||||
描述不能为空
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<nz-form-label nzRequired>专栏描述</nz-form-label>
|
||||
<nz-form-control nzErrorTip="请输入专栏描述">
|
||||
<input nz-input formControlName="brief" />
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<nz-form-item>
|
||||
<nz-form-label [nzSpan]="6" nzRequired>专栏封面</nz-form-label>
|
||||
<nz-form-control [nzSpan]="14" [nzErrorTip]="errorTpl1" nzHasFeedback>
|
||||
<input nz-input type="text" formControlName="cover"/>
|
||||
<nz-form-label nzRequired>关注人数</nz-form-label>
|
||||
<nz-form-control nzErrorTip="请输入有效的关注人数">
|
||||
<input nz-input type="number" formControlName="followNum" />
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<nz-form-item>
|
||||
<nz-form-label [nzSpan]="6" nzRequired>关注人数</nz-form-label>
|
||||
<nz-form-control [nzSpan]="14" [nzErrorTip]="errorTpl1" nzHasFeedback>
|
||||
<input nz-input type="number" formControlName="followNum"/>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-label [nzSpan]="6" nzRequired>购买人数</nz-form-label>
|
||||
<nz-form-control [nzSpan]="14" [nzErrorTip]="errorTpl1" nzHasFeedback>
|
||||
<input nz-input type="number" formControlName="purchaseNum"/>
|
||||
<nz-form-label nzRequired>购买人数</nz-form-label>
|
||||
<nz-form-control nzErrorTip="请输入有效的购买人数">
|
||||
<input nz-input type="number" formControlName="purchaseNum" />
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button nz-button nzType="default" (click)="handleCancel()">取消</button>
|
||||
<button nz-button nzType="primary" (click)="handleOk()" [nzLoading]="isSaving" [disabled]="!validateForm.valid">保存</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -0,0 +1,27 @@
|
||||
.modal-footer {
|
||||
margin-top: 24px;
|
||||
text-align: right;
|
||||
|
||||
button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
nz-form-item {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
nz-form-label {
|
||||
font-weight: 500;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
<nz-card>
|
||||
<!-- 搜索区域 -->
|
||||
<div class="search-area">
|
||||
<nz-input-group [nzSuffix]="suffixIconSearch">
|
||||
<input type="text" nz-input placeholder="搜索文章名称" [(ngModel)]="searchParams['search_like_username']" (ngModelChange)="onValueChange.next($event)" />
|
||||
</nz-input-group>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<nz-table #basicTable
|
||||
style="width: 100%"
|
||||
nzSize="middle"
|
||||
nzHideOnSinglePage
|
||||
[nzLoading]="loading"
|
||||
[nzShowTotal]="totalTemplate"
|
||||
[nzFrontPagination]="false"
|
||||
[nzPageSizeOptions]="[10,20,30]"
|
||||
[nzShowSizeChanger]="true"
|
||||
[nzPageIndex]="pageData?.pageNumber! +1 "
|
||||
[nzPageSize]="pageData?.pageSize!"
|
||||
[nzTotal]="pageData?.totalElements!"
|
||||
[nzShowPagination]="true"
|
||||
[nzData]="pageData?.items!"
|
||||
(nzPageIndexChange)="changePaginationPage($event)"
|
||||
(nzPageSizeChange)="changePaginationSize($event)">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>文章ID</th>
|
||||
<th>文章名称</th>
|
||||
<th>发布时间</th>
|
||||
<th>价格(元)</th>
|
||||
<th>折扣</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let data of basicTable.data">
|
||||
<td>{{ data?.article?.eventId }}</td>
|
||||
<td>{{ data?.article?.title }}</td>
|
||||
<td>{{ data?.article?.releaseDate }} {{ data?.article?.releaseTime }}</td>
|
||||
<td>{{ data?.price?.amount ? data.price.amount / 100:'默认价格' }}</td>
|
||||
<td>{{ data?.price?.discount ? data.price.discount * 100 :'默认折扣'}}%</td>
|
||||
<td>
|
||||
<a (click)="editPrice(data)">编辑价格</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</nz-table>
|
||||
<ng-template #totalTemplate let-range="range" let-total>
|
||||
第<span style="padding: 0 0.5em;font-weight: bolder">{{range[0]}}-{{range[1]}}</span>个,共<span
|
||||
style="padding: 0 0.5em;font-weight: bolder">{{total}}</span>个
|
||||
</ng-template>
|
||||
</nz-card>
|
||||
|
||||
<ng-template #suffixIconSearch>
|
||||
<span nz-icon nzType="search"></span>
|
||||
</ng-template>
|
@ -0,0 +1,155 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Page } from '../../../core/models/page';
|
||||
import { ArticlePrice } from '../../../core/models/price';
|
||||
import { PriceService } from '../price.service';
|
||||
import { debounceTime, distinctUntilChanged, Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { ChangeDetectorRef } from '@angular/core';
|
||||
import { NzMessageModule, NzMessageService } from 'ng-zorro-antd/message';
|
||||
import { NzModalModule, NzModalService } from 'ng-zorro-antd/modal';
|
||||
import { NzFormModule } from 'ng-zorro-antd/form';
|
||||
import { NzInputModule } from 'ng-zorro-antd/input';
|
||||
import { NzInputNumberModule } from 'ng-zorro-antd/input-number';
|
||||
import { NzCardModule } from 'ng-zorro-antd/card';
|
||||
import { NzTableModule } from 'ng-zorro-antd/table';
|
||||
import { NzIconModule } from 'ng-zorro-antd/icon';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { NgForOf } from "@angular/common";
|
||||
import { SetArticlePriceComponent } from "./set-article-price/set-article-price.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-article',
|
||||
standalone: true,
|
||||
imports: [
|
||||
NzMessageModule,
|
||||
NzModalModule,
|
||||
NzFormModule,
|
||||
NzInputModule,
|
||||
NzInputNumberModule,
|
||||
NzCardModule,
|
||||
NzTableModule,
|
||||
NzIconModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgForOf,
|
||||
SetArticlePriceComponent
|
||||
],
|
||||
templateUrl: './article.component.html',
|
||||
styleUrl: './article.component.scss'
|
||||
})
|
||||
export class ArticleComponent implements OnInit, OnDestroy {
|
||||
pageData: Page<ArticlePrice> = new Page<ArticlePrice>();
|
||||
searchParams: { [param: string]: any } = {
|
||||
page: 0,
|
||||
size: 10,
|
||||
search_like_username: ''
|
||||
};
|
||||
onValueChange = new Subject<string>();
|
||||
loading = false;
|
||||
destroy$ = new Subject();
|
||||
editForm: FormGroup;
|
||||
|
||||
constructor(
|
||||
private priceService: PriceService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private message: NzMessageService,
|
||||
private modal: NzModalService,
|
||||
private fb: FormBuilder
|
||||
) {
|
||||
this.onValueChange.pipe(
|
||||
debounceTime(300),
|
||||
distinctUntilChanged(),
|
||||
takeUntil(this.destroy$)
|
||||
).subscribe(() => {
|
||||
this.searchParams['page'] = 0;
|
||||
this.cdr.markForCheck();
|
||||
this.loadData();
|
||||
});
|
||||
|
||||
this.editForm = this.fb.group({
|
||||
price: [null, [Validators.required, Validators.min(0)]],
|
||||
discount: [1, [Validators.required, Validators.min(0), Validators.max(1)]]
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
console.log('Component initialized');
|
||||
this.onValueChange
|
||||
.pipe(debounceTime(500))
|
||||
.subscribe(() => {
|
||||
this.loadData();
|
||||
});
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next(null);
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
loadData() {
|
||||
console.log('Loading data with params:', this.searchParams);
|
||||
this.loading = true;
|
||||
this.cdr.detectChanges();
|
||||
|
||||
this.priceService.pageOfArticle(this.searchParams)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: data => {
|
||||
console.log('Raw response data:', data);
|
||||
this.pageData = data;
|
||||
console.log('Processed pageData:', this.pageData);
|
||||
if (this.pageData?.items?.length > 0) {
|
||||
console.log('First item:', this.pageData.items[0]);
|
||||
console.log('First item article:', this.pageData.items[0]?.article);
|
||||
console.log('First item price:', this.pageData.items[0]?.price);
|
||||
}
|
||||
this.loading = false;
|
||||
this.cdr.detectChanges();
|
||||
},
|
||||
error: error => {
|
||||
console.error('Error loading data:', error);
|
||||
this.loading = false;
|
||||
this.message.error("获取文章数据失败!");
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
changePaginationPage(page: number): void {
|
||||
console.log('Changing page to:', page);
|
||||
this.searchParams['page'] = page - 1;
|
||||
this.cdr.detectChanges();
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
changePaginationSize(size: number): void {
|
||||
console.log('Changing page size to:', size);
|
||||
this.searchParams['page'] = 0;
|
||||
this.searchParams['size'] = size;
|
||||
this.cdr.detectChanges();
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
editPrice(article: ArticlePrice) {
|
||||
console.log('Editing article:', article);
|
||||
const modalRef = this.modal.create<SetArticlePriceComponent>({
|
||||
nzTitle: '编辑文章价格',
|
||||
nzContent: SetArticlePriceComponent,
|
||||
nzData: article,
|
||||
nzFooter: null
|
||||
});
|
||||
|
||||
modalRef.afterClose.subscribe(result => {
|
||||
if (result) {
|
||||
this.loadData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 将元转换为分
|
||||
private convertYuanToCents(yuan: number): number {
|
||||
return Math.round(yuan * 100);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<form nz-form [formGroup]="editForm" nzLayout="vertical">
|
||||
<nz-form-item>
|
||||
<nz-form-label>价格(元)</nz-form-label>
|
||||
<nz-form-control>
|
||||
<nz-input-number formControlName="price" [nzMin]="0" [nzStep]="0.01"></nz-input-number>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<nz-form-item>
|
||||
<nz-form-label>折扣</nz-form-label>
|
||||
<nz-form-control>
|
||||
<nz-input-number formControlName="discount" [nzMin]="0" [nzMax]="1" [nzStep]="0.1"></nz-input-number>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button nz-button nzType="default" (click)="handleCancel()">取消</button>
|
||||
<button nz-button nzType="primary" (click)="handleOk()" [disabled]="!editForm.valid">保存</button>
|
||||
</div>
|
||||
</form>
|
@ -0,0 +1,21 @@
|
||||
:host {
|
||||
display: block;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
nz-form-item {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
nz-input-number {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
margin-top: 24px;
|
||||
text-align: right;
|
||||
|
||||
button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { NzFormModule } from 'ng-zorro-antd/form';
|
||||
import { NzInputNumberModule } from 'ng-zorro-antd/input-number';
|
||||
import { NzModalRef, NZ_MODAL_DATA } from 'ng-zorro-antd/modal';
|
||||
import {ArticlePrice, Price, PriceType} from '../../../../core/models/price';
|
||||
import { PriceService } from '../../price.service';
|
||||
import { NzMessageService } from 'ng-zorro-antd/message';
|
||||
import { NzButtonModule } from 'ng-zorro-antd/button';
|
||||
|
||||
@Component({
|
||||
selector: 'app-set-article-price',
|
||||
standalone: true,
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
NzFormModule,
|
||||
NzInputNumberModule,
|
||||
NzButtonModule
|
||||
],
|
||||
templateUrl: './set-article-price.component.html',
|
||||
styleUrls: ['./set-article-price.component.scss']
|
||||
})
|
||||
export class SetArticlePriceComponent {
|
||||
editForm: FormGroup;
|
||||
article: ArticlePrice;
|
||||
priceType = PriceType;
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private modalRef: NzModalRef,
|
||||
private priceService: PriceService,
|
||||
private message: NzMessageService,
|
||||
@Inject(NZ_MODAL_DATA) data: ArticlePrice
|
||||
) {
|
||||
this.article = data;
|
||||
console.log(this.article)
|
||||
const price = data?.price?.amount || 0;
|
||||
const discount = data?.price?.discount || 1;
|
||||
|
||||
this.editForm = this.fb.group({
|
||||
price: [price / 100, [Validators.required, Validators.min(0)]],
|
||||
discount: [discount, [Validators.required, Validators.min(0), Validators.max(1)]]
|
||||
});
|
||||
}
|
||||
|
||||
handleOk(): void {
|
||||
if (this.editForm.valid) {
|
||||
const formValue = this.editForm.value;
|
||||
const priceData = {
|
||||
targetId:this.article.article.eventId,
|
||||
type:this.priceType.TypeArticle,
|
||||
amount: Math.round(formValue.price * 100),
|
||||
discount: formValue.discount
|
||||
};
|
||||
|
||||
this.priceService.update(priceData as Price).subscribe({
|
||||
next: () => {
|
||||
this.message.success('价格更新成功');
|
||||
this.modalRef.close(true);
|
||||
},
|
||||
error: (error) => {
|
||||
this.message.error('价格更新失败');
|
||||
console.error('Error updating price:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleCancel(): void {
|
||||
this.modalRef.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
<nz-card>
|
||||
<!-- 搜索区域 -->
|
||||
<div class="search-area">
|
||||
<nz-input-group [nzSuffix]="suffixIconSearch">
|
||||
<input type="text" nz-input placeholder="搜索专栏名称" [(ngModel)]="searchParams['search_like_username']" (ngModelChange)="onValueChange.next($event)" />
|
||||
</nz-input-group>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<nz-table #basicTable
|
||||
style="width: 100%"
|
||||
nzSize="middle"
|
||||
nzHideOnSinglePage
|
||||
[nzLoading]="loading"
|
||||
[nzShowTotal]="totalTemplate"
|
||||
[nzFrontPagination]="false"
|
||||
[nzPageSizeOptions]="[10,20,30]"
|
||||
[nzShowSizeChanger]="true"
|
||||
[nzPageIndex]="pageData?.pageNumber! +1 "
|
||||
[nzPageSize]="pageData?.pageSize!"
|
||||
[nzTotal]="pageData?.totalElements!"
|
||||
[nzShowPagination]="true"
|
||||
[nzData]="pageData?.items!"
|
||||
(nzPageIndexChange)="changePaginationPage($event)"
|
||||
(nzPageSizeChange)="changePaginationSize($event)">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>专栏ID</th>
|
||||
<th>专栏名称</th>
|
||||
<th>一个月价格(元)</th>
|
||||
<th>三个月价格(元)</th>
|
||||
<th>半年价格(元)</th>
|
||||
<th>一年价格(元)</th>
|
||||
<th>首月折扣</th>
|
||||
<th>折扣</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let data of basicTable.data">
|
||||
<td>{{ data.column.id }}</td>
|
||||
<td>{{ data.column.title }}</td>
|
||||
<td>{{ data.price?.oneMonthPrice ? data.price.oneMonthPrice / 100 :'默认价格' }}</td>
|
||||
<td>{{ data.price?.threeMonthsPrice ? data.price.threeMonthsPrice / 100 :'默认价格'}}</td>
|
||||
<td>{{ data.price?.sixMonthsPrice ? data.price.sixMonthsPrice / 100 :'默认价格'}}</td>
|
||||
<td>{{ data.price?.oneYearPrice ? data.price.oneYearPrice / 100 :'默认价格'}}</td>
|
||||
<td>{{ data.price?.firstMontDiscount ? data.price.firstMontDiscount * 100 :'默认折扣'}}</td>
|
||||
<td>{{ data.price?.discount ? data.price.discount * 100 :'默认折扣'}}</td>
|
||||
<td>
|
||||
<a (click)="editPrice(data)">编辑价格</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</nz-table>
|
||||
<ng-template #totalTemplate let-range="range" let-total>
|
||||
第<span style="padding: 0 0.5em;font-weight: bolder">{{range[0]}}-{{range[1]}}</span>个,共<span
|
||||
style="padding: 0 0.5em;font-weight: bolder">{{total}}</span>个
|
||||
</ng-template>
|
||||
</nz-card>
|
||||
|
||||
<ng-template #suffixIconSearch>
|
||||
<span nz-icon nzType="search"></span>
|
||||
</ng-template>
|
@ -0,0 +1,137 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Page } from '../../../core/models/page';
|
||||
import { ColumnPrice } from '../../../core/models/price';
|
||||
import { PriceService } from '../price.service';
|
||||
import { debounceTime, distinctUntilChanged, Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { ChangeDetectorRef } from '@angular/core';
|
||||
import { NzMessageModule, NzMessageService } from 'ng-zorro-antd/message';
|
||||
import { NzModalModule, NzModalService } from 'ng-zorro-antd/modal';
|
||||
import { NzFormModule } from 'ng-zorro-antd/form';
|
||||
import { NzInputModule } from 'ng-zorro-antd/input';
|
||||
import { NzInputNumberModule } from 'ng-zorro-antd/input-number';
|
||||
import { NzCardModule } from 'ng-zorro-antd/card';
|
||||
import { NzTableModule } from 'ng-zorro-antd/table';
|
||||
import { NzIconModule } from 'ng-zorro-antd/icon';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import {SetColumnPriceComponent} from "./set-column-price/set-column-price.component";
|
||||
import {NgForOf} from "@angular/common";
|
||||
|
||||
@Component({
|
||||
selector: 'app-column',
|
||||
standalone: true,
|
||||
imports: [
|
||||
NzMessageModule,
|
||||
NzModalModule,
|
||||
NzFormModule,
|
||||
NzInputModule,
|
||||
NzInputNumberModule,
|
||||
NzCardModule,
|
||||
NzTableModule,
|
||||
NzIconModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgForOf
|
||||
],
|
||||
templateUrl: './column.component.html',
|
||||
styleUrl: './column.component.scss'
|
||||
})
|
||||
export class ColumnComponent implements OnInit, OnDestroy {
|
||||
pageData: Page<ColumnPrice> = new Page<ColumnPrice>();
|
||||
searchParams: { [param: string]: any } = {
|
||||
page: 0,
|
||||
size: 10,
|
||||
search_like_username: ''
|
||||
};
|
||||
onValueChange = new Subject<string>();
|
||||
loading = false;
|
||||
destroy$ = new Subject();
|
||||
editForm: FormGroup;
|
||||
|
||||
constructor(
|
||||
private priceService: PriceService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private message: NzMessageService,
|
||||
private modal: NzModalService,
|
||||
private fb: FormBuilder
|
||||
) {
|
||||
this.onValueChange.pipe(
|
||||
debounceTime(300),
|
||||
distinctUntilChanged(),
|
||||
takeUntil(this.destroy$)
|
||||
).subscribe(() => {
|
||||
this.searchParams['page'] = 0;
|
||||
this.cdr.markForCheck();
|
||||
this.loadData();
|
||||
});
|
||||
|
||||
this.editForm = this.fb.group({
|
||||
oneMonthPrice: [null, [Validators.required, Validators.min(0)]],
|
||||
threeMonthsPrice: [null, [Validators.required, Validators.min(0)]],
|
||||
sixMonthsPrice: [null, [Validators.required, Validators.min(0)]],
|
||||
oneYearPrice: [null, [Validators.required, Validators.min(0)]]
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next(null);
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
loadData() {
|
||||
this.loading = true;
|
||||
this.cdr.detectChanges();
|
||||
this.priceService.pageOfColumn(this.searchParams)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: data => {
|
||||
this.loading = false;
|
||||
this.pageData = data;
|
||||
},
|
||||
error: data => {
|
||||
console.log(data)
|
||||
this.loading = false;
|
||||
this.message.error("获取专栏数据失败!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
changePaginationPage(page: number): void {
|
||||
this.searchParams['page'] = page - 1;
|
||||
this.cdr.detectChanges();
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
changePaginationSize(size: number): void {
|
||||
this.searchParams['page'] = 0;
|
||||
this.searchParams['size'] = size;
|
||||
this.cdr.detectChanges();
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
editPrice(columnPrice: ColumnPrice) {
|
||||
console.log('Editing column:', columnPrice);
|
||||
const modalRef = this.modal.create<SetColumnPriceComponent>({
|
||||
nzTitle: '编辑专栏价格',
|
||||
nzContent: SetColumnPriceComponent,
|
||||
nzData: columnPrice,
|
||||
nzFooter: null
|
||||
});
|
||||
|
||||
modalRef.afterClose.subscribe(result => {
|
||||
if (result) {
|
||||
this.loadData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 将元转换为分
|
||||
private convertYuanToCents(yuan: number): number {
|
||||
return Math.round(yuan * 100);
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
<form nz-form [formGroup]="editForm" nzLayout="vertical">
|
||||
<nz-form-item>
|
||||
<nz-form-label>一个月价格</nz-form-label>
|
||||
<nz-form-control nzErrorTip="请输入有效的价格">
|
||||
<nz-input-number class="full-width" formControlName="oneMonthPrice" [nzMin]="0" [nzStep]="0.01" [nzPrecision]="2" [nzPlaceHolder]="'请输入价格'" [nzFormatter]="yuanFormatter" [nzParser]="yuanParser"></nz-input-number>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<nz-form-item>
|
||||
<nz-form-label>三个月价格</nz-form-label>
|
||||
<nz-form-control nzErrorTip="请输入有效的价格">
|
||||
<nz-input-number class="full-width" formControlName="threeMonthsPrice" [nzMin]="0" [nzStep]="0.01" [nzPrecision]="2" [nzPlaceHolder]="'请输入价格'" [nzFormatter]="yuanFormatter" [nzParser]="yuanParser"></nz-input-number>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<nz-form-item>
|
||||
<nz-form-label>半年价格</nz-form-label>
|
||||
<nz-form-control nzErrorTip="请输入有效的价格">
|
||||
<nz-input-number class="full-width" formControlName="sixMonthsPrice" [nzMin]="0" [nzStep]="0.01" [nzPrecision]="2" [nzPlaceHolder]="'请输入价格'" [nzFormatter]="yuanFormatter" [nzParser]="yuanParser"></nz-input-number>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<nz-form-item>
|
||||
<nz-form-label>一年价格</nz-form-label>
|
||||
<nz-form-control nzErrorTip="请输入有效的价格">
|
||||
<nz-input-number class="full-width" formControlName="oneYearPrice" [nzMin]="0" [nzStep]="0.01" [nzPrecision]="2" [nzPlaceHolder]="'请输入价格'" [nzFormatter]="yuanFormatter" [nzParser]="yuanParser"></nz-input-number>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<nz-form-item>
|
||||
<nz-form-label>首月折扣</nz-form-label>
|
||||
<nz-form-control nzErrorTip="请输入0-1之间的折扣">
|
||||
<nz-input-number class="full-width" formControlName="firstMontDiscount" [nzMin]="0" [nzMax]="1" [nzStep]="0.1" [nzPrecision]="2" [nzPlaceHolder]="'请输入折扣'" [nzFormatter]="percentFormatter" [nzParser]="percentParser"></nz-input-number>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<nz-form-item>
|
||||
<nz-form-label>折扣</nz-form-label>
|
||||
<nz-form-control nzErrorTip="请输入0-1之间的折扣">
|
||||
<nz-input-number class="full-width" formControlName="discount" [nzMin]="0" [nzMax]="1" [nzStep]="0.1" [nzPrecision]="2" [nzPlaceHolder]="'请输入折扣'" [nzFormatter]="percentFormatter" [nzParser]="percentParser"></nz-input-number>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button nz-button nzType="default" (click)="handleCancel()">取消</button>
|
||||
<button nz-button nzType="primary" (click)="handleOk()" [disabled]="!editForm.valid">保存</button>
|
||||
</div>
|
||||
</form>
|
@ -0,0 +1,31 @@
|
||||
.modal-footer {
|
||||
margin-top: 24px;
|
||||
text-align: right;
|
||||
|
||||
button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
nz-form-item {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
nz-form-label {
|
||||
font-weight: 500;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
|
||||
nz-input-number {
|
||||
width: 100%;
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { NzFormModule } from 'ng-zorro-antd/form';
|
||||
import { NzInputNumberModule } from 'ng-zorro-antd/input-number';
|
||||
import { NzModalRef, NZ_MODAL_DATA } from 'ng-zorro-antd/modal';
|
||||
import { ColumnPrice } from '../../../../core/models/price';
|
||||
import { PriceService } from '../../price.service';
|
||||
import { NzMessageService } from 'ng-zorro-antd/message';
|
||||
import { NzButtonModule } from 'ng-zorro-antd/button';
|
||||
|
||||
@Component({
|
||||
selector: 'app-set-column-price',
|
||||
standalone: true,
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
NzFormModule,
|
||||
NzInputNumberModule,
|
||||
NzButtonModule
|
||||
],
|
||||
templateUrl: './set-column-price.component.html',
|
||||
styleUrls: ['./set-column-price.component.scss']
|
||||
})
|
||||
export class SetColumnPriceComponent {
|
||||
editForm: FormGroup;
|
||||
column: ColumnPrice;
|
||||
|
||||
// 价格格式化器
|
||||
yuanFormatter = (value: number): string => {
|
||||
return value ? `¥ ${value}` : '';
|
||||
};
|
||||
|
||||
yuanParser = (value: string): string => {
|
||||
return value.replace(/[^0-9.]/g, '');
|
||||
};
|
||||
|
||||
// 百分比格式化器
|
||||
percentFormatter = (value: number): string => {
|
||||
return value ? `${(value * 100).toFixed(0)}%` : '';
|
||||
};
|
||||
|
||||
percentParser = (value: string): string => {
|
||||
return value.replace('%', '').trim();
|
||||
};
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private modalRef: NzModalRef,
|
||||
private priceService: PriceService,
|
||||
private message: NzMessageService,
|
||||
@Inject(NZ_MODAL_DATA) data: ColumnPrice
|
||||
) {
|
||||
this.column = data;
|
||||
const price = data?.price || {};
|
||||
|
||||
this.editForm = this.fb.group({
|
||||
oneMonthPrice: [price?.oneMonthPrice ? price.oneMonthPrice / 100 : 0, [Validators.required, Validators.min(0)]],
|
||||
threeMonthsPrice: [price?.threeMonthsPrice ? price.threeMonthsPrice / 100 : 0, [Validators.required, Validators.min(0)]],
|
||||
sixMonthsPrice: [price?.sixMonthsPrice ? price.sixMonthsPrice / 100 : 0, [Validators.required, Validators.min(0)]],
|
||||
oneYearPrice: [price?.oneYearPrice ? price.oneYearPrice / 100 : 0, [Validators.required, Validators.min(0)]],
|
||||
firstMontDiscount: [price?.firstMontDiscount || 1, [Validators.required, Validators.min(0), Validators.max(1)]],
|
||||
discount: [price?.discount || 1, [Validators.required, Validators.min(0), Validators.max(1)]]
|
||||
});
|
||||
}
|
||||
|
||||
handleOk(): void {
|
||||
if (this.editForm.valid) {
|
||||
const formValue = this.editForm.value;
|
||||
const priceData = {
|
||||
...this.column.price,
|
||||
oneMonthPrice: Math.round(formValue.oneMonthPrice * 100),
|
||||
threeMonthsPrice: Math.round(formValue.threeMonthsPrice * 100),
|
||||
sixMonthsPrice: Math.round(formValue.sixMonthsPrice * 100),
|
||||
oneYearPrice: Math.round(formValue.oneYearPrice * 100),
|
||||
firstMontDiscount: formValue.firstMontDiscount,
|
||||
discount: formValue.discount
|
||||
};
|
||||
|
||||
this.priceService.update(priceData).subscribe({
|
||||
next: () => {
|
||||
this.message.success('价格更新成功');
|
||||
this.modalRef.close(true);
|
||||
},
|
||||
error: (error) => {
|
||||
this.message.error('价格更新失败');
|
||||
console.error('Error updating price:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleCancel(): void {
|
||||
this.modalRef.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { NzFormModule } from 'ng-zorro-antd/form';
|
||||
import { NzInputModule } from 'ng-zorro-antd/input';
|
||||
import { NzButtonModule } from 'ng-zorro-antd/button';
|
||||
import { NzMessageService } from 'ng-zorro-antd/message';
|
||||
import { PriceService } from '../price.service';
|
||||
import { PriceDefault } from '../../../core/models/price';
|
||||
import { NzInputNumberModule } from 'ng-zorro-antd/input-number';
|
||||
import { NzCardModule } from 'ng-zorro-antd/card';
|
||||
|
||||
@Component({
|
||||
selector: 'app-price-default',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NzFormModule,
|
||||
NzInputModule,
|
||||
NzButtonModule,
|
||||
NzInputNumberModule,
|
||||
NzCardModule,
|
||||
],
|
||||
templateUrl: './price-default.component.html',
|
||||
styleUrl: './price-default.component.scss'
|
||||
})
|
||||
export class PriceDefaultComponent implements OnInit {
|
||||
priceForm: FormGroup;
|
||||
loading = false;
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private priceService: PriceService,
|
||||
private message: NzMessageService
|
||||
) {
|
||||
this.priceForm = this.fb.group({
|
||||
amount: [null, [Validators.required, Validators.min(0)]],
|
||||
firstMontDiscount: [null, [Validators.required, Validators.min(0), Validators.max(100)]],
|
||||
oneMonthPrice: [null, [Validators.required, Validators.min(0)]],
|
||||
threeMonthsPrice: [null, [Validators.required, Validators.min(0)]],
|
||||
sixMonthsPrice: [null, [Validators.required, Validators.min(0)]],
|
||||
oneYearPrice: [null, [Validators.required, Validators.min(0)]],
|
||||
discount: [null, [Validators.required, Validators.min(0), Validators.max(100)]]
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.loadDefaultPrice();
|
||||
}
|
||||
|
||||
// 将分转换为元
|
||||
private convertCentsToYuan(cents: number): number {
|
||||
return cents / 100;
|
||||
}
|
||||
|
||||
// 将元转换为分
|
||||
private convertYuanToCents(yuan: number): number {
|
||||
return Math.round(yuan * 100);
|
||||
}
|
||||
|
||||
loadDefaultPrice() {
|
||||
this.loading = true;
|
||||
this.priceService.getDefaultPrice().subscribe({
|
||||
next: (data) => {
|
||||
// 将后端返回的分转换为元
|
||||
const formData = {
|
||||
...data,
|
||||
amount: this.convertCentsToYuan(data.amount),
|
||||
oneMonthPrice: this.convertCentsToYuan(data.oneMonthPrice),
|
||||
threeMonthsPrice: this.convertCentsToYuan(data.threeMonthsPrice),
|
||||
sixMonthsPrice: this.convertCentsToYuan(data.sixMonthsPrice),
|
||||
oneYearPrice: this.convertCentsToYuan(data.oneYearPrice)
|
||||
};
|
||||
this.priceForm.patchValue(formData);
|
||||
this.loading = false;
|
||||
},
|
||||
error: (error) => {
|
||||
this.message.error('获取默认价格失败');
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
if (this.priceForm.valid) {
|
||||
this.loading = true;
|
||||
const formValue = this.priceForm.value;
|
||||
|
||||
// 将表单中的元转换为分
|
||||
const priceData: PriceDefault = {
|
||||
id: 1,
|
||||
amount: this.convertYuanToCents(formValue.amount),
|
||||
firstMontDiscount: formValue.firstMontDiscount,
|
||||
oneMonthPrice: this.convertYuanToCents(formValue.oneMonthPrice),
|
||||
threeMonthsPrice: this.convertYuanToCents(formValue.threeMonthsPrice),
|
||||
sixMonthsPrice: this.convertYuanToCents(formValue.sixMonthsPrice),
|
||||
oneYearPrice: this.convertYuanToCents(formValue.oneYearPrice),
|
||||
discount: formValue.discount
|
||||
};
|
||||
|
||||
this.priceService.updateDefaultPrice(priceData).subscribe({
|
||||
next: () => {
|
||||
this.message.success('更新默认价格成功');
|
||||
this.loadDefaultPrice();
|
||||
},
|
||||
error: (error) => {
|
||||
this.message.error('更新默认价格失败');
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Object.values(this.priceForm.controls).forEach(control => {
|
||||
if (control.invalid) {
|
||||
control.markAsTouched();
|
||||
control.updateValueAndValidity({ onlySelf: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,28 @@
|
||||
|
||||
<nz-page-header nzTitle="Title" nzSubtitle="This is a subtitle">
|
||||
<nz-page-header nzTitle="价格管理" nzSubtitle="专栏和文章价格管理界面">
|
||||
<nz-breadcrumb nz-page-header-breadcrumb>
|
||||
<nz-breadcrumb-item>First-level Menu</nz-breadcrumb-item>
|
||||
<nz-breadcrumb-item>后台管理</nz-breadcrumb-item>
|
||||
<nz-breadcrumb-item>
|
||||
<a>Second-level Menu</a>
|
||||
<a>价格管理</a>
|
||||
</nz-breadcrumb-item>
|
||||
<nz-breadcrumb-item>Third-level Menu</nz-breadcrumb-item>
|
||||
<nz-breadcrumb-item>价格管理</nz-breadcrumb-item>
|
||||
</nz-breadcrumb>
|
||||
</nz-page-header>
|
||||
|
||||
<br />
|
||||
<nz-card style="width: 100%;">
|
||||
<nz-card-tab>
|
||||
<nz-tabset nzSize="large" >
|
||||
<nz-tab nzTitle="默认价格">
|
||||
<app-price-default></app-price-default>
|
||||
</nz-tab>
|
||||
<nz-tab nzTitle="专栏">
|
||||
<app-column></app-column>
|
||||
</nz-tab>
|
||||
<nz-tab nzTitle="文章">
|
||||
<app-article></app-article>
|
||||
</nz-tab>
|
||||
</nz-tabset>
|
||||
</nz-card-tab>
|
||||
|
||||
</nz-card>
|
||||
|
||||
|
@ -1,22 +0,0 @@
|
||||
import { fakeAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { PriceComponent } from './price.component';
|
||||
|
||||
describe('PriceComponent', () => {
|
||||
let component: PriceComponent;
|
||||
let fixture: ComponentFixture<PriceComponent>;
|
||||
|
||||
beforeEach(fakeAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ PriceComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(PriceComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should compile', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,53 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {Observable} from "rxjs";
|
||||
import {Price, PriceDefault} from "../../core/models/price";
|
||||
import {extractData, Page} from "../../core/models/page";
|
||||
import {map} from "rxjs/operators";
|
||||
import {HttpUtils} from "../../core/until/http.utils";
|
||||
import { ArticlePrice, ColumnPrice } from '../../core/models/price';
|
||||
import {Article} from "../../core/models/article";
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class PriceService {
|
||||
|
||||
constructor(private http:HttpClient) { }
|
||||
|
||||
// auth.POST("/create", h.create)
|
||||
// auth.GET("/get-default", h.getPriceDefault)
|
||||
// auth.POST("/update-default", h.updatePriceDefault)
|
||||
// auth.POST("/update", h.update)
|
||||
// auth.GET("/page-article", h.getPriceArticle)
|
||||
// auth.GET("/page-column", h.getPriceColumn)
|
||||
// pageOfArticle(searchParams: { [key: string]: string }):Observable<Page<ArticlePrice>> {
|
||||
// return this.http.get<Page<ArticlePrice>>('/api/price/page-article"',
|
||||
// {params:HttpUtils.getSearchParams(searchParams)}).pipe(
|
||||
// map(response=> extractData<ArticlePrice>(response))
|
||||
// )
|
||||
// }
|
||||
|
||||
pageOfArticle(searchParams:{[key:string]:string}):Observable<Page<ArticlePrice>> {
|
||||
return this.http.get<Page<ArticlePrice>>('/api/price/page-article',{
|
||||
params:HttpUtils.getSearchParams(searchParams)
|
||||
}).pipe(map(response => extractData<ArticlePrice>(response)))
|
||||
}
|
||||
|
||||
pageOfColumn(searchParams:{[key:string]:string}):Observable<Page<ColumnPrice>> {
|
||||
console.log("come in")
|
||||
return this.http.get<Page<ColumnPrice>>('/api/price/page-column',{
|
||||
params:HttpUtils.getSearchParams(searchParams)
|
||||
}).pipe(
|
||||
map(response => extractData<ColumnPrice>(response ))
|
||||
)
|
||||
}
|
||||
getDefaultPrice():Observable<PriceDefault> {
|
||||
return this.http.get<PriceDefault>('/api/price/get-default')
|
||||
}
|
||||
updateDefaultPrice(pd:PriceDefault):Observable<null> {
|
||||
return this.http.post<null>('/api/price/update-default',pd)
|
||||
}
|
||||
update(price: Price): Observable<any> {
|
||||
return this.http.post('/api/price/update', price);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue