diff --git a/backend/app/api/v1/admin/admin.py b/backend/app/api/v1/admin/admin.py index edf6780..924a386 100644 --- a/backend/app/api/v1/admin/admin.py +++ b/backend/app/api/v1/admin/admin.py @@ -2,15 +2,23 @@ # @FileName :users.py # @Time :2024/4/16 下午9:31 # @Author :dayezi +from typing import Union + from fastapi import APIRouter, Request, Query from tortoise.expressions import Q, F +from app.controllers import user_controller +from app.controllers.checked import checkedController +from app.controllers.depart import departController from app.controllers.dutyLog import dutyLogController, dutyNotesController +from app.models import Checked +from app.schemas.checked import ReturnChecked, CheckedCreate from app.settings import settings from app.core.init_db import test_db, set_db from app.log import logger from app.schemas import Success, Fail, SuccessExtra from app.schemas.admin import DbInfo +from app.utils import now router = APIRouter() @@ -76,3 +84,68 @@ async def get_duty_note(id: int = Query(..., description="值班备注id")): data = await dutyNotesController.get(id) data = await data.to_dict() return Success(data=data) + + +@router.get("/getChecked", summary="获取物资送检信息") +async def get_checked( + area: str = Query("glb", description="区域"), + metaType: str = Query("tool", description="工具类型"), + returnStatus: bool = Query(False, description="归还状态"), + page: int = Query(1, description="页码"), + pageSize: int = Query(10, description="每页数量"), +): + q = Q(area__contains=area) & Q(type__contains=metaType) & Q(returnStatus=returnStatus) + total, checked_objs = await checkedController.list(page=page, page_size=pageSize, search=q) + data = [] + for obj in checked_objs: + user = await obj.toCheckUser.all() + userDepart = await departController.get_all_name(user) + material = await obj.material.all() + material_dict = await material.to_dict() + user_dict = await user.to_dict() + user_dict["depart"] = userDepart + obj_dict = await obj.to_dict() + obj_dict["material"] = material_dict + obj_dict["toCheckUser"] = user_dict + try: + user2 = await obj.toReturnUser.all() + user2Depart = await departController.get_all_name(user2) + user_dict2 = await user2.to_dict() + obj_dict["toReturnUser"] = user_dict2 + user_dict2["depart"] = user2Depart + except Exception as e: + logger.warning(f"物资【{material.name}】暂无归还人信息:{e}") + data.append(obj_dict) + return SuccessExtra(data=data, total=total, currentPage=page, pageSize=pageSize) + + +@router.post("/updateChecked", summary="归还送检物资") +async def update_checked(data: ReturnChecked): + obj: Checked = await checkedController.get(id=data.id) + material = await obj.material.all() + material.checking -= obj.number + user = await user_controller.get_by_uuid(data.toReturnUserUUID) + obj.toReturnUser = user + obj.returnStatus = True + obj.note = data.note + obj.returnDate = now() + await obj.save() + return Success(msg="更新成功!") + + +@router.post("/createChecked", summary="创建物资送检信息") +async def create_checked(data: dict): + user = await user_controller.get_by_uuid(data.get("toCheckUserUUID")) + data["toCheckUser"] = user + obj: Checked = await checkedController.create(obj_in=data) + material = await obj.material.all() + material.checking += obj.number + await material.save() + return Success(data=await obj.to_dict(m2m=True)) + + +@router.get("/deleteChecked", summary="删除物资送检信息") +async def delete_checked(data: list[int]): + for id in data: + await checkedController.remove(id) + return Success(msg="删除成功!") diff --git a/backend/app/api/v1/base/base.py b/backend/app/api/v1/base/base.py index a8b95a7..41e6464 100644 --- a/backend/app/api/v1/base/base.py +++ b/backend/app/api/v1/base/base.py @@ -97,7 +97,20 @@ async def refresh_token(refreshToken: refreshTokenSchema): return Success(data=data.model_dump()) -@router.get("/userinfo", summary="查看用户信息", dependencies=[DependAuth]) +@router.post("/userinfo", summary="获取用户UUID") +async def get_user_id(credentials: CredentialsSchema): + user: User = await user_controller.authenticate(credentials) + depart = await departController.get_all_name(user) + data = { + "uuid": user.uuid.__str__(), + "username": user.username, + "phone": user.phone, + "depart": depart + } + return Success(data=data) + + +@router.get("/userinfos", summary="查看用户信息", dependencies=[DependAuth]) async def get_userinfo(): user_id = CTX_USER_ID.get() user_obj = await user_controller.get(id=user_id) diff --git a/backend/app/api/v1/home/home.py b/backend/app/api/v1/home/home.py index e0738a9..80d6f7b 100644 --- a/backend/app/api/v1/home/home.py +++ b/backend/app/api/v1/home/home.py @@ -22,16 +22,17 @@ async def get_home_list( area: str = Query("glb", description="区域"), page: int = Query(1, description="页码"), pageSize: int = Query(10, description="每页数量"), - borrowedStatus: bool = Query(False, description="借用批准状态"), + borrowedStatus: Union[bool, None] = Query(None, description="借用批准状态"), borrowWhether: Union[bool, None] = Query(None, description="借用通过状态"), returnStatus: Union[bool, None] = Query(None, description="归还批准状态") ): - if returnStatus is None: - q = Q(Q(material__area=area), Q(borrowApproveStatus=borrowedStatus)) - else: - q = Q(Q(material__area=area), Q(borrowApproveWhether=borrowWhether), Q(returnApproveStatus=returnStatus)) + q = Q(material__area=area) + if borrowedStatus is not None: + q &= Q(borrowApproveStatus=borrowedStatus) + if borrowWhether is not None: + q &= Q(Q(borrowApproveWhether=borrowWhether), Q(returnApproveStatus=returnStatus)) total, objs = await borrowedController.list(page=page, page_size=pageSize, search=q) - data = [await obj.to_dict(m2m=True, exclude_fields=["password"]) for obj in objs] + data = [await obj.to_dict(m2m=True) for obj in objs] return SuccessExtra(data=data, total=total, currentPage=page, pageSize=pageSize) diff --git a/backend/app/controllers/checked.py b/backend/app/controllers/checked.py new file mode 100644 index 0000000..8e564a0 --- /dev/null +++ b/backend/app/controllers/checked.py @@ -0,0 +1,15 @@ +# coding=utf-8 +# @FileName :checked.py +# @Time :2024/6/20 下午10:29 +# @Author :dayezi +from app.core.crud import CRUDBase +from app.models import Checked +from app.schemas.checked import CheckedCreate, CheckedUpdate + + +class CheckedController(CRUDBase[Checked, CheckedCreate, CheckedUpdate]): + def __init__(self): + super().__init__(model=Checked) + + +checkedController = CheckedController() diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 441cadf..d45c72d 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -5,3 +5,4 @@ from .borrowed import * from .materialType import * from .area import * +from .checked import * diff --git a/backend/app/models/base.py b/backend/app/models/base.py index 3be5492..54c5f61 100644 --- a/backend/app/models/base.py +++ b/backend/app/models/base.py @@ -15,6 +15,8 @@ async def to_dict(self, m2m: bool = False, exclude_fields: list[str] | None = No d = {} for field in self._meta.db_fields: + if field == "password": + continue if field not in exclude_fields: value = getattr(self, field) if isinstance(value, datetime): diff --git a/backend/app/models/checked.py b/backend/app/models/checked.py new file mode 100644 index 0000000..49414fe --- /dev/null +++ b/backend/app/models/checked.py @@ -0,0 +1,25 @@ +# coding=utf-8 +# @FileName :checked.py +# @Time :2024/6/20 下午10:14 +# @Author :dayezi +from tortoise import fields + +from .base import BaseModel, TimestampMixin + + +class Checked(BaseModel, TimestampMixin): + area = fields.CharField(max_length=20, description="所属区域") + type = fields.CharField(max_length=20, description="物资类型") + material = fields.ForeignKeyField("models.Material", related_name="checked_material") + number = fields.IntField(description="送检数量") + toCheckUser = fields.ForeignKeyField('models.User', related_name='checked_user') + returnStatus = fields.BooleanField(default=False, description="归还状态") + returnDate = fields.DatetimeField(null=True, description="归还时间") + toReturnUser = fields.ForeignKeyField('models.User', related_name='return_user', null=True) + note = fields.CharField(max_length=200, null=True, description="备注") + + class Meta: + table = "checked" + + class PydanticMeta: + exclude = ("id", "created_at", "updated_at") diff --git a/backend/app/schemas/borrowed.py b/backend/app/schemas/borrowed.py index 01b8035..960f5a9 100644 --- a/backend/app/schemas/borrowed.py +++ b/backend/app/schemas/borrowed.py @@ -2,9 +2,7 @@ # @FileName :borrowed.py # @Time :2024/5/31 上午12:49 # @Author :dayezi -from typing import Union - -from pydantic import BaseModel, Field +from pydantic import BaseModel from tortoise.contrib.pydantic import pydantic_model_creator from app.models.borrowed import Borrowed from app.schemas.material import MaterialCreate diff --git a/backend/app/schemas/checked.py b/backend/app/schemas/checked.py new file mode 100644 index 0000000..68b05a9 --- /dev/null +++ b/backend/app/schemas/checked.py @@ -0,0 +1,28 @@ +# coding=utf-8 +# @FileName :checked.py +# @Time :2024/6/20 下午10:26 +# @Author :dayezi +from pydantic import BaseModel +from tortoise.contrib.pydantic import pydantic_model_creator + +from app.models import Checked + +CheckedPydantic = pydantic_model_creator(Checked) + + +class CheckedCreate(CheckedPydantic): + + def create_dict(self): + return self.model_dump(exclude={"id", "created_at", "updated_at"}) + + +class CheckedUpdate(CheckedPydantic): + + def update_dict(self): + return self.model_dump(exclude={"id", "created_at", "updated_at"}) + + +class ReturnChecked(BaseModel): + id: int + note: str + toReturnUserUUID: str diff --git a/backend/app/utils/__init__.py b/backend/app/utils/__init__.py index 5c153d2..a7996ff 100644 --- a/backend/app/utils/__init__.py +++ b/backend/app/utils/__init__.py @@ -25,6 +25,7 @@ def now(s: bool = True) -> str | datetime: """ 获取当前时间,datatime格式或 xxxx-xx-xx xx:xx:xx 字符串格式 + :param s: 是否返回格式化字符串 :return: 当前日期时间 """ today = datetime.now() diff --git a/frontend/src/api/admin.ts b/frontend/src/api/admin.ts index 0e7ebdc..81292e8 100644 --- a/frontend/src/api/admin.ts +++ b/frontend/src/api/admin.ts @@ -69,3 +69,33 @@ export const getDutyNote = (id: number) => { } }); }; + +export const createCheckMaterial = (data: object) => { + return http.request("post", baseUrlApi("/admin/createChecked"), { + data + }); +}; + +export const getCheckedMaterial = ( + area: string, + metaType: string, + returnStatus: boolean, + page: number, + pageSize: number +) => { + return http.request("get", baseUrlApi("/admin/getChecked"), { + params: { + area, + metaType, + returnStatus, + page, + pageSize + } + }); +}; + +export const updateCheckedMaterial = (data: object) => { + return http.request("post", baseUrlApi("/admin/updateChecked"), { + data + }); +}; diff --git a/frontend/src/api/home.ts b/frontend/src/api/home.ts index db336ed..72a867e 100644 --- a/frontend/src/api/home.ts +++ b/frontend/src/api/home.ts @@ -37,3 +37,9 @@ export const updateBorrowedInfo = ( data: { borrowStatus, uuid, idList, borrowWhether, returnStatus } }); }; + +export const deleteBorrowed = (idList: number[]) => { + return http.request("post", baseUrlApi("/home/delete"), { + data: { idList } + }); +}; diff --git a/frontend/src/api/user.ts b/frontend/src/api/user.ts index 0a4109b..e16dba7 100644 --- a/frontend/src/api/user.ts +++ b/frontend/src/api/user.ts @@ -1,45 +1,17 @@ import { http } from "@/utils/http"; import { baseUrlApi } from "./utils"; - -export type UserResult = { - code: number; - success: boolean; - msg: string; - data: { - /** 用户名 */ - username: string; - /** uuid */ - uuid: string; - /** 部门 */ - depart: string; - /** 当前登陆用户的角色 */ - roles: [string]; - /** `token` */ - accessToken: string; - /** 用于调用刷新`accessToken`的接口时所需的`token` */ - refreshToken: string; - /** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */ - expires: Date; - }; -}; - -export type RefreshTokenResult = { - code: number; - success: boolean; - msg: string; - data: { - /** `token` */ - accessToken: string; - /** 用于调用刷新`accessToken`的接口时所需的`token` */ - refreshToken: string; - /** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */ - expires: Date; - }; -}; +import type { RefreshTokenResult, LoginResult, UserResult } from "@/types/user"; /** 登录 */ export const getLogin = (data?: object) => { - return http.request("post", baseUrlApi("/base/accessToken"), { + return http.request("post", baseUrlApi("/base/accessToken"), { + data + }); +}; + +/** 获取用户信息 */ +export const getUserInfo = (data?: object) => { + return http.request("post", baseUrlApi("/base/userinfo"), { data }); }; diff --git a/frontend/src/store/modules/user.ts b/frontend/src/store/modules/user.ts index 553a8ef..b1623cc 100644 --- a/frontend/src/store/modules/user.ts +++ b/frontend/src/store/modules/user.ts @@ -5,9 +5,9 @@ import { routerArrays } from "@/layout/types"; import { router, resetRouter } from "@/router"; import { storageLocal } from "@pureadmin/utils"; import { getLogin, refreshTokenApi } from "@/api/user"; -import type { UserResult, RefreshTokenResult } from "@/api/user"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; import { type DataInfo, setToken, removeToken, userKey } from "@/utils/auth"; +import type { RefreshTokenResult, LoginResult } from "@/types/user"; export const useUserStore = defineStore({ id: "pure-user", @@ -51,7 +51,7 @@ export const useUserStore = defineStore({ }, /** 登入 */ async loginByUsername(data) { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { getLogin(data) .then(data => { if (data) { diff --git a/frontend/src/types/user.ts b/frontend/src/types/user.ts new file mode 100644 index 0000000..58462ee --- /dev/null +++ b/frontend/src/types/user.ts @@ -0,0 +1,41 @@ +import type { BaseResult } from "@/types/base"; + +export type UserInfo = { + /** uuid */ + uuid: string; + /** 用户名 */ + username: string; + /** 部门 */ + depart: string; + /** 电话 */ + phone: string; +}; + +export type UserResult = BaseResult & { + data: UserInfo; +}; + +export type LoginResult = BaseResult & { + data: UserInfo & { + /** 当前登陆用户的角色 */ + roles: [string]; + /** `token` */ + accessToken: string; + /** 用于调用刷新`accessToken`的接口时所需的`token` */ + refreshToken: string; + /** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */ + expires: Date; + }; +}; +export type RefreshTokenResult = { + code: number; + msg: string; + data: { + /** `token` */ + accessToken: string; + /** 用于调用刷新`accessToken`的接口时所需的`token` */ + refreshToken: string; + /** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */ + expires: Date; + }; +}; diff --git a/frontend/src/utils/http/index.ts b/frontend/src/utils/http/index.ts index 5eba358..b10b35f 100644 --- a/frontend/src/utils/http/index.ts +++ b/frontend/src/utils/http/index.ts @@ -167,17 +167,14 @@ class PureHttp { } else { errorNotification("服务器异常,请刷新~"); } - } - if (error.response.status == 403) { + } else if (error.response.status == 403) { resetRouter(); router.push("/403").then(() => { errorNotification(error.response.data.msg); }); - } - if (error.response.status == 406) { + } else if (error.response.status == 406) { errorNotification(error.response.data.msg); - } - if (error.response.status == 401) { + } else if (error.response.status == 401) { removeToken(); resetRouter(); router.push("/login").then(() => { diff --git a/frontend/src/views/admin/Approval.vue b/frontend/src/views/admin/Approval.vue deleted file mode 100644 index eb4d10a..0000000 --- a/frontend/src/views/admin/Approval.vue +++ /dev/null @@ -1,641 +0,0 @@ - - - - - diff --git a/frontend/src/views/admin/Approval/index.vue b/frontend/src/views/admin/Approval/index.vue new file mode 100644 index 0000000..fb604fc --- /dev/null +++ b/frontend/src/views/admin/Approval/index.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/frontend/src/views/admin/Approval/panels/borrowing.vue b/frontend/src/views/admin/Approval/panels/borrowing.vue new file mode 100644 index 0000000..e81c685 --- /dev/null +++ b/frontend/src/views/admin/Approval/panels/borrowing.vue @@ -0,0 +1,330 @@ + + + + + diff --git a/frontend/src/views/admin/Approval/panels/returned.vue b/frontend/src/views/admin/Approval/panels/returned.vue new file mode 100644 index 0000000..56c86dd --- /dev/null +++ b/frontend/src/views/admin/Approval/panels/returned.vue @@ -0,0 +1,344 @@ + + + + + diff --git a/frontend/src/views/admin/Approval/panels/returning.vue b/frontend/src/views/admin/Approval/panels/returning.vue new file mode 100644 index 0000000..80ba4f5 --- /dev/null +++ b/frontend/src/views/admin/Approval/panels/returning.vue @@ -0,0 +1,308 @@ + + + + + diff --git a/frontend/src/views/admin/MaterialChecked.vue b/frontend/src/views/admin/MaterialChecked.vue index 5cfcd94..e8ff91b 100644 --- a/frontend/src/views/admin/MaterialChecked.vue +++ b/frontend/src/views/admin/MaterialChecked.vue @@ -1,13 +1,448 @@ - - + diff --git a/frontend/src/views/admin/MaterialMeta.vue b/frontend/src/views/admin/MaterialMeta.vue index 5d7715d..66821ba 100644 --- a/frontend/src/views/admin/MaterialMeta.vue +++ b/frontend/src/views/admin/MaterialMeta.vue @@ -17,13 +17,21 @@ import { MaterialItem } from "@/types/base"; import { addDialog } from "@/components/ReDialog"; import attentionForm from "./utils/attentionForm.vue"; import { SelectOpt, FormItemProps } from "./utils/types"; -import { getDutyOverList, updateDutyOverList } from "@/api/admin"; +import { + createCheckMaterial, + getDutyOverList, + updateDutyOverList +} from "@/api/admin"; import Search from "@iconify-icons/ep/search"; import { message } from "@/utils/message"; +import { useUserStoreHook } from "@/store/modules/user"; defineOptions({ name: "MaterialMeta" }); +const userUUId = computed(() => { + return useUserStoreHook()?.uuid; +}); // 表格ref const tableRef = ref(); // 表格加载控制 @@ -141,11 +149,42 @@ const columns: TableColumnList = [ type="primary" plain onClick={() => { - toCheck(row); + addDialog({ + title: "送检数量", + width: "15%", + contentRenderer: () => ( +
+ +
+ ), + beforeSure(done, { options, index }) { + const data = { + area: row.area, + type: row.type, + material_id: row.id, + number: toCheckNumber.value, + toCheckUserUUID: userUUId.value + }; + createCheckMaterial(data).then(res => { + done(); + successNotification(res.msg); + onSearch(); + }); + }, + beforeCancel(done, { options, index }) { + done(); + toCheckNumber.value = 1; + } + }); }} > 送检 + { errorNotification(err.msg); }); }; -// 表格操作列送检按钮函数 -const toCheck = (row: MaterialItem) => { - console.log(row); -}; +const toCheckNumber = ref(1); // 表格数据 const dataList = reactive([]); diff --git a/frontend/src/views/admin/utils/types.ts b/frontend/src/views/admin/utils/types.ts index 19c7048..fab5629 100644 --- a/frontend/src/views/admin/utils/types.ts +++ b/frontend/src/views/admin/utils/types.ts @@ -12,5 +12,5 @@ export interface FormProps { export type SelectOpt = Array<{ label: string; - value: string; + value: string | boolean | number; }>; diff --git a/frontend/src/views/welcome/dialog/MaterialBorrowDialog.vue b/frontend/src/views/welcome/dialog/MaterialBorrowDialog.vue index d39b740..684bce4 100644 --- a/frontend/src/views/welcome/dialog/MaterialBorrowDialog.vue +++ b/frontend/src/views/welcome/dialog/MaterialBorrowDialog.vue @@ -10,7 +10,7 @@ import { useUserStoreHook } from "@/store/modules/user"; import { addDialog } from "@/components/ReDialog/index"; import verifyDialog from "./VerifyDialog.vue"; import type { userInfo } from "../types"; -import { getLogin } from "@/api/user"; +import { getUserInfo } from "@/api/user"; import type { borrowInfo } from "../types"; type materialItemList = { @@ -108,7 +108,7 @@ const openVerifyDialog = () => { if (curData.account !== "" || curData.password !== "") { accountFormRef.validate(valid => { if (valid) { - getLogin({ + getUserInfo({ username: curData.account, password: curData.password }).then(res => { diff --git a/frontend/src/views/welcome/dialog/VerifyDialog.vue b/frontend/src/views/welcome/dialog/VerifyDialog.vue index b34a3d6..e26c900 100644 --- a/frontend/src/views/welcome/dialog/VerifyDialog.vue +++ b/frontend/src/views/welcome/dialog/VerifyDialog.vue @@ -15,7 +15,9 @@ function getInfoRef() { defineExpose({ getAccountRef, getInfoRef }); type userType = { - userInfo: userInfo; + userInfo: userInfo & { + disable: boolean; + }; }; const props = withDefaults(defineProps(), { @@ -24,7 +26,8 @@ const props = withDefaults(defineProps(), { password: "", name: "", phone: "", - depart: "" + depart: "", + disable: false }) }); @@ -42,7 +45,8 @@ const segmentedOptions: Array = [ }, { label: "手动输入", - value: 1 + value: 1, + disabled: userInfo.disable } ]; type accountType = { @@ -123,7 +127,7 @@ const formSize: Ref<"" | "default" | "small" | "large"> = ref("default");