This commit is contained in:
zlwu committed 2026-08-12 15:48:15 +08:00
1 parent d310da1523
commit ec628e1b8d
79 files changed
+6749 -799

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@
<view class="up-title">添加附件</view>
<view class="up-sub">支持 PDF / 图片 / Office,可多选上传</view>
</view>
<van-icon name="arrow" color="#16a34a" />
<van-icon name="arrow" color="#22c55e" />
</view>
<view
+1 -1
View File
@@ -41,7 +41,7 @@
<van-button size="small" plain hairline type="default" @click.stop="$emit('view', item)">
查看
</van-button>
<van-button size="small" type="primary" color="#16a34a" @click.stop="$emit('action', item)">
<van-button size="small" type="primary" color="#22c55e" @click.stop="$emit('action', item)">
处理
</van-button>
</view>
+17 -2
View File
@@ -6,8 +6,12 @@
class="menu-card"
@click="$emit('select', m)"
>
<view class="menu-icon" :class="toneClass(m)" :style="iconBoxStyle(m)">
<VanIcon :name="m.icon" :color="iconFg(m)" size="52rpx" />
<view
class="menu-icon"
:class="[toneClass(m), plainClass(m)]"
:style="iconBoxStyle(m)"
>
<VanIcon :name="m.icon" :color="iconFg(m)" :size="iconSize(m)" />
</view>
<text class="menu-title">{{ m.title }}</text>
</view>
@@ -32,10 +36,21 @@
iconFg(m) {
return this.colors(m).iconFg || ''
},
isPlainIcon(m) {
const bg = String(this.colors(m).iconBg || '').trim().toLowerCase()
return bg === 'transparent' || bg === 'none'
},
plainClass(m) {
return this.isPlainIcon(m) ? 'menu-icon--plain' : ''
},
iconSize(m) {
return this.isPlainIcon(m) ? '56rpx' : '52rpx'
},
iconBoxStyle(m) {
const { iconBg, iconFg } = this.colors(m)
const style = {}
if (iconBg) style.background = iconBg
if (this.isPlainIcon(m)) style.boxShadow = 'none'
if (iconFg) style.color = iconFg
return style
},
+23 -8
View File
@@ -22,7 +22,7 @@ export default {
name: 'OaAppIcon',
props: {
iconUrl: { type: String, default: '' },
/** sm=32, card=36, md=56 */
/** xs=28, sm=32, card=36, md=40, lg=48 */
size: { type: String, default: 'sm' }
},
computed: {
@@ -51,35 +51,50 @@ export default {
align-items: center;
justify-content: center;
}
.oa-app-icon--xs {
width: 28px;
height: 28px;
border-radius: 7px;
}
.oa-app-icon--sm {
width: 32px;
height: 32px;
border-radius: 8px;
background: #f1f5f9;
}
.oa-app-icon--card {
width: 36px;
height: 36px;
border-radius: 8px;
background: #f1f5f9;
}
.oa-app-icon--md {
width: 56px;
height: 56px;
width: 40px;
height: 40px;
border-radius: 10px;
}
.oa-app-icon--lg {
width: 48px;
height: 48px;
border-radius: 12px;
background: #f1f5f9;
}
.oa-app-icon__glyph {
width: 16px;
height: 16px;
}
.oa-app-icon--xs .oa-app-icon__glyph {
width: 14px;
height: 14px;
}
.oa-app-icon--card .oa-app-icon__glyph {
width: 18px;
height: 18px;
}
.oa-app-icon--md .oa-app-icon__glyph {
width: 26px;
height: 26px;
width: 20px;
height: 20px;
}
.oa-app-icon--lg .oa-app-icon__glyph {
width: 24px;
height: 24px;
}
.oa-app-icon__photo {
width: 100%;
+8 -1
View File
@@ -35,6 +35,8 @@
'eye-o': '◎',
down: '↓',
checked: '✓',
search: '⌕',
clear: '×',
'manager-o': '☺',
'clock-o': '◷',
description: '📄',
@@ -47,7 +49,12 @@
/** 本地位图(小程序 <image> 真机可靠);颜色已烘焙在 PNG 内 */
const PNG_ICONS = {
approve: '/static/menu-icons/start-approval.png'
approve: '/static/menu-icons/start-approval.png',
'oa-drafts': '/static/menu-icons/oa-drafts.png',
'oa-pending': '/static/menu-icons/oa-pending.png',
'oa-done': '/static/menu-icons/oa-done.png',
'oa-mine': '/static/menu-icons/oa-mine.png',
'oa-cc': '/static/menu-icons/oa-cc.png'
}
export default {
+14 -14
View File
@@ -57,42 +57,42 @@
}
.v-tag.is-primary {
background: #f0fdf4;
color: #15803d;
background: #ecfdf5;
color: #16a34a;
}
.v-tag.is-success {
background: #f0fdf4;
color: #189b65;
background: #ecfdf5;
color: #10b981;
}
.v-tag.is-warning {
background: #fff7e8;
color: #cf8a19;
background: #fffbeb;
color: #d97706;
}
.v-tag.is-danger {
background: #fdecec;
color: #d14343;
background: #fef2f2;
color: #ef4444;
}
.v-tag.is-plain.is-primary,
.v-tag.is-plain.is-success {
background: transparent;
border-color: #16a34a;
color: #16a34a;
border-color: #22c55e;
color: #22c55e;
}
.v-tag.is-plain.is-warning {
background: transparent;
border-color: #cf8a19;
color: #cf8a19;
border-color: #d97706;
color: #d97706;
}
.v-tag.is-plain.is-danger {
background: transparent;
border-color: #d14343;
color: #d14343;
border-color: #ef4444;
color: #ef4444;
}
.v-tag.is-plain.is-default {
+9 -9
View File
@@ -36,24 +36,24 @@
"path": "pages/oa/form-sink/index",
"style": {
"navigationBarTitleText": "表单水槽测试",
"navigationBarBackgroundColor": "#16a34a",
"navigationBarTextStyle": "white"
"navigationBarBackgroundColor": "#ecfdf5",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/oa/start/index",
"style": {
"navigationBarTitleText": "发起审批",
"navigationBarBackgroundColor": "#16a34a",
"navigationBarTextStyle": "white"
"navigationBarBackgroundColor": "#ecfdf5",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/oa/start/webview",
"style": {
"navigationBarTitleText": "填写审批",
"navigationBarBackgroundColor": "#16a34a",
"navigationBarTextStyle": "white"
"navigationBarBackgroundColor": "#ecfdf5",
"navigationBarTextStyle": "black"
}
},
{
@@ -86,10 +86,10 @@
}
],
"globalStyle": {
"navigationBarTextStyle": "white",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "中科兴皖",
"navigationBarBackgroundColor": "#16a34a",
"backgroundColor": "#f5f8f7"
"navigationBarBackgroundColor": "#ecfdf5",
"backgroundColor": "#ffffff"
},
"uniIdRouter": {}
}
+9 -9
View File
@@ -13,14 +13,14 @@
<view class="panel">
<view class="panel-hd"><text class="panel-title">按钮 Button</text></view>
<view class="comp-row">
<van-button type="primary" size="small" color="#16a34a" round>主按钮</van-button>
<van-button type="primary" size="small" plain hairline color="#16a34a" round>描边</van-button>
<van-button type="primary" size="small" color="#22c55e" round>主按钮</van-button>
<van-button type="primary" size="small" plain hairline color="#22c55e" round>描边</van-button>
<van-button size="small" plain hairline round>次要</van-button>
<van-button type="danger" size="small" plain hairline round>危险</van-button>
</view>
<van-button
type="primary"
color="#16a34a"
color="#22c55e"
block
round
style="margin-top: 16rpx; height: 88rpx;"
@@ -33,7 +33,7 @@
<view class="panel-hd"><text class="panel-title">状态 Tag</text></view>
<view class="comp-row">
<van-tag type="warning" plain round>待处理</van-tag>
<van-tag type="primary" color="#16a34a" round>进行中</van-tag>
<van-tag type="primary" color="#22c55e" round>进行中</van-tag>
<van-tag type="success" plain round>已完成</van-tag>
<van-tag type="danger" plain round>异常</van-tag>
</view>
@@ -45,7 +45,7 @@
left-icon="volume-o"
text="移动端菜单在「菜单管理 → 移动端菜单」下配置,按角色授权后展示。"
background="#f0fdf4"
color="#15803d"
color="#16a34a"
/>
<view style="min-height: 360rpx; margin-top: 24rpx">
<van-empty
@@ -54,17 +54,17 @@
/>
</view>
<view style="display: flex; justify-content: center; padding: 32rpx 0">
<van-loading size="24px" color="#16a34a">加载中</van-loading>
<van-loading size="24px" color="#22c55e">加载中</van-loading>
</view>
</view>
<view class="panel">
<view class="panel-hd"><text class="panel-title">反馈</text></view>
<view class="comp-row">
<van-button size="small" plain hairline color="#16a34a" @click="toast('操作成功')">
<van-button size="small" plain hairline color="#22c55e" @click="toast('操作成功')">
Toast
</van-button>
<van-button size="small" plain hairline color="#16a34a" @click="showDialog">
<van-button size="small" plain hairline color="#22c55e" @click="showDialog">
Dialog
</van-button>
</view>
@@ -99,7 +99,7 @@
uni.showModal({
title: '确认提交',
content: '提交后将进入审批流程,是否继续?',
confirmColor: '#16a34a'
confirmColor: '#22c55e'
})
}
}
+2 -2
View File
@@ -48,7 +48,7 @@
<view class="detail-section">
<view class="sec-hd"><text class="sec-title">业务进度</text></view>
<van-steps :active="detail.stepActive" active-color="#16a34a">
<van-steps :active="detail.stepActive" active-color="#22c55e">
<van-step v-for="s in detail.steps" :key="s">{{ s }}</van-step>
</van-steps>
</view>
@@ -94,7 +94,7 @@
<view class="detail-footer">
<van-button class="btn-flex" plain hairline type="default" @click="goBack">返回</van-button>
<van-button class="btn-flex" type="primary" color="#16a34a" round @click="goEdit">
<van-button class="btn-flex" type="primary" color="#22c55e" round @click="goEdit">
编辑处理
</van-button>
</view>
+2 -2
View File
@@ -32,7 +32,7 @@
/>
<van-cell title="加急">
<template #right-icon>
<van-switch v-model="form.urgent" size="20px" active-color="#16a34a" />
<van-switch v-model="form.urgent" size="20px" active-color="#22c55e" />
</template>
</van-cell>
</van-cell-group>
@@ -62,7 +62,7 @@
<view class="form-footer">
<van-button class="btn-flex" plain hairline type="default" @click="goBack">取消</van-button>
<van-button class="btn-flex" type="primary" color="#16a34a" round @click="onSave">
<van-button class="btn-flex" type="primary" color="#22c55e" round @click="onSave">
保存
</van-button>
</view>
+4
View File
@@ -26,6 +26,7 @@
shape="round"
background="transparent"
placeholder="搜索单号 / 名称 / 负责人"
@update:model-value="onKeywordInput"
/>
</view>
</view>
@@ -79,6 +80,9 @@
},
methods: {
tabCount,
onKeywordInput(v) {
this.keyword = v == null ? '' : String(v)
},
goHome() {
uni.reLaunch({ url: '/pages/index/index' })
},
+2 -3
View File
@@ -22,7 +22,7 @@
:circular="banners.length > 1"
:indicator-dots="banners.length > 1"
indicator-color="rgba(255,255,255,0.45)"
indicator-active-color="#16a34a"
indicator-active-color="#22c55e"
:autoplay="false"
>
<swiper-item v-for="item in banners" :key="item.id">
@@ -86,8 +86,7 @@
title: '发起审批',
icon: 'approve',
path: '/pages/oa/start/index',
iconBg: '#ffe4e6',
iconFg: '#e11d48'
iconBg: 'transparent'
},
{
id: 'oa-form-sink',
+188 -132
View File
@@ -1,35 +1,49 @@
<template>
<view class="page">
<view class="search-wrap">
<input
class="search"
v-model="searchKeyword"
confirm-type="search"
placeholder="请输入审批单名称"
/>
<view class="toolbar">
<view class="search-box" :class="{ 'is-focus': searchFocused }">
<view class="search-ico" aria-hidden="true" />
<input
class="search-input"
:value="searchKeyword"
confirm-type="search"
placeholder="搜索审批单"
placeholder-class="search-ph"
@input="onSearchInput"
@focus="searchFocused = true"
@blur="searchFocused = false"
/>
<view
v-if="searchKeyword"
class="search-clear"
@click="searchKeyword = ''"
>
<text class="search-clear__x">×</text>
</view>
</view>
<scroll-view scroll-x class="tabs" :show-scrollbar="false">
<view class="tabs-inner">
<view
class="tab"
:class="{ active: activeGroupId === '' }"
@click="activeGroupId = ''"
>
全部
</view>
<view
v-for="g in groupOptions"
:key="g.id"
class="tab"
:class="{ active: activeGroupId === g.id }"
@click="activeGroupId = g.id"
>
{{ g.groupName }}
</view>
</view>
</scroll-view>
</view>
<scroll-view scroll-x class="tabs" :show-scrollbar="false">
<view
class="tab"
:class="{ active: activeGroupId === '' }"
@click="activeGroupId = ''"
>
全部
</view>
<view
v-for="g in groupOptions"
:key="g.id"
class="tab"
:class="{ active: activeGroupId === g.id }"
@click="activeGroupId = g.id"
>
{{ g.groupName }}
</view>
</scroll-view>
<view class="hint">选择一个审批单即可发起</view>
<view v-if="loading" class="state state-loading">
<view class="pulse-dots" aria-hidden="true">
<view class="pulse-dot" />
@@ -45,7 +59,6 @@
<view v-for="sec in displayGroups" :key="sec.id" class="section">
<view class="section-hd">
<text class="section-name">{{ sec.name }}</text>
<view class="section-dot" />
<text class="section-meta">{{ sec.apps.length }} 个表单</text>
</view>
<view class="card-row">
@@ -55,18 +68,8 @@
class="oa-app-card"
@click="onPick(row)"
>
<view class="oa-app-card__main">
<OaAppIcon :icon-url="row.iconUrl" size="card" />
<view class="oa-app-card__body">
<text class="oa-app-card__name">{{ row.appName }}</text>
<text v-if="row.description" class="oa-app-card__desc">{{
row.description
}}</text>
</view>
</view>
<view class="oa-app-card__ops">
<view class="oa-app-card__btn" @click.stop="onPick(row)">发起</view>
</view>
<OaAppIcon :icon-url="row.iconUrl" size="xs" />
<text class="oa-app-card__name">{{ row.appName }}</text>
</view>
</view>
</view>
@@ -77,7 +80,7 @@
<script>
import OaAppIcon from '../../../components/OaAppIcon.vue'
import { fetchOaGroupList, fetchOaTemplates } from '../../../utils/oa-api.js'
import { getToken } from '../../../utils/auth.js'
import { forceRelogin, getToken } from '../../../utils/auth.js'
import { stashOaStartTitle } from '../../../utils/oa-h5.js'
export default {
@@ -88,6 +91,7 @@ export default {
templates: [],
groupOptions: [],
searchKeyword: '',
searchFocused: false,
activeGroupId: ''
}
},
@@ -132,12 +136,21 @@ export default {
},
onShow() {
if (!getToken()) {
uni.showToast({ title: '请先登录', icon: 'none' })
forceRelogin('请先登录')
return
}
this.load()
},
methods: {
onSearchInput(e) {
const v =
e && e.detail && e.detail.value != null
? String(e.detail.value)
: e && e.target
? String(e.target.value || '')
: ''
this.searchKeyword = v
},
async load() {
this.loading = true
try {
@@ -175,55 +188,132 @@ export default {
.page {
min-height: 100vh;
padding-bottom: 24rpx;
background: #f5f8f7;
background: #f5f6f8;
}
.search-wrap {
padding: 20rpx 24rpx 8rpx;
.toolbar {
position: sticky;
top: 0;
z-index: 10;
background: #fff;
}
.search {
.search-box {
display: flex;
align-items: center;
gap: 16rpx;
margin: 20rpx 24rpx 0;
height: 72rpx;
padding: 0 24rpx;
border-radius: 16rpx;
background: #f5f8f7;
border-radius: 20rpx;
border: 1rpx solid #e8eaed;
background: #fafafa;
box-sizing: border-box;
transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
}
.search-box.is-focus {
border-color: #cbd5e1;
background: #fff;
box-shadow: 0 0 0 6rpx rgba(148, 163, 184, 0.18);
}
.search-ico {
position: relative;
width: 26rpx;
height: 26rpx;
border: 3rpx solid #94a3b8;
border-radius: 50%;
box-sizing: border-box;
flex-shrink: 0;
}
.search-ico::after {
content: '';
position: absolute;
width: 12rpx;
height: 3rpx;
right: -8rpx;
bottom: -5rpx;
background: #94a3b8;
border-radius: 3rpx;
transform: rotate(45deg);
}
.search-input {
flex: 1;
height: 72rpx;
font-size: 28rpx;
color: #1f2937;
min-width: 0;
}
.search-ph {
color: #b0b7c3;
}
.search-clear {
display: flex;
align-items: center;
justify-content: center;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background: #e5e7eb;
flex-shrink: 0;
}
.search-clear__x {
font-size: 28rpx;
line-height: 1;
color: #6b7280;
margin-top: -2rpx;
}
.tabs {
width: 100%;
white-space: nowrap;
padding: 16rpx 16rpx 8rpx;
background: #fff;
margin-top: 8rpx;
padding: 0 8rpx;
box-sizing: border-box;
border-bottom: 1rpx solid #f1f5f9;
}
.tabs-inner {
display: inline-flex;
align-items: stretch;
padding: 0 8rpx;
}
.tab {
display: inline-block;
margin-right: 16rpx;
padding: 12rpx 24rpx;
border-radius: 999rpx;
background: #f5f8f7;
color: #6b7b74;
font-size: 26rpx;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
height: 80rpx;
padding: 0 28rpx;
font-size: 28rpx;
color: #64748b;
flex-shrink: 0;
}
.tab.active {
color: #fff;
background: #16a34a;
color: #166534;
font-weight: 600;
}
.hint {
padding: 16rpx 24rpx;
color: #6b7b74;
font-size: 24rpx;
.tab.active::after {
content: '';
position: absolute;
left: 28rpx;
right: 28rpx;
bottom: 0;
height: 4rpx;
border-radius: 4rpx 4rpx 0 0;
background: #22c55e;
}
.state {
padding: 80rpx 24rpx;
text-align: center;
color: #6b7b74;
color: #999;
font-size: 28rpx;
}
/* 对齐 mobile-oa-h5 OaPulseLoading:品牌绿三点跳动 + muted 文案 */
.state-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 28rpx;
padding: 120rpx 24rpx 80rpx;
min-height: 50vh;
padding: 96rpx 32rpx;
box-sizing: border-box;
}
.pulse-dots {
display: flex;
@@ -234,8 +324,8 @@ export default {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: #16a34a;
animation: oa-start-bounce 1.2s ease-in-out infinite;
background: var(--brand-green, #22c55e);
animation: oa-pulse-bounce 1.2s ease-in-out infinite;
}
.pulse-dot:nth-child(2) {
animation-delay: 0.15s;
@@ -247,10 +337,11 @@ export default {
}
.state-loading__text {
font-size: 26rpx;
color: #6b7b74;
font-weight: 500;
color: var(--text-muted, #5f7a6d);
line-height: 1.4;
}
@keyframes oa-start-bounce {
@keyframes oa-pulse-bounce {
0%,
80%,
100% {
@@ -261,30 +352,32 @@ export default {
}
}
.body {
padding: 0 24rpx;
padding: 24rpx 24rpx 0;
display: flex;
flex-direction: column;
gap: 24rpx;
}
.section {
margin-bottom: 28rpx;
background: #fff;
border: 1rpx solid #e5e7eb;
border-radius: 16rpx;
overflow: hidden;
padding: 8rpx 8rpx 12rpx;
box-sizing: border-box;
}
.section-hd {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 16rpx;
min-height: 40rpx;
min-height: 64rpx;
padding: 0 16rpx;
margin-bottom: 4rpx;
}
.section-name {
font-size: 28rpx;
font-weight: 600;
color: #334155;
}
.section-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: #f87171;
flex-shrink: 0;
}
.section-meta {
margin-left: auto;
font-size: 22rpx;
@@ -293,67 +386,30 @@ export default {
.card-row {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.oa-app-card {
width: calc((100% - 16rpx) / 2);
width: 25%;
display: flex;
flex-direction: column;
gap: 16rpx;
min-height: 180rpx;
padding: 20rpx 20rpx 14rpx;
border-radius: 20rpx;
background: #fff;
border: 1px solid #d8e3de;
box-shadow: 0 4rpx 16rpx rgba(15, 23, 42, 0.06);
align-items: center;
gap: 12rpx;
padding: 16rpx 8rpx 20rpx;
box-sizing: border-box;
}
.oa-app-card:active {
opacity: 0.94;
}
.oa-app-card__main {
display: flex;
gap: 20rpx;
align-items: flex-start;
min-width: 0;
}
.oa-app-card__body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 6rpx;
justify-content: center;
opacity: 0.72;
}
.oa-app-card__name {
font-size: 26rpx;
font-weight: 600;
color: #0f172a;
width: 100%;
font-size: 24rpx;
font-weight: 400;
color: #334155;
line-height: 1.35;
word-break: break-word;
white-space: normal;
}
.oa-app-card__desc {
font-size: 22rpx;
color: #94a3b8;
text-align: center;
word-break: break-all;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.oa-app-card__ops {
display: flex;
justify-content: flex-end;
padding-top: 12rpx;
border-top: 1px solid #f1f5f9;
}
.oa-app-card__btn {
height: 44rpx;
line-height: 44rpx;
padding: 0 16rpx;
border-radius: 8rpx;
background: #16a34a;
color: #fff;
font-size: 22rpx;
font-weight: 600;
}
</style>
+3 -4
View File
@@ -8,7 +8,7 @@ import {
buildOaStartUrl,
takeOaStartTitle
} from '../../../utils/oa-h5.js'
import { getToken } from '../../../utils/auth.js'
import { forceRelogin, getToken } from '../../../utils/auth.js'
import { fetchOaTemplateDetail } from '../../../utils/oa-api.js'
const CLOSE_ACTIONS = new Set([
@@ -24,8 +24,7 @@ export default {
},
onLoad(query) {
if (!getToken()) {
uni.showToast({ title: '请先登录', icon: 'none' })
setTimeout(() => uni.navigateBack(), 500)
forceRelogin('请先登录')
return
}
const appId = String((query && query.appId) || '')
@@ -41,7 +40,7 @@ export default {
const src = buildOaStartUrl({ appId, draftId })
if (!/\btoken=/.test(src)) {
uni.showToast({ title: '未登录,无法打开 OA 表单', icon: 'none' })
forceRelogin('请先登录')
return
}
// 对齐水槽:onLoad 内同步赋值,与 pages/oa/form-sink/index 一致
+1 -1
View File
@@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#16a34a" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<polyline points="22 12 16 12 14 15 10 15 8 12 2 12"/>
<path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/>
</svg>

Before

Width:  |  Height:  |  Size: 341 B

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

+1 -4
View File
@@ -16,10 +16,7 @@ view, text {
.page-shell {
min-height: 100vh;
background:
radial-gradient(840rpx 440rpx at 100% 0%, rgba(214, 163, 67, 0.14), transparent 55%),
radial-gradient(760rpx 400rpx at 0% 20%, rgba(22, 163, 74, 0.1), transparent 50%),
linear-gradient(180deg, #e8f5ef 0%, #f4f7f6 40%, #f7f9f8 100%);
background: var(--bg-page);
}
.safe-bottom {
+13 -13
View File
@@ -16,14 +16,14 @@
}
.d-attach-upload:active {
border-color: #16a34a;
border-color: #22c55e;
}
.d-attach-upload .up-icon {
width: 88rpx;
height: 88rpx;
border-radius: 24rpx;
background: linear-gradient(145deg, #4ade80, #16a34a);
background: linear-gradient(145deg, #4ade80, #22c55e);
color: #fff;
display: flex;
align-items: center;
@@ -41,13 +41,13 @@
.d-attach-upload .up-title {
font-size: 26rpx;
font-weight: 700;
color: #15803d;
color: #22c55e;
}
.d-attach-upload .up-sub {
margin-top: 4rpx;
font-size: 22rpx;
color: #6b7b74;
color: #5f7a6d;
line-height: 1.4;
}
@@ -71,17 +71,17 @@
top: 0;
bottom: 0;
width: 6rpx;
background: var(--file-accent, #16a34a);
background: var(--file-accent, #22c55e);
}
.d-attach-card.ft-pdf {
--file-accent: #d14343;
--file-accent: #ef4444;
}
.d-attach-card.ft-img {
--file-accent: #16a34a;
--file-accent: #22c55e;
}
.d-attach-card.ft-xls {
--file-accent: #189b65;
--file-accent: #10b981;
}
.d-attach-card.ft-doc {
--file-accent: #3b82f6;
@@ -105,13 +105,13 @@
}
.d-attach-card.ft-pdf .file-icon {
background: linear-gradient(145deg, #f07171, #d14343);
background: linear-gradient(145deg, #f07171, #ef4444);
}
.d-attach-card.ft-img .file-icon {
background: linear-gradient(145deg, #4ade80, #16a34a);
background: linear-gradient(145deg, #4ade80, #22c55e);
}
.d-attach-card.ft-xls .file-icon {
background: linear-gradient(145deg, #4fbf8a, #189b65);
background: linear-gradient(145deg, #4fbf8a, #10b981);
}
.d-attach-card.ft-doc .file-icon {
background: linear-gradient(145deg, #6ea8ff, #3b82f6);
@@ -169,8 +169,8 @@
}
.d-attach-card .file-ops .btn-preview {
background: var(--brand-green-soft, #f0fdf4);
color: var(--brand-green-dark, #15803d);
background: var(--brand-green-soft, #ecfdf5);
color: var(--brand-green-dark, #16a34a);
}
.d-attach-card .file-ops .btn-download {
@@ -35,7 +35,7 @@
margin: 0 0 16rpx;
font-size: 34rpx;
font-weight: 650;
color: var(--text-primary, #1f2a26);
color: var(--text-primary, #0f291e);
line-height: 1.35;
}
@@ -47,7 +47,7 @@
}
.confirm-dialog__link {
color: var(--brand-green, #16a34a);
color: var(--brand-green, #22c55e);
font-weight: 500;
}
@@ -71,7 +71,7 @@
}
.confirm-dialog__btn--confirm {
color: var(--brand-green, #16a34a);
color: var(--brand-green, #22c55e);
font-weight: 600;
}
+20 -12
View File
@@ -2,9 +2,7 @@
.detail-page {
min-height: 100vh;
background:
radial-gradient(760rpx 400rpx at 100% 8%, rgba(214, 163, 67, 0.14), transparent 55%),
linear-gradient(180deg, #e8f5ef 0%, #f4f7f6 42%, #f7f9f8 100%);
background: var(--bg-page, #ffffff);
padding-bottom: 176rpx;
}
@@ -17,17 +15,20 @@
}
.detail-hero.status-warning {
background: linear-gradient(155deg, #a67b20 0%, #d6a343 45%, #e8c06a 100%);
background: linear-gradient(160deg, #fffbeb 0%, #fde68a 55%, #fbbf24 100%);
color: #92400e;
}
.detail-hero.status-primary {
@include hero-green;
border-radius: 0 0 56rpx 56rpx;
}
.detail-hero.status-danger {
background: linear-gradient(155deg, #9c2f2f 0%, #d14343 50%, #e36a6a 100%);
background: linear-gradient(160deg, #fef2f2 0%, #fecaca 55%, #f87171 100%);
color: #991b1b;
}
.detail-hero.status-success {
background: linear-gradient(155deg, #0f6b48 0%, #189b65 52%, #3bb883 100%);
background: linear-gradient(160deg, #ecfdf5 0%, #a7f3d0 55%, #6ee7b7 100%);
color: #065f46;
}
.detail-hero-body {
@@ -52,13 +53,20 @@
}
.detail-hero .type-chip {
background: rgba(255, 255, 255, 0.2);
color: #fff;
background: rgba(255, 255, 255, 0.35);
color: inherit;
}
.detail-hero.status-primary .type-chip,
.detail-hero.status-success .type-chip,
.detail-hero.status-warning .type-chip,
.detail-hero.status-danger .type-chip {
background: rgba(15, 41, 30, 0.08);
}
.detail-hero .status-chip {
background: #fff;
color: #173128;
color: #0f291e;
}
.detail-hero .hero-title {
@@ -146,12 +154,12 @@
width: 8rpx;
height: 28rpx;
border-radius: 999rpx;
background: linear-gradient(180deg, #22c55e, #15803d);
background: linear-gradient(180deg, #4ade80, #22c55e);
}
.detail-section .sec-hd .sec-more {
font-size: 24rpx;
color: var(--brand-green, #16a34a);
color: var(--brand-green, #22c55e);
}
.kv-list {
@@ -232,7 +240,7 @@
gap: 20rpx;
padding: 24rpx 28rpx calc(24rpx + env(safe-area-inset-bottom));
background: rgba(255, 255, 255, 0.96);
border-top: 1rpx solid var(--border, #dcebe4);
border-top: 1rpx solid var(--border, #d8f3e5);
z-index: 20;
}
+4 -4
View File
@@ -49,14 +49,14 @@
}
.flow-item.done .flow-dot {
background: linear-gradient(145deg, #4ade80, #16a34a);
background: linear-gradient(145deg, #4ade80, #22c55e);
}
.flow-item.warn .flow-dot {
background: linear-gradient(145deg, #ecc06a, #d6a343);
box-shadow: 0 12rpx 28rpx rgba(214, 163, 67, 0.28);
}
.flow-item.danger .flow-dot {
background: linear-gradient(145deg, #ef7a7a, #d14343);
background: linear-gradient(145deg, #ef7a7a, #ef4444);
box-shadow: 0 12rpx 28rpx rgba(209, 67, 67, 0.25);
}
@@ -104,7 +104,7 @@
.flow-item.done .flow-badge {
background: rgba(22, 163, 74, 0.12);
color: #15803d;
color: #22c55e;
}
.flow-item.warn .flow-badge {
background: rgba(214, 163, 67, 0.16);
@@ -134,7 +134,7 @@
align-items: center;
gap: 8rpx;
font-size: 22rpx;
color: #6b7b74;
color: #5f7a6d;
background: rgba(255, 255, 255, 0.9);
border: 1rpx solid #e8f0ec;
border-radius: 999rpx;
+17 -12
View File
@@ -2,7 +2,7 @@
.form-page {
min-height: 100vh;
background: var(--bg-page, #f5f8f7);
background: var(--bg-page, #ffffff);
padding-bottom: 176rpx;
}
@@ -34,7 +34,7 @@
gap: 20rpx;
padding: 24rpx 28rpx calc(24rpx + env(safe-area-inset-bottom));
background: rgba(255, 255, 255, 0.96);
border-top: 1rpx solid var(--border, #dcebe4);
border-top: 1rpx solid var(--border, #d8f3e5);
z-index: 20;
}
@@ -53,15 +53,19 @@
.home-header .brand {
font-size: 24rpx;
opacity: 0.88;
opacity: 0.78;
letter-spacing: 0.04em;
}
.home-logout {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.92);
padding: 8rpx 4rpx;
color: var(--header-fg, #065f46);
opacity: 0.85;
padding: 8rpx 20rpx;
flex-shrink: 0;
border: 1rpx solid rgba(6, 95, 70, 0.35);
border-radius: 12rpx;
line-height: 1.3;
}
.home-header .hello {
@@ -76,7 +80,7 @@
justify-content: space-between;
gap: 16rpx;
font-size: 24rpx;
opacity: 0.92;
opacity: 0.82;
}
.home-header .meta__left {
@@ -90,7 +94,8 @@
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.7);
background: currentColor;
opacity: 0.45;
flex-shrink: 0;
}
@@ -115,21 +120,21 @@
.panel-hd .panel-title {
font-size: 30rpx;
font-weight: 650;
color: var(--text-primary, #1f2a26);
color: var(--text-primary, #0f291e);
}
.panel-hd .hint {
font-size: 22rpx;
color: var(--text-muted, #6b7b74);
color: var(--text-muted, #5f7a6d);
}
.perm-banner {
padding: 20rpx 24rpx;
border-radius: 24rpx;
background: var(--barley-yellow-soft, #fff7e8);
border: 1rpx solid #f0e0b8;
background: var(--barley-yellow-soft, #fffbeb);
border: 1rpx solid #fde68a;
font-size: 24rpx;
color: #8a6520;
color: #a16207;
margin-bottom: 24rpx;
line-height: 1.45;
}
+1 -1
View File
@@ -15,7 +15,7 @@
.home-banner__item {
width: 100%;
height: 100%;
background: #f0fdf4;
background: #ffffff;
}
.home-banner__video,
+15 -15
View File
@@ -13,7 +13,7 @@
right: -100rpx;
bottom: -140rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.08);
background: rgba(6, 95, 70, 0.06);
pointer-events: none;
}
@@ -21,7 +21,7 @@
position: relative;
z-index: 1;
margin: 8rpx 4rpx 28rpx;
color: #fff;
color: var(--header-fg, #065f46);
}
.list-hero-copy .eyebrow {
@@ -48,7 +48,7 @@
.list-hero-copy .subline .em {
font-style: normal;
font-weight: 700;
background: rgba(255, 255, 255, 0.18);
background: rgba(6, 95, 70, 0.1);
border-radius: 999rpx;
padding: 2rpx 16rpx;
}
@@ -81,7 +81,7 @@
.seg-tab {
border-radius: 22rpx;
padding: 16rpx 8rpx;
color: #6b7b74;
color: #5f7a6d;
display: flex;
flex-direction: column;
align-items: center;
@@ -98,13 +98,13 @@
.seg-tab.active {
background: #fff;
color: var(--brand-green-dark, #15803d);
color: var(--brand-green-dark, #16a34a);
font-weight: 650;
box-shadow: 0 8rpx 24rpx rgba(21, 128, 61, 0.14);
}
.seg-tab.active .count {
color: var(--brand-green, #16a34a);
color: var(--brand-green, #22c55e);
}
.list-wrap {
@@ -163,13 +163,13 @@
color: #7a5610;
}
.biz-card.status-primary .type-icon {
background: linear-gradient(145deg, #86efac, #16a34a);
background: linear-gradient(145deg, #86efac, #22c55e);
}
.biz-card.status-danger .type-icon {
background: linear-gradient(145deg, #ffb4b4, #e85a5a);
}
.biz-card.status-success .type-icon {
background: linear-gradient(145deg, #b8efd4, #189b65);
background: linear-gradient(145deg, #b8efd4, #10b981);
}
.biz-card .title-wrap {
@@ -193,7 +193,7 @@
}
.biz-card.status-primary .type-chip {
background: rgba(22, 163, 74, 0.12);
color: #15803d;
color: #22c55e;
}
.biz-card.status-danger .type-chip {
background: rgba(209, 67, 67, 0.12);
@@ -225,11 +225,11 @@
box-shadow: 0 4rpx 16rpx rgba(214, 163, 67, 0.22);
}
.biz-card.status-primary .status-pill {
background: #16a34a;
background: #22c55e;
color: #fff;
}
.biz-card.status-danger .status-pill {
background: #d14343;
background: #ef4444;
color: #fff;
}
.biz-card.status-success .status-pill {
@@ -286,7 +286,7 @@
color: #9a7424;
}
.biz-card.status-primary .side-metric .amt {
color: #15803d;
color: #22c55e;
}
.biz-card.status-danger .side-metric .amt {
color: #b13535;
@@ -351,13 +351,13 @@
color: #a67b20;
}
.biz-card.status-primary .footer .left-hint {
color: #16a34a;
color: #22c55e;
}
.biz-card.status-danger .footer .left-hint {
color: #d14343;
color: #ef4444;
}
.biz-card.status-success .footer .left-hint {
color: #189b65;
color: #10b981;
}
.biz-card .footer .ops {
+19 -6
View File
@@ -11,14 +11,14 @@
flex-direction: column;
align-items: center;
justify-content: flex-start;
gap: 16rpx;
gap: 8rpx;
min-height: 168rpx;
padding: 20rpx 8rpx 12rpx;
border-radius: 24rpx;
}
.menu-card:active {
background: var(--brand-green-soft, #f0fdf4);
background: var(--brand-green-soft, #ecfdf5);
}
.menu-icon {
@@ -31,7 +31,7 @@
background: #eef2f1;
color: #3d524a;
font-size: 48rpx;
box-shadow: inset 0 0 0 1rpx rgba(31, 42, 38, 0.06);
box-shadow: inset 0 0 0 1rpx rgba(15, 41, 30, 0.06);
}
.menu-icon .v-icon--img {
@@ -39,6 +39,19 @@
height: 52rpx;
}
/* 无色底:仅展示位图本身(如发起审批纸飞机),收紧容器避免与标题脱节 */
.menu-icon.menu-icon--plain {
width: 64rpx;
height: 64rpx;
background: transparent;
box-shadow: none;
}
.menu-icon.menu-icon--plain .v-icon--img {
width: 56rpx;
height: 56rpx;
}
.menu-icon.tone-approve {
background: #ffe4e6;
color: #e11d48;
@@ -46,8 +59,8 @@
}
.menu-icon.tone-yellow {
background: var(--barley-yellow-soft, #fff7e8);
color: #a67b20;
background: var(--barley-yellow-soft, #fffbeb);
color: #a16207;
}
.menu-icon.tone-slate {
@@ -58,7 +71,7 @@
.menu-title {
font-size: 24rpx;
line-height: 1.25;
color: var(--text-primary, #1f2a26);
color: var(--text-primary, #0f291e);
text-align: center;
max-width: 144rpx;
word-break: break-all;
+6 -6
View File
@@ -10,7 +10,7 @@
z-index: 10;
background: #fff;
padding-top: env(safe-area-inset-top);
border-bottom: 1rpx solid var(--border, #dcebe4);
border-bottom: 1rpx solid var(--border, #d8f3e5);
}
.protocol-nav__bar {
@@ -41,7 +41,7 @@
.protocol-nav__back-icon {
font-size: 56rpx;
line-height: 1;
color: #1f2a26;
color: #0f291e;
font-weight: 300;
margin-top: -4rpx;
}
@@ -51,7 +51,7 @@
text-align: center;
font-size: 32rpx;
font-weight: 600;
color: #1f2a26;
color: #0f291e;
}
.protocol-body {
@@ -61,7 +61,7 @@
.protocol-meta {
margin-bottom: 28rpx;
font-size: 24rpx;
color: var(--text-muted, #6b7b74);
color: var(--text-muted, #5f7a6d);
}
.protocol-section {
@@ -72,14 +72,14 @@
margin-bottom: 16rpx;
font-size: 30rpx;
font-weight: 650;
color: var(--text-primary, #1f2a26);
color: var(--text-primary, #0f291e);
line-height: 1.4;
}
.protocol-section__p {
margin-bottom: 12rpx;
font-size: 26rpx;
color: var(--text-primary, #1f2a26);
color: var(--text-primary, #0f291e);
line-height: 1.7;
text-align: justify;
}
+15 -15
View File
@@ -32,13 +32,13 @@
}
.todo-stat.tone-warn::before {
background: var(--warning, #cf8a19);
background: var(--warning, #d97706);
}
.todo-stat.tone-pay::before {
background: var(--barley-yellow, #d6a343);
background: var(--barley-yellow, #fbbf24);
}
.todo-stat.tone-danger::before {
background: var(--danger, #d14343);
background: var(--danger, #ef4444);
}
.todo-stat.tone-warn {
@@ -56,7 +56,7 @@
font-size: 44rpx;
font-weight: 700;
line-height: 1.1;
color: var(--text-primary, #1f2a26);
color: var(--text-primary, #0f291e);
font-variant-numeric: tabular-nums;
}
@@ -67,14 +67,14 @@
color: #9a7424;
}
.todo-stat.tone-danger .num {
color: var(--danger, #d14343);
color: var(--danger, #ef4444);
}
.todo-stat .label {
display: block;
margin-top: 8rpx;
font-size: 22rpx;
color: var(--text-muted, #6b7b74);
color: var(--text-muted, #5f7a6d);
}
.todo-stat .stat-icon {
@@ -92,7 +92,7 @@
.todo-stat.tone-warn .stat-icon {
background: rgba(207, 138, 25, 0.12);
color: var(--warning, #cf8a19);
color: var(--warning, #d97706);
}
.todo-stat.tone-pay .stat-icon {
background: rgba(214, 163, 67, 0.16);
@@ -100,7 +100,7 @@
}
.todo-stat.tone-danger .stat-icon {
background: rgba(209, 67, 67, 0.12);
color: var(--danger, #d14343);
color: var(--danger, #ef4444);
}
.todo-list {
@@ -115,13 +115,13 @@
gap: 20rpx;
width: 100%;
padding: 20rpx;
border: 1rpx solid var(--border, #dcebe4);
border: 1rpx solid var(--border, #d8f3e5);
border-radius: 24rpx;
background: #fff;
}
.todo-item:active {
background: var(--brand-green-soft, #f0fdf4);
background: var(--brand-green-soft, #ecfdf5);
border-color: #b7e5d0;
}
@@ -138,15 +138,15 @@
.todo-item .ti-icon.i-approve {
background: #fff3e0;
color: var(--warning, #cf8a19);
color: var(--warning, #d97706);
}
.todo-item .ti-icon.i-pay {
background: var(--barley-yellow-soft, #fff7e8);
background: var(--barley-yellow-soft, #fffbeb);
color: #a67b20;
}
.todo-item .ti-icon.i-alert {
background: #fdecec;
color: var(--danger, #d14343);
color: var(--danger, #ef4444);
}
.todo-item .ti-body {
@@ -157,14 +157,14 @@
.todo-item .ti-title {
font-size: 26rpx;
font-weight: 600;
color: var(--text-primary, #1f2a26);
color: var(--text-primary, #0f291e);
line-height: 1.3;
}
.todo-item .ti-desc {
margin-top: 4rpx;
font-size: 22rpx;
color: var(--text-muted, #6b7b74);
color: var(--text-muted, #5f7a6d);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
+3 -3
View File
@@ -1,6 +1,6 @@
@mixin hero-green {
background: linear-gradient(155deg, #14532d 0%, #16a34a 48%, #22c55e 100%);
color: #ffffff;
background: linear-gradient(160deg, #f0fdf4 0%, #d1fae5 58%, #a7f3d0 100%);
color: var(--header-fg, #065f46);
border-radius: 0 0 56rpx 56rpx;
position: relative;
overflow: hidden;
@@ -8,7 +8,7 @@
@mixin surface-card {
background: var(--surface, #ffffff);
border: 1rpx solid var(--border, #dcebe4);
border: 1rpx solid var(--border, #d8f3e5);
border-radius: var(--radius-card, 28rpx);
box-shadow: var(--shadow-soft);
}
+17 -14
View File
@@ -1,17 +1,19 @@
// 唯一 Token 源 — 品牌绿(青葱),禁止 web-admin #10b981
$brand-green: #16a34a;
$brand-green-dark: #15803d;
$brand-green-soft: #f0fdf4;
$barley-yellow: #d6a343;
$barley-yellow-soft: #fff7e8;
$bg-page: #f5f8f7;
// 唯一 Token 源 — 方案 A「薄荷微风」:明亮清爽,勿用深墨绿顶栏
// 主色 #22c55e;成功语义可用 #10b981,但主按钮勿单独改成 web-admin 主色链
$brand-green: #22c55e;
$brand-green-dark: #16a34a;
$brand-green-soft: #ecfdf5;
$header-fg: #065f46;
$barley-yellow: #fbbf24;
$barley-yellow-soft: #fffbeb;
$bg-page: #ffffff;
$surface: #ffffff;
$text-primary: #1f2a26;
$text-muted: #6b7b74;
$border: #dcebe4;
$danger: #d14343;
$warning: #cf8a19;
$success: #189b65;
$text-primary: #0f291e;
$text-muted: #5f7a6d;
$border: #d8f3e5;
$danger: #ef4444;
$warning: #d97706;
$success: #10b981;
$radius-card: 28rpx;
$radius-lg: 40rpx;
@@ -19,6 +21,7 @@ $radius-lg: 40rpx;
--brand-green: #{$brand-green};
--brand-green-dark: #{$brand-green-dark};
--brand-green-soft: #{$brand-green-soft};
--header-fg: #{$header-fg};
--barley-yellow: #{$barley-yellow};
--barley-yellow-soft: #{$barley-yellow-soft};
--bg-page: #{$bg-page};
@@ -31,5 +34,5 @@ $radius-lg: 40rpx;
--success: #{$success};
--radius-card: #{$radius-card};
--radius-lg: #{$radius-lg};
--shadow-soft: 0 8rpx 32rpx rgba(21, 128, 61, 0.08);
--shadow-soft: 0 8rpx 32rpx rgba(22, 163, 74, 0.07);
}
+41 -14
View File
@@ -1,21 +1,48 @@
/* Vant 4 CSS 变量 — 对齐品牌绿 Token,禁止 #10b981 */
/* Vant 4 CSS 变量 — 方案 A 薄荷微风 */
page {
--van-primary-color: #16a34a;
--van-success-color: #189b65;
--van-danger-color: #d14343;
--van-warning-color: #cf8a19;
--van-primary-color: #22c55e;
--van-success-color: #10b981;
--van-danger-color: #ef4444;
--van-warning-color: #d97706;
--van-button-primary-background: #16a34a;
--van-button-primary-border-color: #16a34a;
--van-button-primary-background: #22c55e;
--van-button-primary-border-color: #22c55e;
--van-nav-bar-background: transparent;
--van-nav-bar-title-text-color: #ffffff;
--van-nav-bar-icon-color: #ffffff;
--van-nav-bar-title-text-color: #065f46;
--van-nav-bar-icon-color: #065f46;
--van-tabs-bottom-bar-color: #16a34a;
--van-tag-primary-color: #16a34a;
--van-steps-active-color: #16a34a;
--van-tabs-bottom-bar-color: #22c55e;
--van-tag-primary-color: #22c55e;
--van-steps-active-color: #22c55e;
--van-notice-bar-background: #f0fdf4;
--van-notice-bar-text-color: #15803d;
--van-notice-bar-background: #ecfdf5;
--van-notice-bar-text-color: #16a34a;
/* 表单 Cell / Field 更紧凑(选择框与输入行) */
--van-cell-vertical-padding: 8px;
--van-cell-horizontal-padding: 16px;
--van-cell-font-size: 14px;
--van-cell-line-height: 22px;
--van-field-input-text-color: #0f291e;
--van-field-placeholder-text-color: #5f7a6d;
}
/* 选择类触发条(is-link / readonly):再压矮一点 */
.van-field.van-cell--clickable,
.van-field--readonly.van-cell--clickable {
min-height: 40px;
}
.van-field.van-cell--clickable .van-field__control,
.van-field--readonly.van-cell--clickable .van-field__control {
font-size: 13px;
min-height: 22px;
line-height: 22px;
}
.van-field.van-cell--clickable .van-cell__right-icon,
.van-field--readonly.van-cell--clickable .van-cell__right-icon {
font-size: 14px;
line-height: 22px;
}
+98
View File
@@ -0,0 +1,98 @@
/** 本地登录会话(无网络依赖,供 request / auth 共用,避免循环引用) */
const TOKEN_KEY = 'zkxw_mobile_token'
const REFRESH_TOKEN_KEY = 'zkxw_mobile_refresh_token'
const USER_KEY = 'zkxw_mobile_user'
export function getToken() {
try {
return uni.getStorageSync(TOKEN_KEY) || ''
} catch (e) {
return ''
}
}
export function getRefreshToken() {
try {
return uni.getStorageSync(REFRESH_TOKEN_KEY) || ''
} catch (e) {
return ''
}
}
export function isLoggedIn() {
return !!getToken()
}
export function getUser() {
try {
const raw = uni.getStorageSync(USER_KEY)
return raw ? JSON.parse(raw) : null
} catch (e) {
return null
}
}
export function clearSession() {
try {
uni.removeStorageSync(TOKEN_KEY)
uni.removeStorageSync(REFRESH_TOKEN_KEY)
uni.removeStorageSync(USER_KEY)
} catch (e) {
/* ignore */
}
}
export function saveSession({ token, refreshToken, userInfo }) {
uni.setStorageSync(TOKEN_KEY, token || '')
uni.setStorageSync(REFRESH_TOKEN_KEY, refreshToken || '')
uni.setStorageSync(USER_KEY, JSON.stringify(userInfo || null))
}
/** 更新本地缓存的用户信息(保留 token)。 */
export function updateStoredUser(userInfo) {
try {
uni.setStorageSync(USER_KEY, JSON.stringify(userInfo || null))
} catch (e) {
/* ignore */
}
}
let _reloginPending = false
function isOnLoginPage() {
try {
const pages = getCurrentPages()
const cur = pages && pages[pages.length - 1]
const route = (cur && cur.route) || ''
return String(route).indexOf('pages/login') !== -1
} catch (e) {
return false
}
}
/**
* Token 失效:清本地会话并回到登录页(防抖,避免并发 401 连跳)。
* @param {string} [message]
*/
export function forceRelogin(message) {
if (_reloginPending) return
clearSession()
if (isOnLoginPage()) return
_reloginPending = true
const title =
(typeof message === 'string' && message.trim()) ||
'登录已失效,请重新登录'
uni.showToast({ title, icon: 'none', duration: 2000 })
setTimeout(() => {
uni.reLaunch({
url: '/pages/login/index',
complete() {
_reloginPending = false
},
fail() {
_reloginPending = false
}
})
}, 400)
}
+18 -57
View File
@@ -1,65 +1,26 @@
import { validateCredentials } from './validate-credentials.js'
import { buildEncryptedLoginBody } from './login-rsa.js'
import { get, post } from './request.js'
import {
getToken,
getRefreshToken,
isLoggedIn,
getUser,
clearSession,
saveSession,
updateStoredUser,
forceRelogin
} from './auth-session.js'
export { validateCredentials }
const TOKEN_KEY = 'zkxw_mobile_token'
const REFRESH_TOKEN_KEY = 'zkxw_mobile_refresh_token'
const USER_KEY = 'zkxw_mobile_user'
export function getToken() {
try {
return uni.getStorageSync(TOKEN_KEY) || ''
} catch (e) {
return ''
}
}
export function getRefreshToken() {
try {
return uni.getStorageSync(REFRESH_TOKEN_KEY) || ''
} catch (e) {
return ''
}
}
export function isLoggedIn() {
return !!getToken()
}
export function getUser() {
try {
const raw = uni.getStorageSync(USER_KEY)
return raw ? JSON.parse(raw) : null
} catch (e) {
return null
}
}
export function clearSession() {
try {
uni.removeStorageSync(TOKEN_KEY)
uni.removeStorageSync(REFRESH_TOKEN_KEY)
uni.removeStorageSync(USER_KEY)
} catch (e) {
/* ignore */
}
}
function saveSession({ token, refreshToken, userInfo }) {
uni.setStorageSync(TOKEN_KEY, token || '')
uni.setStorageSync(REFRESH_TOKEN_KEY, refreshToken || '')
uni.setStorageSync(USER_KEY, JSON.stringify(userInfo || null))
}
/** 更新本地缓存的用户信息(保留 token)。 */
export function updateStoredUser(userInfo) {
try {
uni.setStorageSync(USER_KEY, JSON.stringify(userInfo || null))
} catch (e) {
/* ignore */
}
export {
getToken,
getRefreshToken,
isLoggedIn,
getUser,
clearSession,
updateStoredUser,
forceRelogin
}
/**
+1 -1
View File
@@ -4,7 +4,7 @@
*
* 微信公众平台须配置 request / 业务域名:www.chengyantek.cn(HTTPS,安全组放行 443)
*/
const USE_REMOTE_TEST = false
const USE_REMOTE_TEST = true
/** 本机联调 */
const LOCAL_API_BASE_URL = 'http://127.0.0.1:8000/api'
+21 -1
View File
@@ -2,16 +2,36 @@
* 主页宫格图标配色:每项可自定义 iconBg / iconFg。
* 未配置时按 icon 名回退到此处默认色对。
*/
/** OA 宫格 PNG 自带颜色,不套色底 */
const OA_PNG_ICONS = new Set([
'approve',
'oa-drafts',
'oa-pending',
'oa-done',
'oa-mine',
'oa-cc'
])
export const MENU_ICON_COLOR_DEFAULTS = {
approve: { iconBg: '#ffe4e6', iconFg: '#e11d48' },
approve: { iconBg: 'transparent', iconFg: '' },
'oa-drafts': { iconBg: 'transparent', iconFg: '' },
'oa-pending': { iconBg: 'transparent', iconFg: '' },
'oa-done': { iconBg: 'transparent', iconFg: '' },
'oa-mine': { iconBg: 'transparent', iconFg: '' },
'oa-cc': { iconBg: 'transparent', iconFg: '' },
'orders-o': { iconBg: '#eef2f1', iconFg: '#3d524a' },
'apps-o': { iconBg: '#eef2f1', iconFg: '#3d524a' }
}
export function resolveMenuIconColors(menu) {
const icon = String(menu?.icon || '').trim()
// OA PNG 自带色:忽略接口下发的色底,避免灰底盖住图标
if (OA_PNG_ICONS.has(icon)) {
return { iconBg: 'transparent', iconFg: '' }
}
const fallback = MENU_ICON_COLOR_DEFAULTS[icon] || {}
const iconBg = String(menu?.iconBg || menu?.icon_bg || fallback.iconBg || '').trim()
const iconFg = String(menu?.iconFg || menu?.icon_fg || fallback.iconFg || '').trim()
return { iconBg, iconFg }
}
+44 -2
View File
@@ -1,4 +1,42 @@
import { API_BASE_URL, REQUEST_TIMEOUT_MS } from './config.js'
import { forceRelogin } from './auth-session.js'
/** 这些接口的业务 code=401 表示凭据错误等,不是会话过期 */
const AUTH_EXEMPT_PATHS = [
'/web/auth/login',
'/web/auth/login-rsa-public-key',
'/web/auth/refresh-token',
'/mini/auth/wechat-login'
]
function isAuthExemptPath(path) {
const p = String(path || '')
for (let i = 0; i < AUTH_EXEMPT_PATHS.length; i++) {
if (p.indexOf(AUTH_EXEMPT_PATHS[i]) !== -1) return true
}
return false
}
function hasBearerToken(header) {
const auth = (header && header.Authorization) || ''
return /^Bearer\s+\S+/i.test(String(auth))
}
/**
* HTTP 401,或已带 token 的业务 code=401 → 强制重新登录。
* 登录接口豁免,避免「账号密码错误」被当成会话失效。
*/
function maybeForceRelogin(options, httpStatus, body) {
if (isAuthExemptPath(options && options.url)) return
const bizCode = body && body.code
const msg =
(body && typeof body.message === 'string' && body.message.trim()) ||
(body && typeof body.detail === 'string' && body.detail.trim()) ||
''
if (httpStatus === 401 || (bizCode === 401 && hasBearerToken(options && options.header))) {
forceRelogin(msg || '登录已失效,请重新登录')
}
}
/**
* 解析后端统一响应。成功返回 data;失败抛 Error(message 为业务文案)。
@@ -41,6 +79,7 @@ export function request(options) {
{ 'Content-Type': 'application/json' },
options.header || {}
)
const reqOpts = { url: options.url, header }
return new Promise((resolve, reject) => {
uni.request({
@@ -51,13 +90,16 @@ export function request(options) {
timeout: REQUEST_TIMEOUT_MS,
success(res) {
try {
const body = res.data
maybeForceRelogin(reqOpts, res.statusCode, body)
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(unwrapResponse(res.data, res.statusCode))
resolve(unwrapResponse(body, res.statusCode))
return
}
// HTTP 非 2xx:若 body 仍是业务包,优先展示 message
try {
resolve(unwrapResponse(res.data, res.statusCode))
resolve(unwrapResponse(body, res.statusCode))
} catch (bizErr) {
reject(bizErr)
}