refactor(user-center): 将企业转账组件中的n-button替换为普通button

简化按钮样式实现,移除不必要的属性配置
This commit is contained in:
邹方成 2025-12-04 14:44:23 +08:00
commit f1c1db580c
11 changed files with 111 additions and 63 deletions

View File

@ -1,8 +1,14 @@
FROM node:18.12.0-alpine3.16 AS web FROM node:18-alpine AS web
WORKDIR /opt/vue-fastapi-admin WORKDIR /opt/vue-fastapi-admin
COPY /web ./web COPY /web ./web
RUN npm install -g pnpm && cd /opt/vue-fastapi-admin/web && pnpm install --registry=https://registry.npmmirror.com && pnpm run build
# 安装pnpm并设置配置
RUN npm install -g pnpm && \
cd /opt/vue-fastapi-admin/web && \
pnpm config set registry https://registry.npmmirror.com && \
pnpm install && \
pnpm run build
FROM python:3.11-slim-bullseye FROM python:3.11-slim-bullseye

View File

@ -117,13 +117,23 @@ async def create_with_receipt(payload: AppCreateInvoiceWithReceipt, current_user
) )
inv = await invoice_controller.create(inv_data) inv = await invoice_controller.create(inv_data)
if payload.receipt_urls: if payload.receipt_urls:
receipts = [] urls = payload.receipt_urls
for url in payload.receipt_urls: main_url = urls[0] if isinstance(urls, list) and urls else None
receipt = await invoice_controller.create_receipt(inv.id, PaymentReceiptCreate(url=url, note=payload.note)) receipt = await invoice_controller.create_receipt(
detail = await invoice_controller.get_receipt_by_id(receipt.id) inv.id,
if detail: PaymentReceiptCreate(url=main_url, note=payload.note, extra=urls)
receipts.append(detail) )
return Success(data={"invoice_id": inv.id, "receipts": receipts}, msg="创建并上传成功") detail = await invoice_controller.get_receipt_by_id(receipt.id)
return Success(data={"invoice_id": inv.id, "receipts": [detail] if detail else []}, msg="创建并上传成功")
if isinstance(payload.receipt_url, list) and payload.receipt_url:
urls = payload.receipt_url
main_url = urls[0]
receipt = await invoice_controller.create_receipt(
inv.id,
PaymentReceiptCreate(url=main_url, note=payload.note, extra=urls)
)
detail = await invoice_controller.get_receipt_by_id(receipt.id)
return Success(data={"invoice_id": inv.id, "receipts": [detail] if detail else []}, msg="创建并上传成功")
if payload.receipt_url: if payload.receipt_url:
receipt = await invoice_controller.create_receipt(inv.id, PaymentReceiptCreate(url=payload.receipt_url, note=payload.note)) receipt = await invoice_controller.create_receipt(inv.id, PaymentReceiptCreate(url=payload.receipt_url, note=payload.note))
detail = await invoice_controller.get_receipt_by_id(receipt.id) detail = await invoice_controller.get_receipt_by_id(receipt.id)

View File

@ -307,19 +307,13 @@ async def _perform_valuation_calculation(user_id: int, data: UserValuationCreate
except Exception: except Exception:
pass pass
# 步骤4最后更新状态为成功 # 步骤4计算完成,保持状态为 pending等待后台审核
try: try:
result = await valuation_controller.update_calc(
valuation_id,
ValuationAssessmentUpdate(
status='success'
)
)
logger.info("valuation.status_updated valuation_id={} status=success", valuation_id)
except Exception as e:
logger.warning("valuation.failed_to_update_status valuation_id={} err={}", valuation_id, repr(e))
# 即使状态更新失败,也尝试获取结果用于日志
result = await valuation_controller.get_by_id(valuation_id) result = await valuation_controller.get_by_id(valuation_id)
logger.info("valuation.calc_finished valuation_id={} status=pending", valuation_id)
except Exception as e:
logger.warning("valuation.failed_to_fetch_after_calc valuation_id={} err={}", valuation_id, repr(e))
result = None
logger.info("valuation.background_calc_success user_id={} valuation_id={}", user_id, valuation_id) logger.info("valuation.background_calc_success user_id={} valuation_id={}", user_id, valuation_id)

View File

@ -85,6 +85,11 @@ async def send_email(payload: SendEmailRequest = Body(...)):
raise HTTPException(status_code=400, detail="收件方地址域名不可用或未正确解析") raise HTTPException(status_code=400, detail="收件方地址域名不可用或未正确解析")
if payload.file_urls: if payload.file_urls:
urls.extend([u.strip().strip('`') for u in payload.file_urls if isinstance(u, str)]) urls.extend([u.strip().strip('`') for u in payload.file_urls if isinstance(u, str)])
if payload.file_url:
if isinstance(payload.file_url, str):
urls.append(payload.file_url.strip().strip('`'))
elif isinstance(payload.file_url, list):
urls.extend([u.strip().strip('`') for u in payload.file_url if isinstance(u, str)])
if urls: if urls:
try: try:
async with httpx.AsyncClient(timeout=10) as client: async with httpx.AsyncClient(timeout=10) as client:
@ -122,20 +127,25 @@ async def send_email(payload: SendEmailRequest = Body(...)):
else: else:
logger.error("transactions.email_send_fail email={} err={}", payload.email, error) logger.error("transactions.email_send_fail email={} err={}", payload.email, error)
if status == "OK" and payload.receipt_id: if payload.receipt_id:
try: try:
r = await PaymentReceipt.filter(id=payload.receipt_id).first() r = await PaymentReceipt.filter(id=payload.receipt_id).first()
if r: if r:
r.extra = (r.extra or {}) | payload.model_dump()
await r.save()
try: try:
inv = await r.invoice inv = await r.invoice
if inv: if inv:
inv.status = "invoiced" s = str(payload.status or "").lower()
if s in {"invoiced", "success"}:
target = "invoiced"
elif s in {"refunded", "rejected", "pending"}:
target = s
else:
target = "invoiced"
inv.status = target
await inv.save() await inv.save()
logger.info("transactions.invoice_mark_invoiced receipt_id={} invoice_id={}", payload.receipt_id, inv.id) logger.info("transactions.invoice_status_updated receipt_id={} invoice_id={} status={}", payload.receipt_id, inv.id, target)
except Exception as e2: except Exception as e2:
logger.warning("transactions.invoice_mark_invoiced_fail receipt_id={} err={}", payload.receipt_id, str(e2)) logger.warning("transactions.invoice_status_update_fail receipt_id={} err={}", payload.receipt_id, str(e2))
except Exception as e: except Exception as e:
logger.error("transactions.email_extra_save_fail id={} err={}", payload.receipt_id, str(e)) logger.error("transactions.email_extra_save_fail id={} err={}", payload.receipt_id, str(e))

View File

@ -271,20 +271,23 @@ class InvoiceController(CRUDBase[Invoice, InvoiceCreate, InvoiceUpdate]):
items = [] items = []
for r in rows: for r in rows:
inv = await r.invoice inv = await r.invoice
urls = []
if isinstance(r.extra, list):
urls = [str(u) for u in r.extra if u]
elif isinstance(r.extra, dict):
v = r.extra.get("urls")
if isinstance(v, list):
urls = [str(u) for u in v if u]
if not urls:
urls = [r.url] if r.url else []
receipts = [{"id": r.id, "url": u, "note": r.note, "verified": r.verified} for u in urls]
items.append({ items.append({
"id": r.id, "id": r.id,
"invoice_id": getattr(inv, "id", None), "invoice_id": getattr(inv, "id", None),
"submitted_at": r.created_at.isoformat() if r.created_at else "", "submitted_at": r.created_at.isoformat() if r.created_at else "",
"receipt_uploaded_at": r.updated_at.isoformat() if getattr(r, "updated_at", None) else "", "receipt_uploaded_at": r.updated_at.isoformat() if getattr(r, "updated_at", None) else "",
"extra": r.extra, "extra": r.extra,
"receipts": [ "receipts": receipts,
{
"id": r.id,
"url": r.url,
"note": r.note,
"verified": r.verified,
}
],
"phone": inv.phone, "phone": inv.phone,
"wechat": inv.wechat, "wechat": inv.wechat,
"company_name": inv.company_name, "company_name": inv.company_name,
@ -313,20 +316,23 @@ class InvoiceController(CRUDBase[Invoice, InvoiceCreate, InvoiceUpdate]):
if not r: if not r:
return None return None
inv = await r.invoice inv = await r.invoice
urls = []
if isinstance(r.extra, list):
urls = [str(u) for u in r.extra if u]
elif isinstance(r.extra, dict):
v = r.extra.get("urls")
if isinstance(v, list):
urls = [str(u) for u in v if u]
if not urls:
urls = [r.url] if r.url else []
receipts = [{"id": r.id, "url": u, "note": r.note, "verified": r.verified} for u in urls]
return { return {
"id": r.id, "id": r.id,
"invoice_id": getattr(inv, "id", None), "invoice_id": getattr(inv, "id", None),
"submitted_at": r.created_at.isoformat() if r.created_at else "", "submitted_at": r.created_at.isoformat() if r.created_at else "",
"receipt_uploaded_at": r.updated_at.isoformat() if getattr(r, "updated_at", None) else "", "receipt_uploaded_at": r.updated_at.isoformat() if getattr(r, "updated_at", None) else "",
"extra": r.extra, "extra": r.extra,
"receipts": [ "receipts": receipts,
{
"id": r.id,
"url": r.url,
"note": r.note,
"verified": r.verified,
}
],
"phone": inv.phone, "phone": inv.phone,
"wechat": inv.wechat, "wechat": inv.wechat,
"company_name": inv.company_name, "company_name": inv.company_name,

View File

@ -813,7 +813,7 @@ class ValuationController:
return None return None
from datetime import datetime from datetime import datetime
update_data = {"status": "pending", "audited_at": datetime.now(), "updated_at": datetime.now()} update_data = {"status": "success", "audited_at": datetime.now(), "updated_at": datetime.now()}
if admin_notes: if admin_notes:
update_data["admin_notes"] = admin_notes update_data["admin_notes"] = admin_notes
@ -852,6 +852,7 @@ class ValuationController:
if not valuation: if not valuation:
return None return None
update_data = data.model_dump(exclude_unset=True) update_data = data.model_dump(exclude_unset=True)
valuation.status ="pending"
if update_data: if update_data:
await valuation.update_from_dict(update_data) await valuation.update_from_dict(update_data)
await valuation.save() await valuation.save()

View File

@ -1,4 +1,4 @@
from typing import Optional, List from typing import Optional, List, Union, Dict, Any
from pydantic import BaseModel, Field, EmailStr, field_validator, model_validator from pydantic import BaseModel, Field, EmailStr, field_validator, model_validator
@ -115,7 +115,7 @@ class UpdateType(BaseModel):
class PaymentReceiptCreate(BaseModel): class PaymentReceiptCreate(BaseModel):
url: str = Field(..., min_length=1, max_length=512) url: str = Field(..., min_length=1, max_length=512)
note: Optional[str] = Field(None, max_length=256) note: Optional[str] = Field(None, max_length=256)
extra: Optional[dict] = None extra: Optional[Union[List[str], Dict[str, Any]]] = None
class PaymentReceiptOut(BaseModel): class PaymentReceiptOut(BaseModel):
@ -124,7 +124,7 @@ class PaymentReceiptOut(BaseModel):
note: Optional[str] note: Optional[str]
verified: bool verified: bool
created_at: str created_at: str
extra: Optional[dict] = None extra: Optional[Union[List[str], Dict[str, Any]]] = None
class AppCreateInvoiceWithReceipt(BaseModel): class AppCreateInvoiceWithReceipt(BaseModel):
@ -133,7 +133,7 @@ class AppCreateInvoiceWithReceipt(BaseModel):
invoice_type: Optional[str] = Field(None, pattern=r"^(special|normal)$") invoice_type: Optional[str] = Field(None, pattern=r"^(special|normal)$")
# 兼容前端索引字段:"0"→normal"1"→special # 兼容前端索引字段:"0"→normal"1"→special
invoiceTypeIndex: Optional[str] = None invoiceTypeIndex: Optional[str] = None
receipt_url: Optional[str] = Field(None, max_length=512) receipt_url: Optional[Union[str, List[str]]] = Field(None)
receipt_urls: Optional[List[str]] = None receipt_urls: Optional[List[str]] = None
note: Optional[str] = Field(None, max_length=256) note: Optional[str] = Field(None, max_length=256)
@ -145,14 +145,26 @@ class AppCreateInvoiceWithReceipt(BaseModel):
@field_validator('receipt_url', mode='before') @field_validator('receipt_url', mode='before')
@classmethod @classmethod
def _clean_receipt_url(cls, v): def _clean_receipt_url(cls, v):
if isinstance(v, list) and v: if isinstance(v, list):
v = v[0] cleaned: List[str] = []
for item in v:
if isinstance(item, str):
s = item.strip()
if s.startswith('`') and s.endswith('`'):
s = s[1:-1].strip()
while s.endswith('\\'):
s = s[:-1].strip()
if s:
cleaned.append(s)
return cleaned or None
if isinstance(v, str): if isinstance(v, str):
s = v.strip() s = v.strip()
if s.startswith('`') and s.endswith('`'): if s.startswith('`') and s.endswith('`'):
s = s[1:-1].strip() s = s[1:-1].strip()
while s.endswith('\\'):
s = s[:-1].strip()
return s or None return s or None
return v return None
@field_validator('receipt_urls', mode='before') @field_validator('receipt_urls', mode='before')
@classmethod @classmethod
@ -162,10 +174,18 @@ class AppCreateInvoiceWithReceipt(BaseModel):
if isinstance(v, str): if isinstance(v, str):
v = [v] v = [v]
if isinstance(v, list): if isinstance(v, list):
seen = set()
cleaned = [] cleaned = []
for item in v: for item in v:
if isinstance(item, str) and item.strip(): if isinstance(item, str):
cleaned.append(item.strip()) s = item.strip()
if s.startswith('`') and s.endswith('`'):
s = s[1:-1].strip()
while s.endswith('\\'):
s = s[:-1].strip()
if s and s not in seen:
seen.add(s)
cleaned.append(s)
return cleaned or None return cleaned or None
return None return None

View File

@ -8,6 +8,8 @@ class SendEmailRequest(BaseModel):
subject: Optional[str] = Field(None, description="邮件主题") subject: Optional[str] = Field(None, description="邮件主题")
body: str = Field(..., description="文案内容") body: str = Field(..., description="文案内容")
file_urls: Optional[List[str]] = Field(None, description="附件URL列表") file_urls: Optional[List[str]] = Field(None, description="附件URL列表")
file_url: Optional[Union[str, List[str]]] = Field(None, description="附件URL或列表(兼容前端传参)")
status: Optional[str] = Field(None, description="开票状态标记: success|invoiced|rejected|refunded")
class SendEmailBody(BaseModel): class SendEmailBody(BaseModel):

View File

@ -1,5 +1,6 @@
#!/bin/sh #!/bin/sh
set -e set -e
nginx # nginx
python run.py python run.py

View File

@ -185,16 +185,13 @@
</n-form-item> </n-form-item>
<div class="btn-container"> <div class="btn-container">
<n-button <button
type="primary" class="primary-btn"
class="submit-btn"
:disabled="!isFormValid" :disabled="!isFormValid"
@click="handleUploadSubmit" @click="handleUploadSubmit"
color="#A30113"
style="width: 200px; height: 40px"
> >
确认上传 确认上传
</n-button> </button>
</div> </div>
</n-form> </n-form>
</div> </div>

View File

@ -37,8 +37,8 @@
export DOCKER_DEFAULT_PLATFORM=linux/amd64 export DOCKER_DEFAULT_PLATFORM=linux/amd64
docker build -t zfc931912343/guzhi-fastapi-admin:v2.7 . docker build -t zfc931912343/guzhi-fastapi-admin:v3.1 .
docker push zfc931912343/guzhi-fastapi-admin:v2.7 docker push zfc931912343/guzhi-fastapi-admin:v3.1
# 运行容器 # 运行容器
@ -73,6 +73,7 @@ docker pull nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v1.4 &&
docker pull nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v1.7 && docker rm -f guzhi_dev && docker run -itd --name=guzhi_dev -p 9990:80 -v ~/guzhi-data-dev/static/images:/opt/vue-fastapi-admin/app/static/images --restart=unless-stopped -e TZ=Asia/Shanghai nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v1.7 docker pull nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v1.7 && docker rm -f guzhi_dev && docker run -itd --name=guzhi_dev -p 9990:80 -v ~/guzhi-data-dev/static/images:/opt/vue-fastapi-admin/app/static/images --restart=unless-stopped -e TZ=Asia/Shanghai nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v1.7
1
docker pull nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v3.1 && docker rm -f guzhi_pro && docker run -itd --name=guzhi_pro -p 8080:80 -v ~/guzhi-data/static/images:/opt/vue-fastapi-admin/app/static/images --restart=unless-stopped -e TZ=Asia/Shanghai nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v3.1
docker pull nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v2.5 && docker rm -f guzhi_pro && docker run -itd --name=guzhi_pro -p 8080:80 -v ~/guzhi-data/static/images:/opt/vue-fastapi-admin/app/static/images --restart=unless-stopped -e TZ=Asia/Shanghai nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v2.5 docker pull nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v3.2 && docker rm -f guzhi_dev && docker run -itd --name=guzhi_dev -p 9990:9999 -v ~/guzhi-data/static:/opt/vue-fastapi-admin/app/static --restart=unless-stopped -e TZ=Asia/Shanghai nbg2akd8w5diy8.xuanyuan.run/zfc931912343/guzhi-fastapi-admin:v3.2