feat/ui: 优化订单列表和详情页面,更新登录界面

- 移除 components.d.ts 中未使用的 VanCheckbox 组件引用
- 在 Detail 页面添加常用审批意见标签功能
- 调整 List 页面样式,优化搜索栏设计- 更新 Login 页面布局和样式,移除不必要的元素
master
chenhao 2025-08-28 17:33:10 +08:00
parent 411253a277
commit 92dd433eee
4 changed files with 236 additions and 99 deletions

2
components.d.ts vendored
View File

@ -11,7 +11,6 @@ declare module 'vue' {
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
VanButton: typeof import('vant/es')['Button'] VanButton: typeof import('vant/es')['Button']
VanCellGroup: typeof import('vant/es')['CellGroup'] VanCellGroup: typeof import('vant/es')['CellGroup']
VanCheckbox: typeof import('vant/es')['Checkbox']
VanEmpty: typeof import('vant/es')['Empty'] VanEmpty: typeof import('vant/es')['Empty']
VanField: typeof import('vant/es')['Field'] VanField: typeof import('vant/es')['Field']
VanForm: typeof import('vant/es')['Form'] VanForm: typeof import('vant/es')['Form']
@ -26,5 +25,6 @@ declare module 'vue' {
VanSteps: typeof import('vant/es')['Steps'] VanSteps: typeof import('vant/es')['Steps']
VanTab: typeof import('vant/es')['Tab'] VanTab: typeof import('vant/es')['Tab']
VanTabs: typeof import('vant/es')['Tabs'] VanTabs: typeof import('vant/es')['Tabs']
VanTag: typeof import('vant/es')['Tag']
} }
} }

View File

@ -372,7 +372,7 @@
v-model:show="approvalDialogVisible" v-model:show="approvalDialogVisible"
position="bottom" position="bottom"
round round
:style="{ height: '40%' }" :style="{ height: '50%' }"
> >
<div class="approval-dialog"> <div class="approval-dialog">
<div class="dialog-header"> <div class="dialog-header">
@ -380,15 +380,35 @@
<van-icon name="cross" @click="approvalDialogVisible = false"/> <van-icon name="cross" @click="approvalDialogVisible = false"/>
</div> </div>
<div class="dialog-body"> <div class="dialog-body">
<van-field <!-- 默认意见标签 -->
v-model="approvalOpinion" <div class="opinion-tags">
type="textarea" <div class="tags-title">常用意见</div>
:placeholder="currentApprovalStatus === 0 ? '请输入驳回原因' : '请输入审批意见'" <div class="tags-container">
rows="4" <van-tag
autosize v-for="tag in getOpinionTags()"
maxlength="500" :key="tag"
show-word-limit :type="selectedTag === tag ? 'primary' : 'default'"
/> size="medium"
@click="selectTag(tag)"
class="opinion-tag"
>
{{ tag }}
</van-tag>
</div>
</div>
<!-- 意见输入框 -->
<div class="opinion-input">
<van-field
v-model="approvalOpinion"
type="textarea"
:placeholder="currentApprovalStatus === 0 ? '请输入驳回原因' : '请输入审批意见'"
rows="4"
autosize
maxlength="500"
show-word-limit
/>
</div>
</div> </div>
<div class="dialog-footer"> <div class="dialog-footer">
<van-button <van-button
@ -432,6 +452,7 @@ const approvalDialogVisible = ref(false)
const approvalOpinion = ref('') const approvalOpinion = ref('')
const currentApprovalStatus = ref<ApprovalStatus>(3) const currentApprovalStatus = ref<ApprovalStatus>(3)
const submitting = ref(false) const submitting = ref(false)
const selectedTag = ref('')
// Tab // Tab
const activeTab = ref('order') const activeTab = ref('order')
@ -542,9 +563,48 @@ const previewFile = (file: AttachmentFile) => {
const showApprovalDialog = (status: ApprovalStatus) => { const showApprovalDialog = (status: ApprovalStatus) => {
currentApprovalStatus.value = status currentApprovalStatus.value = status
approvalOpinion.value = '' approvalOpinion.value = ''
selectedTag.value = ''
approvalDialogVisible.value = true approvalDialogVisible.value = true
} }
//
const getOpinionTags = () => {
if (currentApprovalStatus.value === 0) {
//
return [
'资料不齐全,请补充相关文件',
'订单金额需要重新核实',
'客户信息有误,请修正',
'产品配置不符合要求',
'需要提供更多技术细节',
'合同条款需要调整'
]
} else {
//
return [
'审核通过,订单信息完整',
'符合公司政策,同意执行',
'客户资质良好,建议通过',
'产品配置合理,批准发货',
'风险可控,同意此订单',
'经审查无误,予以批准'
]
}
}
//
const selectTag = (tag: string) => {
if (selectedTag.value === tag) {
//
selectedTag.value = ''
approvalOpinion.value = ''
} else {
//
selectedTag.value = tag
approvalOpinion.value = tag
}
}
// //
const submitApproval = async () => { const submitApproval = async () => {
if (!currentOrder.value || !currentOrder.value.todo) { if (!currentOrder.value || !currentOrder.value.todo) {
@ -846,6 +906,7 @@ onMounted(async () => {
.dialog-body { .dialog-body {
flex: 1; flex: 1;
padding: var(--spacing-lg); padding: var(--spacing-lg);
overflow-y: auto;
} }
.dialog-footer { .dialog-footer {
@ -854,6 +915,45 @@ onMounted(async () => {
} }
} }
//
.opinion-tags {
margin-bottom: var(--spacing-lg);
.tags-title {
font-size: 14px;
color: var(--text-color-secondary);
margin-bottom: var(--spacing-md);
font-weight: 500;
}
.tags-container {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-sm);
.opinion-tag {
cursor: pointer;
transition: all 0.2s ease;
&:hover {
transform: translateY(-1px);
}
&:active {
transform: translateY(0);
}
}
}
}
.opinion-input {
.van-field {
border: 1px solid var(--divider-color);
border-radius: var(--border-radius-sm);
background: var(--background-color-secondary);
}
}
:deep(.van-steps--vertical) { :deep(.van-steps--vertical) {
padding-left: 0; padding-left: 0;
} }

View File

@ -1,12 +1,22 @@
<template> <template>
<div class="order-list-page"> <div class="order-list-page">
<!-- 搜索栏 --> <!-- 搜索栏 -->
<van-search <div class="search-container">
v-model="searchKeyword" <van-search
placeholder="搜索订单编号、客户名称" v-model="searchKeyword"
@search="handleSearch" placeholder="搜索订单编号、客户名称"
@clear="handleClear" @search="handleSearch"
/> @clear="handleClear"
class="custom-search"
>
<template #left-icon>
<svg class="search-icon" viewBox="0 0 24 24" fill="none">
<circle cx="11" cy="11" r="8" stroke="currentColor" stroke-width="2"/>
<path d="M21 21L16.65 16.65" stroke="currentColor" stroke-width="2"/>
</svg>
</template>
</van-search>
</div>
<!-- 下拉刷新 --> <!-- 下拉刷新 -->
<van-pull-refresh v-model="refreshing" @refresh="onRefresh"> <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
@ -134,39 +144,25 @@ onMounted(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.order-list-page { .order-list-page {
min-height: 100vh; min-height: 100vh;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); background: #ffffff;
padding: 20px; padding: 16px;
box-sizing: border-box; box-sizing: border-box;
position: relative;
} }
.order-item { .order-item {
background: white; background: #ffffff;
margin-bottom: 16px; margin-bottom: 16px;
padding: 20px; padding: 16px;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s ease;
border-radius: 12px; border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
position: relative; border: 1px solid #f0f0f0;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 4px;
background: linear-gradient(90deg, #1989fa, #07c160);
}
&:active { &:active {
transform: translateY(2px); transform: translateY(1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
&:hover {
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
} }
} }
@ -182,35 +178,39 @@ onMounted(() => {
.order-code { .order-code {
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
color: #333; color: #333333;
} }
.status-tag { .status-tag {
font-size: 12px; font-size: 12px;
padding: 4px 10px; padding: 4px 12px;
border-radius: 20px; border-radius: 12px;
font-weight: 500; font-weight: 500;
&.pending { &.pending {
background-color: #FFF7E6; background: #FFF3E0;
color: #FA8C16; color: #FF9800;
border: 1px solid #FFE0B2;
} }
&.approved { &.approved {
background-color: #F6FFED; background: #E8F5E8;
color: #52C41A; color: #4CAF50;
border: 1px solid #C8E6C9;
} }
&.rejected { &.rejected {
background-color: #FFF2F0; background: #FFEBEE;
color: #FF4D4F; color: #F44336;
border: 1px solid #FFCDD2;
} }
} }
.order-info { .order-info {
.info-row { .info-row {
display: flex; display: flex;
margin-bottom: 12px; margin-bottom: 8px;
align-items: center;
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
@ -218,19 +218,22 @@ onMounted(() => {
.label { .label {
width: 80px; width: 80px;
color: #666; color: #666666;
font-size: 14px; font-size: 14px;
font-weight: 400;
flex-shrink: 0; flex-shrink: 0;
} }
.value { .value {
flex: 1; flex: 1;
color: #333; color: #333333;
font-size: 14px; font-size: 14px;
font-weight: 400;
word-break: break-all; word-break: break-all;
line-height: 1.4;
&.amount { &.amount {
color: #FF6600; color: #1976D2;
font-weight: 600; font-weight: 600;
} }
} }
@ -238,31 +241,71 @@ onMounted(() => {
} }
.empty-state { .empty-state {
padding: 60px 20px; padding: 80px 20px;
text-align: center; text-align: center;
position: relative;
:deep(.van-empty) { :deep(.van-empty) {
.van-empty__image { .van-empty__image {
width: 120px; width: 160px;
height: 120px; height: 160px;
filter: drop-shadow(0 8px 32px rgba(102, 126, 234, 0.2));
animation: float 3s ease-in-out infinite;
} }
.van-empty__description { .van-empty__description {
font-size: 16px; font-size: 18px;
color: #666; color: rgba(255, 255, 255, 0.9);
font-weight: 500;
margin-top: 24px;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
} }
} }
} }
//
.search-container {
position: relative;
z-index: 10;
margin-bottom: 20px;
}
// //
:deep(.van-search) { :deep(.custom-search) {
margin-bottom: 16px; border-radius: 16px;
border-radius: 12px;
overflow: hidden; overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08);
backdrop-filter: blur(20px);
background: rgba(255, 255, 255, 0.95);
border: 1px solid rgba(255, 255, 255, 0.2);
transition: all 0.3s ease;
&:focus-within {
transform: translateY(-2px);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15), 0 4px 12px rgba(0, 0, 0, 0.1);
}
.van-search__content { .van-search__content {
background: white; background: transparent;
padding: 12px 16px;
.van-field__control {
font-size: 15px;
font-weight: 500;
color: #333;
&::placeholder {
color: #999;
font-weight: normal;
}
}
} }
} }
.search-icon {
width: 20px;
height: 20px;
color: #666;
margin-right: 8px;
}
</style> </style>

View File

@ -4,12 +4,31 @@
<div class="login-header"> <div class="login-header">
<div class="logo-placeholder"> <div class="logo-placeholder">
<svg class="logo-icon" viewBox="0 0 100 100"> <svg class="logo-icon" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="#1989fa" /> <!-- 立体盒子效果 -->
<path d="M30,50 L45,65 L70,35" stroke="white" stroke-width="8" fill="none" /> <defs>
<linearGradient id="topFace" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#4A90E2;stop-opacity:1" />
<stop offset="100%" style="stop-color:#357ABD;stop-opacity:1" />
</linearGradient>
<linearGradient id="leftFace" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#357ABD;stop-opacity:1" />
<stop offset="100%" style="stop-color:#2E6B9D;stop-opacity:1" />
</linearGradient>
<linearGradient id="rightFace" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#2E6B9D;stop-opacity:1" />
<stop offset="100%" style="stop-color:#1E4A6B;stop-opacity:1" />
</linearGradient>
</defs>
<!-- 立体盒子 -->
<!-- 顶面 -->
<polygon points="20,35 50,20 80,35 50,50" fill="url(#topFace)" />
<!-- 左面 -->
<polygon points="20,35 50,50 50,80 20,65" fill="url(#leftFace)" />
<!-- 右面 -->
<polygon points="50,50 80,35 80,65 50,80" fill="url(#rightFace)" />
</svg> </svg>
</div> </div>
<h1 class="login-title">OMS</h1> <h1 class="login-title">欢迎登录OMS</h1>
<p class="login-subtitle">Order Management System</p>
</div> </div>
<van-form @submit="onSubmit" class="login-form"> <van-form @submit="onSubmit" class="login-form">
@ -17,18 +36,11 @@
<van-field <van-field
v-model="username" v-model="username"
name="username" name="username"
label="用户名" label="账号"
placeholder="请输入用户名" placeholder="请输入账号"
:rules="[{ required: true, message: '请填写用户名' }]" :rules="[{ required: true, message: '请填写账号' }]"
class="login-input" class="login-input"
> />
<template #left-icon>
<svg class="input-icon" viewBox="0 0 100 100">
<circle cx="50" cy="35" r="15" fill="none" stroke="#1989fa" stroke-width="8" />
<path d="M25,85 Q50,65 75,85" stroke="#1989fa" stroke-width="8" fill="none" />
</svg>
</template>
</van-field>
<van-field <van-field
v-model="password" v-model="password"
@ -38,33 +50,15 @@
placeholder="请输入密码" placeholder="请输入密码"
:rules="[{ required: true, message: '请填写密码' }]" :rules="[{ required: true, message: '请填写密码' }]"
class="login-input" class="login-input"
> />
<template #left-icon>
<svg class="input-icon" viewBox="0 0 100 100">
<rect x="20" y="40" width="60" height="40" rx="5" fill="none" stroke="#1989fa" stroke-width="8" />
<circle cx="50" cy="25" r="10" fill="#1989fa" />
</svg>
</template>
</van-field>
</van-cell-group> </van-cell-group>
<div class="login-options">
<van-checkbox v-model="rememberMe" name="rememberMe" checked-color="#1989fa">
记住密码
</van-checkbox>
<a href="javascript:void(0)" class="forgot-password">忘记密码</a>
</div>
<div class="login-button-container"> <div class="login-button-container">
<van-button round block type="primary" native-type="submit" :loading="loading" class="login-button"> <van-button round block type="primary" native-type="submit" :loading="loading" class="login-button">
登录 登录
</van-button> </van-button>
</div> </div>
</van-form> </van-form>
<div class="login-footer">
<p class="footer-text">© 2025 订单管理系统 - 安全可靠的订单管理平台</p>
</div>
</div> </div>
</div> </div>
</template> </template>