Compare commits
14 Commits
5e77d4fdb1
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| ff5747c21b | |||
| b9db7d7f5c | |||
| 4c896c69fc | |||
| 7f2a02d6ca | |||
| 94024faba9 | |||
| df04d0f4ba | |||
| 79499a5de5 | |||
| b6b999e47a | |||
| 1f93f4cd91 | |||
| 0a1a312418 | |||
| 6e8bb2e0a0 | |||
| fb312af2fc | |||
| 471cfe6188 | |||
| 892acc6d23 |
+11
-8
@@ -51,7 +51,9 @@
|
|||||||
<text class="lab">审批链</text>
|
<text class="lab">审批链</text>
|
||||||
<text class="hint">{{ suggestedLabel || '将按固定审批链流转' }}</text>
|
<text class="hint">{{ suggestedLabel || '将按固定审批链流转' }}</text>
|
||||||
</view>
|
</view>
|
||||||
<wd-button type="primary" block size="large" @click="submit">提交申请</wd-button>
|
<view class="submit">
|
||||||
|
<wd-button type="primary" block size="large" @click="submit">提交申请</wd-button>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -164,14 +166,15 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.body { padding: 24rpx 28rpx 48rpx; }
|
.body { padding: 16rpx 24rpx 48rpx; }
|
||||||
.field { margin-bottom: 24rpx; }
|
.field { margin-bottom: 24rpx; }
|
||||||
.lab { font-size: 26rpx; color: #374151; display: block; margin-bottom: 10rpx; }
|
.lab { font-size: 26rpx; color: #6B7280; display: block; margin-bottom: 10rpx; }
|
||||||
.box { min-height: 92rpx; background: #fff; border-radius: 16rpx; padding: 22rpx 24rpx; font-size: 30rpx; border: 1rpx solid rgba(17,24,39,0.06); }
|
.box { min-height: 92rpx; background: #fff; border-radius: 16rpx; padding: 22rpx 24rpx; font-size: 30rpx; color: #111827; border: 1rpx solid rgba(17,24,39,0.06); }
|
||||||
.box.tall { min-height: 180rpx; }
|
.box.tall { min-height: 180rpx; }
|
||||||
.box textarea, .box input { width: 100%; font-size: 30rpx; }
|
.box textarea, .box input { width: 100%; font-size: 30rpx; color: #111827; }
|
||||||
.inv { padding: 24rpx; margin-bottom: 16rpx; }
|
.inv { padding: 24rpx; margin-bottom: 16rpx; }
|
||||||
.inv .box { margin-top: 12rpx; }
|
.inv .box { margin-top: 12rpx; background: #F5F7FA; }
|
||||||
.link { color: #1677FF; font-size: 28rpx; display: block; margin-bottom: 20rpx; }
|
.link { color: #1677FF; font-size: 28rpx; font-weight: 600; display: block; margin: 4rpx 8rpx 24rpx; }
|
||||||
.hint { font-size: 24rpx; color: #6B7280; line-height: 1.5; }
|
.hint { font-size: 26rpx; color: #111827; line-height: 1.5; }
|
||||||
|
.submit { margin-top: 8rpx; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+46
-12
@@ -5,17 +5,25 @@
|
|||||||
<view v-if="!canApply" class="deny">当前账号没有发起申请权限</view>
|
<view v-if="!canApply" class="deny">当前账号没有发起申请权限</view>
|
||||||
<view v-else>
|
<view v-else>
|
||||||
<view class="search">
|
<view class="search">
|
||||||
<input v-model="q" placeholder="搜索审批流程或申请事项..." />
|
<wd-icon name="search" size="28rpx" color="#9CA3AF" />
|
||||||
|
<input v-model="q" placeholder="搜索审批流程或申请事项" placeholder-class="ph" confirm-type="search" />
|
||||||
|
<view v-if="q" class="clear" @click="q = ''">
|
||||||
|
<wd-icon name="close-bold" size="22rpx" color="#9CA3AF" />
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-if="!groups.length" class="empty">没有匹配的申请</view>
|
||||||
<view v-for="g in groups" :key="g.category" class="block">
|
<view v-for="g in groups" :key="g.category" class="block">
|
||||||
<text class="cat">{{ g.category }}</text>
|
<text class="cat">{{ g.category }}</text>
|
||||||
<view class="card">
|
<view class="card">
|
||||||
<view v-for="k in g.items" :key="k.kind" class="item" @click="open(k)">
|
<view v-for="k in g.items" :key="k.kind" class="item" @click="open(k)">
|
||||||
<view>
|
<view class="ico" :style="{ background: visual(k).bg }">
|
||||||
|
<wd-icon :name="visual(k).name" size="36rpx" :color="visual(k).color" />
|
||||||
|
</view>
|
||||||
|
<view class="txt">
|
||||||
<text class="t">{{ k.title }}</text>
|
<text class="t">{{ k.title }}</text>
|
||||||
<text class="d">{{ k.hint }}</text>
|
<text class="d">{{ k.hint }}</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="arr">›</text>
|
<wd-icon name="arrow-right" size="28rpx" color="#D1D5DB" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -29,6 +37,7 @@ import FyHeader from '../../components/fy-header.vue'
|
|||||||
import FyScroll from '../../components/fy-scroll.vue'
|
import FyScroll from '../../components/fy-scroll.vue'
|
||||||
import { store } from '../../utils/store.js'
|
import { store } from '../../utils/store.js'
|
||||||
import { groupedApplyKinds, visibleApplyKinds } from '../../utils/apply.js'
|
import { groupedApplyKinds, visibleApplyKinds } from '../../utils/apply.js'
|
||||||
|
import { tileVisual } from '../../utils/nav.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { FyHeader, FyScroll },
|
components: { FyHeader, FyScroll },
|
||||||
@@ -50,6 +59,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
visual(k) {
|
||||||
|
return tileVisual(k)
|
||||||
|
},
|
||||||
open(k) {
|
open(k) {
|
||||||
uni.navigateTo({ url: '/pages/apply/form?kind=' + k.kind })
|
uni.navigateTo({ url: '/pages/apply/form?kind=' + k.kind })
|
||||||
}
|
}
|
||||||
@@ -58,13 +70,35 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.search { padding: 16rpx 24rpx; }
|
.search {
|
||||||
.search input { height: 80rpx; background: #F3F6FB; border-radius: 16rpx; padding: 0 24rpx; }
|
margin: 16rpx 24rpx 8rpx;
|
||||||
.block { margin: 8rpx 24rpx 24rpx; }
|
height: 72rpx;
|
||||||
.cat { font-size: 22rpx; color: #4B5563; font-weight: 600; }
|
padding: 0 20rpx;
|
||||||
.item { display: flex; justify-content: space-between; align-items: center; padding: 24rpx; border-bottom: 1rpx solid #E8ECF2; }
|
background: #fff;
|
||||||
.t { display: block; font-size: 28rpx; font-weight: 600; }
|
border-radius: 16rpx;
|
||||||
.d { font-size: 22rpx; color: #6B7280; }
|
display: flex;
|
||||||
.arr { color: #D1D5DB; }
|
align-items: center;
|
||||||
.deny { padding: 80rpx; text-align: center; color: #6B7280; }
|
gap: 12rpx;
|
||||||
|
border: 1rpx solid rgba(17, 24, 39, 0.06);
|
||||||
|
}
|
||||||
|
.search input { flex: 1; height: 72rpx; font-size: 28rpx; }
|
||||||
|
.ph { color: #9CA3AF; }
|
||||||
|
.clear { width: 40rpx; height: 40rpx; display: flex; align-items: center; justify-content: center; }
|
||||||
|
.block { margin: 20rpx 24rpx 8rpx; }
|
||||||
|
.cat { display: block; margin: 0 8rpx 12rpx; font-size: 24rpx; color: #6B7280; font-weight: 600; }
|
||||||
|
.item { display: flex; align-items: center; padding: 22rpx 24rpx; }
|
||||||
|
.item + .item { border-top: 1rpx solid #F0F2F5; }
|
||||||
|
.ico {
|
||||||
|
width: 72rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.txt { flex: 1; min-width: 0; margin: 0 20rpx; }
|
||||||
|
.t { display: block; font-size: 30rpx; font-weight: 600; color: #111827; }
|
||||||
|
.d { display: block; margin-top: 6rpx; font-size: 24rpx; color: #6B7280; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.empty, .deny { padding: 120rpx 40rpx; text-align: center; color: #6B7280; font-size: 28rpx; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+19
-10
@@ -2,14 +2,20 @@
|
|||||||
<view class="page edge">
|
<view class="page edge">
|
||||||
<fy-header :title="title" back />
|
<fy-header :title="title" back />
|
||||||
<fy-scroll class="edge-scroll" @refresh="load">
|
<fy-scroll class="edge-scroll" @refresh="load">
|
||||||
<view v-if="loading" class="empty">加载中…</view>
|
<view v-if="loading" class="empty">
|
||||||
<view v-else-if="!rows.length" class="empty">暂无记录</view>
|
<wd-skeleton theme="paragraph" />
|
||||||
|
</view>
|
||||||
|
<wd-status-tip v-else-if="!rows.length" image="content" tip="暂无记录" />
|
||||||
<view v-for="r in rows" :key="r.id || r.no" class="card item" @click="open(r)">
|
<view v-for="r in rows" :key="r.id || r.no" class="card item" @click="open(r)">
|
||||||
<view>
|
<view class="main">
|
||||||
<text class="t">{{ r.title || r.no || r.typeLabel }}</text>
|
<text class="t">{{ r.title || r.no || r.typeLabel }}</text>
|
||||||
<text class="s">{{ r.status || '' }} · {{ r.createdAt || r.date || '' }}</text>
|
<text class="s">{{ r.createdAt || r.date || '' }}</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="amt">{{ r.amount != null ? '¥' + r.amount : '' }}</text>
|
<view class="side">
|
||||||
|
<text v-if="r.amount != null" class="amt">¥{{ r.amount }}</text>
|
||||||
|
<wd-tag v-if="r.status" type="primary" plain>{{ r.status }}</wd-tag>
|
||||||
|
</view>
|
||||||
|
<text class="arr">›</text>
|
||||||
</view>
|
</view>
|
||||||
</fy-scroll>
|
</fy-scroll>
|
||||||
</view>
|
</view>
|
||||||
@@ -54,9 +60,12 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.item { margin: 16rpx 24rpx; padding: 24rpx; display: flex; justify-content: space-between; }
|
.item { margin: 16rpx 24rpx; padding: 24rpx; display: flex; align-items: center; }
|
||||||
.t { display: block; font-size: 28rpx; font-weight: 600; }
|
.main { flex: 1; min-width: 0; }
|
||||||
.s { font-size: 22rpx; color: #6B7280; }
|
.t { display: block; font-size: 28rpx; font-weight: 650; color: #111827; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
.amt { color: #1677FF; font-weight: 700; }
|
.s { display: block; margin-top: 8rpx; font-size: 24rpx; color: #6B7280; }
|
||||||
.empty { padding: 80rpx; text-align: center; color: #6B7280; }
|
.side { display: flex; flex-direction: column; align-items: flex-end; gap: 8rpx; margin-left: 16rpx; }
|
||||||
|
.amt { color: #1677FF; font-weight: 700; font-size: 28rpx; }
|
||||||
|
.arr { color: #C4C9D4; font-size: 32rpx; margin-left: 8rpx; }
|
||||||
|
.empty { padding: 80rpx 40rpx; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+2
-2
@@ -95,11 +95,11 @@ export default {
|
|||||||
.stage { flex: 1; position: relative; }
|
.stage { flex: 1; position: relative; }
|
||||||
.wv { position: absolute; inset: 0; }
|
.wv { position: absolute; inset: 0; }
|
||||||
.wait { height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; }
|
.wait { height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; }
|
||||||
.av { width: 160rpx; height: 160rpx; border-radius: 50%; background: #1677FF; display: flex; align-items: center; justify-content: center; font-size: 64rpx; font-weight: 700; }
|
.av { width: 160rpx; height: 160rpx; border-radius: 40rpx; background: #E8F1FF; color: #1677FF; display: flex; align-items: center; justify-content: center; font-size: 64rpx; font-weight: 700; }
|
||||||
.name { margin-top: 24rpx; font-size: 36rpx; font-weight: 700; }
|
.name { margin-top: 24rpx; font-size: 36rpx; font-weight: 700; }
|
||||||
.st { margin-top: 8rpx; color: rgba(255,255,255,0.65); }
|
.st { margin-top: 8rpx; color: rgba(255,255,255,0.65); }
|
||||||
.acts { display: flex; justify-content: center; gap: 32rpx; padding: 32rpx 24rpx calc(32rpx + env(safe-area-inset-bottom)); }
|
.acts { display: flex; justify-content: center; gap: 32rpx; padding: 32rpx 24rpx calc(32rpx + env(safe-area-inset-bottom)); }
|
||||||
.btn { min-width: 200rpx; height: 80rpx; border-radius: 999rpx; display: flex; align-items: center; justify-content: center; font-size: 28rpx; font-weight: 600; }
|
.btn { min-width: 200rpx; height: 80rpx; border-radius: 999rpx; display: flex; align-items: center; justify-content: center; font-size: 28rpx; font-weight: 600; }
|
||||||
.ok { background: #006c49; }
|
.ok { background: #16A34A; }
|
||||||
.no { background: #DC2626; }
|
.no { background: #DC2626; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+10
-8
@@ -26,7 +26,7 @@
|
|||||||
<view v-if="!list.length" class="empty">暂无消息,发一条试试</view>
|
<view v-if="!list.length" class="empty">暂无消息,发一条试试</view>
|
||||||
<view v-for="(m, i) in list" :key="m.id || i">
|
<view v-for="(m, i) in list" :key="m.id || i">
|
||||||
<view v-if="showTime(i)" class="chip-time">{{ timeOf(m) }}</view>
|
<view v-if="showTime(i)" class="chip-time">{{ timeOf(m) }}</view>
|
||||||
<view v-if="m.recalled" class="sys">{{ m.text || '已撤回一条消息' }}</view>
|
<view v-if="m.recalled || m.kind === 'system'" class="sys">{{ m.text || '已撤回一条消息' }}</view>
|
||||||
<view
|
<view
|
||||||
v-else
|
v-else
|
||||||
class="row"
|
class="row"
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
<view v-if="selecting" class="ck" :class="{ on: isPicked(m) }" />
|
<view v-if="selecting" class="ck" :class="{ on: isPicked(m) }" />
|
||||||
<view v-if="!m.mine" class="av" @click.stop="openSender(m)">
|
<view v-if="!m.mine" class="av" @click.stop="openSender(m)">
|
||||||
<fy-file-mark v-if="isFiles" />
|
<fy-file-mark v-if="isFiles" />
|
||||||
<image v-else-if="avatarOf(m)" :src="avatarOf(m)" mode="aspectFill" />
|
<image v-else-if="avatarOf(m)" class="face" :src="avatarOf(m)" mode="aspectFill" />
|
||||||
<text v-else>{{ initialOf(m) }}</text>
|
<text v-else>{{ initialOf(m) }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="col" :class="{ mine: m.mine }">
|
<view class="col" :class="{ mine: m.mine }">
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
<text v-if="m.mine" class="tm" :class="{ read: readDone(m) }" @click="showRead(m)">{{ readLabel(m) }}</text>
|
<text v-if="m.mine" class="tm" :class="{ read: readDone(m) }" @click="showRead(m)">{{ readLabel(m) }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="m.mine" class="av self">
|
<view v-if="m.mine" class="av self">
|
||||||
<image v-if="myAvatar" :src="myAvatar" mode="aspectFill" />
|
<image v-if="myAvatar" class="face" :src="myAvatar" mode="aspectFill" />
|
||||||
<text v-else>{{ myInitial }}</text>
|
<text v-else>{{ myInitial }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -160,11 +160,11 @@
|
|||||||
<wd-icon name="folder" size="52rpx" color="#111827" />
|
<wd-icon name="folder" size="52rpx" color="#111827" />
|
||||||
<text>文件</text>
|
<text>文件</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="!isGroup" class="tile" @click="startCall(false)">
|
<view v-if="!isGroup && !isFiles" class="tile" @click="startCall(false)">
|
||||||
<wd-icon name="call" size="52rpx" color="#111827" />
|
<wd-icon name="call" size="52rpx" color="#111827" />
|
||||||
<text>语音通话</text>
|
<text>语音通话</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="!isGroup" class="tile" @click="startCall(true)">
|
<view v-if="!isGroup && !isFiles" class="tile" @click="startCall(true)">
|
||||||
<wd-icon name="video" size="52rpx" color="#111827" />
|
<wd-icon name="video" size="52rpx" color="#111827" />
|
||||||
<text>视频通话</text>
|
<text>视频通话</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -779,7 +779,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
openSender(m) {
|
openSender(m) {
|
||||||
if (m.fromId) uni.navigateTo({ url: '/pages/contact/detail?id=' + encodeURIComponent(m.fromId) })
|
const id = m && m.fromId
|
||||||
|
if (!id || !store.person(id)) return
|
||||||
|
uni.navigateTo({ url: '/pages/contact/detail?id=' + encodeURIComponent(id) })
|
||||||
},
|
},
|
||||||
goMore() {
|
goMore() {
|
||||||
if (this.isGroup) uni.navigateTo({ url: '/pages/chat/group?sid=' + encodeURIComponent(this.sid) })
|
if (this.isGroup) uni.navigateTo({ url: '/pages/chat/group?sid=' + encodeURIComponent(this.sid) })
|
||||||
@@ -836,8 +838,8 @@ export default {
|
|||||||
.col { max-width: 72%; display: flex; flex-direction: column; }
|
.col { max-width: 72%; display: flex; flex-direction: column; }
|
||||||
.col.mine { align-items: flex-end; }
|
.col.mine { align-items: flex-end; }
|
||||||
.av { width: 76rpx; height: 76rpx; border-radius: 16rpx; background: #E8F1FF; color: #1677FF; overflow: hidden; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 28rpx; font-weight: 650; }
|
.av { width: 76rpx; height: 76rpx; border-radius: 16rpx; background: #E8F1FF; color: #1677FF; overflow: hidden; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 28rpx; font-weight: 650; }
|
||||||
.av.self { background: #1677FF; color: #fff; }
|
.av.self { background: #E8F1FF; color: #1677FF; }
|
||||||
.av image { width: 76rpx; height: 76rpx; }
|
.face { width: 76rpx; height: 76rpx; display: block; }
|
||||||
.who { font-size: 22rpx; color: #9CA3AF; margin: 0 4rpx 8rpx; }
|
.who { font-size: 22rpx; color: #9CA3AF; margin: 0 4rpx 8rpx; }
|
||||||
.ball {
|
.ball {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
|||||||
+14
-12
@@ -8,7 +8,8 @@
|
|||||||
<view class="card">
|
<view class="card">
|
||||||
<view v-for="p in people" :key="p.id" class="item" @click="toggle(p.id)">
|
<view v-for="p in people" :key="p.id" class="item" @click="toggle(p.id)">
|
||||||
<view class="box" :class="{ on: picked.includes(p.id) }" />
|
<view class="box" :class="{ on: picked.includes(p.id) }" />
|
||||||
<view class="av">{{ p.name.slice(0, 1) }}</view>
|
<image v-if="p.avatar" class="av img" :src="p.avatar" mode="aspectFill" />
|
||||||
|
<view v-else class="av">{{ (p.name || '?').slice(0, 1) }}</view>
|
||||||
<view class="meta">
|
<view class="meta">
|
||||||
<text class="n">{{ p.name }}</text>
|
<text class="n">{{ p.name }}</text>
|
||||||
<text class="s">{{ p.title }} · {{ p.department }}</text>
|
<text class="s">{{ p.title }} · {{ p.department }}</text>
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="pad">
|
<view class="pad">
|
||||||
<view class="btn-primary" :class="{ off: !can }" @click="ok">创建并进入</view>
|
<wd-button type="primary" block size="large" :disabled="!can" @click="ok">创建并进入</wd-button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -65,17 +66,18 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.field { margin: 24rpx; padding: 8rpx 24rpx; }
|
.field { margin: 16rpx 24rpx; padding: 8rpx 24rpx; }
|
||||||
.field input { height: 80rpx; font-size: 30rpx; }
|
.field input { height: 88rpx; font-size: 30rpx; color: #111827; }
|
||||||
.hint { margin: 0 32rpx 12rpx; font-size: 22rpx; color: #6B7280; }
|
.hint { margin: 0 32rpx 12rpx; font-size: 24rpx; color: #6B7280; }
|
||||||
.card { margin: 0 24rpx 24rpx; overflow: hidden; }
|
.card { margin: 0 24rpx 24rpx; overflow: hidden; }
|
||||||
.item { display: flex; align-items: center; padding: 20rpx 24rpx; }
|
.item { display: flex; align-items: center; padding: 0 24rpx; }
|
||||||
.box { width: 36rpx; height: 36rpx; border: 2rpx solid #D1D5DB; border-radius: 8rpx; margin-right: 16rpx; }
|
.item + .item .meta { border-top: 1rpx solid #F0F2F5; }
|
||||||
|
.box { width: 36rpx; height: 36rpx; border: 2rpx solid #D1D5DB; border-radius: 8rpx; margin-right: 16rpx; flex-shrink: 0; }
|
||||||
.box.on { background: #1677FF; border-color: #1677FF; }
|
.box.on { background: #1677FF; border-color: #1677FF; }
|
||||||
.av { width: 72rpx; height: 72rpx; border-radius: 50%; background: #E8F1FF; color: #1677FF; display: flex; align-items: center; justify-content: center; font-weight: 600; }
|
.av { width: 72rpx; height: 72rpx; border-radius: 18rpx; background: #E8F1FF; color: #1677FF; display: flex; align-items: center; justify-content: center; font-weight: 600; margin: 16rpx 0; flex-shrink: 0; overflow: hidden; }
|
||||||
.meta { margin-left: 16rpx; }
|
.av.img { display: block; }
|
||||||
.n { display: block; font-size: 28rpx; font-weight: 600; }
|
.meta { margin-left: 16rpx; flex: 1; min-width: 0; padding: 18rpx 0; }
|
||||||
.s { font-size: 22rpx; color: #6B7280; }
|
.n { display: block; font-size: 28rpx; font-weight: 600; color: #111827; }
|
||||||
|
.s { display: block; margin-top: 4rpx; font-size: 22rpx; color: #6B7280; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
.pad { padding: 0 24rpx 40rpx; }
|
.pad { padding: 0 24rpx 40rpx; }
|
||||||
.off { opacity: 0.45; }
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+11
-4
@@ -3,7 +3,10 @@
|
|||||||
<fy-header title="群聊设置" back />
|
<fy-header title="群聊设置" back />
|
||||||
<view class="card head">
|
<view class="card head">
|
||||||
<view class="grid">
|
<view class="grid">
|
||||||
<view v-for="p in members.slice(0, 4)" :key="p.id" class="cell">{{ (p.name || '?').slice(0, 1) }}</view>
|
<view v-for="p in members.slice(0, 4)" :key="p.id" class="cell">
|
||||||
|
<image v-if="p.avatar" class="cell-img" :src="p.avatar" mode="aspectFill" />
|
||||||
|
<text v-else>{{ (p.name || '?').slice(0, 1) }}</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="meta">
|
<view class="meta">
|
||||||
<text class="gn">{{ title }}</text>
|
<text class="gn">{{ title }}</text>
|
||||||
@@ -17,7 +20,8 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="mems">
|
<view class="mems">
|
||||||
<view v-for="p in members" :key="p.id" class="mem" @click="open(p)">
|
<view v-for="p in members" :key="p.id" class="mem" @click="open(p)">
|
||||||
<view class="mav">{{ (p.name || '?').slice(0, 1) }}</view>
|
<image v-if="p.avatar" class="mav img" :src="p.avatar" mode="aspectFill" />
|
||||||
|
<view v-else class="mav">{{ (p.name || '?').slice(0, 1) }}</view>
|
||||||
<text class="mn">{{ p.name }}</text>
|
<text class="mn">{{ p.name }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -76,6 +80,7 @@ export default {
|
|||||||
this.members = ids.map((id) => store.person(id) || { id, name: id, title: '', department: '' })
|
this.members = ids.map((id) => store.person(id) || { id, name: id, title: '', department: '' })
|
||||||
},
|
},
|
||||||
open(p) {
|
open(p) {
|
||||||
|
if (!p || !store.person(p.id)) return
|
||||||
uni.navigateTo({ url: '/pages/contact/detail?id=' + encodeURIComponent(p.id) })
|
uni.navigateTo({ url: '/pages/contact/detail?id=' + encodeURIComponent(p.id) })
|
||||||
},
|
},
|
||||||
toggle(field) {
|
toggle(field) {
|
||||||
@@ -106,7 +111,8 @@ export default {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.head { margin: 24rpx; padding: 28rpx; display: flex; align-items: center; }
|
.head { margin: 24rpx; padding: 28rpx; display: flex; align-items: center; }
|
||||||
.grid { width: 96rpx; height: 96rpx; background: #E8F1FF; border-radius: 22rpx; display: flex; flex-wrap: wrap; padding: 8rpx; box-sizing: border-box; }
|
.grid { width: 96rpx; height: 96rpx; background: #E8F1FF; border-radius: 22rpx; display: flex; flex-wrap: wrap; padding: 8rpx; box-sizing: border-box; }
|
||||||
.cell { width: 36rpx; height: 36rpx; margin: 2rpx; background: #fff; color: #1677FF; font-size: 18rpx; font-weight: 700; display: flex; align-items: center; justify-content: center; border-radius: 8rpx; }
|
.cell { width: 36rpx; height: 36rpx; margin: 2rpx; background: #fff; color: #1677FF; font-size: 18rpx; font-weight: 700; display: flex; align-items: center; justify-content: center; border-radius: 8rpx; overflow: hidden; }
|
||||||
|
.cell-img { width: 36rpx; height: 36rpx; display: block; }
|
||||||
.meta { margin-left: 20rpx; }
|
.meta { margin-left: 20rpx; }
|
||||||
.gn { font-size: 32rpx; font-weight: 700; display: block; }
|
.gn { font-size: 32rpx; font-weight: 700; display: block; }
|
||||||
.gc { font-size: 22rpx; color: #6B7280; }
|
.gc { font-size: 22rpx; color: #6B7280; }
|
||||||
@@ -114,7 +120,8 @@ export default {
|
|||||||
.cap { padding: 20rpx 24rpx 8rpx; font-size: 26rpx; font-weight: 700; }
|
.cap { padding: 20rpx 24rpx 8rpx; font-size: 26rpx; font-weight: 700; }
|
||||||
.mems { display: flex; flex-wrap: wrap; padding: 8rpx 16rpx 24rpx; }
|
.mems { display: flex; flex-wrap: wrap; padding: 8rpx 16rpx 24rpx; }
|
||||||
.mem { width: 20%; display: flex; flex-direction: column; align-items: center; margin-bottom: 16rpx; }
|
.mem { width: 20%; display: flex; flex-direction: column; align-items: center; margin-bottom: 16rpx; }
|
||||||
.mav { width: 80rpx; height: 80rpx; border-radius: 18rpx; background: #E8F1FF; color: #1677FF; display: flex; align-items: center; justify-content: center; font-weight: 700; }
|
.mav { width: 80rpx; height: 80rpx; border-radius: 18rpx; background: #E8F1FF; color: #1677FF; display: flex; align-items: center; justify-content: center; font-weight: 700; overflow: hidden; }
|
||||||
|
.mav.img { display: block; }
|
||||||
.mn { font-size: 20rpx; margin-top: 8rpx; }
|
.mn { font-size: 20rpx; margin-top: 8rpx; }
|
||||||
.row { display: flex; justify-content: space-between; align-items: center; padding: 28rpx 24rpx; font-size: 28rpx; border-bottom: 1rpx solid #F0F2F5; }
|
.row { display: flex; justify-content: space-between; align-items: center; padding: 28rpx 24rpx; font-size: 28rpx; border-bottom: 1rpx solid #F0F2F5; }
|
||||||
.row:last-child { border-bottom: none; }
|
.row:last-child { border-bottom: none; }
|
||||||
|
|||||||
+24
-26
@@ -1,36 +1,33 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="page">
|
<view class="page edge">
|
||||||
<fy-header title="工作通知" back />
|
<fy-header title="工作通知" back />
|
||||||
|
<fy-scroll class="edge-scroll" @refresh="load">
|
||||||
<view class="bar">
|
<view class="bar">
|
||||||
<text>审批 · 协同 · 投标 · 用章 · 公告</text>
|
<text>审批 · 协同 · 投标 · 用章 · 公告</text>
|
||||||
<text class="badge">{{ items.length }} 项待办</text>
|
<text class="count">{{ items.length }} 项待办</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="!items.length" class="empty">暂无待办通知</view>
|
<wd-status-tip v-if="!items.length" image="content" tip="暂无待办通知" />
|
||||||
<view v-for="t in items" :key="t.no || t.id" class="card item" @click="open(t)">
|
<view v-for="t in items" :key="t.no || t.id" class="card item" @click="open(t)">
|
||||||
<view class="stripe" />
|
<view class="main">
|
||||||
<view class="body">
|
<text class="type">{{ t.typeLabel || t.tag || t.type || '待办' }}</text>
|
||||||
<view class="top">
|
|
||||||
<text class="kind">{{ t.typeLabel || t.tag || t.type || '待办' }}</text>
|
|
||||||
<text class="time">{{ t.createdAt || t.time || '' }}</text>
|
|
||||||
</view>
|
|
||||||
<text class="title">{{ t.title || t.summary || t.no }}</text>
|
<text class="title">{{ t.title || t.summary || t.no }}</text>
|
||||||
<text class="sub">{{ t.submitter || t.applicant || '' }}{{ t.dept ? ' · ' + t.dept : '' }}</text>
|
<text class="sub">{{ metaOf(t) }}</text>
|
||||||
<view class="acts">
|
|
||||||
<text class="go">去办理</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
<text class="arr">›</text>
|
||||||
</view>
|
</view>
|
||||||
|
</fy-scroll>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import FyHeader from '../../components/fy-header.vue'
|
import FyHeader from '../../components/fy-header.vue'
|
||||||
|
import FyScroll from '../../components/fy-scroll.vue'
|
||||||
import { store } from '../../utils/store.js'
|
import { store } from '../../utils/store.js'
|
||||||
import { asList, fmtTime } from '../../utils/format.js'
|
import { asList, fmtTime } from '../../utils/format.js'
|
||||||
import { oaGet } from '../../utils/api.js'
|
import { oaGet } from '../../utils/api.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { FyHeader },
|
components: { FyHeader, FyScroll },
|
||||||
data() {
|
data() {
|
||||||
return { items: [] }
|
return { items: [] }
|
||||||
},
|
},
|
||||||
@@ -51,6 +48,11 @@ export default {
|
|||||||
const conv = store.conversations.find((c) => c.type === 'work')
|
const conv = store.conversations.find((c) => c.type === 'work')
|
||||||
if (conv) conv.unread = 0
|
if (conv) conv.unread = 0
|
||||||
},
|
},
|
||||||
|
metaOf(t) {
|
||||||
|
const who = [t.submitter || t.applicant, t.dept].filter(Boolean).join(' · ')
|
||||||
|
const time = t.createdAt || t.time || ''
|
||||||
|
return [who, time].filter(Boolean).join(' · ')
|
||||||
|
},
|
||||||
open(t) {
|
open(t) {
|
||||||
const no = t.no || t.id || ''
|
const no = t.no || t.id || ''
|
||||||
if (String(t.source || t.type || '') === 'work' || t.taskId) {
|
if (String(t.source || t.type || '') === 'work' || t.taskId) {
|
||||||
@@ -64,16 +66,12 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.bar { margin: 16rpx 24rpx; background: #F3F6FB; border-radius: 16rpx; padding: 16rpx 20rpx; display: flex; justify-content: space-between; font-size: 22rpx; color: #4B5563; }
|
.bar { margin: 16rpx 24rpx 8rpx; padding: 0 8rpx; display: flex; justify-content: space-between; align-items: center; font-size: 22rpx; color: #6B7280; }
|
||||||
.item { margin: 0 24rpx 20rpx; display: flex; overflow: hidden; }
|
.count { color: #1677FF; font-weight: 600; }
|
||||||
.stripe { width: 8rpx; background: #1677FF; }
|
.item { margin: 16rpx 24rpx; padding: 24rpx; display: flex; align-items: center; }
|
||||||
.body { flex: 1; padding: 24rpx; }
|
.main { flex: 1; min-width: 0; }
|
||||||
.top { display: flex; justify-content: space-between; }
|
.type { display: inline-block; font-size: 22rpx; color: #1677FF; background: #E8F1FF; border-radius: 8rpx; padding: 2rpx 10rpx; }
|
||||||
.kind { font-size: 28rpx; font-weight: 700; }
|
.title { display: block; margin-top: 10rpx; font-size: 28rpx; font-weight: 650; color: #111827; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
.time { font-size: 22rpx; color: #6B7280; }
|
.sub { display: block; margin-top: 6rpx; font-size: 24rpx; color: #6B7280; }
|
||||||
.title { display: block; font-size: 28rpx; font-weight: 600; margin: 12rpx 0 6rpx; }
|
.arr { color: #C4C9D4; font-size: 32rpx; margin-left: 8rpx; }
|
||||||
.sub { font-size: 22rpx; color: #6B7280; }
|
|
||||||
.acts { margin-top: 16rpx; }
|
|
||||||
.go { color: #1677FF; font-size: 24rpx; font-weight: 600; }
|
|
||||||
.empty { text-align: center; color: #6B7280; padding: 80rpx; }
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+15
-13
@@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="page">
|
<view class="page">
|
||||||
<fy-header title="同事资料" back />
|
<fy-header title="同事资料" back />
|
||||||
<view v-if="p" class="body">
|
<wd-status-tip v-if="!p" image="content" tip="未找到这位同事" />
|
||||||
|
<view v-else class="body">
|
||||||
<view class="card profile">
|
<view class="card profile">
|
||||||
<image v-if="p.avatar" class="av img" :src="p.avatar" mode="aspectFill" />
|
<image v-if="p.avatar" class="av img" :src="p.avatar" mode="aspectFill" />
|
||||||
<view v-else class="av">{{ p.name.slice(0, 1) }}</view>
|
<view v-else class="av">{{ p.name.slice(0, 1) }}</view>
|
||||||
@@ -108,22 +109,23 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.body { padding: 24rpx; }
|
.body { padding: 16rpx 24rpx 48rpx; }
|
||||||
.profile { padding: 28rpx; display: flex; align-items: center; }
|
.profile { padding: 32rpx 24rpx; display: flex; align-items: center; }
|
||||||
.av { width: 128rpx; height: 128rpx; border-radius: 20rpx; background: #1677FF; color: #fff; display: flex; align-items: center; justify-content: center; font-size: 48rpx; font-weight: 700; }
|
.av { width: 112rpx; height: 112rpx; border-radius: 24rpx; background: #E8F1FF; color: #1677FF; display: flex; align-items: center; justify-content: center; font-size: 44rpx; font-weight: 650; }
|
||||||
.av.img { border-radius: 20rpx; }
|
.av.img { border-radius: 24rpx; }
|
||||||
.info { margin-left: 24rpx; flex: 1; min-width: 0; }
|
.info { margin-left: 20rpx; flex: 1; min-width: 0; }
|
||||||
.nm { display: flex; align-items: center; gap: 12rpx; }
|
.nm { display: flex; align-items: center; gap: 12rpx; }
|
||||||
.n { font-size: 36rpx; font-weight: 700; }
|
.n { font-size: 36rpx; font-weight: 650; color: #111827; }
|
||||||
.tag { font-size: 20rpx; background: #E8F1FF; color: #4B5563; padding: 2rpx 10rpx; border-radius: 8rpx; }
|
.tag { font-size: 22rpx; background: #E8F8EF; color: #16A34A; padding: 2rpx 10rpx; border-radius: 8rpx; }
|
||||||
.s, .ph { display: block; font-size: 24rpx; color: #6B7280; margin-top: 8rpx; }
|
.s, .ph { display: block; font-size: 24rpx; color: #6B7280; margin-top: 6rpx; }
|
||||||
.copy { color: #1677FF; margin-left: 12rpx; }
|
.copy { color: #1677FF; margin-left: 12rpx; }
|
||||||
.acts { display: flex; gap: 16rpx; margin: 20rpx 0; }
|
.acts { display: flex; gap: 16rpx; margin: 20rpx 0; }
|
||||||
.act { flex: 1; background: #fff; border-radius: 16rpx; padding: 20rpx 8rpx; display: flex; flex-direction: column; align-items: center; font-size: 22rpx; font-weight: 600; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.03); }
|
.act { flex: 1; background: #fff; border-radius: 20rpx; border: 1rpx solid rgba(17,24,39,0.06); padding: 20rpx 8rpx; display: flex; flex-direction: column; align-items: center; font-size: 22rpx; font-weight: 600; color: #111827; }
|
||||||
.ai { font-size: 36rpx; color: #1677FF; margin-bottom: 8rpx; }
|
.ai { font-size: 36rpx; color: #1677FF; margin-bottom: 8rpx; }
|
||||||
.rows { overflow: hidden; margin-bottom: 20rpx; }
|
.rows { overflow: hidden; margin-bottom: 20rpx; }
|
||||||
.row { display: flex; justify-content: space-between; padding: 28rpx 24rpx; font-size: 28rpx; border-bottom: 1rpx solid #E8ECF2; }
|
.row { display: flex; justify-content: space-between; align-items: center; padding: 28rpx 24rpx; font-size: 28rpx; }
|
||||||
.k { color: #4B5563; }
|
.row + .row { border-top: 1rpx solid #F0F2F5; }
|
||||||
|
.k { color: #6B7280; }
|
||||||
.v { color: #111827; }
|
.v { color: #111827; }
|
||||||
.brand { color: #1677FF; }
|
.brand { color: #1677FF; font-weight: 600; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+10
-7
@@ -16,7 +16,9 @@
|
|||||||
<text class="lab">说明</text>
|
<text class="lab">说明</text>
|
||||||
<view class="box tall"><textarea v-model="content" /></view>
|
<view class="box tall"><textarea v-model="content" /></view>
|
||||||
</view>
|
</view>
|
||||||
<view class="btn-primary" @click="ok">创建</view>
|
<view class="submit">
|
||||||
|
<wd-button type="primary" block size="large" @click="ok">创建</wd-button>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -65,10 +67,11 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.body { padding: 24rpx; }
|
.body { padding: 16rpx 24rpx 48rpx; }
|
||||||
.field { margin-bottom: 20rpx; }
|
.field { margin-bottom: 24rpx; }
|
||||||
.lab { font-size: 24rpx; color: #4B5563; display: block; margin-bottom: 8rpx; }
|
.lab { font-size: 26rpx; color: #6B7280; display: block; margin-bottom: 10rpx; }
|
||||||
.box { min-height: 88rpx; background: #fff; border-radius: 16rpx; padding: 20rpx 24rpx; }
|
.box { min-height: 92rpx; background: #fff; border-radius: 16rpx; padding: 22rpx 24rpx; border: 1rpx solid rgba(17,24,39,0.06); }
|
||||||
.box.tall { min-height: 160rpx; }
|
.box.tall { min-height: 180rpx; }
|
||||||
.box input, .box textarea { width: 100%; }
|
.box input, .box textarea { width: 100%; font-size: 30rpx; color: #111827; }
|
||||||
|
.submit { margin-top: 8rpx; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+22
-7
@@ -3,10 +3,20 @@
|
|||||||
<fy-header title="协同详情" back />
|
<fy-header title="协同详情" back />
|
||||||
<view v-if="row" class="body">
|
<view v-if="row" class="body">
|
||||||
<view class="card">
|
<view class="card">
|
||||||
|
<view class="top">
|
||||||
|
<text class="type">协同</text>
|
||||||
|
<wd-tag :type="row.status === 'done' ? 'success' : 'primary'" plain>{{ row.status === 'done' ? '已办结' : '待办理' }}</wd-tag>
|
||||||
|
</view>
|
||||||
<text class="t">{{ row.title }}</text>
|
<text class="t">{{ row.title }}</text>
|
||||||
<text class="s">发起 {{ row.assignerName || '' }} · 办理 {{ row.assigneeName || '' }}</text>
|
<view class="kv">
|
||||||
<text class="s">{{ row.content || row.remark || '' }}</text>
|
<text class="k">发起</text>
|
||||||
<text class="chip">{{ row.status === 'done' ? '已办结' : '待办理' }}</text>
|
<text class="v">{{ row.assignerName || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="kv">
|
||||||
|
<text class="k">办理</text>
|
||||||
|
<text class="v">{{ row.assigneeName || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
<text v-if="row.content || row.remark" class="c">{{ row.content || row.remark }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="canDone" class="btn-primary" @click="done">标记已办理</view>
|
<view v-if="canDone" class="btn-primary" @click="done">标记已办理</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -55,9 +65,14 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.body { padding: 24rpx; }
|
.body { padding: 16rpx 24rpx 48rpx; }
|
||||||
.card { padding: 28rpx; margin-bottom: 24rpx; }
|
.card { padding: 28rpx; margin-bottom: 24rpx; }
|
||||||
.t { display: block; font-size: 34rpx; font-weight: 700; }
|
.top { display: flex; align-items: center; justify-content: space-between; }
|
||||||
.s { display: block; font-size: 24rpx; color: #6B7280; margin-top: 8rpx; }
|
.type { font-size: 22rpx; color: #1677FF; background: #E8F1FF; border-radius: 8rpx; padding: 2rpx 10rpx; }
|
||||||
.chip { display: inline-block; margin-top: 16rpx; }
|
.t { display: block; margin-top: 16rpx; font-size: 34rpx; font-weight: 650; color: #111827; line-height: 1.4; }
|
||||||
|
.kv { display: flex; justify-content: space-between; padding: 20rpx 0; }
|
||||||
|
.kv + .kv { border-top: 1rpx solid #F0F2F5; }
|
||||||
|
.k { color: #6B7280; font-size: 26rpx; }
|
||||||
|
.v { font-size: 26rpx; color: #111827; }
|
||||||
|
.c { display: block; margin-top: 8rpx; padding-top: 16rpx; border-top: 1rpx solid #F0F2F5; font-size: 28rpx; color: #374151; line-height: 1.65; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+21
-15
@@ -7,20 +7,24 @@
|
|||||||
</fy-header>
|
</fy-header>
|
||||||
<fy-scroll class="edge-scroll" @refresh="load">
|
<fy-scroll class="edge-scroll" @refresh="load">
|
||||||
<view class="sec">待我办理</view>
|
<view class="sec">待我办理</view>
|
||||||
<view v-if="!pending.length" class="empty">暂无</view>
|
<wd-status-tip v-if="!pending.length" image="content" tip="暂无待办" />
|
||||||
<view v-for="t in pending" :key="t.id" class="card item" @click="open(t)">
|
<view v-for="t in pending" :key="t.id" class="card item" @click="open(t)">
|
||||||
<view class="stripe" />
|
<view class="main">
|
||||||
<view class="body">
|
<text class="type">待办理</text>
|
||||||
<text class="t">{{ t.title }}</text>
|
<text class="t">{{ t.title }}</text>
|
||||||
<text class="s">{{ t.assignerName || '' }} · {{ t.dueAt || '' }}</text>
|
<text class="s">{{ lineOf(t.assignerName, t.dueAt) }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
<text class="arr">›</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="sec">我发起的</view>
|
<view class="sec">我发起的</view>
|
||||||
|
<wd-status-tip v-if="!mine.length" image="content" tip="暂无发起" />
|
||||||
<view v-for="t in mine" :key="'m' + t.id" class="card item" @click="open(t)">
|
<view v-for="t in mine" :key="'m' + t.id" class="card item" @click="open(t)">
|
||||||
<view class="body wide">
|
<view class="main">
|
||||||
|
<text class="type">协同</text>
|
||||||
<text class="t">{{ t.title }}</text>
|
<text class="t">{{ t.title }}</text>
|
||||||
<text class="s">{{ t.assigneeName || '' }} · {{ t.status || '' }}</text>
|
<text class="s">{{ lineOf(t.assigneeName, t.status) }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
<text class="arr">›</text>
|
||||||
</view>
|
</view>
|
||||||
</fy-scroll>
|
</fy-scroll>
|
||||||
</view>
|
</view>
|
||||||
@@ -49,6 +53,9 @@ export default {
|
|||||||
this.mine = asList(await oaGet('/work-tasks?tab=review', store.token))
|
this.mine = asList(await oaGet('/work-tasks?tab=review', store.token))
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
},
|
},
|
||||||
|
lineOf(a, b) {
|
||||||
|
return [a, b].filter(Boolean).join(' · ')
|
||||||
|
},
|
||||||
open(t) {
|
open(t) {
|
||||||
uni.navigateTo({ url: '/pages/coop/detail?id=' + encodeURIComponent(t.id) })
|
uni.navigateTo({ url: '/pages/coop/detail?id=' + encodeURIComponent(t.id) })
|
||||||
},
|
},
|
||||||
@@ -60,13 +67,12 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.sec { padding: 24rpx 24rpx 8rpx; font-size: 28rpx; font-weight: 700; }
|
.sec { padding: 24rpx 32rpx 4rpx; font-size: 26rpx; font-weight: 650; color: #6B7280; }
|
||||||
.item { margin: 12rpx 24rpx; display: flex; overflow: hidden; }
|
.item { margin: 16rpx 24rpx; padding: 24rpx; display: flex; align-items: center; }
|
||||||
.stripe { width: 8rpx; background: #1677FF; }
|
.main { flex: 1; min-width: 0; }
|
||||||
.body { flex: 1; padding: 24rpx; }
|
.type { display: inline-block; font-size: 22rpx; color: #1677FF; background: #E8F1FF; border-radius: 8rpx; padding: 2rpx 10rpx; }
|
||||||
.body.wide { padding: 24rpx; }
|
.t { display: block; margin-top: 10rpx; font-size: 28rpx; font-weight: 650; color: #111827; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
.t { display: block; font-size: 28rpx; font-weight: 600; }
|
.s { display: block; margin-top: 6rpx; font-size: 24rpx; color: #6B7280; }
|
||||||
.s { font-size: 22rpx; color: #6B7280; }
|
.arr { color: #C4C9D4; font-size: 32rpx; margin-left: 8rpx; }
|
||||||
.empty { padding: 24rpx; text-align: center; color: #6B7280; }
|
.add { color: #1677FF; font-size: 26rpx; font-weight: 650; padding: 8rpx; }
|
||||||
.add { color: #1677FF; font-size: 26rpx; font-weight: 700; padding: 8rpx; }
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+28
-18
@@ -1,23 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="page">
|
<view class="page edge">
|
||||||
<fy-header :title="title" back />
|
<fy-header :title="title" back />
|
||||||
<view v-if="!allowed" class="deny">当前账号没有「{{ title }}」查看权限</view>
|
<view v-if="!allowed" class="deny">当前账号没有「{{ title }}」查看权限</view>
|
||||||
<view v-else>
|
<fy-scroll v-else class="edge-scroll" @refresh="load">
|
||||||
<scroll-view scroll-x class="tabs">
|
<wd-tabs v-if="tabs.length" v-model="tab" slidable="always" @change="onTab">
|
||||||
<text v-for="t in tabs" :key="t.id" class="tab" :class="{ on: tab === t.id }" @click="switchTab(t)">{{ t.title }}</text>
|
<wd-tab v-for="t in tabs" :key="t.id" :title="t.title" :name="t.id" />
|
||||||
</scroll-view>
|
</wd-tabs>
|
||||||
<view v-if="loading" class="empty">加载中…</view>
|
<view v-if="loading" class="empty">
|
||||||
<view v-else-if="!rows.length" class="empty">暂无数据</view>
|
<wd-skeleton theme="paragraph" />
|
||||||
<view v-for="r in rows" :key="r.id || r.no || r.code" class="card item">
|
|
||||||
<text class="t">{{ titleOf(r) }}</text>
|
|
||||||
<text class="s">{{ subOf(r) }}</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
<wd-status-tip v-else-if="!rows.length" image="content" tip="暂无数据" />
|
||||||
|
<view v-for="r in rows" :key="r.id || r.no || r.code" class="card item">
|
||||||
|
<view class="main">
|
||||||
|
<text class="t">{{ titleOf(r) }}</text>
|
||||||
|
<text v-if="subOf(r)" class="s">{{ subOf(r) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</fy-scroll>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import FyHeader from '../../components/fy-header.vue'
|
import FyHeader from '../../components/fy-header.vue'
|
||||||
|
import FyScroll from '../../components/fy-scroll.vue'
|
||||||
import { store } from '../../utils/store.js'
|
import { store } from '../../utils/store.js'
|
||||||
import { ERP_MODULES, ERP_TABS } from '../../utils/catalog.js'
|
import { ERP_MODULES, ERP_TABS } from '../../utils/catalog.js'
|
||||||
import { asList, firstText } from '../../utils/format.js'
|
import { asList, firstText } from '../../utils/format.js'
|
||||||
@@ -25,7 +30,7 @@ import { oaGet } from '../../utils/api.js'
|
|||||||
import { canMenu } from '../../utils/perms.js'
|
import { canMenu } from '../../utils/perms.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { FyHeader },
|
components: { FyHeader, FyScroll },
|
||||||
data() {
|
data() {
|
||||||
return { id: '', tab: '', rows: [], loading: false }
|
return { id: '', tab: '', rows: [], loading: false }
|
||||||
},
|
},
|
||||||
@@ -45,6 +50,12 @@ export default {
|
|||||||
if (this.allowed) this.load()
|
if (this.allowed) this.load()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onTab(e) {
|
||||||
|
const id = (e && e.name) || this.tab
|
||||||
|
if (id === this.tab && this.rows.length) return
|
||||||
|
this.tab = id
|
||||||
|
this.load()
|
||||||
|
},
|
||||||
switchTab(t) {
|
switchTab(t) {
|
||||||
this.tab = t.id
|
this.tab = t.id
|
||||||
this.load()
|
this.load()
|
||||||
@@ -110,11 +121,10 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.tabs { white-space: nowrap; padding: 16rpx 16rpx 0; }
|
|
||||||
.tab { display: inline-block; padding: 12rpx 20rpx; font-size: 24rpx; color: #6B7280; }
|
|
||||||
.tab.on { color: #1677FF; font-weight: 700; border-bottom: 4rpx solid #1677FF; }
|
|
||||||
.item { margin: 16rpx 24rpx; padding: 24rpx; }
|
.item { margin: 16rpx 24rpx; padding: 24rpx; }
|
||||||
.t { display: block; font-weight: 600; font-size: 28rpx; }
|
.main { min-width: 0; }
|
||||||
.s { font-size: 22rpx; color: #6B7280; }
|
.t { display: block; font-weight: 650; font-size: 28rpx; color: #111827; }
|
||||||
.empty, .deny { padding: 80rpx 40rpx; text-align: center; color: #6B7280; }
|
.s { display: block; margin-top: 8rpx; font-size: 24rpx; color: #6B7280; }
|
||||||
|
.empty { padding: 40rpx 28rpx; }
|
||||||
|
.deny { padding: 120rpx 40rpx; text-align: center; color: #6B7280; font-size: 28rpx; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -4,8 +4,11 @@
|
|||||||
<fy-scroll class="edge-scroll" @refresh="load">
|
<fy-scroll class="edge-scroll" @refresh="load">
|
||||||
<wd-status-tip v-if="!rows.length" image="content" tip="暂无公告" />
|
<wd-status-tip v-if="!rows.length" image="content" tip="暂无公告" />
|
||||||
<view v-for="r in rows" :key="r.id" class="card item">
|
<view v-for="r in rows" :key="r.id" class="card item">
|
||||||
|
<view class="top">
|
||||||
|
<text class="chip">公告</text>
|
||||||
|
<text class="s">{{ r.createdAt || r.publishAt || '' }}</text>
|
||||||
|
</view>
|
||||||
<text class="t">{{ r.title }}</text>
|
<text class="t">{{ r.title }}</text>
|
||||||
<text class="s">{{ r.createdAt || r.publishAt || '' }}</text>
|
|
||||||
<text class="c">{{ r.content || r.summary || '' }}</text>
|
<text class="c">{{ r.content || r.summary || '' }}</text>
|
||||||
</view>
|
</view>
|
||||||
</fy-scroll>
|
</fy-scroll>
|
||||||
@@ -32,9 +35,9 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.item { margin: 16rpx 28rpx; padding: 28rpx; }
|
.item { margin: 16rpx 24rpx; padding: 28rpx 24rpx; }
|
||||||
.t { display: block; font-weight: 650; font-size: 30rpx; line-height: 1.45; }
|
.top { display: flex; align-items: center; justify-content: space-between; }
|
||||||
.s { display: block; font-size: 24rpx; color: #6B7280; margin-top: 10rpx; }
|
.s { font-size: 22rpx; color: #9CA3AF; }
|
||||||
.c { display: block; font-size: 28rpx; margin-top: 16rpx; color: #374151; line-height: 1.65; }
|
.t { display: block; margin-top: 16rpx; font-weight: 650; font-size: 30rpx; line-height: 1.45; color: #111827; }
|
||||||
.empty { padding: 120rpx 40rpx; text-align: center; color: #6B7280; font-size: 28rpx; }
|
.c { display: block; font-size: 26rpx; margin-top: 12rpx; color: #4B5563; line-height: 1.65; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+78
-21
@@ -2,16 +2,31 @@
|
|||||||
<view class="page">
|
<view class="page">
|
||||||
<fy-header title="修改密码" back />
|
<fy-header title="修改密码" back />
|
||||||
<view class="body">
|
<view class="body">
|
||||||
<text class="hint">首次登录或管理员要求改密,请设置新密码后继续。</text>
|
<view class="hero">
|
||||||
<view class="field">
|
<view class="mark">
|
||||||
<text class="lab">新密码</text>
|
<wd-icon name="lock-on" size="44rpx" color="#1677FF" />
|
||||||
<view class="box"><input v-model="next" password placeholder="至少 6 位" /></view>
|
</view>
|
||||||
|
<text class="title">设置新密码</text>
|
||||||
|
<text class="hint">首次登录或管理员要求改密时,请设置新密码后继续使用。</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="field">
|
<view class="card group">
|
||||||
<text class="lab">确认新密码</text>
|
<view class="field">
|
||||||
<view class="box"><input v-model="confirm" password placeholder="再输入一次" /></view>
|
<text class="lab">新密码</text>
|
||||||
|
<view class="box">
|
||||||
|
<input v-model="next" :password="!showNext" placeholder="至少 6 位" placeholder-class="ph" />
|
||||||
|
<text class="eye" @click="showNext = !showNext">{{ showNext ? '隐藏' : '显示' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="field">
|
||||||
|
<text class="lab">确认新密码</text>
|
||||||
|
<view class="box" :class="{ bad: mismatch }">
|
||||||
|
<input v-model="confirm" :password="!showConfirm" placeholder="再输入一次" placeholder-class="ph" />
|
||||||
|
<text class="eye" @click="showConfirm = !showConfirm">{{ showConfirm ? '隐藏' : '显示' }}</text>
|
||||||
|
</view>
|
||||||
|
<text v-if="mismatch" class="tip">两次输入不一致</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="btn-primary" @click="ok">确认修改</view>
|
<wd-button type="primary" block size="large" @click="ok">确认修改</wd-button>
|
||||||
<text v-if="error" class="err">{{ error }}</text>
|
<text v-if="error" class="err">{{ error }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -24,38 +39,80 @@ import { toast } from '../../utils/format.js'
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { FyHeader },
|
components: { FyHeader },
|
||||||
|
computed: {
|
||||||
|
mismatch() {
|
||||||
|
return this.confirm.length > 0 && this.next !== this.confirm
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return { next: '', confirm: '', error: '', off: null, left: false }
|
return { next: '', confirm: '', error: '', off: null, left: false, waiting: false, showNext: false, showConfirm: false }
|
||||||
},
|
},
|
||||||
onShow() {
|
onShow() {
|
||||||
if (this.off) this.off()
|
if (this.off) this.off()
|
||||||
this.off = store.on(() => {
|
this.off = store.on(() => {
|
||||||
this.error = store.error
|
this.error = store.error
|
||||||
if (this.left || store.phase !== 'app') return
|
if (this.left || !this.waiting || store.phase !== 'app') return
|
||||||
this.left = true
|
this.leave()
|
||||||
uni.switchTab({ url: '/pages/tabs/messages' })
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onHide() {
|
onHide() {
|
||||||
if (this.off) this.off()
|
this.drop()
|
||||||
this.off = null
|
},
|
||||||
|
onUnload() {
|
||||||
|
this.drop()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
drop() {
|
||||||
|
if (this.off) this.off()
|
||||||
|
this.off = null
|
||||||
|
},
|
||||||
|
leave() {
|
||||||
|
this.left = true
|
||||||
|
this.waiting = false
|
||||||
|
uni.switchTab({ url: '/pages/tabs/messages' })
|
||||||
|
},
|
||||||
async ok() {
|
async ok() {
|
||||||
if (!this.next || this.next.length < 6) return toast('密码至少 6 位')
|
if (!this.next || this.next.length < 6) return toast('密码至少 6 位')
|
||||||
if (this.next !== this.confirm) return toast('两次密码不一致')
|
if (this.next !== this.confirm) return toast('两次密码不一致')
|
||||||
|
this.waiting = true
|
||||||
await store.changePassword(this.next, this.confirm)
|
await store.changePassword(this.next, this.confirm)
|
||||||
|
if (store.error) this.waiting = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.body { padding: 40rpx; }
|
.body { padding: 32rpx 32rpx 48rpx; }
|
||||||
.hint { font-size: 26rpx; color: #6B7280; display: block; margin-bottom: 32rpx; }
|
.hero { display: flex; flex-direction: column; align-items: center; margin: 16rpx 0 36rpx; }
|
||||||
.field { margin-bottom: 24rpx; }
|
.mark {
|
||||||
.lab { font-size: 24rpx; color: #4B5563; display: block; margin-bottom: 8rpx; }
|
width: 96rpx;
|
||||||
.box { height: 96rpx; background: #fff; border-radius: 16rpx; padding: 0 24rpx; display: flex; align-items: center; }
|
height: 96rpx;
|
||||||
.box input { flex: 1; }
|
border-radius: 28rpx;
|
||||||
.err { color: #DC2626; font-size: 24rpx; margin-top: 16rpx; display: block; }
|
background: #E8F1FF;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
.title { font-size: 36rpx; font-weight: 650; color: #111827; }
|
||||||
|
.hint { margin-top: 10rpx; font-size: 24rpx; color: #6B7280; line-height: 1.55; text-align: center; }
|
||||||
|
.group { padding: 8rpx 28rpx 28rpx; margin-bottom: 32rpx; }
|
||||||
|
.field { margin-top: 24rpx; }
|
||||||
|
.lab { font-size: 26rpx; color: #374151; display: block; margin-bottom: 12rpx; }
|
||||||
|
.box {
|
||||||
|
height: 96rpx;
|
||||||
|
background: #F5F7FA;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 1rpx solid transparent;
|
||||||
|
}
|
||||||
|
.box.bad { border-color: #FECACA; background: #FEF2F2; }
|
||||||
|
.box input { flex: 1; font-size: 30rpx; color: #111827; }
|
||||||
|
.ph { color: #9CA3AF; }
|
||||||
|
.eye { font-size: 26rpx; color: #1677FF; padding-left: 16rpx; }
|
||||||
|
.tip { display: block; margin-top: 10rpx; font-size: 22rpx; color: #DC2626; }
|
||||||
|
.err { color: #DC2626; font-size: 26rpx; margin-top: 20rpx; display: block; text-align: center; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+16
-4
@@ -2,8 +2,12 @@
|
|||||||
<view class="page">
|
<view class="page">
|
||||||
<fy-header title="附件预览" back />
|
<fy-header title="附件预览" back />
|
||||||
<view class="body">
|
<view class="body">
|
||||||
<text class="name">{{ name || '附件' }}</text>
|
<view class="card sheet">
|
||||||
<view class="btn-primary" @click="open">打开 / 预览</view>
|
<view class="mark">文</view>
|
||||||
|
<text class="name">{{ name || '附件' }}</text>
|
||||||
|
<text class="hint">系统会用本机应用打开这个附件</text>
|
||||||
|
</view>
|
||||||
|
<wd-button type="primary" block size="large" @click="open">打开 / 预览</wd-button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -31,6 +35,14 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.body { padding: 48rpx 32rpx; }
|
.body { padding: 32rpx 24rpx; }
|
||||||
.name { display: block; font-size: 28rpx; margin-bottom: 32rpx; text-align: center; }
|
.sheet { padding: 48rpx 32rpx; margin-bottom: 32rpx; display: flex; flex-direction: column; align-items: center; }
|
||||||
|
.mark {
|
||||||
|
width: 112rpx; height: 112rpx; border-radius: 28rpx;
|
||||||
|
background: #E8F1FF; color: #1677FF;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
font-size: 44rpx; font-weight: 650;
|
||||||
|
}
|
||||||
|
.name { display: block; margin-top: 24rpx; font-size: 30rpx; font-weight: 650; color: #111827; text-align: center; word-break: break-all; }
|
||||||
|
.hint { display: block; margin-top: 12rpx; font-size: 24rpx; color: #6B7280; text-align: center; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+24
-15
@@ -1,32 +1,38 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="page">
|
<view class="page edge">
|
||||||
<fy-header title="工作汇报" back />
|
<fy-header title="工作汇报" back />
|
||||||
<view v-if="!can" class="deny">当前账号没有写汇报权限</view>
|
<view v-if="!can" class="deny">当前账号没有写汇报权限</view>
|
||||||
<view v-else>
|
<fy-scroll v-else class="edge-scroll" @refresh="load">
|
||||||
<view class="card form">
|
<view class="card form">
|
||||||
|
<text class="lab">类型</text>
|
||||||
<picker :range="kinds" @change="(e) => kind = kinds[e.detail.value]">
|
<picker :range="kinds" @change="(e) => kind = kinds[e.detail.value]">
|
||||||
<view class="box">{{ kind }}</view>
|
<view class="box">{{ kind }}</view>
|
||||||
</picker>
|
</picker>
|
||||||
|
<text class="lab">内容</text>
|
||||||
<view class="box tall"><textarea v-model="content" placeholder="今日工作内容 / 周报摘要" /></view>
|
<view class="box tall"><textarea v-model="content" placeholder="今日工作内容 / 周报摘要" /></view>
|
||||||
<view class="btn-primary" @click="submit">提交汇报</view>
|
<wd-button type="primary" block size="large" @click="submit">提交汇报</wd-button>
|
||||||
</view>
|
</view>
|
||||||
|
<wd-status-tip v-if="!rows.length" image="content" tip="暂无汇报" />
|
||||||
<view v-for="r in rows" :key="r.id" class="card item">
|
<view v-for="r in rows" :key="r.id" class="card item">
|
||||||
<text class="t">{{ r.title || r.kind || '汇报' }}</text>
|
<view class="top">
|
||||||
<text class="s">{{ r.createdAt || r.date || '' }}</text>
|
<text class="type">{{ r.kind || r.title || '汇报' }}</text>
|
||||||
<text class="c">{{ r.content || r.summary || '' }}</text>
|
<text class="s">{{ r.createdAt || r.date || '' }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="c">{{ r.content || r.summary || r.title || '' }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</fy-scroll>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import FyHeader from '../../components/fy-header.vue'
|
import FyHeader from '../../components/fy-header.vue'
|
||||||
|
import FyScroll from '../../components/fy-scroll.vue'
|
||||||
import { store } from '../../utils/store.js'
|
import { store } from '../../utils/store.js'
|
||||||
import { asList, toast } from '../../utils/format.js'
|
import { asList, toast } from '../../utils/format.js'
|
||||||
import { oaGet, oaPost } from '../../utils/api.js'
|
import { oaGet, oaPost } from '../../utils/api.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { FyHeader },
|
components: { FyHeader, FyScroll },
|
||||||
data() {
|
data() {
|
||||||
return { kinds: ['日报', '周报', '月报', '工时'], kind: '日报', content: '', rows: [] }
|
return { kinds: ['日报', '周报', '月报', '工时'], kind: '日报', content: '', rows: [] }
|
||||||
},
|
},
|
||||||
@@ -58,12 +64,15 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.form { margin: 24rpx; padding: 24rpx; }
|
.form { margin: 16rpx 24rpx; padding: 24rpx; }
|
||||||
.box { background: #F3F6FB; border-radius: 12rpx; padding: 20rpx; margin-bottom: 16rpx; }
|
.lab { display: block; font-size: 26rpx; color: #6B7280; margin-bottom: 10rpx; }
|
||||||
.box.tall { min-height: 160rpx; }
|
.box { background: #F5F7FA; border-radius: 16rpx; padding: 22rpx 24rpx; margin-bottom: 20rpx; font-size: 30rpx; color: #111827; }
|
||||||
|
.box.tall { min-height: 180rpx; }
|
||||||
|
.box textarea { width: 100%; font-size: 30rpx; }
|
||||||
.item { margin: 0 24rpx 16rpx; padding: 24rpx; }
|
.item { margin: 0 24rpx 16rpx; padding: 24rpx; }
|
||||||
.t { display: block; font-weight: 600; }
|
.top { display: flex; align-items: center; justify-content: space-between; }
|
||||||
.s { font-size: 22rpx; color: #6B7280; }
|
.type { font-size: 22rpx; color: #1677FF; background: #E8F1FF; border-radius: 8rpx; padding: 2rpx 10rpx; }
|
||||||
.c { display: block; font-size: 26rpx; margin-top: 8rpx; }
|
.s { font-size: 22rpx; color: #9CA3AF; }
|
||||||
.deny { padding: 80rpx; text-align: center; color: #6B7280; }
|
.c { display: block; font-size: 28rpx; margin-top: 14rpx; color: #374151; line-height: 1.65; }
|
||||||
|
.deny { padding: 120rpx 40rpx; text-align: center; color: #6B7280; font-size: 28rpx; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
<view class="page edge">
|
<view class="page edge">
|
||||||
<fy-header title="通讯录">
|
<fy-header title="通讯录">
|
||||||
<template #right>
|
<template #right>
|
||||||
<text class="ico" @click="goGroup">建群</text>
|
<view class="ico" @click="goGroup">
|
||||||
|
<wd-icon name="usergroup" size="40rpx" color="#111827" />
|
||||||
|
</view>
|
||||||
</template>
|
</template>
|
||||||
</fy-header>
|
</fy-header>
|
||||||
<fy-scroll class="edge-scroll" dock @refresh="reload">
|
<fy-scroll class="edge-scroll" dock @refresh="reload">
|
||||||
@@ -226,5 +228,5 @@ export default {
|
|||||||
.mini { width: 64rpx; height: 64rpx; display: flex; align-items: center; justify-content: center; }
|
.mini { width: 64rpx; height: 64rpx; display: flex; align-items: center; justify-content: center; }
|
||||||
.empty { text-align: center; color: #6B7280; padding: 120rpx 40rpx; font-size: 28rpx; }
|
.empty { text-align: center; color: #6B7280; padding: 120rpx 40rpx; font-size: 28rpx; }
|
||||||
.foot { display: block; text-align: center; color: #9CA3AF; font-size: 22rpx; padding: 28rpx 0 48rpx; }
|
.foot { display: block; text-align: center; color: #9CA3AF; font-size: 22rpx; padding: 28rpx 0 48rpx; }
|
||||||
.ico { padding: 8rpx 4rpx; color: #1677FF; font-size: 28rpx; font-weight: 600; }
|
.ico { width: 56rpx; height: 56rpx; display: flex; align-items: center; justify-content: center; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -15,10 +15,6 @@
|
|||||||
<wd-icon name="usergroup" size="32rpx" color="#374151" />
|
<wd-icon name="usergroup" size="32rpx" color="#374151" />
|
||||||
<text>发起群聊</text>
|
<text>发起群聊</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="drop-item" @click="addMate">
|
|
||||||
<wd-icon name="user-add" size="32rpx" color="#374151" />
|
|
||||||
<text>添加同事</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
<fy-scroll class="edge-scroll list" dock @refresh="reload">
|
<fy-scroll class="edge-scroll list" dock @refresh="reload">
|
||||||
<view class="finder" @click="goSearch">
|
<view class="finder" @click="goSearch">
|
||||||
@@ -37,6 +33,9 @@
|
|||||||
<view class="av-wrap">
|
<view class="av-wrap">
|
||||||
<image v-if="c.avatar && c.type !== 'files'" class="av" :src="c.avatar" mode="aspectFill" />
|
<image v-if="c.avatar && c.type !== 'files'" class="av" :src="c.avatar" mode="aspectFill" />
|
||||||
<view v-else-if="c.type === 'files'" class="av"><fy-file-mark /></view>
|
<view v-else-if="c.type === 'files'" class="av"><fy-file-mark /></view>
|
||||||
|
<view v-else-if="facesOf(c).length" class="av mosaic">
|
||||||
|
<image v-for="f in facesOf(c)" :key="f.id" class="tile" :src="f.avatar" mode="aspectFill" />
|
||||||
|
</view>
|
||||||
<view v-else class="av txt" :class="avClass(c)">{{ avText(c) }}</view>
|
<view v-else class="av txt" :class="avClass(c)">{{ avText(c) }}</view>
|
||||||
<text v-if="c.unread && !c.muted" class="badge abs">{{ c.unread > 99 ? '99+' : c.unread }}</text>
|
<text v-if="c.unread && !c.muted" class="badge abs">{{ c.unread > 99 ? '99+' : c.unread }}</text>
|
||||||
<text v-else-if="c.unread && c.muted" class="dotu" />
|
<text v-else-if="c.unread && c.muted" class="dotu" />
|
||||||
@@ -102,6 +101,10 @@ export default {
|
|||||||
reload() {
|
reload() {
|
||||||
store.refreshPeoplePhotos().finally(() => this.pull())
|
store.refreshPeoplePhotos().finally(() => this.pull())
|
||||||
},
|
},
|
||||||
|
facesOf(c) {
|
||||||
|
if (!c || c.type !== 'group' || c.avatar) return []
|
||||||
|
return (c.memberIds || []).map((id) => store.person(id)).filter((p) => p && p.avatar).slice(0, 4)
|
||||||
|
},
|
||||||
avText(c) {
|
avText(c) {
|
||||||
if (c.type === 'work') return '工'
|
if (c.type === 'work') return '工'
|
||||||
if (c.type === 'group') return '群'
|
if (c.type === 'group') return '群'
|
||||||
@@ -149,10 +152,6 @@ export default {
|
|||||||
this.menu = false
|
this.menu = false
|
||||||
uni.navigateTo({ url: '/pages/chat/create' })
|
uni.navigateTo({ url: '/pages/chat/create' })
|
||||||
},
|
},
|
||||||
addMate() {
|
|
||||||
this.menu = false
|
|
||||||
uni.navigateTo({ url: '/pages/search/search' })
|
|
||||||
},
|
|
||||||
goSearch() {
|
goSearch() {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/search/search',
|
url: '/pages/search/search',
|
||||||
@@ -187,6 +186,8 @@ export default {
|
|||||||
.av.work { background: #1677FF; color: #fff; }
|
.av.work { background: #1677FF; color: #fff; }
|
||||||
.av.file { background: #F3F6FB; color: #4B5563; }
|
.av.file { background: #F3F6FB; color: #4B5563; }
|
||||||
.av.plain { background: #E8F1FF; color: #1677FF; }
|
.av.plain { background: #E8F1FF; color: #1677FF; }
|
||||||
|
.mosaic { display: flex; flex-wrap: wrap; align-content: center; justify-content: center; gap: 4rpx; background: #E8F1FF; padding: 6rpx; box-sizing: border-box; }
|
||||||
|
.tile { width: 38rpx; height: 38rpx; border-radius: 8rpx; }
|
||||||
.abs { position: absolute; top: -8rpx; right: -8rpx; }
|
.abs { position: absolute; top: -8rpx; right: -8rpx; }
|
||||||
.dotu { position: absolute; top: -4rpx; right: -4rpx; width: 16rpx; height: 16rpx; border-radius: 50%; background: #DC2626; }
|
.dotu { position: absolute; top: -4rpx; right: -4rpx; width: 16rpx; height: 16rpx; border-radius: 50%; background: #DC2626; }
|
||||||
.meta { flex: 1; margin-left: 20rpx; min-width: 0; padding: 8rpx 0 18rpx; border-bottom: 1rpx solid #F0F2F5; }
|
.meta { flex: 1; margin-left: 20rpx; min-width: 0; padding: 8rpx 0 18rpx; border-bottom: 1rpx solid #F0F2F5; }
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
<view class="page edge">
|
<view class="page edge">
|
||||||
<fy-header :title="greet">
|
<fy-header :title="greet">
|
||||||
<template #right>
|
<template #right>
|
||||||
<text class="ico" @click="goSearch">搜索</text>
|
<view class="ico" @click="goSearch">
|
||||||
|
<wd-icon name="search" size="40rpx" color="#111827" />
|
||||||
|
</view>
|
||||||
</template>
|
</template>
|
||||||
</fy-header>
|
</fy-header>
|
||||||
<fy-scroll class="edge-scroll" dock @refresh="reload">
|
<fy-scroll class="edge-scroll" dock @refresh="reload">
|
||||||
@@ -268,5 +270,5 @@ export default {
|
|||||||
.empty-card { background: #fff; border-radius: 24rpx; padding: 48rpx 32rpx; text-align: center; border: 1rpx solid rgba(17,24,39,0.06); }
|
.empty-card { background: #fff; border-radius: 24rpx; padding: 48rpx 32rpx; text-align: center; border: 1rpx solid rgba(17,24,39,0.06); }
|
||||||
.et { display: block; font-size: 28rpx; font-weight: 600; }
|
.et { display: block; font-size: 28rpx; font-weight: 600; }
|
||||||
.es { display: block; font-size: 24rpx; color: #6B7280; margin-top: 8rpx; }
|
.es { display: block; font-size: 24rpx; color: #6B7280; margin-top: 8rpx; }
|
||||||
.ico { padding: 8rpx 8rpx; font-size: 28rpx; color: #1677FF; }
|
.ico { width: 56rpx; height: 56rpx; display: flex; align-items: center; justify-content: center; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+12
-3
@@ -3,8 +3,12 @@
|
|||||||
<fy-header title="审批详情" back />
|
<fy-header title="审批详情" back />
|
||||||
<view v-if="row" class="body">
|
<view v-if="row" class="body">
|
||||||
<view class="card">
|
<view class="card">
|
||||||
|
<view class="top">
|
||||||
|
<text class="type">{{ row.typeLabel || row.type || '审批' }}</text>
|
||||||
|
<wd-tag type="primary" plain>{{ statusText }}</wd-tag>
|
||||||
|
</view>
|
||||||
<text class="t">{{ row.title || row.no }}</text>
|
<text class="t">{{ row.title || row.no }}</text>
|
||||||
<text class="s">{{ row.typeLabel || row.type }} · {{ row.submitter || '' }}</text>
|
<text class="s">{{ row.submitter || '' }}</text>
|
||||||
<text class="s">单号 {{ row.no }}</text>
|
<text class="s">单号 {{ row.no }}</text>
|
||||||
<text v-if="row.node" class="s">当前节点 {{ row.node }}</text>
|
<text v-if="row.node" class="s">当前节点 {{ row.node }}</text>
|
||||||
<text v-if="row.amount != null && row.amount !== ''" class="amt">¥ {{ row.amount }}</text>
|
<text v-if="row.amount != null && row.amount !== ''" class="amt">¥ {{ row.amount }}</text>
|
||||||
@@ -56,7 +60,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-else class="empty">{{ err || '加载中…' }}</view>
|
<wd-status-tip v-else image="content" :tip="err || '加载中…'" />
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -65,7 +69,7 @@ import FyHeader from '../../components/fy-header.vue'
|
|||||||
import FyFiles from '../../components/fy-files.vue'
|
import FyFiles from '../../components/fy-files.vue'
|
||||||
import { store } from '../../utils/store.js'
|
import { store } from '../../utils/store.js'
|
||||||
import { oaGet } from '../../utils/api.js'
|
import { oaGet } from '../../utils/api.js'
|
||||||
import { toast } from '../../utils/format.js'
|
import { flowStatus, toast } from '../../utils/format.js'
|
||||||
import { collectFiles, fileId } from '../../utils/files.js'
|
import { collectFiles, fileId } from '../../utils/files.js'
|
||||||
|
|
||||||
const LABELS = {
|
const LABELS = {
|
||||||
@@ -120,6 +124,9 @@ export default {
|
|||||||
const logs = (this.row && this.row.logs) || []
|
const logs = (this.row && this.row.logs) || []
|
||||||
return Array.isArray(logs) ? [...logs].reverse() : []
|
return Array.isArray(logs) ? [...logs].reverse() : []
|
||||||
},
|
},
|
||||||
|
statusText() {
|
||||||
|
return flowStatus(this.row && this.row.status)
|
||||||
|
},
|
||||||
canAct() {
|
canAct() {
|
||||||
if (!this.row) return false
|
if (!this.row) return false
|
||||||
const st = String(this.row.status || '')
|
const st = String(this.row.status || '')
|
||||||
@@ -155,6 +162,8 @@ export default {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.body { padding: 24rpx 28rpx 200rpx; }
|
.body { padding: 24rpx 28rpx 200rpx; }
|
||||||
.card { padding: 28rpx; margin-bottom: 16rpx; }
|
.card { padding: 28rpx; margin-bottom: 16rpx; }
|
||||||
|
.top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12rpx; }
|
||||||
|
.type { font-size: 22rpx; color: #1677FF; background: #E8F1FF; border-radius: 8rpx; padding: 2rpx 10rpx; }
|
||||||
.block { padding: 24rpx 28rpx; }
|
.block { padding: 24rpx 28rpx; }
|
||||||
.h { display: block; font-size: 30rpx; font-weight: 650; margin-bottom: 16rpx; }
|
.h { display: block; font-size: 30rpx; font-weight: 650; margin-bottom: 16rpx; }
|
||||||
.t { display: block; font-size: 34rpx; font-weight: 650; line-height: 1.4; }
|
.t { display: block; font-size: 34rpx; font-weight: 650; line-height: 1.4; }
|
||||||
|
|||||||
+3
-2
@@ -17,7 +17,7 @@
|
|||||||
<text class="t">{{ r.title || r.no }}</text>
|
<text class="t">{{ r.title || r.no }}</text>
|
||||||
<text class="s">{{ r.submitter || r.assigneeName || '' }}</text>
|
<text class="s">{{ r.submitter || r.assigneeName || '' }}</text>
|
||||||
</view>
|
</view>
|
||||||
<wd-tag type="primary" plain>{{ r.status || '待处理' }}</wd-tag>
|
<wd-tag type="primary" plain>{{ statusText(r) }}</wd-tag>
|
||||||
<text class="arr">›</text>
|
<text class="arr">›</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
import FyHeader from '../../components/fy-header.vue'
|
import FyHeader from '../../components/fy-header.vue'
|
||||||
import FyScroll from '../../components/fy-scroll.vue'
|
import FyScroll from '../../components/fy-scroll.vue'
|
||||||
import { store } from '../../utils/store.js'
|
import { store } from '../../utils/store.js'
|
||||||
import { asList } from '../../utils/format.js'
|
import { asList, flowStatus } from '../../utils/format.js'
|
||||||
import { oaGet } from '../../utils/api.js'
|
import { oaGet } from '../../utils/api.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -82,6 +82,7 @@ export default {
|
|||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
statusText(r) { return flowStatus(r && r.status) },
|
||||||
open(r) {
|
open(r) {
|
||||||
if (r.source === 'work' || r.taskId) {
|
if (r.source === 'work' || r.taskId) {
|
||||||
uni.navigateTo({ url: '/pages/coop/detail?id=' + encodeURIComponent(r.id || r.taskId) })
|
uni.navigateTo({ url: '/pages/coop/detail?id=' + encodeURIComponent(r.id || r.taskId) })
|
||||||
|
|||||||
@@ -148,6 +148,34 @@ export function convType(s) {
|
|||||||
return 'direct'
|
return 'direct'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FLOW_STATUS = {
|
||||||
|
pending: '待审批',
|
||||||
|
waiting: '待处理',
|
||||||
|
processing: '审批中',
|
||||||
|
reviewing: '审阅中',
|
||||||
|
review: '待审阅',
|
||||||
|
approved: '已通过',
|
||||||
|
passed: '已通过',
|
||||||
|
rejected: '已驳回',
|
||||||
|
refused: '已拒绝',
|
||||||
|
denied: '已拒绝',
|
||||||
|
cancelled: '已撤销',
|
||||||
|
canceled: '已撤销',
|
||||||
|
withdrawn: '已撤回',
|
||||||
|
draft: '草稿',
|
||||||
|
done: '已完成',
|
||||||
|
completed: '已完成',
|
||||||
|
handled: '已处理',
|
||||||
|
reviewed: '已审阅',
|
||||||
|
running: '进行中'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function flowStatus(status) {
|
||||||
|
const raw = String(status || '').trim()
|
||||||
|
if (!raw) return '待处理'
|
||||||
|
return FLOW_STATUS[raw.toLowerCase()] || raw
|
||||||
|
}
|
||||||
|
|
||||||
export function toast(title, icon = 'none') {
|
export function toast(title, icon = 'none') {
|
||||||
uni.showToast({ title: String(title || ''), icon, duration: 2200 })
|
uni.showToast({ title: String(title || ''), icon, duration: 2200 })
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-15
@@ -487,7 +487,7 @@ export const store = {
|
|||||||
return msg
|
return msg
|
||||||
})
|
})
|
||||||
const seen = new Set(incoming.map((m) => m.id).filter(Boolean))
|
const seen = new Set(incoming.map((m) => m.id).filter(Boolean))
|
||||||
const extras = (conv.messages || []).filter((m) => m.mine && m.id && !seen.has(m.id) && Date.now() - Number(m.time) < 120000)
|
const extras = (conv.messages || []).filter((m) => m.id && !seen.has(m.id) && (m.kind === 'system' || (m.mine && Date.now() - Number(m.time) < 120000)))
|
||||||
conv.messages = incoming.concat(extras)
|
conv.messages = incoming.concat(extras)
|
||||||
conv.unread = 0
|
conv.unread = 0
|
||||||
await api.markRead(conv.id, this.token)
|
await api.markRead(conv.id, this.token)
|
||||||
@@ -794,12 +794,15 @@ export const store = {
|
|||||||
async createGroup(name, memberIds) {
|
async createGroup(name, memberIds) {
|
||||||
const ids = [...new Set([this.me && this.me.id, ...(memberIds || []).map(String)])].filter(Boolean)
|
const ids = [...new Set([this.me && this.me.id, ...(memberIds || []).map(String)])].filter(Boolean)
|
||||||
const title = String(name || '').trim() || '群聊'
|
const title = String(name || '').trim() || '群聊'
|
||||||
let sid = ''
|
if (ids.length < 2) throw new Error('请至少再选一位同事')
|
||||||
try {
|
const data = await api.createGroup({
|
||||||
const data = await api.createGroup({ name: title, memberIds: ids }, this.token)
|
title,
|
||||||
sid = String((data && (data.id || data.sessionId)) || '')
|
name: title,
|
||||||
} catch (_) {}
|
memberIds: ids,
|
||||||
if (!sid) sid = 'g:' + Date.now()
|
members: ids
|
||||||
|
}, this.token)
|
||||||
|
const sid = String((data && (data.id || data.sessionId)) || '')
|
||||||
|
if (!sid) throw new Error('创建失败')
|
||||||
let conv = this.findConv(sid)
|
let conv = this.findConv(sid)
|
||||||
if (!conv) {
|
if (!conv) {
|
||||||
conv = {
|
conv = {
|
||||||
@@ -809,8 +812,16 @@ export const store = {
|
|||||||
avatar: '',
|
avatar: '',
|
||||||
peerId: '',
|
peerId: '',
|
||||||
memberIds: ids,
|
memberIds: ids,
|
||||||
messages: [],
|
|
||||||
unread: 0,
|
unread: 0,
|
||||||
|
messages: [{
|
||||||
|
id: 'sys-' + sid,
|
||||||
|
kind: 'system',
|
||||||
|
text: '你创建了群聊',
|
||||||
|
time: Date.now(),
|
||||||
|
mine: false,
|
||||||
|
fromId: '',
|
||||||
|
recalled: false
|
||||||
|
}],
|
||||||
lastMessage: '你创建了群聊',
|
lastMessage: '你创建了群聊',
|
||||||
lastTime: fmtListTime(Date.now()),
|
lastTime: fmtListTime(Date.now()),
|
||||||
pinned: false,
|
pinned: false,
|
||||||
@@ -854,13 +865,15 @@ export const store = {
|
|||||||
try {
|
try {
|
||||||
this.attendRules.locations = asList(await api.oaGet('/profile/attendance/locations', this.token))
|
this.attendRules.locations = asList(await api.oaGet('/profile/attendance/locations', this.token))
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
try {
|
if (this.can('system', 'attend')) {
|
||||||
const pack = await api.oaGet('/system/attend-rules', this.token)
|
try {
|
||||||
if (pack && typeof pack === 'object') {
|
const pack = await api.oaGet('/system/attend-rules', this.token)
|
||||||
this.attendRules.workStart = pack.workStart || this.attendRules.workStart
|
if (pack && typeof pack === 'object') {
|
||||||
this.attendRules.workEnd = pack.workEnd || this.attendRules.workEnd
|
this.attendRules.workStart = pack.workStart || this.attendRules.workStart
|
||||||
}
|
this.attendRules.workEnd = pack.workEnd || this.attendRules.workEnd
|
||||||
} catch (_) {}
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
this.attendance = asList(await api.oaGet('/profile/attendance', this.token))
|
this.attendance = asList(await api.oaGet('/profile/attendance', this.token))
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|||||||
Reference in New Issue
Block a user