From d20caa9c8ddf3f49ebbfc7b2bd21bf88168eb9db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E5=BF=97?= Date: Thu, 19 Mar 2026 09:01:05 +0800 Subject: [PATCH] up --- README.ja-JP.md | 157 -- README.md | 157 -- README.zh-CN.md | 157 -- apps/web-antd/.env.production | 2 +- apps/web-antd/package.json | 4 +- apps/web-antd/public/favicon.ico | Bin 5430 -> 4286 bytes apps/web-antd/src/api/admin/booking.ts | 78 - apps/web-antd/src/api/admin/classroom.ts | 11 +- apps/web-antd/src/api/admin/index.ts | 1 - .../src/locales/langs/zh-CN/page.json | 4 - .../src/router/routes/modules/booking.ts | 19 - .../views/_core/authentication/code-login.vue | 3 +- .../src/views/_core/authentication/login.vue | 1 - apps/web-antd/src/views/booking/list.vue | 291 --- apps/web-antd/src/views/class/list.vue | 11 +- apps/web-antd/src/views/classroom/detail.vue | 59 +- apps/web-antd/src/views/classroom/list.vue | 655 +++++- apps/web-antd/src/views/school/detail.vue | 192 +- apps/web-antd/src/views/school/list.vue | 193 +- apps/web-antd/src/views/student/detail.vue | 7 +- apps/web-antd/src/views/student/list.vue | 27 + apps/web-antd/vite.config.mts | 2 +- doc/座位布局导出功能说明.md | 74 + .../@core/base/shared/src/constants/vben.ts | 2 +- .../__snapshots__/config.test.ts.snap | 2 +- packages/@core/preferences/src/config.ts | 2 +- .../common-ui/src/ui/authentication/login.vue | 48 - .../layouts/src/basic/copyright/copyright.vue | 13 - .../src/langs/zh-CN/authentication.json | 4 +- playground/src/api/examples/download.ts | 4 +- .../demos/features/file-download/index.vue | 2 +- playground/src/views/examples/form/basic.vue | 2 +- pnpm-lock.yaml | 1969 ++--------------- 33 files changed, 1157 insertions(+), 2996 deletions(-) delete mode 100644 README.ja-JP.md delete mode 100644 README.md delete mode 100644 README.zh-CN.md delete mode 100644 apps/web-antd/src/api/admin/booking.ts delete mode 100644 apps/web-antd/src/router/routes/modules/booking.ts delete mode 100644 apps/web-antd/src/views/booking/list.vue create mode 100644 doc/座位布局导出功能说明.md diff --git a/README.ja-JP.md b/README.ja-JP.md deleted file mode 100644 index 4ce285a..0000000 --- a/README.ja-JP.md +++ /dev/null @@ -1,157 +0,0 @@ -
- - VbenAdmin Logo - -
-
- -[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE) - -

Vue Vben Admin

-
- -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vbenjs_vue-vben-admin&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin) ![codeql](https://github.com/vbenjs/vue-vben-admin/actions/workflows/codeql.yml/badge.svg) ![build](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml/badge.svg) ![ci](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml/badge.svg) ![deploy](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml/badge.svg) - -**日本語** | [English](./README.md) | [中文](./README.zh-CN.md) - -## 紹介 - -Vue Vben Adminは、最新の`vue3`、`vite`、`TypeScript`などの主流技術を使用して開発された、無料でオープンソースの中・後端テンプレートです。すぐに使える中・後端のフロントエンドソリューションとして、学習の参考にもなります。 - -## アップグレード通知 - -これは最新バージョン `5.0` であり、以前のバージョンとは互換性がありません。新しいプロジェクトを開始する場合は、最新バージョンを使用することをお勧めします。古いバージョンを表示したい場合は、[v2ブランチ](https://github.com/vbenjs/vue-vben-admin/tree/v2)を使用してください。 - -## 特徴 - -- **最新技術スタック**:Vue 3やViteなどの最先端フロントエンド技術で開発 -- **TypeScript**:アプリケーション規模のJavaScriptのための言語 -- **テーマ**:複数のテーマカラーが利用可能で、カスタマイズオプションも豊富 -- **国際化**:完全な内蔵国際化サポート -- **権限管理**:動的ルートベースの権限生成ソリューションを内蔵 - -## プレビュー - -- [Vben Admin](https://vben.pro/) - フルバージョンの中国語サイト - -テストアカウント:vben/123456 - -
- VbenAdmin Logo - VbenAdmin Logo - VbenAdmin Logo -
- -### Gitpodを使用 - -Gitpod(GitHub用の無料オンライン開発環境)でプロジェクトを開き、すぐにコーディングを開始します。 - -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin) - -## ドキュメント - -[ドキュメント](https://doc.vben.pro/) - -## インストールと使用 - -1. プロジェクトコードを取得 - -```bash -git clone https://github.com/vbenjs/vue-vben-admin.git -``` - -2. 依存関係のインストール - -```bash -cd vue-vben-admin -npm i -g corepack -pnpm install -``` - -3. 実行 - -```bash -pnpm dev -``` - -4. ビルド - -```bash -pnpm build -``` - -## 変更ログ - -[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases) - -## 貢献方法 - -ご参加をお待ちしております![Issueを提出](https://github.com/anncwb/vue-vben-admin/issues/new/choose)するか、Pull Requestを送信してください。 - -**Pull Request プロセス:** - -1. コードをフォーク -2. 自分のブランチを作成:`git checkout -b feat/xxxx` -3. 変更をコミット:`git commit -am 'feat(function): add xxxxx'` -4. ブランチをプッシュ:`git push origin feat/xxxx` -5. `pull request`を送信 - -## Git貢献提出規則 - -参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 規則 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)) - -- `feat` 新機能の追加 -- `fix` 問題/バグの修正 -- `style` コードスタイルに関連し、実行結果に影響しない -- `perf` 最適化/パフォーマンス向上 -- `refactor` リファクタリング -- `revert` 変更の取り消し -- `test` テスト関連 -- `docs` ドキュメント/注釈 -- `chore` 依存関係の更新/スキャフォールディング設定の変更など -- `ci` 継続的インテグレーション -- `types` 型定義ファイルの変更 - -## ブラウザサポート - -ローカル開発には `Chrome 80+` ブラウザを推奨します - -モダンブラウザをサポートし、IEはサポートしません - -| [Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | -| :-: | :-: | :-: | :-: | -| 最新2バージョン | 最新2バージョン | 最新2バージョン | 最新2バージョン | - -## メンテナー - -[@Vben](https://github.com/anncwb) - -## スター歴史 - -[![Star History Chart](https://api.star-history.com/svg?repos=vbenjs/vue-vben-admin&type=Date)](https://star-history.com/#vbenjs/vue-vben-admin&Date) - -## 寄付 - -このプロジェクトが役に立つと思われた場合、作者にコーヒーを一杯おごってサポートを示すことができます! - -![donate](https://unpkg.com/@vbenjs/static-source@0.1.7/source/sponsor.png) - -Paypal Me - -## 貢献者 - - - Contribution Leaderboard - - - - Contributors - - -## Discord - -- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions) - -## ライセンス - -[MIT © Vben-2020](./LICENSE) diff --git a/README.md b/README.md deleted file mode 100644 index ce8e897..0000000 --- a/README.md +++ /dev/null @@ -1,157 +0,0 @@ -
- - VbenAdmin Logo - -
-
- -[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE) - -

Vue Vben Admin

-
- -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vbenjs_vue-vben-admin&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin) [![codeql](https://github.com/vbenjs/vue-vben-admin/actions/workflows/codeql.yml/badge.svg)](https://github.com/vbenjs/vue-vben-admin/actions/workflows/codeql.yml) [![build](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml/badge.svg)](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml) [![ci](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml/badge.svg)](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml) [![deploy](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml/badge.svg)](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml) - -**English** | [中文](./README.zh-CN.md) | [日本語](./README.ja-JP.md) - -## Introduction - -Vue Vben Admin is a free and open source middle and back-end template. Using the latest `vue3`, `vite`, `TypeScript` and other mainstream technology development, the out-of-the-box middle and back-end front-end solutions can also be used for learning reference. - -## Upgrade Notice - -This is the latest version, 5.0, and it is not compatible with previous versions. If you are starting a new project, it is recommended to use the latest version. If you wish to view the old version, please use the [v2 branch](https://github.com/vbenjs/vue-vben-admin/tree/v2). - -## Features - -- **Latest Technology Stack**: Developed with cutting-edge front-end technologies like Vue 3 and Vite -- **TypeScript**: A language for application-scale JavaScript -- **Themes**: Multiple theme colors available with customizable options -- **Internationalization**: Comprehensive built-in internationalization support -- **Permissions**: Built-in solution for dynamic route-based permission generation - -## Preview - -- [Vben Admin](https://vben.pro/) - Full version Chinese site - -Test Account: vben/123456 - -
- VbenAdmin Logo - VbenAdmin Logo - VbenAdmin Logo -
- -### Use Gitpod - -Open the project in Gitpod (free online dev environment for GitHub) and start coding immediately. - -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin) - -## Documentation - -[Document](https://doc.vben.pro/) - -## Install and Use - -1. Get the project code - -```bash -git clone https://github.com/vbenjs/vue-vben-admin.git -``` - -2. Install dependencies - -```bash -cd vue-vben-admin -npm i -g corepack -pnpm install -``` - -3. Run - -```bash -pnpm dev -``` - -4. Build - -```bash -pnpm build -``` - -## Change Log - -[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases) - -## How to Contribute - -You are very welcome to join! [Raise an issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) or submit a Pull Request. - -**Pull Request Process:** - -1. Fork the code -2. Create your branch: `git checkout -b feat/xxxx` -3. Submit your changes: `git commit -am 'feat(function): add xxxxx'` -4. Push your branch: `git push origin feat/xxxx` -5. Submit `pull request` - -## Git Contribution Submission Specification - -Reference [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) specification ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)) - -- `feat` Add new features -- `fix` Fix the problem/BUG -- `style` The code style is related and does not affect the running result -- `perf` Optimization/performance improvement -- `refactor` Refactor -- `revert` Undo edit -- `test` Test related -- `docs` Documentation/notes -- `chore` Dependency update/scaffolding configuration modification etc. -- `ci` Continuous integration -- `types` Type definition file changes - -## Browser Support - -The `Chrome 80+` browser is recommended for local development - -Support modern browsers, not IE - -| [Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | -| :-: | :-: | :-: | :-: | -| last 2 versions | last 2 versions | last 2 versions | last 2 versions | - -## Maintainer - -[@Vben](https://github.com/anncwb) - -## Star History - -[![Star History Chart](https://api.star-history.com/svg?repos=vbenjs/vue-vben-admin&type=Date)](https://star-history.com/#vbenjs/vue-vben-admin&Date) - -## Donate - -If you think this project is helpful to you, you can help the author buy a cup of coffee to show your support! - -![donate](https://unpkg.com/@vbenjs/static-source@0.1.7/source/sponsor.png) - -Paypal Me - -## Contributors - - - Contribution Leaderboard - - - - Contributors - - -## Discord - -- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions) - -## License - -[MIT © Vben-2020](./LICENSE) diff --git a/README.zh-CN.md b/README.zh-CN.md deleted file mode 100644 index d3193ef..0000000 --- a/README.zh-CN.md +++ /dev/null @@ -1,157 +0,0 @@ -
- - VbenAdmin Logo - -
-
- -[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE) - -

Vue Vben Admin

-
- -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vbenjs_vue-vben-admin&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin) ![codeql](https://github.com/vbenjs/vue-vben-admin/actions/workflows/codeql.yml/badge.svg) ![build](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml/badge.svg) ![ci](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml/badge.svg) ![deploy](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml/badge.svg) - -**中文** | [English](./README.md) | [日本語](./README.ja-JP.md) - -## 简介 - -Vue Vben Admin 是 Vue Vben Admin 的升级版本。作为一个免费开源的中后台模板,它采用了最新的 Vue 3、Vite、TypeScript 等主流技术开发,开箱即用,可用于中后台前端开发,也适合学习参考。 - -## 升级提示 - -该版本为最新版本 `5.0`,与其他版本不兼容,如果你是新项目,建议使用最新版本。如果你想查看旧版本,请使用 [v2 分支](https://github.com/vbenjs/vue-vben-admin/tree/v2) - -## 特性 - -- **最新技术栈**:使用 Vue3/vite 等前端前沿技术开发 -- **TypeScript**:应用程序级 JavaScript 的语言 -- **主题**:提供多套主题色彩,可配置自定义主题 -- **国际化**:内置完善的国际化方案 -- **权限**:内置完善的动态路由权限生成方案 - -## 预览 - -- [Vben Admin](https://vben.pro/) - 完整版中文站点 - -测试账号:vben/123456 - -
- VbenAdmin Logo - VbenAdmin Logo - VbenAdmin Logo -
- -### 使用 Gitpod - -在 Gitpod(适用于 GitHub 的免费在线开发环境)中打开项目,并立即开始编码。 - -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin) - -## 文档 - -[文档地址](https://doc.vben.pro/) - -## 安装使用 - -1. 获取项目代码 - -```bash -git clone https://github.com/vbenjs/vue-vben-admin.git -``` - -2. 安装依赖 - -```bash -cd vue-vben-admin -npm i -g corepack -pnpm install -``` - -3. 运行 - -```bash -pnpm dev -``` - -4. 打包 - -```bash -pnpm build -``` - -## 更新日志 - -[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases) - -## 如何贡献 - -非常欢迎你的加入![提一个 Issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) 或者提交一个 Pull Request。 - -**Pull Request 流程:** - -1. Fork 代码 -2. 创建自己的分支:`git checkout -b feature/xxxx` -3. 提交你的修改:`git commit -am 'feat(function): add xxxxx'` -4. 推送您的分支:`git push origin feature/xxxx` -5. 提交 `pull request` - -## Git 贡献提交规范 - -参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)) - -- `feat` 增加新功能 -- `fix` 修复问题/BUG -- `style` 代码风格相关无影响运行结果的 -- `perf` 优化/性能提升 -- `refactor` 重构 -- `revert` 撤销修改 -- `test` 测试相关 -- `docs` 文档/注释 -- `chore` 依赖更新/脚手架配置修改等 -- `ci` 持续集成 -- `types` 类型定义文件更改 - -## 浏览器支持 - -本地开发推荐使用 `Chrome 80+` 浏览器 - -支持现代浏览器,不支持 IE - -| [Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | -| :-: | :-: | :-: | :-: | -| last 2 versions | last 2 versions | last 2 versions | last 2 versions | - -## 维护者 - -[@Vben](https://github.com/anncwb) - -## Star 历史 - -[![Star History Chart](https://api.star-history.com/svg?repos=vbenjs/vue-vben-admin&type=Date)](https://star-history.com/#vbenjs/vue-vben-admin&Date) - -## 捐赠 - -如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持! - -![donate](https://unpkg.com/@vbenjs/static-source@0.1.7/source/sponsor.png) - -Paypal Me - -## 贡献者 - - - Contribution Leaderboard - - - - Contributors - - -## Discord - -- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions) - -## 许可证 - -[MIT © Vben-2020](./LICENSE) diff --git a/apps/web-antd/.env.production b/apps/web-antd/.env.production index e84c54f..9852876 100644 --- a/apps/web-antd/.env.production +++ b/apps/web-antd/.env.production @@ -1,6 +1,6 @@ # 生产环境配置 VITE_BASE=/super/ -VITE_GLOB_API_URL=http://xz.dhdjy.com +VITE_GLOB_API_URL=https://xz.dhdjy.com VITE_ROUTER_HISTORY=history VITE_COMPRESS=gzip VITE_PWA=false diff --git a/apps/web-antd/package.json b/apps/web-antd/package.json index db7d007..33ff449 100644 --- a/apps/web-antd/package.json +++ b/apps/web-antd/package.json @@ -45,6 +45,8 @@ "dayjs": "catalog:", "pinia": "catalog:", "vue": "catalog:", - "vue-router": "catalog:" + "vue-router": "catalog:", + "xlsx": "^0.18.5", + "xlsx-js-style": "^1.2.0" } } diff --git a/apps/web-antd/public/favicon.ico b/apps/web-antd/public/favicon.ico index fcf9818e2cf855039b272bdbfbb202d3ff3fa159..a52c968c1785000c702ee89924e7bbd484b4c25e 100644 GIT binary patch literal 4286 zcmcgw>r+%^7~lSbKJ^76vWr@d<#hW{AIj<8G?O!x)AXUMm6J_Es0gBDHeo^zhG?UN zc_n4J>~fQ2%V<+$~y|{g2PwBsrGs1U~MEoTPU+ z7VVT7v5JggcY8hQ9&`0ueXCe?o*8j36&!emYJS;Dy)_reHZ$SYWhFhFjwR|U`-wVB zjww&6>y5d`cSq5WRG##v2QFE@J4%wN)%-10rF=v=dpA(}_8`jI`2aOujHKC#A<<(c z9rWz~Wi2M0*J9j5OLp5bzOcQ6&ob6iPxa@wQ^r@Jl(p+Y>Ml=VtYaBok4zN<-jyfb zni#YOA{2pq_{)T$0&QW&c z8p_;pAC30Z1>`BQ=x|Uw`wj6@p7a*$Fc%OXNH*4+Gh;=bZDw5PZ}vCy{C+C>{&`J) zYzEm!Od7*Ns5v^ZP!&s)S7&_!#A9T?wMm?*xu{@W=xA1yl1 zy~4QX$Q1Q3=lP=0f+upr$ngOCVy-o??_ghCJ-S(ZH+O4zMH)|jHK#t4vm#HMGWJnn z+(z*!;<5bb+q!r`hsohCi6e_KMi-5$Lr>LNu^n<74`Q+8hu5g_(r#+bij}xhv088F zP<#4wv4MSlN_Ul<9_{1$_B1u5?Vy3C9IkIl95(#AQ|vO_enm^H*WQipN;yM+Lnhn6 zYb)RcdZV71qu0@*ZB}qt>#H}4{ASKe%=HY!kSS^%*QhYvG~nC7^5O3Ri&^L(4>1UT zpdRU!4_w9ZcUR0^-?I!h<%%rxp7oMTO|F`t#6t+pPmZt-zEu9_sNva>tKH|Y&rL~9 z_m5UqZ8J9|?$jEr#-ch`{Had?Ykq><4@>rW$>|KfKU;IY(-JSbmU7HP9%}FPic}iw zZ6GB#gXr3YaNRzz=Eb|@EB6>|irCf~@LOw6ocQ2cTEsuLo1dmkzHjrsep0u5!DY;XB?n^fodcmLIIX^w-UJrB@;8C+m;+1-?(&G%cp8^h+ zzqDwR-l8n=4O-%!3-_P3Ac4vfHxYaYpMam%K<|*n_h-Y!U4jK(dJFdJP)oiRDc36} z-@j+6^5h3n!<5)VO;q}Vs*@kCC|{8k#Igz)%l9Ko#JO@}Hb`D->5sk6(&G{XCiWj< z5BLugY#CNc;Ah~ zdM)>!$jR|PE$($sHGmoKvq%s3d+tFm4E7edk4FDk%(V>mz?#3|dt&2JaNAwP7wE=12WKeRi%7P#qlq5U-yaF&a=-32D#RFDN105ZucK~naT0GEbqN~Z9g(i33V=lY+k0oCozH{Meo1u=PB-Y~z z={Hg5in$lAJH1WnF5;^8%r@?iU*#IOisc3oUaMB yr=;GX=H&4=?1SGY!yV;kcfdG`xZh7 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'/api/admin/booking/list', { - params, - responseReturn: 'body', - }); -} - -/** - * 同意座位变更申请 - * POST /api/admin/booking/approve - */ -export async function approveBookingApi(data: BookingApi.ApproveParams) { - return requestClient.post('/api/admin/booking/approve', data); -} - -/** - * 拒绝座位变更申请 - * POST /api/admin/booking/reject - */ -export async function rejectBookingApi(data: BookingApi.RejectParams) { - return requestClient.post('/api/admin/booking/reject', data); -} - diff --git a/apps/web-antd/src/api/admin/classroom.ts b/apps/web-antd/src/api/admin/classroom.ts index 7e13de9..ef82ed0 100644 --- a/apps/web-antd/src/api/admin/classroom.ts +++ b/apps/web-antd/src/api/admin/classroom.ts @@ -103,7 +103,9 @@ export namespace ClassroomApi { /** 取消选座参数 */ export interface CancelBookingParams { - id: number; + id?: number; // 预订ID(优先使用) + seat_number?: string; // 座位号(当没有预订ID时使用) + classroomId?: number; // 教室ID(使用座位号时必填) } /** 批量取消选座参数 */ @@ -123,6 +125,8 @@ export namespace ClassroomApi { student_name: string; student_mobile: string; select_time: string; + booking_id?: number; // 预订ID(用于取消选座) + student_id?: number; // 学生ID(用于分配座位) } } @@ -211,7 +215,10 @@ export async function randomAssignSeatsApi(data: ClassroomApi.RandomAssignSeatsP * GET /api/admin/classroom/getUnassignedStudents */ export async function getUnassignedStudentsApi(params: ClassroomApi.UnassignedStudentsParams) { - return requestClient.get('/api/admin/classroom/getUnassignedStudents', { params }); + return requestClient.get('/api/admin/classroom/getUnassignedStudents', { + params, + responseReturn: 'body', + }); } /** diff --git a/apps/web-antd/src/api/admin/index.ts b/apps/web-antd/src/api/admin/index.ts index d30a29d..571dac8 100644 --- a/apps/web-antd/src/api/admin/index.ts +++ b/apps/web-antd/src/api/admin/index.ts @@ -1,5 +1,4 @@ export * from './classroom'; -export * from './booking'; export * from './student'; export * from './class'; export * from './teacher'; diff --git a/apps/web-antd/src/locales/langs/zh-CN/page.json b/apps/web-antd/src/locales/langs/zh-CN/page.json index c0ba614..4c199e5 100644 --- a/apps/web-antd/src/locales/langs/zh-CN/page.json +++ b/apps/web-antd/src/locales/langs/zh-CN/page.json @@ -16,10 +16,6 @@ "list": "教室列表", "detail": "教室详情" }, - "booking": { - "title": "预订管理", - "list": "预订列表" - }, "student": { "title": "学生管理", "list": "学生列表", diff --git a/apps/web-antd/src/router/routes/modules/booking.ts b/apps/web-antd/src/router/routes/modules/booking.ts deleted file mode 100644 index bb2cc33..0000000 --- a/apps/web-antd/src/router/routes/modules/booking.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { $t } from '#/locales'; - -const routes: RouteRecordRaw[] = [ - { - name: 'BookingList', - path: '/booking/list', - component: () => import('#/views/booking/list.vue'), - meta: { - icon: 'lucide:calendar-check', - order: 2, - title: $t('page.booking.title'), - }, - }, -]; - -export default routes; - diff --git a/apps/web-antd/src/views/_core/authentication/code-login.vue b/apps/web-antd/src/views/_core/authentication/code-login.vue index 6b76017..61c4e48 100644 --- a/apps/web-antd/src/views/_core/authentication/code-login.vue +++ b/apps/web-antd/src/views/_core/authentication/code-login.vue @@ -1,6 +1,5 @@ diff --git a/apps/web-antd/src/views/_core/authentication/login.vue b/apps/web-antd/src/views/_core/authentication/login.vue index c91c62d..848c40b 100644 --- a/apps/web-antd/src/views/_core/authentication/login.vue +++ b/apps/web-antd/src/views/_core/authentication/login.vue @@ -1,6 +1,5 @@ - - - diff --git a/apps/web-antd/src/views/class/list.vue b/apps/web-antd/src/views/class/list.vue index aebb25d..ffeb50a 100644 --- a/apps/web-antd/src/views/class/list.vue +++ b/apps/web-antd/src/views/class/list.vue @@ -3,10 +3,10 @@ import { ref, reactive, onMounted } from 'vue'; import { useRouter } from 'vue-router'; import { Page } from '@vben/common-ui'; import { Button, Card, Table, Space, message, Modal, Input } from 'ant-design-vue'; -import { - getClassListApi, +import { + getClassListApi, deleteClassApi, - type ClassApi + type ClassApi } from '#/api'; defineOptions({ name: 'ClassList' }); @@ -53,7 +53,6 @@ const columns = [ { title: '教室', key: 'classroom_name', - width: 150, }, { title: '学生人数', @@ -114,8 +113,8 @@ const handleAdd = () => { }; const handleEdit = (record: ClassApi.ClassInfo) => { - router.push({ - name: 'ClassDetail', + router.push({ + name: 'ClassDetail', params: { id: record.id } }); }; diff --git a/apps/web-antd/src/views/classroom/detail.vue b/apps/web-antd/src/views/classroom/detail.vue index 40555b1..947302f 100644 --- a/apps/web-antd/src/views/classroom/detail.vue +++ b/apps/web-antd/src/views/classroom/detail.vue @@ -2,7 +2,8 @@ import { ref, onMounted, computed } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import { Page } from '@vben/common-ui'; -import { Card, Form, Input, Select, InputNumber, Button, message, Tabs, Radio } from 'ant-design-vue'; +import { Card, Form, Input, Select, InputNumber, Button, message, Tabs, Radio, DatePicker } from 'ant-design-vue'; +import dayjs, { type Dayjs } from 'dayjs'; import { getClassroomDetailApi, saveClassroomApi, @@ -24,7 +25,10 @@ const schoolList = ref([]); const teacherList = ref([]); const schoolListLoading = ref(false); const teacherListLoading = ref(false); -const formData = ref({ +const formData = ref({ name: '', building: '', floor: '', @@ -35,6 +39,8 @@ const formData = ref({ teacher_id: undefined, description: '', status: 1, + fiexd_start_time: null, + fiexd_end_time: null, }); const isEdit = computed(() => !!route.params.id); @@ -60,6 +66,13 @@ const handleSubmit = async () => { delete data.floor; delete data.room; } + // 处理选座时间字段,将 dayjs 对象转换为字符串 + if (formData.value.fiexd_start_time) { + (data as any).fiexd_start_time = formData.value.fiexd_start_time.format('YYYY-MM-DD HH:mm:ss'); + } + if (formData.value.fiexd_end_time) { + (data as any).fiexd_end_time = formData.value.fiexd_end_time.format('YYYY-MM-DD HH:mm:ss'); + } await saveClassroomApi(data); message.success(isEdit.value ? '更新成功' : '创建成功'); router.back(); @@ -93,6 +106,8 @@ const fetchDetail = async () => { teacher_id: data.teacher_id, description: data.description || '', status: data.status !== undefined ? data.status : 1, + fiexd_start_time: (data as any).fiexd_start_time ? dayjs((data as any).fiexd_start_time) : null, + fiexd_end_time: (data as any).fiexd_end_time ? dayjs((data as any).fiexd_end_time) : null, }; } else { message.error(res?.message || res?.msg || '获取教室详情失败'); @@ -141,6 +156,17 @@ const fetchTeacherList = async () => { } }; +// 验证选座结束时间 +const validateEndTime = (_rule: any, value: Dayjs | null) => { + return new Promise((resolve, reject) => { + if (value && formData.value.fiexd_start_time && value.isBefore(formData.value.fiexd_start_time)) { + reject('选座结束时间必须晚于开始时间'); + } else { + resolve(); + } + }); +}; + onMounted(() => { fetchSchoolList(); fetchTeacherList(); @@ -253,6 +279,35 @@ onMounted(() => { + + + + + + 启用 diff --git a/apps/web-antd/src/views/classroom/list.vue b/apps/web-antd/src/views/classroom/list.vue index 3cdc6ff..1bb52e9 100644 --- a/apps/web-antd/src/views/classroom/list.vue +++ b/apps/web-antd/src/views/classroom/list.vue @@ -9,9 +9,16 @@ import { batchDeleteClassroomApi, getClassroomSeatListApi, getClassroomDetailApi, + getClassroomSchoolListApi, + getClassroomStatusApi, + cancelBookingApi, + cancelBookingAutoApi, + assignSeatApi, + getUnassignedStudentsApi, type ClassroomApi } from '#/api'; import SeatLayoutEditor from '#/components/classroom/SeatLayoutEditor.vue'; +import * as XLSX from 'xlsx-js-style'; defineOptions({ name: 'ClassroomList' }); @@ -41,10 +48,13 @@ const seatListPagination = reactive({ current: 1, pageSize: 10, }); +const selectedSeatKeys = ref([]); const layoutVisible = ref(false); const layoutLoading = ref(false); const currentClassroomId = ref(null); const currentLayout = ref(null); +const schoolList = ref>([]); +const schoolListLoading = ref(false); const columns = [ { @@ -59,27 +69,32 @@ const columns = [ key: 'name', }, { - title: '楼栋', - dataIndex: 'building', - key: 'building', + title: '校区', + dataIndex: 'school_name', + key: 'school_name', customRender: ({ record }: { record: ClassroomApi.ClassroomInfo }) => { - return record.location?.building || record.building || '-'; + return (record as any).school_name || '-'; }, }, { - title: '楼层', - dataIndex: 'floor', - key: 'floor', + title: '地址', + dataIndex: 'address', + key: 'address', customRender: ({ record }: { record: ClassroomApi.ClassroomInfo }) => { - return record.location?.floor || record.floor || '-'; - }, - }, - { - title: '房间号', - dataIndex: 'room', - key: 'room', - customRender: ({ record }: { record: ClassroomApi.ClassroomInfo }) => { - return record.location?.room || '-'; + const building = record.location?.building || record.building || ''; + const floor = record.location?.floor || record.floor || ''; + const room = record.location?.room || ''; + const addressParts: string[] = []; + if (building && building.trim() !== '') { + addressParts.push(building); + } + if (floor && floor.trim() !== '') { + addressParts.push(`${floor}层`); + } + if (room && room.trim() !== '') { + addressParts.push(`${room}号`); + } + return addressParts.length > 0 ? addressParts.join('') : '-'; }, }, { @@ -213,10 +228,23 @@ const handleViewStatus = async (record: ClassroomApi.ClassroomInfo) => { } seatListVisible.value = true; + currentClassroomId.value = record.id; currentClassroomName.value = record.name || ''; seatListLoading.value = true; seatListData.value = []; + // 同时加载布局数据,以便导出布局 + currentLayout.value = null; + try { + const layoutRes = await getClassroomDetailApi({ id: record.id }); + if (layoutRes && (layoutRes.code === 0 || layoutRes.code === 200)) { + const data = layoutRes.data || layoutRes; + currentLayout.value = data.layout || null; + } + } catch (error) { + console.warn('获取教室布局失败:', error); + } + try { const res = await getClassroomSeatListApi({ id: record.id }); if (res && (res.code === 0 || res.code === 200)) { @@ -288,6 +316,273 @@ const handleLayoutSaved = (layout: ClassroomApi.ClassroomLayout) => { // handleCloseLayout(); }; +// 导出座位布局为Excel +const handleExportLayout = async () => { + if (!currentLayout.value || !currentClassroomId.value) { + message.warning('没有布局数据可导出'); + return; + } + + try { + // 获取座位状态数据 + let seatStatusMap = new Map(); + try { + const statusRes = await getClassroomSeatListApi({ id: currentClassroomId.value }); + if (statusRes && (statusRes.code === 0 || statusRes.code === 200)) { + const seatList = Array.isArray(statusRes.data) ? statusRes.data : []; + seatList.forEach((seat: ClassroomApi.SeatInfo) => { + if (seat.is_selected === 1 && seat.seat_number) { + seatStatusMap.set(seat.seat_number, { + name: seat.student_name || '', + date: seat.select_time || '', + }); + } + }); + } + } catch (error) { + console.warn('获取座位状态失败,将只导出布局信息:', error); + } + + const layout = currentLayout.value; + const cols = layout.cols || 0; + const rows = layout.rows || 0; + const cells = layout.cells || []; + + if (cols === 0 || rows === 0) { + message.warning('布局数据为空,无法导出'); + return; + } + + // 创建单元格映射 + const cellMap = new Map(); + cells.forEach((cell) => { + const key = `${cell.col},${cell.row}`; + cellMap.set(key, cell); + }); + + // 先创建数据数组 + const data: any[][] = []; + + // 第一行:列标题(空单元格 + A, B, C...) + const headerRow: any[] = ['']; + for (let col = 0; col < cols; col++) { + // 处理超过26列的情况(AA, AB, AC...) + let colChar = ''; + if (col < 26) { + colChar = String.fromCharCode(65 + col); // A-Z + } else { + const first = Math.floor(col / 26) - 1; + const second = col % 26; + colChar = String.fromCharCode(65 + first) + String.fromCharCode(65 + second); + } + headerRow.push(colChar); + } + data.push(headerRow); + + // 填充数据行 + for (let row = 0; row < rows; row++) { + const dataRow: any[] = [row + 1]; // 行号 + for (let col = 0; col < cols; col++) { + const key = `${col},${row}`; + const cell = cellMap.get(key); + + if (!cell || cell.merged) { + // 被合并的单元格或空白单元格 + dataRow.push(''); + } else { + let cellValue = ''; + + if (cell.type === 'seat') { + const seatNumber = cell.number || ''; + const status = cell.status === 0 ? '不可选' : ''; + const seatStatus = seatStatusMap.get(seatNumber); + + if (seatStatus) { + // 已预订:编号、姓名、日期 + cellValue = `${seatNumber} ${seatStatus.name} ${seatStatus.date}`; + } else if (status === '不可选') { + // 不可选 + cellValue = `${seatNumber} 不可选`; + } else { + // 空闲 + cellValue = `${seatNumber} 空闲`; + } + } else if (cell.type === 'aisle') { + cellValue = '过道'; + } else if (cell.type === 'projector') { + cellValue = '投影'; + } else if (cell.type === 'pillar') { + cellValue = '柱子'; + } else if (cell.type === 'door') { + cellValue = '门'; + } else { + // empty + cellValue = ''; + } + + dataRow.push(cellValue); + } + } + data.push(dataRow); + } + + // 使用 aoa_to_sheet 创建工作表 + const ws = XLSX.utils.aoa_to_sheet(data); + + // 设置样式 + const range = XLSX.utils.decode_range(ws['!ref'] || 'A1'); + + // 定义边框样式(所有单元格统一使用) + const borderStyle = { + top: { style: 'thin', color: { rgb: '000000' } }, + bottom: { style: 'thin', color: { rgb: '000000' } }, + left: { style: 'thin', color: { rgb: '000000' } }, + right: { style: 'thin', color: { rgb: '000000' } }, + }; + + // 设置列标题样式 + for (let c = 0; c <= range.e.c; c++) { + const cellRef = XLSX.utils.encode_cell({ r: 0, c }); + if (!ws[cellRef]) ws[cellRef] = { v: '', t: 's' }; + if (c === 0) { + // 行号列标题 + ws[cellRef].s = { + fill: { fgColor: { rgb: 'F6FFED' } }, + font: { bold: true }, + alignment: { horizontal: 'center', vertical: 'center' }, + border: borderStyle, + }; + } else { + // 列标题 + ws[cellRef].s = { + fill: { fgColor: { rgb: 'E6F7FF' } }, + font: { bold: true, color: { rgb: '1890FF' } }, + alignment: { horizontal: 'center', vertical: 'center' }, + border: borderStyle, + }; + } + } + + // 设置行号列样式 + for (let r = 1; r <= range.e.r; r++) { + const cellRef = XLSX.utils.encode_cell({ r, c: 0 }); + if (!ws[cellRef]) ws[cellRef] = { v: r, t: 'n' }; + ws[cellRef].s = { + fill: { fgColor: { rgb: 'F6FFED' } }, + font: { bold: true, color: { rgb: '52C41A' } }, + alignment: { horizontal: 'center', vertical: 'center' }, + border: borderStyle, + }; + } + + // 设置数据单元格样式 + for (let r = 1; r <= range.e.r; r++) { + for (let c = 1; c <= range.e.c; c++) { + const cellRef = XLSX.utils.encode_cell({ r, c }); + if (!ws[cellRef]) continue; + + const key = `${c - 1},${r - 1}`; + const cell = cellMap.get(key); + + if (!cell || cell.merged) { + ws[cellRef].s = { + fill: { fgColor: { rgb: 'FFFFFF' } }, + alignment: { horizontal: 'center', vertical: 'center' }, + border: borderStyle, + }; + } else { + let cellStyle: any = { + alignment: { horizontal: 'center', vertical: 'center', wrapText: true }, + border: borderStyle, + }; + + if (cell.type === 'seat') { + const seatNumber = cell.number || ''; + const status = cell.status === 0 ? '不可选' : ''; + const seatStatus = seatStatusMap.get(seatNumber); + + if (seatStatus) { + // 已预订(蓝色背景) + cellStyle.fill = { fgColor: { rgb: '4472C4' } }; + cellStyle.font = { color: { rgb: 'FFFFFF' }, bold: true }; + } else if (status === '不可选') { + // 不可选(红色背景) + cellStyle.fill = { fgColor: { rgb: 'FF0000' } }; + cellStyle.font = { color: { rgb: 'FFFFFF' }, bold: true }; + } else { + // 空闲(绿色背景) + cellStyle.fill = { fgColor: { rgb: '70AD47' } }; + cellStyle.font = { color: { rgb: 'FFFFFF' }, bold: true }; + } + } else if (cell.type === 'aisle') { + cellStyle.fill = { fgColor: { rgb: 'FFC000' } }; + cellStyle.font = { color: { rgb: 'FFFFFF' }, bold: true }; + } else if (cell.type === 'projector') { + cellStyle.fill = { fgColor: { rgb: '00B0F0' } }; + cellStyle.font = { color: { rgb: 'FFFFFF' }, bold: true }; + } else if (cell.type === 'pillar') { + cellStyle.fill = { fgColor: { rgb: '808080' } }; + cellStyle.font = { color: { rgb: 'FFFFFF' }, bold: true }; + } else if (cell.type === 'door') { + cellStyle.fill = { fgColor: { rgb: '1890FF' } }; + cellStyle.font = { color: { rgb: 'FFFFFF' }, bold: true }; + } else { + // empty + cellStyle.fill = { fgColor: { rgb: 'FFFFFF' } }; + } + + ws[cellRef].s = cellStyle; + } + } + } + + // 设置列宽(统一为10) + ws['!cols'] = [{ wch: 10 }]; // 行号列 + for (let i = 0; i < cols; i++) { + ws['!cols'].push({ wch: 10 }); + } + + // 设置行高(加倍高度,便于阅读) + ws['!rows'] = [{ hpt: 40 }]; // 标题行 + for (let i = 0; i < rows; i++) { + ws['!rows'].push({ hpt: 60 }); + } + + // 设置合并单元格(如果有) + const merges: any[] = []; + cells.forEach((cell) => { + if (!cell.merged) { + const colspan = cell.colspan || 1; + const rowspan = cell.rowspan || 1; + if (colspan > 1 || rowspan > 1) { + // Excel合并单元格:结束位置 = 起始位置 + 跨度 - 1 + // 注意:Excel的行列从0开始,但我们的数据从1开始(因为有标题行) + merges.push({ + s: { r: cell.row + 1, c: cell.col + 1 }, + e: { r: cell.row + rowspan, c: cell.col + colspan }, + }); + } + } + }); + if (merges.length > 0) { + ws['!merges'] = merges; + } + + // 创建工作簿并添加工作表 + const wb = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(wb, ws, '座位布局'); + + // 导出文件 + const fileName = `${currentClassroomName.value || '教室'}_座位布局_${new Date().toISOString().slice(0, 10)}.xlsx`; + XLSX.writeFile(wb, fileName); + + message.success('导出成功'); + } catch (error: any) { + console.error('导出失败:', error); + message.error(error?.message || '导出失败'); + } +}; + // 处理座位列表分页变化 const handleSeatListTableChange = (pag: any) => { if (pag) { @@ -296,6 +591,214 @@ const handleSeatListTableChange = (pag: any) => { } }; +// 取消选座 +const handleCancelBooking = async (record: ClassroomApi.SeatInfo) => { + if (!currentClassroomId.value) { + message.error('教室ID不存在'); + return; + } + + // 如果没有booking_id,尝试通过座位号取消 + let bookingId = record.booking_id; + + // 如果没有booking_id,尝试通过座位状态API获取 + if (!bookingId && record.is_selected === 1) { + try { + const statusRes = await getClassroomStatusApi({ id: currentClassroomId.value }); + if (statusRes && (statusRes.code === 0 || statusRes.code === 200)) { + const statusData = statusRes.data || statusRes; + // 查找对应座位的预订ID + if (Array.isArray(statusData)) { + const seatStatus = statusData.find((item: any) => item.seat_number === record.seat_number); + if (seatStatus && seatStatus.booking_id) { + bookingId = seatStatus.booking_id; + } + } else if (statusData.seats && Array.isArray(statusData.seats)) { + const seatStatus = statusData.seats.find((item: any) => item.seat_number === record.seat_number); + if (seatStatus && seatStatus.booking_id) { + bookingId = seatStatus.booking_id; + } + } + } + } catch (error) { + console.warn('获取座位状态失败:', error); + } + } + + Modal.confirm({ + title: '确认取消选座', + content: `确定要取消座位 ${record.seat_number} 的选座吗?`, + onOk: async () => { + try { + // 优先使用booking_id,如果没有则使用seat_number + if (bookingId) { + await cancelBookingApi({ id: bookingId }); + } else { + // 尝试使用座位号取消 + await cancelBookingApi({ + seat_number: record.seat_number, + classroomId: currentClassroomId.value + }); + } + message.success('取消选座成功'); + // 重新加载座位列表 + if (currentClassroomId.value) { + const res = await getClassroomSeatListApi({ id: currentClassroomId.value }); + if (res && (res.code === 0 || res.code === 200)) { + seatListData.value = Array.isArray(res.data) ? res.data : []; + selectedSeatKeys.value = []; + } + } + } catch (error: any) { + console.error('取消选座失败:', error); + message.error(error?.response?.data?.message || error?.message || '取消选座失败'); + } + }, + }); +}; + +// 批量取消选座 +const handleBatchCancelBooking = () => { + if (selectedSeatKeys.value.length === 0) { + message.warning('请选择要取消的座位'); + return; + } + + // 获取选中的已选座位 + const selectedSeats = seatListData.value.filter( + (seat) => selectedSeatKeys.value.includes(seat.seat_number) && seat.is_selected === 1 && seat.booking_id + ); + + if (selectedSeats.length === 0) { + message.warning('请选择已选座的座位'); + return; + } + + const bookingIds = selectedSeats.map((seat) => seat.booking_id!).filter((id) => id); + + if (bookingIds.length === 0) { + message.warning('选中的座位中没有有效的预订ID'); + return; + } + + Modal.confirm({ + title: '确认批量取消选座', + content: `确定要取消 ${bookingIds.length} 个座位的选座吗?`, + onOk: async () => { + try { + await cancelBookingAutoApi({ ids: bookingIds }); + message.success('批量取消选座成功'); + // 重新加载座位列表 + if (currentClassroomId.value) { + const res = await getClassroomSeatListApi({ id: currentClassroomId.value }); + if (res && (res.code === 0 || res.code === 200)) { + seatListData.value = Array.isArray(res.data) ? res.data : []; + selectedSeatKeys.value = []; + } + } + } catch (error: any) { + console.error('批量取消选座失败:', error); + message.error(error?.response?.data?.message || error?.message || '批量取消选座失败'); + } + }, + }); +}; + +// 分配座位相关 +const assignSeatVisible = ref(false); +const assignSeatRecord = ref(null); +const unassignedStudents = ref>([]); +const unassignedStudentsLoading = ref(false); +const selectedStudentId = ref(undefined); + +// 打开分配座位弹窗 +const handleAssignSeat = async (record: ClassroomApi.SeatInfo) => { + if (!currentClassroomId.value) { + message.error('教室ID不存在'); + return; + } + + assignSeatRecord.value = record; + assignSeatVisible.value = true; + selectedStudentId.value = undefined; + unassignedStudents.value = []; + unassignedStudentsLoading.value = true; + + try { + const res = await getUnassignedStudentsApi({ classroomId: currentClassroomId.value }); + if (res && (res.code === 0 || res.code === 200)) { + const data = res.data || res; + unassignedStudents.value = Array.isArray(data) ? data : []; + if (unassignedStudents.value.length === 0) { + message.warning('没有未分配座位的学生'); + } + } else { + message.error(res?.message || res?.msg || '获取学生列表失败'); + unassignedStudents.value = []; + } + } catch (error: any) { + console.error('获取学生列表失败:', error); + message.error(error?.response?.data?.message || error?.message || '获取学生列表失败'); + unassignedStudents.value = []; + } finally { + unassignedStudentsLoading.value = false; + } +}; + +// 确认分配座位 +const handleConfirmAssignSeat = async () => { + if (!assignSeatRecord.value || !selectedStudentId.value || !currentClassroomId.value) { + message.warning('请选择学生'); + return; + } + + if (!currentLayout.value) { + message.error('布局数据不存在,无法分配座位'); + return; + } + + // 从布局数据中找到座位的row和col + const seatNumber = assignSeatRecord.value.seat_number; + const cells = currentLayout.value.cells || []; + const seatCell = cells.find((cell) => cell.type === 'seat' && cell.number === seatNumber); + + if (!seatCell) { + message.error('找不到对应的座位信息'); + return; + } + + try { + await assignSeatApi({ + classroomId: currentClassroomId.value, + studentId: selectedStudentId.value, + row: seatCell.row, + col: seatCell.col, + number: seatNumber, + }); + message.success('分配座位成功'); + assignSeatVisible.value = false; + assignSeatRecord.value = null; + selectedStudentId.value = undefined; + + // 重新加载座位列表 + const res = await getClassroomSeatListApi({ id: currentClassroomId.value }); + if (res && (res.code === 0 || res.code === 200)) { + seatListData.value = Array.isArray(res.data) ? res.data : []; + } + } catch (error: any) { + console.error('分配座位失败:', error); + message.error(error?.response?.data?.message || error?.message || '分配座位失败'); + } +}; + +// 关闭分配座位弹窗 +const handleCloseAssignSeat = () => { + assignSeatVisible.value = false; + assignSeatRecord.value = null; + selectedStudentId.value = undefined; + unassignedStudents.value = []; +}; + // 导出座位列表 const handleExportSeatList = () => { if (seatListData.value.length === 0) { @@ -345,7 +848,28 @@ const handleExportSeatList = () => { message.success('导出成功'); }; +// 获取校区列表 +const fetchSchoolList = async () => { + schoolListLoading.value = true; + try { + const res = await getClassroomSchoolListApi(); + if (Array.isArray(res)) { + schoolList.value = res; + } else if (res && (res.code === 0 || res.code === 200)) { + schoolList.value = Array.isArray(res.data) ? res.data : []; + } else { + schoolList.value = []; + } + } catch (error) { + console.error('获取校区列表失败:', error); + schoolList.value = []; + } finally { + schoolListLoading.value = false; + } +}; + onMounted(() => { + fetchSchoolList(); fetchData(); }); @@ -367,6 +891,27 @@ onMounted(() => { style="width: 200px" @press-enter="handleSearch" /> + + + {{ student.name }} ({{ student.phone || '' }}) + + + +
+ + + + +
+ + >({ name: '', @@ -29,18 +24,7 @@ const formData = ref>({ description: '', }); -// 账号管理相关 -const accountList = ref([]); -const accountFormData = ref>({ - username: '', - password: '', - status: 1, -}); -const accountModalVisible = ref(false); -const isAccountEdit = ref(false); - const isEdit = computed(() => !!route.params.id); -const schoolId = computed(() => isEdit.value ? Number(route.params.id) : 0); const handleSubmit = async () => { try { @@ -92,114 +76,9 @@ const fetchDetail = async () => { } }; -// 账号管理相关方法 -const accountColumns = [ - { - title: 'ID', - dataIndex: 'id', - key: 'id', - width: 80, - }, - { - title: '用户名', - dataIndex: 'username', - key: 'username', - }, - { - title: '状态', - dataIndex: 'status', - key: 'status', - width: 100, - }, - { - title: '操作', - key: 'action', - width: 200, - }, -]; - -const fetchAccountList = async () => { - if (!isEdit.value) return; - accountLoading.value = true; - try { - const res = await getSchoolAccountListApi({ school_id: schoolId.value }); - // 支持 code 为 0 或 200 的成功响应 - if (res && (res.code === 0 || res.code === 200)) { - // 根据实际返回格式,数据在 res.data 数组中 - const data = Array.isArray(res.data) ? res.data : []; - accountList.value = data; - } else { - accountList.value = []; - } - } catch (error) { - console.error('获取账号列表失败:', error); - accountList.value = []; - } finally { - accountLoading.value = false; - } -}; - -const handleAddAccount = () => { - isAccountEdit.value = false; - accountFormData.value = { - username: '', - password: '', - status: 1, - }; - accountModalVisible.value = true; -}; - -const handleEditAccount = (record: SchoolApi.AccountInfo) => { - isAccountEdit.value = true; - accountFormData.value = { - id: record.id, - username: record.username || '', - password: '', // 编辑时不显示密码 - status: record.status !== undefined ? record.status : 1, - }; - accountModalVisible.value = true; -}; - -const handleDeleteAccount = (record: SchoolApi.AccountInfo) => { - Modal.confirm({ - title: '确认删除', - content: `确定要删除账号"${record.username}"吗?`, - onOk: async () => { - try { - await deleteSchoolAccountApi({ id: record.id! }); - message.success('删除成功'); - fetchAccountList(); - } catch (error) { - console.error('删除失败:', error); - } - }, - }); -}; - -const handleAccountSubmit = async () => { - try { - await accountFormRef.value.validate(); - accountLoading.value = true; - const data = { - ...accountFormData.value, - school_id: schoolId.value, - }; - await saveSchoolAccountApi(data); - message.success(isAccountEdit.value ? '更新成功' : '创建成功'); - accountModalVisible.value = false; - fetchAccountList(); - } catch (error: any) { - console.error('保存账号失败:', error); - message.error(error?.response?.data?.message || error?.response?.data?.msg || '保存账号失败'); - } finally { - accountLoading.value = false; - } -}; - onMounted(() => { if (isEdit.value) { fetchDetail(); - fetchAccountList(); } }); @@ -240,78 +119,9 @@ onMounted(() => {
- -
- -
- - -
-
- - -
- - - - - -
- 留空则不修改密码 -
-
- - - 启用 - 禁用 - - -
-
diff --git a/apps/web-antd/src/views/school/list.vue b/apps/web-antd/src/views/school/list.vue index 7bb4a0f..6494767 100644 --- a/apps/web-antd/src/views/school/list.vue +++ b/apps/web-antd/src/views/school/list.vue @@ -2,11 +2,14 @@ import { ref, reactive, onMounted } from 'vue'; import { useRouter } from 'vue-router'; import { Page } from '@vben/common-ui'; -import { Button, Card, Table, Space, message, Modal, Input } from 'ant-design-vue'; -import { - getSchoolListApi, +import { Button, Card, Table, Space, message, Modal, Input, Form, Radio } from 'ant-design-vue'; +import { + deleteSchoolAccountApi, deleteSchoolApi, - type SchoolApi + getSchoolAccountListApi, + getSchoolListApi, + saveSchoolAccountApi, + type SchoolApi } from '#/api'; defineOptions({ name: 'SchoolList' }); @@ -58,7 +61,7 @@ const columns = [ { title: '操作', key: 'action', - width: 200, + width: 280, }, ]; @@ -95,8 +98,8 @@ const handleSearch = () => { }; const handleEdit = (record: SchoolApi.SchoolInfo) => { - router.push({ - name: 'SchoolDetail', + router.push({ + name: 'SchoolDetail', params: { id: record.id } }); }; @@ -117,6 +120,113 @@ const handleDelete = (record: SchoolApi.SchoolInfo) => { }); }; +// 账号管理 +const accountLoading = ref(false); +const accountModalVisible = ref(false); +const accountFormRef = ref(); +const accountList = ref([]); +const accountColumns = [ + { title: 'ID', dataIndex: 'id', key: 'id', width: 80 }, + { title: '用户名', dataIndex: 'username', key: 'username' }, + { title: '状态', dataIndex: 'status', key: 'status', width: 100 }, + { title: '操作', key: 'action', width: 200 }, +]; +const currentSchool = ref(null); +const isAccountEdit = ref(false); +const accountFormData = ref>({ + username: '', + password: '', + status: 1, +}); + +const fetchAccountList = async (schoolId: number) => { + accountLoading.value = true; + try { + const res = await getSchoolAccountListApi({ school_id: schoolId }); + if (res && (res.code === 0 || res.code === 200)) { + accountList.value = Array.isArray(res.data) ? res.data : []; + } else { + accountList.value = []; + } + } catch (error) { + console.error('获取账号列表失败:', error); + accountList.value = []; + } finally { + accountLoading.value = false; + } +}; + +const openAccountModal = (record: SchoolApi.SchoolInfo) => { + currentSchool.value = record; + isAccountEdit.value = false; + accountFormData.value = { + username: '', + password: '', + status: 1, + }; + fetchAccountList(record.id!); + accountModalVisible.value = true; +}; + +const handleAddAccount = () => { + isAccountEdit.value = false; + accountFormData.value = { + username: '', + password: '', + status: 1, + }; +}; + +const handleEditAccount = (record: SchoolApi.AccountInfo) => { + isAccountEdit.value = true; + accountFormData.value = { + id: record.id, + username: record.username || '', + password: '', + status: record.status !== undefined ? record.status : 1, + }; +}; + +const handleDeleteAccount = (record: SchoolApi.AccountInfo) => { + Modal.confirm({ + title: '确认删除', + content: `确定要删除账号"${record.username}"吗?`, + onOk: async () => { + if (!record.id) return; + try { + await deleteSchoolAccountApi({ id: record.id }); + message.success('删除成功'); + if (currentSchool.value?.id) { + fetchAccountList(currentSchool.value.id); + } + } catch (error) { + console.error('删除失败:', error); + } + }, + }); +}; + +const handleAccountSubmit = async () => { + if (!currentSchool.value?.id) return; + try { + await accountFormRef.value.validate(); + accountLoading.value = true; + const data = { + ...accountFormData.value, + school_id: currentSchool.value.id, + }; + await saveSchoolAccountApi(data); + message.success(isAccountEdit.value ? '更新成功' : '创建成功'); + fetchAccountList(currentSchool.value.id); + accountModalVisible.value = false; + } catch (error: any) { + console.error('保存账号失败:', error); + message.error(error?.response?.data?.message || error?.response?.data?.msg || '保存账号失败'); + } finally { + accountLoading.value = false; + } +}; + const handleTableChange = (pag: any) => { pagination.current = pag.current; pagination.pageSize = pag.pageSize; @@ -171,6 +281,7 @@ onMounted(() => { + + +
+ +
+ + +
+ +
+ + + + + +
+ 留空则不修改密码 +
+
+ + + 启用 + 禁用 + + +
+
diff --git a/apps/web-antd/src/views/student/detail.vue b/apps/web-antd/src/views/student/detail.vue index 8cc999f..345f3cb 100644 --- a/apps/web-antd/src/views/student/detail.vue +++ b/apps/web-antd/src/views/student/detail.vue @@ -19,7 +19,6 @@ const loading = ref(false); const classList = ref([]); const classListLoading = ref(false); const formData = ref>({ - student_id: '', name: '', gender: undefined, class_id: undefined, @@ -124,14 +123,14 @@ onMounted(() => { :wrapper-col="{ span: 20 }" > (''); const selectedClassId = ref(undefined); const classList = ref([]); const fileList = ref([]); +const classListLoading = ref(false); // 获取班级列表 const fetchClassList = async () => { + classListLoading.value = true; try { const res = await getClassListApi({ page: 1, limit: 1000 }); if (res && (res.code === 0 || res.code === 200)) { @@ -196,7 +198,10 @@ const fetchClassList = async () => { classList.value = []; } } catch (error) { + console.error('获取班级列表失败:', error); classList.value = []; + } finally { + classListLoading.value = false; } }; @@ -309,6 +314,7 @@ const handleCancelImport = () => { }; onMounted(() => { + fetchClassList(); fetchData(); }); @@ -339,6 +345,27 @@ onMounted(() => { allow-clear @press-enter="handleSearch" /> +