Browse Source

feat(client): 添加网站配置并优化相关功能

- 新增 config.json 文件用于网站配置
- 添加 ConfigService 服务加载配置信息
- 更新 App 组件和 Login 组件以使用新配置
- 修改版权组件以动态显示备案号和公司名称
- 优化组织信息组件,添加技术支持联系功能
- 更新页面标题策略以使用配置中的网站名称
main
kely 3 days ago
parent
commit
bb422b7c31
  1. 17
      projects/admin/src/assets/config.json
  2. 2
      projects/cdk/src/app-settings/index.ts
  3. 10
      projects/cdk/src/shared/components/copyright/copyright.component.html
  4. 12
      projects/cdk/src/shared/components/copyright/copyright.component.ts
  5. 105
      projects/cdk/src/types/index.ts
  6. 23
      projects/client/src/app/app.component.ts
  7. 15
      projects/client/src/app/app.module.ts
  8. 4
      projects/client/src/app/components/app-layout/app-layout.component.html
  9. 64
      projects/client/src/app/components/app-layout/app-layout.component.ts
  10. 54
      projects/client/src/app/pages/login/login.component.html
  11. 66
      projects/client/src/app/pages/login/login.component.ts
  12. 4
      projects/client/src/app/pages/system/org-info/org-info.component.html
  13. 106
      projects/client/src/app/pages/system/org-info/org-info.component.ts
  14. 94
      projects/client/src/app/services/client-api.service.ts
  15. 38
      projects/client/src/app/services/config.service.ts
  16. 23
      projects/client/src/app/services/title.service.ts
  17. 17
      projects/client/src/assets/config.json
  18. 0
      projects/client/src/assets/favicon.ico
  19. 22
      projects/client/src/index.html

17
projects/admin/src/assets/config.json

@ -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": ""
}
}

2
projects/cdk/src/app-settings/index.ts

@ -1 +1 @@
export const appName = "营养配餐系统"; export const appName = '营养配餐系统'

10
projects/cdk/src/shared/components/copyright/copyright.component.html

@ -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>

12
projects/cdk/src/shared/components/copyright/copyright.component.ts

@ -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()
} }

105
projects/cdk/src/types/index.ts

@ -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
} }

23
projects/client/src/app/app.component.ts

@ -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 : '')
}
}
}
} }

15
projects/client/src/app/app.module.ts

@ -1,4 +1,4 @@
import { NgModule } from '@angular/core' import { APP_INITIALIZER, NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser' import { BrowserModule } from '@angular/platform-browser'
import { NZ_DATE_CONFIG, NZ_DATE_LOCALE, NZ_I18N } from 'ng-zorro-antd/i18n' import { NZ_DATE_CONFIG, NZ_DATE_LOCALE, NZ_I18N } from 'ng-zorro-antd/i18n'
@ -41,6 +41,11 @@ import { IngredientModule } from '@cdk/ingredient/ingredient.module'
import { NgxPermissionsModule } from 'ngx-permissions' import { NgxPermissionsModule } from 'ngx-permissions'
import { TemplatePageTitleStrategy } from './services/title.service' import { TemplatePageTitleStrategy } from './services/title.service'
import { TitleStrategy } from '@angular/router' import { TitleStrategy } from '@angular/router'
import { ConfigService } from './services/config.service'
function initializeApp(configService: ConfigService) {
return () => configService.loadConfig().toPromise() // 返回一个 Promise
}
registerLocaleData(zh) registerLocaleData(zh)
@ -95,6 +100,14 @@ registerLocaleData(zh)
firstDayOfWeek: 1, firstDayOfWeek: 1,
}, },
}, },
ConfigService,
{
provide: APP_INITIALIZER,
useFactory: initializeApp,
deps: [ConfigService],
multi: true,
},
], ],
bootstrap: [AppComponent], bootstrap: [AppComponent],
}) })

4
projects/client/src/app/components/app-layout/app-layout.component.html

@ -7,9 +7,9 @@
<nz-header class="app-header"> <nz-header class="app-header">
<div class="flex items-center justify-between h-full"> <div class="flex items-center justify-between h-full">
<div class="logo flex items-center h-full"> <div class="logo flex items-center h-full">
<img class="block h-[40px] mr-1" src="../assets/images/jl-logo.png" /> <img class="block h-[40px] mr-1" src="{{ appConfig?.assets?.logo }}" />
<span class="text-lg text-white font-bold"> <span class="text-lg text-white font-bold">
{{ appName }} {{ appConfig?.websiteInfo?.name }}
</span> </span>
</div> </div>
<div class="profile"> <div class="profile">

64
projects/client/src/app/components/app-layout/app-layout.component.ts

@ -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'])
}, },
}); })
} }
} }

54
projects/client/src/app/pages/login/login.component.html

@ -8,58 +8,32 @@
</div> --> </div> -->
<div class="card"> <div class="card">
<div class="img flex items-center justify-center"> <div class="img flex items-center justify-center">
<img src="assets/images/jl-logo.png" /> <img src="{{ appConfig?.assets?.loginBackground }}" />
</div> </div>
<div class="form py-5 px-10 flex-1"> <div class="form py-5 px-10 flex-1">
<div class="form-inner"> <div class="form-inner">
<h2 class="mt-4 text-3xl font-bold">{{ appName }}</h2> <h2 class="mt-4 text-3xl font-bold">{{ appConfig?.websiteInfo?.name }}</h2>
<h3 class="mt-10 text-xl">登录</h3> <h3 class="mt-10 text-xl">登录</h3>
<form <form nz-form [formGroup]="loginForm" class="mt-10">
nz-form
[formGroup]="loginForm"
class="mt-10"
>
<nz-form-item> <nz-form-item>
<nz-form-control [nzErrorTip]="formErrorTipsTpl"> <nz-form-control [nzErrorTip]="formErrorTipsTpl">
<nz-input-group <nz-input-group [nzPrefix]="prefixTemplateUser" nzSize="large">
[nzPrefix]="prefixTemplateUser" <input nz-input nzSize="large" placeholder="账户" formControlName="uid" />
nzSize="large"
>
<input
nz-input
nzSize="large"
placeholder="账户"
formControlName="uid"
/>
</nz-input-group> </nz-input-group>
<ng-template #prefixTemplateUser> <ng-template #prefixTemplateUser>
<span <span nz-icon nzType="user"></span>
nz-icon
nzType="user"
></span>
<nz-divider nzType="vertical"></nz-divider> <nz-divider nzType="vertical"></nz-divider>
</ng-template> </ng-template>
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<nz-form-item> <nz-form-item>
<nz-form-control [nzErrorTip]="formErrorTipsTpl"> <nz-form-control [nzErrorTip]="formErrorTipsTpl">
<nz-input-group <nz-input-group [nzPrefix]="prefixTemplatePassword" nzSize="large">
[nzPrefix]="prefixTemplatePassword" <input nz-input type="password" placeholder="密码" formControlName="pwd" />
nzSize="large"
>
<input
nz-input
type="password"
placeholder="密码"
formControlName="pwd"
/>
</nz-input-group> </nz-input-group>
<ng-template #prefixTemplatePassword> <ng-template #prefixTemplatePassword>
<span <span nz-icon nzType="lock"></span>
nz-icon
nzType="lock"
></span>
<nz-divider nzType="vertical"></nz-divider> <nz-divider nzType="vertical"></nz-divider>
</ng-template> </ng-template>
</nz-form-control> </nz-form-control>
@ -85,14 +59,14 @@
</div> </div>
</div> </div>
<div class="copyright"> <div class="copyright">
<lib-copyright></lib-copyright> <lib-copyright
[icpLicense]="appConfig?.copyrightInfo?.icpLicense"
[companyName]="appConfig?.copyrightInfo?.companyName"
></lib-copyright>
</div> </div>
</section> </section>
<ng-template <ng-template #formErrorTipsTpl let-control>
#formErrorTipsTpl
let-control
>
<div class="text-left"> <div class="text-left">
<form-error-tips [control]="control"></form-error-tips> <form-error-tips [control]="control"></form-error-tips>
</div> </div>

66
projects/client/src/app/pages/login/login.component.ts

@ -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(["/"]);
} }
} }
} }

4
projects/client/src/app/pages/system/org-info/org-info.component.html

@ -72,7 +72,7 @@
<nz-form-label> 账号到期时间 </nz-form-label> <nz-form-label> 账号到期时间 </nz-form-label>
<nz-form-control> <nz-form-control>
{{ org?.expire | date : 'yyyy-MM-dd' }} {{ org?.expire | date : 'yyyy-MM-dd' }}
<a class="ml-4" (click)="updateAccount(org?.expire, updateAccountInfoTpl)"> 续费 </a> <a class="ml-4" (click)="updateAccount(org?.expire, updateAccountInfoTpl)"> 技术支持 </a>
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
</div> </div>
@ -84,7 +84,7 @@
<!-- <span> <!-- <span>
{{org.contacts ?? '-'}} {{org.contacts ?? '-'}}
</span> --> </span> -->
<span> 028-85463212 </span> <span>{{ appConfig?.contactInfo?.phoneNumber ?? '-' }} </span>
</div> </div>
</ng-template> </ng-template>

106
projects/client/src/app/pages/system/org-info/org-info.component.ts

@ -1,20 +1,22 @@
import { ChangeDetectorRef, Component, OnInit, TemplateRef } from "@angular/core"; import { ChangeDetectorRef, Component, OnInit, TemplateRef } from '@angular/core'
import { NzModalService } from "ng-zorro-antd/modal"; import { NzModalService } from 'ng-zorro-antd/modal'
import { format } from "date-fns"; import { format } from 'date-fns'
import { ApiService } from "@cdk/services"; import { ApiService } from '@cdk/services'
import { FormControl, FormGroup } from "@angular/forms"; import { FormControl, FormGroup } from '@angular/forms'
import { FormValidators } from "@cdk/validators"; import { FormValidators } from '@cdk/validators'
import { Utils } from "@cdk/utils"; import { Utils } from '@cdk/utils'
import { NzMessageService } from "ng-zorro-antd/message"; import { NzMessageService } from 'ng-zorro-antd/message'
import { OrgFormComponent } from "../../../components"; import { OrgFormComponent } from '../../../components'
import { lastValueFrom } from "rxjs"; import { lastValueFrom } from 'rxjs'
import { MD5 } from "crypto-js"; import { MD5 } from 'crypto-js'
import { Router } from "@angular/router"; import { Router } from '@angular/router'
import { WebSiteConfig } from '@cdk/public-api'
import { ConfigService } from '@client/app/services/config.service'
@Component({ @Component({
selector: "app-org-info", selector: 'app-org-info',
templateUrl: "./org-info.component.html", templateUrl: './org-info.component.html',
styleUrls: ["./org-info.component.less"], styleUrls: ['./org-info.component.less'],
}) })
export class OrgInfoComponent implements OnInit { export class OrgInfoComponent implements OnInit {
constructor( constructor(
@ -22,40 +24,46 @@ export class OrgInfoComponent implements OnInit {
private modal: NzModalService, private modal: NzModalService,
private msg: NzMessageService, private msg: NzMessageService,
private router: Router, private router: Router,
private cdr: ChangeDetectorRef private configService: ConfigService,
) {} ) {}
account: any = this.api.account; account: any = this.api.account
pwdForm = new FormGroup({ pwdForm = new FormGroup({
oldPwd: new FormControl("", [FormValidators.required("请输入原密码")]), oldPwd: new FormControl('', [FormValidators.required('请输入原密码')]),
newPwd: new FormControl("", [FormValidators.required("请输入新密码")]), newPwd: new FormControl('', [FormValidators.required('请输入新密码')]),
rePwd: new FormControl("", [FormValidators.required("请再次输入新密码")]), rePwd: new FormControl('', [FormValidators.required('请再次输入新密码')]),
}); })
appConfig: WebSiteConfig | null = null
ngOnInit(): void {} ngOnInit(): void {
this.appConfig = this.configService.getConfig()
}
updateAccount(type: string | number, nzContent: TemplateRef<{}>) { updateAccount(type: string | number, nzContent: TemplateRef<{}>) {
const nzTitle = const nzTitle =
type === "uid" type === 'uid'
? "如需修改超级管理员账号,请联系:" ? '如需修改超级管理员账号,请联系:'
: `账号将于 ${format(type as number, "yyyy-MM-dd")} 到期,续费请联系`; : `账号将于 ${format(type as number, 'yyyy-MM-dd')} 到期,续费请联系`
this.modal.info({ this.modal.info({
nzTitle, nzTitle: '技术支持请联系',
nzContent, nzContent,
nzOkText: "知道了", nzOkText: '知道了',
}); })
} }
updateOrg() { updateOrg() {
this.modal.create({ this.modal.create({
nzTitle: "修改单位基础信息", nzTitle: '修改单位基础信息',
nzContent: OrgFormComponent, nzContent: OrgFormComponent,
nzData: this.account.vender, nzData: this.account.vender,
nzOnOk: async (e) => { nzOnOk: async (e) => {
if (Utils.validateFormGroup(e.formGroup)) { if (Utils.validateFormGroup(e.formGroup)) {
const res = await lastValueFrom(this.api.saveOrg({ ...e.formGroup.value, venderId: this.account.vender.id })); const res = await lastValueFrom(
this.msg.success(res.desc); this.api.saveOrg({ ...e.formGroup.value, venderId: this.account.vender.id }),
)
this.msg.success(res.desc)
// localStorage.setItem( // localStorage.setItem(
// this.api.accountKey, // this.api.accountKey,
// JSON.stringify({ // JSON.stringify({
@ -68,46 +76,46 @@ export class OrgInfoComponent implements OnInit {
// }) // })
// ); // );
// window.location.reload(); // window.location.reload();
this.api.getOrgInfo().subscribe(() => {}); this.api.getOrgInfo().subscribe(() => {})
return true; return true
} }
return false; return false
}, },
}); })
} }
cancelPwdForm() { cancelPwdForm() {
this.pwdForm.reset(); this.pwdForm.reset()
} }
changePassword(nzContent: TemplateRef<{}>) { changePassword(nzContent: TemplateRef<{}>) {
this.modal.create({ this.modal.create({
nzTitle: "修改密码", nzTitle: '修改密码',
nzContent, nzContent,
nzOnOk: async () => { nzOnOk: async () => {
if (Utils.validateFormGroup(this.pwdForm)) { if (Utils.validateFormGroup(this.pwdForm)) {
const { value } = this.pwdForm; const { value } = this.pwdForm
if (value.newPwd !== value.rePwd) { if (value.newPwd !== value.rePwd) {
this.msg.error("两次密码输入不一致"); this.msg.error('两次密码输入不一致')
return false; return false
} }
const res = await lastValueFrom( const res = await lastValueFrom(
this.api.updatePassword({ this.api.updatePassword({
oldPassword: MD5(value.oldPwd!).toString().slice(-16), oldPassword: MD5(value.oldPwd!).toString().slice(-16),
password: MD5(value.newPwd!).toString().slice(-16), password: MD5(value.newPwd!).toString().slice(-16),
// name: // name:
}) }),
); )
this.msg.success(res.desc); this.msg.success(res.desc)
this.api.logout().subscribe(() => { this.api.logout().subscribe(() => {
this.msg.success("请重新登录"); this.msg.success('请重新登录')
this.router.navigate(["/login"]); this.router.navigate(['/login'])
}); })
return true; return true
} }
return false; return false
}, },
nzOnCancel: this.cancelPwdForm.bind(this), nzOnCancel: this.cancelPwdForm.bind(this),
}); })
} }
} }

94
projects/client/src/app/services/client-api.service.ts

@ -1,5 +1,5 @@
import { HttpClient, HttpParams } from "@angular/common/http"; import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from "@angular/core"; import { Injectable } from '@angular/core'
import { import {
Utils, Utils,
AnyObject, AnyObject,
@ -10,120 +10,120 @@ import {
GlobalEnum, GlobalEnum,
OrgConfigDTO, OrgConfigDTO,
ClientAccountDTO, ClientAccountDTO,
} from "@cdk/public-api"; } from '@cdk/public-api'
import { Observable, map, of, tap } from "rxjs"; import { Observable, map, of, tap } from 'rxjs'
@Injectable({ @Injectable({
providedIn: "root", providedIn: 'root',
}) })
export class ClientApiServicde { export class ClientApiServicde {
constructor(private http: HttpClient) { constructor(private http: HttpClient) {
try { try {
const strageAccount = localStorage.getItem(this.accountKey); const strageAccount = localStorage.getItem(this.accountKey)
if (strageAccount) { if (strageAccount) {
this.account = JSON.parse(strageAccount); this.account = JSON.parse(strageAccount)
} }
} catch (error) { } catch (error) {
console.error("获取用户信息失败", error); console.error('获取用户信息失败', error)
} }
} }
public account!: ClientAccountDTO; public account!: ClientAccountDTO
public accountKey = "CATERING_ACCOUNT"; public accountKey = 'CATERING_ACCOUNT'
globalEnum!: GlobalEnum; globalEnum!: GlobalEnum
page(v: {}, q: {}) { page(v: {}, q: {}) {
return this.http.get<any>("https://jsonplaceholder.typicode.com/users", v).pipe( return this.http.get<any>('https://jsonplaceholder.typicode.com/users', v).pipe(
map((r) => { map((r) => {
return { return {
total: 10, total: 10,
content: r, content: r,
}; }
}) }),
); )
} }
getAllEnum(force?: boolean): Observable<GlobalEnum> { getAllEnum(force?: boolean): Observable<GlobalEnum> {
if (this.globalEnum && !force) { if (this.globalEnum && !force) {
return of(this.globalEnum); return of(this.globalEnum)
} }
return this.http.get<ResponseType<GlobalEnum>>("/api/basic/enum").pipe( return this.http.get<ResponseType<GlobalEnum>>('/api/basic/enum').pipe(
map((res) => { map((res) => {
return res.body; return res.body
}), }),
tap((r) => { tap((r) => {
this.globalEnum = r; this.globalEnum = r
}) }),
); )
} }
login(v: {}) { login(v: {}) {
const params = Utils.objectToHttpParams(v); const params = Utils.objectToHttpParams(v)
return this.http.get<ResponseType>("/api/login", { params }); return this.http.get<ResponseType>('/api/login', { params })
} }
logout() { logout() {
return this.http.get<ResponseType>("/api/logout"); return this.http.get<ResponseType>('/api/logout')
} }
getRoleList() { getRoleList() {
return this.http.get<ResponseType<UserRoleDTO[]>>("/api/role"); return this.http.get<ResponseType<UserRoleDTO[]>>('/api/role')
} }
deleteRole(roleId: string) { deleteRole(roleId: string) {
const params = new HttpParams().set("roleId", roleId); const params = new HttpParams().set('roleId', roleId)
return this.http.delete<ResponseType>("/api/role", { return this.http.delete<ResponseType>('/api/role', {
params, params,
}); })
} }
updateRole(rule: AnyObject) { updateRole(rule: AnyObject) {
const body = Utils.objectToFormData(rule); const body = Utils.objectToFormData(rule)
const method = rule["roleId"] ? "post" : "put"; const method = rule['roleId'] ? 'post' : 'put'
return this.http[method]<ResponseType>("/api/role", body); return this.http[method]<ResponseType>('/api/role', body)
} }
getRolePerms() { getRolePerms() {
return this.http.get<ResponseType<PermItemDTO[]>>("/api/role/item"); return this.http.get<ResponseType<PermItemDTO[]>>('/api/role/item')
} }
getUserList() { getUserList() {
return this.http.get<ResponseType<UserDTO[]>>("/api/user"); return this.http.get<ResponseType<UserDTO[]>>('/api/user')
} }
checkUid(uid: string) { checkUid(uid: string) {
const params = new HttpParams().set("uid", uid); const params = new HttpParams().set('uid', uid)
return this.http.delete<ResponseType>("/api/user/check", { return this.http.delete<ResponseType>('/api/user/check', {
params, params,
}); })
} }
saveUser(user: AnyObject, edit: boolean) { saveUser(user: AnyObject, edit: boolean) {
const body = Utils.objectToFormData(user); const body = Utils.objectToFormData(user)
const method = edit ? "post" : "put"; const method = edit ? 'post' : 'put'
return this.http[method]<ResponseType>("/api/user", body); return this.http[method]<ResponseType>('/api/user', body)
} }
deleteUser(uid: string) { deleteUser(uid: string) {
const params = new HttpParams().set("uid", uid); const params = new HttpParams().set('uid', uid)
return this.http.delete<ResponseType>("/api/user", { return this.http.delete<ResponseType>('/api/user', {
params, params,
}); })
} }
saveOrg(org: AnyObject) { saveOrg(org: AnyObject) {
const body = Utils.objectToFormData(org); const body = Utils.objectToFormData(org)
return this.http.post<ResponseType>(" /api/vender", body); return this.http.post<ResponseType>(' /api/vender', body)
} }
getOrgConfig() { getOrgConfig() {
return this.http.get<ResponseType<OrgConfigDTO>>("/api/vender/config"); return this.http.get<ResponseType<OrgConfigDTO>>('/api/vender/config')
} }
saveOrgConfig(config: AnyObject) { saveOrgConfig(config: AnyObject) {
const body = Utils.objectToFormData(config); const body = Utils.objectToFormData(config)
return this.http.post<ResponseType>("/api/vender/config", body); return this.http.post<ResponseType>('/api/vender/config', body)
} }
} }

38
projects/client/src/app/services/config.service.ts

@ -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
}
}

23
projects/client/src/app/services/title.service.ts

@ -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)
} }
} }

17
projects/client/src/assets/config.json

@ -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": ""
}
}

0
projects/client/src/favicon.ico → projects/client/src/assets/favicon.ico

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

22
projects/client/src/index.html

@ -1,16 +1,14 @@
<!doctype html> <!DOCTYPE html>
<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> <body>
<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> <app-root></app-root>
</body> </body>
</html> </html>
Loading…
Cancel
Save