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"> |
|||
<a |
|||
href="https://beian.miit.gov.cn/" |
|||
target="_blank" |
|||
class="text-black text-opacity-70" |
|||
> |
|||
蜀ICP备2023038072号 |
|||
<a *ngIf="icpLicense" href="https://beian.miit.gov.cn/" target="_blank" class="text-black text-opacity-70"> |
|||
{{ icpLicense }} |
|||
</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> |
|||
|
@ -1,10 +1,12 @@ |
|||
import { Component } from "@angular/core"; |
|||
import { Component, Input } from '@angular/core' |
|||
|
|||
@Component({ |
|||
selector: "lib-copyright", |
|||
templateUrl: "./copyright.component.html", |
|||
styleUrls: ["./copyright.component.less"], |
|||
selector: 'lib-copyright', |
|||
templateUrl: './copyright.component.html', |
|||
styleUrls: ['./copyright.component.less'], |
|||
}) |
|||
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 = { |
|||
key: string; |
|||
measurement: string; |
|||
nrv: number; |
|||
value: string; |
|||
}; |
|||
key: string |
|||
measurement: string |
|||
nrv: number |
|||
value: string |
|||
} |
|||
|
|||
export type OptionItemInterface = Augmented<{ |
|||
label: string; |
|||
value: string; |
|||
}>; |
|||
label: string |
|||
value: string |
|||
}> |
|||
|
|||
export interface ResponseType<T = any> { |
|||
body: T; |
|||
code: number; |
|||
desc: string; |
|||
success: boolean; |
|||
body: T |
|||
code: number |
|||
desc: string |
|||
success: boolean |
|||
} |
|||
export type MyResponse<R = any> = ResponseType<R>; |
|||
export type MyResponse<R = any> = ResponseType<R> |
|||
|
|||
export interface TableListColumns { |
|||
key: string; |
|||
title: string; |
|||
visible?: boolean; |
|||
width?: string; |
|||
sort?: boolean; |
|||
order?: number; |
|||
disabled?: boolean; |
|||
coverStorage?: boolean; |
|||
key: string |
|||
title: string |
|||
visible?: boolean |
|||
width?: string |
|||
sort?: boolean |
|||
order?: number |
|||
disabled?: boolean |
|||
coverStorage?: boolean |
|||
} |
|||
|
|||
export interface TableOperation { |
|||
title: string; |
|||
href?: string; |
|||
link?: string[]; |
|||
target?: string; |
|||
premissions: string[]; |
|||
danger?: boolean; |
|||
disabled?: boolean; |
|||
onClick?: (v: DecSafeAny) => void; |
|||
visible?: (v: DecSafeAny) => boolean; |
|||
title: string |
|||
href?: string |
|||
link?: string[] |
|||
target?: string |
|||
premissions: string[] |
|||
danger?: boolean |
|||
disabled?: boolean |
|||
onClick?: (v: DecSafeAny) => void |
|||
visible?: (v: DecSafeAny) => boolean |
|||
} |
|||
|
|||
export interface PageResult<T = DecSafeAny> { |
|||
total: number; |
|||
content: T[]; |
|||
totalElements: number; |
|||
totalPages: number; |
|||
total: number |
|||
content: T[] |
|||
totalElements: 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({ |
|||
selector: 'app-root', |
|||
templateUrl: './app.component.html', |
|||
styleUrls: ['./app.component.less'] |
|||
selector: 'app-root', |
|||
templateUrl: './app.component.html', |
|||
styleUrls: ['./app.component.less'], |
|||
}) |
|||
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 { NavigationEnd, Router, RouterModule } from "@angular/router"; |
|||
import { NzMessageService } from "ng-zorro-antd/message"; |
|||
import { Component, OnInit } from '@angular/core' |
|||
import { NavigationEnd, Router, RouterModule } from '@angular/router' |
|||
import { NzMessageService } from 'ng-zorro-antd/message' |
|||
|
|||
import { NzModalService } from "ng-zorro-antd/modal"; |
|||
import { Subject, filter, lastValueFrom, takeUntil } from "rxjs"; |
|||
import { ApiService } from "@cdk/services"; |
|||
import { appName } from "@cdk/app-settings"; |
|||
import { NzModalService } from 'ng-zorro-antd/modal' |
|||
import { Subject, filter, lastValueFrom, takeUntil } from 'rxjs' |
|||
import { ApiService } from '@cdk/services' |
|||
|
|||
import { WebSiteConfig } from '@cdk/types' |
|||
import { ConfigService } from '@client/app/services/config.service' |
|||
|
|||
@Component({ |
|||
selector: "app-layout", |
|||
templateUrl: "./app-layout.component.html", |
|||
styleUrls: ["./app-layout.component.less"], |
|||
selector: 'app-layout', |
|||
templateUrl: './app-layout.component.html', |
|||
styleUrls: ['./app-layout.component.less'], |
|||
}) |
|||
export class AppLayoutComponent implements OnInit { |
|||
constructor( |
|||
private router: Router, |
|||
private modal: NzModalService, |
|||
private msg: NzMessageService, |
|||
private api: ApiService |
|||
private api: ApiService, |
|||
private configService: ConfigService, |
|||
) { |
|||
this.router.events |
|||
.pipe( |
|||
takeUntil(this.unSubscribe$), |
|||
filter((e): e is NavigationEnd => e instanceof NavigationEnd) |
|||
filter((e): e is NavigationEnd => e instanceof NavigationEnd), |
|||
) |
|||
.subscribe((e) => { |
|||
this.currentUrl = e.url; |
|||
this.fullPage = ["/ingredient/preview", "/data-vis"].some((s) => e.url.startsWith(s)); |
|||
}); |
|||
this.currentUrl = e.url |
|||
this.fullPage = ['/ingredient/preview', '/data-vis'].some((s) => e.url.startsWith(s)) |
|||
}) |
|||
} |
|||
|
|||
fullPage = false; |
|||
account = this.api.account; |
|||
|
|||
appName = appName; |
|||
fullPage = false |
|||
account = this.api.account |
|||
|
|||
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() { |
|||
window.open("/data-vis"); |
|||
window.open('/data-vis') |
|||
} |
|||
|
|||
logout() { |
|||
this.modal.confirm({ |
|||
nzTitle: "警告", |
|||
nzContent: "是否要退出登录?", |
|||
nzTitle: '警告', |
|||
nzContent: '是否要退出登录?', |
|||
nzOnOk: async () => { |
|||
const res = await lastValueFrom(this.api.logout()); |
|||
this.msg.success(res.desc); |
|||
localStorage.removeItem(this.api.accountKey); |
|||
this.router.navigate(["/login"]); |
|||
const res = await lastValueFrom(this.api.logout()) |
|||
this.msg.success(res.desc) |
|||
localStorage.removeItem(this.api.accountKey) |
|||
this.router.navigate(['/login']) |
|||
}, |
|||
}); |
|||
}) |
|||
} |
|||
} |
|||
|
@ -1,48 +1,56 @@ |
|||
import { Component } from "@angular/core"; |
|||
import { FormControl, FormGroup } from "@angular/forms"; |
|||
import { Router } from "@angular/router"; |
|||
import { NzMessageService } from "ng-zorro-antd/message"; |
|||
import { FormValidators } from "projects/cdk/src/public-api"; |
|||
import { Utils } from "projects/cdk/src/utils"; |
|||
import { finalize, lastValueFrom } from "rxjs"; |
|||
import { MD5 } from "crypto-js"; |
|||
import { ApiService } from "@cdk/services"; |
|||
import { appName } from "@cdk/app-settings"; |
|||
import { Component } from '@angular/core' |
|||
import { FormControl, FormGroup } from '@angular/forms' |
|||
import { Router } from '@angular/router' |
|||
import { NzMessageService } from 'ng-zorro-antd/message' |
|||
import { FormValidators, WebSiteConfig } from 'projects/cdk/src/public-api' |
|||
import { Utils } from 'projects/cdk/src/utils' |
|||
import { finalize, lastValueFrom } from 'rxjs' |
|||
import { MD5 } from 'crypto-js' |
|||
import { ApiService } from '@cdk/services' |
|||
|
|||
import { ConfigService } from '@client/app/services/config.service' |
|||
|
|||
@Component({ |
|||
selector: "app-login", |
|||
templateUrl: "./login.component.html", |
|||
styleUrls: ["./login.component.less"], |
|||
selector: 'app-login', |
|||
templateUrl: './login.component.html', |
|||
styleUrls: ['./login.component.less'], |
|||
}) |
|||
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({ |
|||
uid: new FormControl("", [FormValidators.required("请输入账户")]), |
|||
pwd: new FormControl("", [FormValidators.required("请输入密码")]), |
|||
}); |
|||
uid: 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() { |
|||
if (Utils.validateFormGroup(this.loginForm)) { |
|||
const { value } = this.loginForm; |
|||
const pwd = value.pwd as string; |
|||
value["pwd"] = MD5(pwd).toString().slice(-16).toUpperCase(); |
|||
this.loading = true; |
|||
const { value } = this.loginForm |
|||
const pwd = value.pwd as string |
|||
value['pwd'] = MD5(pwd).toString().slice(-16).toUpperCase() |
|||
this.loading = true |
|||
const res = await lastValueFrom( |
|||
this.api.login(value).pipe( |
|||
finalize(() => { |
|||
this.loading = false; |
|||
}) |
|||
) |
|||
); |
|||
this.msg.success(res.desc); |
|||
this.router.navigate(["/"]); |
|||
this.loading = false |
|||
}), |
|||
), |
|||
) |
|||
this.msg.success(res.desc) |
|||
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 { Title } from "@angular/platform-browser"; |
|||
import { RouterStateSnapshot, TitleStrategy } from "@angular/router"; |
|||
import { OnInit } from '@angular/core' |
|||
import { ConfigService } from '@client/app/services/config.service' |
|||
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 { |
|||
constructor(private readonly title: Title) { |
|||
super(); |
|||
fullTitle = '' |
|||
constructor(private readonly title: Title, private ConfigService: ConfigService) { |
|||
super() |
|||
} |
|||
|
|||
override updateTitle(routerState: RouterStateSnapshot) { |
|||
const title = this.buildTitle(routerState); |
|||
let fullTitle = "营养配餐系统"; |
|||
const title = this.buildTitle(routerState) |
|||
let fullTitle = this.ConfigService.getConfig()?.websiteInfo?.name || '' |
|||
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> |
|||
<head> |
|||
<meta charset="utf-8" /> |
|||
<title></title> |
|||
<base href="/" /> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1" /> |
|||
<link rel="icon" id="favicon" /> |
|||
</head> |
|||
|
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<title>营养配餐系统</title> |
|||
<base href="/"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|||
<link rel="icon" type="image/x-icon" href="favicon.ico"> |
|||
</head> |
|||
|
|||
<body> |
|||
<app-root></app-root> |
|||
</body> |
|||
|
|||
</html> |
|||
<body> |
|||
<app-root></app-root> |
|||
</body> |
|||
</html> |
|||
|
Loading…
Reference in new issue