Browse Source
- 新增 config.json 文件用于网站配置 - 添加 ConfigService 服务加载配置信息 - 更新 App 组件和 Login 组件以使用新配置 - 修改版权组件以动态显示备案号和公司名称 - 优化组织信息组件,添加技术支持联系功能 - 更新页面标题策略以使用配置中的网站名称main
19 changed files with 404 additions and 282 deletions
@ -0,0 +1,17 @@ |
|||||
|
{ |
||||
|
"websiteInfo": { |
||||
|
"name": "营养配餐实训系统" |
||||
|
}, |
||||
|
"assets": { |
||||
|
"logo": "/assets/images/jl-logo.png", |
||||
|
"favicon": "/assets/favicon.ico", |
||||
|
"loginBackground": "/assets/images/jl-logo.png" |
||||
|
}, |
||||
|
"contactInfo": { |
||||
|
"phoneNumber": "19181752603" |
||||
|
}, |
||||
|
"copyrightInfo": { |
||||
|
"icpLicense": "蜀ICP备2023038072号", |
||||
|
"companyName": "" |
||||
|
} |
||||
|
} |
@ -1 +1 @@ |
|||||
export const appName = "营养配餐系统"; |
export const appName = '营养配餐系统' |
||||
|
@ -1,10 +1,6 @@ |
|||||
<div class="flex items-center justify-center pb-2"> |
<div class="flex items-center justify-center pb-2"> |
||||
<a |
<a *ngIf="icpLicense" href="https://beian.miit.gov.cn/" target="_blank" class="text-black text-opacity-70"> |
||||
href="https://beian.miit.gov.cn/" |
{{ icpLicense }} |
||||
target="_blank" |
|
||||
class="text-black text-opacity-70" |
|
||||
> |
|
||||
蜀ICP备2023038072号 |
|
||||
</a> |
</a> |
||||
<span class="text-black text-opacity-70 ml-4"> ©{{ year }} 成都积络科技有限公司</span> |
<span *ngIf="companyName" class="text-black text-opacity-70 ml-4"> ©{{ year }} {{ companyName }}</span> |
||||
</div> |
</div> |
||||
|
@ -1,10 +1,12 @@ |
|||||
import { Component } from "@angular/core"; |
import { Component, Input } from '@angular/core' |
||||
|
|
||||
@Component({ |
@Component({ |
||||
selector: "lib-copyright", |
selector: 'lib-copyright', |
||||
templateUrl: "./copyright.component.html", |
templateUrl: './copyright.component.html', |
||||
styleUrls: ["./copyright.component.less"], |
styleUrls: ['./copyright.component.less'], |
||||
}) |
}) |
||||
export class CopyrightComponent { |
export class CopyrightComponent { |
||||
year = new Date().getFullYear(); |
@Input() icpLicense?: string |
||||
|
@Input() companyName?: string |
||||
|
year = new Date().getFullYear() |
||||
} |
} |
||||
|
@ -1,59 +1,86 @@ |
|||||
export type AnyObject = { [k: string]: any }; |
export type AnyObject = { [k: string]: any } |
||||
|
|
||||
export type DecSafeAny = any; |
export type DecSafeAny = any |
||||
|
|
||||
export type DecText = number | string; |
export type DecText = number | string |
||||
|
|
||||
export type Augmented<O extends object> = O & AnyObject; |
export type Augmented<O extends object> = O & AnyObject |
||||
|
|
||||
export type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>; |
export type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>> |
||||
|
|
||||
export type NutrientInterface = { |
export type NutrientInterface = { |
||||
key: string; |
key: string |
||||
measurement: string; |
measurement: string |
||||
nrv: number; |
nrv: number |
||||
value: string; |
value: string |
||||
}; |
} |
||||
|
|
||||
export type OptionItemInterface = Augmented<{ |
export type OptionItemInterface = Augmented<{ |
||||
label: string; |
label: string |
||||
value: string; |
value: string |
||||
}>; |
}> |
||||
|
|
||||
export interface ResponseType<T = any> { |
export interface ResponseType<T = any> { |
||||
body: T; |
body: T |
||||
code: number; |
code: number |
||||
desc: string; |
desc: string |
||||
success: boolean; |
success: boolean |
||||
} |
} |
||||
export type MyResponse<R = any> = ResponseType<R>; |
export type MyResponse<R = any> = ResponseType<R> |
||||
|
|
||||
export interface TableListColumns { |
export interface TableListColumns { |
||||
key: string; |
key: string |
||||
title: string; |
title: string |
||||
visible?: boolean; |
visible?: boolean |
||||
width?: string; |
width?: string |
||||
sort?: boolean; |
sort?: boolean |
||||
order?: number; |
order?: number |
||||
disabled?: boolean; |
disabled?: boolean |
||||
coverStorage?: boolean; |
coverStorage?: boolean |
||||
} |
} |
||||
|
|
||||
export interface TableOperation { |
export interface TableOperation { |
||||
title: string; |
title: string |
||||
href?: string; |
href?: string |
||||
link?: string[]; |
link?: string[] |
||||
target?: string; |
target?: string |
||||
premissions: string[]; |
premissions: string[] |
||||
danger?: boolean; |
danger?: boolean |
||||
disabled?: boolean; |
disabled?: boolean |
||||
onClick?: (v: DecSafeAny) => void; |
onClick?: (v: DecSafeAny) => void |
||||
visible?: (v: DecSafeAny) => boolean; |
visible?: (v: DecSafeAny) => boolean |
||||
} |
} |
||||
|
|
||||
export interface PageResult<T = DecSafeAny> { |
export interface PageResult<T = DecSafeAny> { |
||||
total: number; |
total: number |
||||
content: T[]; |
content: T[] |
||||
totalElements: number; |
totalElements: number |
||||
totalPages: number; |
totalPages: number |
||||
|
} |
||||
|
|
||||
|
export interface WebSiteConfig { |
||||
|
websiteInfo: WebsiteInfo |
||||
|
assets: Assets |
||||
|
contactInfo: ContactInfo |
||||
|
copyrightInfo: CopyrightInfo |
||||
|
} |
||||
|
|
||||
|
export interface Assets { |
||||
|
logo: string |
||||
|
favicon: string |
||||
|
loginBackground: string |
||||
|
} |
||||
|
|
||||
|
export interface ContactInfo { |
||||
|
phoneNumber: string |
||||
|
} |
||||
|
|
||||
|
export interface CopyrightInfo { |
||||
|
icpLicense: string |
||||
|
companyName: string |
||||
|
yeah: number |
||||
|
} |
||||
|
|
||||
|
export interface WebsiteInfo { |
||||
|
name: string |
||||
} |
} |
||||
|
@ -1,10 +1,27 @@ |
|||||
import { Component } from '@angular/core'; |
import { Component } from '@angular/core' |
||||
|
import { WebSiteConfig } from '@cdk/types' |
||||
|
import { ConfigService } from './services/config.service' |
||||
|
|
||||
@Component({ |
@Component({ |
||||
selector: 'app-root', |
selector: 'app-root', |
||||
templateUrl: './app.component.html', |
templateUrl: './app.component.html', |
||||
styleUrls: ['./app.component.less'] |
styleUrls: ['./app.component.less'], |
||||
}) |
}) |
||||
export class AppComponent { |
export class AppComponent { |
||||
title = 'client'; |
title = 'client' |
||||
|
|
||||
|
appConfig: WebSiteConfig | null = null |
||||
|
constructor(private configService: ConfigService) {} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.appConfig = this.configService.getConfig() |
||||
|
if (this.appConfig) { |
||||
|
if (this.appConfig?.assets?.favicon) { |
||||
|
;(document.querySelector('#favicon') as HTMLLinkElement).href = this.appConfig.assets.favicon |
||||
|
} |
||||
|
if (this.appConfig?.websiteInfo?.name) { |
||||
|
document.title = this.appConfig.websiteInfo.name + (document.title ? ' - ' + document.title : '') |
||||
|
} |
||||
|
} |
||||
|
} |
||||
} |
} |
||||
|
@ -1,60 +1,64 @@ |
|||||
import { Component, OnInit } from "@angular/core"; |
import { Component, OnInit } from '@angular/core' |
||||
import { NavigationEnd, Router, RouterModule } from "@angular/router"; |
import { NavigationEnd, Router, RouterModule } from '@angular/router' |
||||
import { NzMessageService } from "ng-zorro-antd/message"; |
import { NzMessageService } from 'ng-zorro-antd/message' |
||||
|
|
||||
import { NzModalService } from "ng-zorro-antd/modal"; |
import { NzModalService } from 'ng-zorro-antd/modal' |
||||
import { Subject, filter, lastValueFrom, takeUntil } from "rxjs"; |
import { Subject, filter, lastValueFrom, takeUntil } from 'rxjs' |
||||
import { ApiService } from "@cdk/services"; |
import { ApiService } from '@cdk/services' |
||||
import { appName } from "@cdk/app-settings"; |
|
||||
|
import { WebSiteConfig } from '@cdk/types' |
||||
|
import { ConfigService } from '@client/app/services/config.service' |
||||
|
|
||||
@Component({ |
@Component({ |
||||
selector: "app-layout", |
selector: 'app-layout', |
||||
templateUrl: "./app-layout.component.html", |
templateUrl: './app-layout.component.html', |
||||
styleUrls: ["./app-layout.component.less"], |
styleUrls: ['./app-layout.component.less'], |
||||
}) |
}) |
||||
export class AppLayoutComponent implements OnInit { |
export class AppLayoutComponent implements OnInit { |
||||
constructor( |
constructor( |
||||
private router: Router, |
private router: Router, |
||||
private modal: NzModalService, |
private modal: NzModalService, |
||||
private msg: NzMessageService, |
private msg: NzMessageService, |
||||
private api: ApiService |
private api: ApiService, |
||||
|
private configService: ConfigService, |
||||
) { |
) { |
||||
this.router.events |
this.router.events |
||||
.pipe( |
.pipe( |
||||
takeUntil(this.unSubscribe$), |
takeUntil(this.unSubscribe$), |
||||
filter((e): e is NavigationEnd => e instanceof NavigationEnd) |
filter((e): e is NavigationEnd => e instanceof NavigationEnd), |
||||
) |
) |
||||
.subscribe((e) => { |
.subscribe((e) => { |
||||
this.currentUrl = e.url; |
this.currentUrl = e.url |
||||
this.fullPage = ["/ingredient/preview", "/data-vis"].some((s) => e.url.startsWith(s)); |
this.fullPage = ['/ingredient/preview', '/data-vis'].some((s) => e.url.startsWith(s)) |
||||
}); |
}) |
||||
} |
} |
||||
|
|
||||
fullPage = false; |
fullPage = false |
||||
account = this.api.account; |
account = this.api.account |
||||
|
|
||||
appName = appName; |
|
||||
|
|
||||
unSubscribe$ = new Subject<void>(); |
unSubscribe$ = new Subject<void>() |
||||
|
|
||||
currentUrl: string = ""; |
currentUrl: string = '' |
||||
|
appConfig: WebSiteConfig | null = null |
||||
|
|
||||
ngOnInit(): void {} |
ngOnInit(): void { |
||||
|
this.appConfig = this.configService.getConfig() |
||||
|
} |
||||
|
|
||||
openDataVis() { |
openDataVis() { |
||||
window.open("/data-vis"); |
window.open('/data-vis') |
||||
} |
} |
||||
|
|
||||
logout() { |
logout() { |
||||
this.modal.confirm({ |
this.modal.confirm({ |
||||
nzTitle: "警告", |
nzTitle: '警告', |
||||
nzContent: "是否要退出登录?", |
nzContent: '是否要退出登录?', |
||||
nzOnOk: async () => { |
nzOnOk: async () => { |
||||
const res = await lastValueFrom(this.api.logout()); |
const res = await lastValueFrom(this.api.logout()) |
||||
this.msg.success(res.desc); |
this.msg.success(res.desc) |
||||
localStorage.removeItem(this.api.accountKey); |
localStorage.removeItem(this.api.accountKey) |
||||
this.router.navigate(["/login"]); |
this.router.navigate(['/login']) |
||||
}, |
}, |
||||
}); |
}) |
||||
} |
} |
||||
} |
} |
||||
|
@ -1,48 +1,56 @@ |
|||||
import { Component } from "@angular/core"; |
import { Component } from '@angular/core' |
||||
import { FormControl, FormGroup } from "@angular/forms"; |
import { FormControl, FormGroup } from '@angular/forms' |
||||
import { Router } from "@angular/router"; |
import { Router } from '@angular/router' |
||||
import { NzMessageService } from "ng-zorro-antd/message"; |
import { NzMessageService } from 'ng-zorro-antd/message' |
||||
import { FormValidators } from "projects/cdk/src/public-api"; |
import { FormValidators, WebSiteConfig } from 'projects/cdk/src/public-api' |
||||
import { Utils } from "projects/cdk/src/utils"; |
import { Utils } from 'projects/cdk/src/utils' |
||||
import { finalize, lastValueFrom } from "rxjs"; |
import { finalize, lastValueFrom } from 'rxjs' |
||||
import { MD5 } from "crypto-js"; |
import { MD5 } from 'crypto-js' |
||||
import { ApiService } from "@cdk/services"; |
import { ApiService } from '@cdk/services' |
||||
import { appName } from "@cdk/app-settings"; |
|
||||
|
import { ConfigService } from '@client/app/services/config.service' |
||||
|
|
||||
@Component({ |
@Component({ |
||||
selector: "app-login", |
selector: 'app-login', |
||||
templateUrl: "./login.component.html", |
templateUrl: './login.component.html', |
||||
styleUrls: ["./login.component.less"], |
styleUrls: ['./login.component.less'], |
||||
}) |
}) |
||||
export class LoginComponent { |
export class LoginComponent { |
||||
constructor(private msg: NzMessageService, private api: ApiService, private router: Router) {} |
constructor( |
||||
|
private configService: ConfigService, |
||||
|
private msg: NzMessageService, |
||||
|
private api: ApiService, |
||||
|
private router: Router, |
||||
|
) {} |
||||
|
|
||||
public loginForm = new FormGroup({ |
public loginForm = new FormGroup({ |
||||
uid: new FormControl("", [FormValidators.required("请输入账户")]), |
uid: new FormControl('', [FormValidators.required('请输入账户')]), |
||||
pwd: new FormControl("", [FormValidators.required("请输入密码")]), |
pwd: new FormControl('', [FormValidators.required('请输入密码')]), |
||||
}); |
}) |
||||
|
|
||||
public loading: boolean = false; |
public loading: boolean = false |
||||
|
|
||||
appName = appName; |
appConfig: WebSiteConfig | null = null |
||||
|
|
||||
ngOnInit(): void {} |
ngOnInit(): void { |
||||
|
this.appConfig = this.configService.getConfig() |
||||
|
} |
||||
|
|
||||
async onLogin() { |
async onLogin() { |
||||
if (Utils.validateFormGroup(this.loginForm)) { |
if (Utils.validateFormGroup(this.loginForm)) { |
||||
const { value } = this.loginForm; |
const { value } = this.loginForm |
||||
const pwd = value.pwd as string; |
const pwd = value.pwd as string |
||||
value["pwd"] = MD5(pwd).toString().slice(-16).toUpperCase(); |
value['pwd'] = MD5(pwd).toString().slice(-16).toUpperCase() |
||||
this.loading = true; |
this.loading = true |
||||
const res = await lastValueFrom( |
const res = await lastValueFrom( |
||||
this.api.login(value).pipe( |
this.api.login(value).pipe( |
||||
finalize(() => { |
finalize(() => { |
||||
this.loading = false; |
this.loading = false |
||||
}) |
}), |
||||
|
), |
||||
) |
) |
||||
); |
this.msg.success(res.desc) |
||||
this.msg.success(res.desc); |
this.router.navigate(['/']) |
||||
this.router.navigate(["/"]); |
|
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
@ -0,0 +1,38 @@ |
|||||
|
import { Injectable } from '@angular/core' |
||||
|
import { HttpClient } from '@angular/common/http' |
||||
|
import { Observable, of, throwError } from 'rxjs' |
||||
|
import { tap, catchError } from 'rxjs/operators' |
||||
|
import { WebSiteConfig } from '@cdk/public-api' |
||||
|
|
||||
|
@Injectable({ |
||||
|
providedIn: 'root', |
||||
|
}) |
||||
|
export class ConfigService { |
||||
|
private configUrl = 'assets/config.json' |
||||
|
private appConfig: WebSiteConfig | null = null |
||||
|
|
||||
|
constructor(private http: HttpClient) {} |
||||
|
|
||||
|
loadConfig(): Observable<WebSiteConfig> { |
||||
|
if (this.appConfig) { |
||||
|
return of(this.appConfig) |
||||
|
} |
||||
|
|
||||
|
console.log(123) |
||||
|
|
||||
|
return this.http.get<WebSiteConfig>(this.configUrl).pipe( |
||||
|
tap((config) => { |
||||
|
this.appConfig = config |
||||
|
}), |
||||
|
catchError((error) => { |
||||
|
console.error('加载 config.json 失败:', error) |
||||
|
|
||||
|
return throwError(() => new Error('无法加载应用配置')) |
||||
|
}), |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
getConfig(): WebSiteConfig | null { |
||||
|
return this.appConfig |
||||
|
} |
||||
|
} |
@ -1,19 +1,22 @@ |
|||||
import { Injectable } from "@angular/core"; |
import { OnInit } from '@angular/core' |
||||
import { Title } from "@angular/platform-browser"; |
import { ConfigService } from '@client/app/services/config.service' |
||||
import { RouterStateSnapshot, TitleStrategy } from "@angular/router"; |
import { Injectable } from '@angular/core' |
||||
|
import { Title } from '@angular/platform-browser' |
||||
|
import { RouterStateSnapshot, TitleStrategy } from '@angular/router' |
||||
|
|
||||
@Injectable({ providedIn: "root" }) |
@Injectable({ providedIn: 'root' }) |
||||
export class TemplatePageTitleStrategy extends TitleStrategy { |
export class TemplatePageTitleStrategy extends TitleStrategy { |
||||
constructor(private readonly title: Title) { |
fullTitle = '' |
||||
super(); |
constructor(private readonly title: Title, private ConfigService: ConfigService) { |
||||
|
super() |
||||
} |
} |
||||
|
|
||||
override updateTitle(routerState: RouterStateSnapshot) { |
override updateTitle(routerState: RouterStateSnapshot) { |
||||
const title = this.buildTitle(routerState); |
const title = this.buildTitle(routerState) |
||||
let fullTitle = "营养配餐系统"; |
let fullTitle = this.ConfigService.getConfig()?.websiteInfo?.name || '' |
||||
if (title !== undefined) { |
if (title !== undefined) { |
||||
fullTitle += ` | ${title}`; |
fullTitle += ` | ${title}` |
||||
} |
} |
||||
this.title.setTitle(fullTitle); |
this.title.setTitle(fullTitle) |
||||
} |
} |
||||
} |
} |
||||
|
@ -0,0 +1,17 @@ |
|||||
|
{ |
||||
|
"websiteInfo": { |
||||
|
"name": "营养配餐实训系统" |
||||
|
}, |
||||
|
"assets": { |
||||
|
"logo": "/assets/images/jl-logo.png", |
||||
|
"favicon": "/assets/favicon.ico", |
||||
|
"loginBackground": "/assets/images/jl-logo.png" |
||||
|
}, |
||||
|
"contactInfo": { |
||||
|
"phoneNumber": "19181752603" |
||||
|
}, |
||||
|
"copyrightInfo": { |
||||
|
"icpLicense": "蜀ICP备2023038072号", |
||||
|
"companyName": "" |
||||
|
} |
||||
|
} |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
@ -1,16 +1,14 @@ |
|||||
<!doctype html> |
<!DOCTYPE html> |
||||
<html> |
<html> |
||||
|
|
||||
<head> |
<head> |
||||
<meta charset="utf-8"> |
<meta charset="utf-8" /> |
||||
<title>营养配餐系统</title> |
<title></title> |
||||
<base href="/"> |
<base href="/" /> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1"> |
<meta name="viewport" content="width=device-width, initial-scale=1" /> |
||||
<link rel="icon" type="image/x-icon" href="favicon.ico"> |
<link rel="icon" id="favicon" /> |
||||
</head> |
</head> |
||||
|
|
||||
<body> |
<body> |
||||
<app-root></app-root> |
<app-root></app-root> |
||||
</body> |
</body> |
||||
|
|
||||
</html> |
</html> |
Loading…
Reference in new issue