This commit is contained in:
oneao committed 2026-04-02 17:24:43 +08:00
1 parent 9e00adf2fe
commit ae0d245605
34 files changed
+1460 -162

No files matched your search

@@ -1,11 +1,486 @@
<script lang="ts" setup>
<script setup lang="ts">
import { toast } from 'sard-uniapp'
import { ref } from 'vue'
import ShDialog from '@/components/sh-dialog/index.vue'
import { useDeviceInfo } from '@/hooks/useDeviceInfo'
import { loaddataApi, searchdatapageindexApi } from '@/request/api'
import { useUserStore } from '@/store'
import { useCountsStore } from '@/store/counts'
import { groupFieldsBySpan } from '@/utils/common'
const countsStore = useCountsStore()
const userStore = useUserStore()
const { isMobile } = useDeviceInfo()
const auditComponentData = ref({
moduleId: 'car_daily',
saveTable: 'car_daily',
state: '',
row: null,
})
const pagingRef = ref<any>(null)
const dataList = ref<any[]>([])
const modLevelList = ref<any[]>([])
const showShDialog = ref(false)
// 自定义 tabs
const tabList = computed(() => [
{ name: '待审', count: countsStore.baoxiao.v_dsh || 0 },
{ name: '已处理', count: countsStore.baoxiao.v_ash || 0 },
{ name: '全部', count: countsStore.baoxiao.all_cnt || 0 },
])
const activeTab = ref(tabList.value[0])
const itemFields = [
{ label: '账单日期', value: item => item.b_date, span: 12 },
{ label: '结算单位', value: item => item.v_dw_name, span: 12 },
{ label: '报销金额', value: item => item.b_amount_root, span: 6 },
{ label: '币种', value: item => item.b_money_id, span: 6 },
{ label: '汇率', value: item => item.b_exrate, span: 6 },
{ label: '折后金额', value: item => item.b_amount, span: 6 },
]
const groupedFields = groupFieldsBySpan(itemFields)
// 搜索关键字
const searchValue = ref('')
onShow(async () => {
await loadModLevelList()
// await pagingRef.value?.reload()
countsStore.loadTaskCounts(['baoxiao'])
})
async function loadModLevelList() {
await loaddataApi({
viewname: 'v_sh_b_set',
searchcondition: `b_user_id='${userStore.userInfo.userid}' and b_module_id = 'car_daily'`,
orderfield: 'b_level',
soption: '',
}).then((res) => {
modLevelList.value = res.data
})
}
// 切换 tab
function switchTab(tab: any) {
activeTab.value = tab
pagingRef.value.reload()
}
// 搜索
function handleSearch() {
pagingRef.value.reload()
}
function getTagType(state) {
switch (state) {
case '审核通过':
return 'success'
case '草拟':
return 'info'
case '审核驳回':
return 'danger'
default:
return 'primary'
}
}
// 分页请求
async function queryList(pageNo: number, pageSize: number) {
toast.loading('正在加载中')
const userid = userStore.userInfo.userid
let sql = '2 > 1 '
let mlvSearch = ''
let slevel = ''
for (let i in modLevelList.value) {
let item = modLevelList.value[i]
slevel = item.b_level
let viewname = item.b_ViewTable
let condition = item.b_condition
let j = Number.parseInt(slevel) - 1
if (mlvSearch !== '') {
mlvSearch = `${mlvSearch} or `
}
mlvSearch = `${mlvSearch}(b_module_id = '${item.b_module_id}'`
if (viewname !== '' && condition !== '') {
mlvSearch = `${mlvSearch} and b_id in (select b_id from ${viewname} where ${condition})`
}
let canlevel = ` and b_level in('${j}','${slevel}')`
if (activeTab.value.name === '待审') {
canlevel = ` and b_level = '${j}'`
}
if (activeTab.value.name === '已处理') {
canlevel = ` and b_level = '${slevel}'`
}
if (canlevel !== '') {
mlvSearch = mlvSearch + canlevel
}
mlvSearch = `${mlvSearch})`
}
if (mlvSearch) {
sql = `${sql} and (${mlvSearch})`
}
if (searchValue.value) {
sql = `${sql} and b_id like '%${searchValue.value}%'`
}
searchdatapageindexApi({
viewname: 'v_sh_recorddata',
searchcondition: sql,
userid,
page: pageNo,
rows: pageSize,
orderfield: 'b_ss_date desc',
})
.then((res) => {
const { rows, _total } = res.data
let idList = []
rows.forEach((row) => {
idList.push(row.b_id)
})
let sql2 = '2 > 1'
if (idList.length > 0) {
let idString = idList.map(id => `'${id}'`).join(',')
sql2 = `b_id IN (${idString})`
}
else {
sql2 = `b_id IN (NULL)`
}
loaddataApi({
viewname: 'v_car_daily',
searchcondition: sql2,
orderfield: 'b_date desc',
soption: '',
}).then((res) => {
let rows2 = res.data
rows2.forEach((row) => {
if (row.b_id) {
let slevel: string | number = ''
let cansh = false
let canbh = false
let bModuleId = ''
let bLevel: string | number = ''
const foundItem = rows.find(item => item.b_id === row.b_id)
if (foundItem) {
bModuleId = foundItem.b_module_id
bLevel = foundItem.b_level
row.b_module_id = bModuleId
}
for (const modLevel of modLevelList.value) {
if (modLevel.b_module_id === bModuleId) {
slevel = modLevel.b_level
slevel = Number.parseInt(slevel as string)
if (Number.isNaN(slevel)) {
slevel = 0
}
let j = slevel - 1
if (bLevel === j) {
cansh = true
}
if (bLevel === slevel) {
canbh = true
}
}
}
if (canbh || cansh) {
if (cansh) {
row.tongGuoButton = true
row.boHuiButton = true
}
else if (canbh) {
row.tongGuoButton = false
row.boHuiButton = true
}
}
else {
row.tongGuoButton = false
row.boHuiButton = false
}
}
})
if (rows2) {
console.log(rows2)
pagingRef.value.complete(rows2)
}
})
})
.finally(() => {
toast.hide()
})
}
async function handleApproval(item: any, state: string) {
auditComponentData.value.row = item
auditComponentData.value.state = state
showShDialog.value = true
}
function handleShSuccess() {
pagingRef.value.reload()
countsStore.loadTaskCounts(['baoxiao'])
}
function handleToProcess(item) {
uni.navigateTo({
url: `/pages-sub/process/index?b_id=${item.b_id}&b_module_id=${item.b_module_id}&v_modulename=费用报销`,
})
}
</script>
<template>
<view class="" />
<view>
<z-paging ref="pagingRef" v-model="dataList" auto-show-back-to-top @query="queryList">
<template #top>
<sar-search
v-model="searchValue"
placeholder="业务单号"
@search="handleSearch"
/>
<view class="tabs">
<view
v-for="tab in tabList"
:key="tab.name"
class="tabs__item"
:class="{ 'tabs__item--active': activeTab.name === tab.name }"
@click="switchTab(tab)"
>
<sar-badge v-if="tab.count > 0" :value="tab.count">
{{ tab.name }}
</sar-badge>
<text v-else>
{{ tab.name }}
</text>
</view>
</view>
</template>
<!-- 列表内容 -->
<view
v-for="(item, index) in dataList"
:key="index"
class="list-item"
:class="[!isMobile ? 'list-item--desktop' : '']"
@click="handleToProcess(item)"
>
<view class="list-item__header">
<view class="list-item__main">
<!-- <sar-checkbox size="36rpx" /> -->
<text>
{{ item.b_id }}
</text>
</view>
<view class="list-item__extra">
<sar-tag :theme="getTagType(item.b_state)">
{{ item.b_state }}
</sar-tag>
</view>
</view>
<view class="list-item__body">
<sar-row v-for="(row, rowIndex) in groupedFields" :key="rowIndex">
<sar-col
v-for="(col, colIndex) in row"
:key="colIndex"
:span="col.span || 6"
>
<view class="list-item__info">
<view class="list-item__label">
{{ col.label }}:
</view>
<view class="list-item__value">
{{ col.value(item) }}
</view>
</view>
</sar-col>
</sar-row>
</view>
<view class="list-item__footer">
<sar-button v-if="item.boHuiButton" theme="danger" size="mini" icon="close" inline @click.stop="handleApproval(item, '审核驳回')">
驳回
</sar-button>
<sar-button v-if="item.tongGuoButton" size="mini" icon="success" inline @click.stop="handleApproval(item, '审核通过')">
通过
</sar-button>
</view>
<view class="list-item__line" />
</view>
<!-- <template #bottom>
<view class="bottom-bar">
<sar-checkbox>
全选
</sar-checkbox>
<sar-button block size="small" icon="success">
批量通过
</sar-button>
</view>
</template> -->
</z-paging>
<ShDialog v-model="showShDialog" :component-data="auditComponentData" @success="handleShSuccess" />
</view>
</template>
<style lang="scss" scoped>
<style scoped lang="scss">
@import '@/style/mixins.scss';
.dialog-loading {
display: flex;
justify-content: center;
align-items: center;
height: 200rpx;
}
.bottom-bar {
display: flex;
align-items: center;
padding: 24rpx 36rpx;
background: #fff;
border-top: 2rpx solid #ddd;
@include flexGap(32rpx, '');
}
/* tab 栏 */
.tabs {
display: flex;
border-bottom: 2rpx solid #eee;
background-color: #fff;
.tabs__item {
flex: 1;
text-align: center;
padding: 16rpx 0;
font-size: 30rpx;
color: #888;
}
.tabs__item--active {
color: var(--sar-primary);
font-weight: bold;
border-bottom: 4rpx solid var(--sar-primary);
}
}
/* 列表项 */
.list-item {
position: relative;
padding: 24rpx 16rpx;
background-color: #fff;
transition: all 0.2s;
&:active {
background-color: #f5f5f5;
}
&.list-item--desktop {
.list-item__header {
max-width: 1000rpx;
margin: 0 auto;
}
.list-item__body {
max-width: 1000rpx;
margin: 0 auto;
}
.list-item__footer {
max-width: 1000rpx;
margin: 0 auto;
}
}
.list-item__header {
display: flex;
justify-content: space-between;
.list-item__main {
display: flex;
align-items: center;
@include flexGap(16rpx, '');
font-weight: bold;
font-size: 36rpx;
}
.list-item__extra {
display: flex;
align-items: center;
@include flexGap(16rpx, '');
.list-item__finishdate {
font-size: 28rpx;
color: #666;
}
}
}
.list-item__body {
padding: 24rpx 0rpx;
display: flex;
flex-direction: column;
@include flexGap(16rpx, 'column');
.list-item__info {
display: flex;
align-items: start;
width: 100%;
font-size: 32rpx;
line-height: 1;
.list-item__label {
display: block;
min-width: 192rpx;
color: #4b5563;
text-align: right;
white-space: nowrap;
}
.list-item__value {
flex: 1;
color: #111827;
word-break: break-all;
}
}
}
.list-item__footer {
display: flex;
justify-content: flex-end;
@include flexGap(36rpx, '');
:deep(.sar-button) {
padding: 0rpx 48rpx;
font-size: 32rpx;
}
}
.list-item__line {
position: absolute;
bottom: 0rpx;
left: 0rpx;
height: 1px;
width: 100%;
background-color: #dcdcdc;
}
}
</style>
@@ -44,8 +44,6 @@ async function loadFileData() {
url: item.mx_mapfilename,
}
})
console.log(fileList.value)
})
}
@@ -59,13 +59,11 @@ onShow(async () => {
async function loadModLevelList() {
await loaddataApi({
viewname: 'v_sh_b_set',
searchcondition: `b_user_id='${userStore.userInfo.userid}'`,
searchcondition: `b_user_id='${userStore.userInfo.userid}' AND b_module_id = 'cw_zdorder_f'`,
orderfield: 'b_level',
soption: '',
}).then((res) => {
modLevelList.value = res.data.filter(item => item.b_module_id === 'cw_zdorder_f')
console.log(modLevelList.value)
modLevelList.value = res.data
})
}
@@ -149,12 +147,13 @@ async function queryList(pageNo: number, pageSize: number) {
orderfield: 'b_ss_date desc',
})
.then((res) => {
const { rows, _total } = res.data
const { rows, total } = res.data
let idList = []
rows.forEach((row) => {
idList.push(row.b_id)
})
let sql2 = '2 > 1'
if (idList.length > 0) {
let idString = idList.map(id => `'${id}'`).join(',')
@@ -319,10 +318,10 @@ function handleToProcess(item) {
</view>
<view class="list-item__footer">
<sar-button v-if="item.boHuiButton" theme="danger" size="mini" icon="close" inline @click="handleApproval(item, '审核驳回')">
<sar-button v-if="item.boHuiButton" theme="danger" size="mini" icon="close" inline @click.stop="handleApproval(item, '审核驳回')">
驳回
</sar-button>
<sar-button v-if="item.tongGuoButton" size="mini" icon="success" inline @click="handleApproval(item, '审核通过')">
<sar-button v-if="item.tongGuoButton" size="mini" icon="success" inline @click.stop="handleApproval(item, '审核通过')">
通过
</sar-button>
</view>
@@ -421,9 +420,9 @@ function handleToProcess(item) {
.list-item__main {
display: flex;
align-items: center;
@include flexGap(16rpx, '');
font-weight: bold;
font-size: 36rpx;
@include flexGap(16rpx, '');
}
.list-item__extra {
display: flex;
@@ -0,0 +1,59 @@
<script setup lang="ts">
import type { UploadFile, UploadFileItem } from 'sard-uniapp'
import { loaddataAttachApi } from '@/request/api'
const props = defineProps({
componentData: {
type: Object,
},
})
const fileList = ref<UploadFileItem[]>([])
const father = ref('')
const moduleid = ref('')
const cateid = ref('')
onLoad(() => {
father.value = props.componentData.b_id
moduleid.value = props.componentData.b_module_id
nextTick(() => {
loadFileData()
})
})
async function loadFileData() {
let searchcondition = ` father = '${father.value}' and mx_moduleid = '${moduleid.value}'`
if (cateid.value !== '') {
searchcondition += ` and mx_cate_id = '${cateid.value}'`
}
loaddataAttachApi({
viewname: 'bf_files',
searchcondition,
orderfield: 'mx_xh',
soption: '',
}).then((res) => {
fileList.value = res.data.map((item) => {
return {
...item,
name: item.mx_filename,
url: item.mx_mapfilename,
}
})
})
}
</script>
<template>
<view class="file">
<sar-result v-if="fileList.length === 0" status="info" title="提示" description="暂无附件" />
<sar-upload v-model="fileList" :readonly="true" />
</view>
</template>
<style lang="scss" scoped>
.file {
padding: 36rpx;
}
</style>
@@ -1,4 +1,5 @@
<script lang="ts" setup>
import File from './file.vue'
import Information from './information.vue'
import Step from './step.vue'
@@ -69,6 +70,7 @@ onLoad((options) => {
<Step v-if="segmentedValue === '流程'" :component-data="data" />
<Information v-else-if="segmentedValue === '详细'" :component-data="data" />
<File v-else-if="segmentedValue === '附件'" :component-data="data" />
</view>
</template>
@@ -1,5 +1,4 @@
<script lang="ts" setup>
import LoadingPage from '@/components/loading-page/index.vue'
import { loaddataApi } from '@/request/api'
const props = defineProps({
@@ -7,33 +6,124 @@ const props = defineProps({
type: Object,
},
})
const data = ref<any[]>([])
const loading = ref<boolean>(true)
const loading = ref(true)
const stepsData = ref<Array<any>>([])
const progress = ref({
finish: 0,
all: 0,
})
const computedProgress = computed(() => {
if (progress.value.all === 0) {
return 0
}
return Math.round((progress.value.finish / progress.value.all) * 100)
})
onLoad(() => {
initData()
})
async function initData() {
loading.value = true
await loaddataApi({
viewname: 'v_sh_record',
orderfield: 'b_id desc',
// 定义两个请求
const request1 = loaddataApi({
viewname: 'v_sh_setjd',
orderfield: 'b_level',
searchcondition: `b_module_id = '${props.componentData.b_module_id}'`,
soption: '',
})
const request2 = loaddataApi({
viewname: 'v_sh_modshrecord',
orderfield: 'b_level',
searchcondition: `b_module_id = '${props.componentData.b_module_id}' and b_dj_id = '${props.componentData.b_id}'`,
soption: '',
}).then((res) => {
data.value = res.data
})
loading.value = false
// 使用 Promise.all 等待两个请求都完成
Promise.all([request1, request2])
.then(([res1, res2]) => {
const stepData = res1.data
const stepItemData = res2.data
progress.value.finish = stepItemData.length
progress.value.all = stepData.length
let current = true
stepData.forEach((item, index) => {
const obj = {
type: 0,
current: false,
b_name: '',
v_user_name: '',
b_sh_memo: '',
b_sh_datetime: '',
b_level: item.b_level,
consume_time: '',
}
const stepItem = stepItemData[index]
if (stepItem !== undefined) {
obj.type = 2
obj.v_user_name = stepItem.v_user_name
obj.b_sh_memo = stepItem.b_sh_memo
obj.b_sh_datetime = stepItem.b_sh_datetime
obj.b_name = stepItem.b_name
// 计算审批耗时,需要计算与上一级的耗时
if (obj.b_level !== 0) {
// 计算与上一个的耗时
const lastItem = stepItemData[index - 1]
obj.consume_time = computedTimeDifference(lastItem.b_sh_datetime, obj.b_sh_datetime)
}
}
else {
if (current) {
obj.current = true
obj.type = 1
current = false
}
if (item.b_level === 0) {
obj.b_name = `送审`
}
else {
obj.b_name = `${getCharNumber(item.b_level)}审通过`
}
obj.v_user_name = item.v_dshusername
}
stepsData.value.push(obj)
})
stepsData.value.reverse()
console.log(stepsData.value)
})
.finally(() => {
loading.value = false
})
}
function computedConsumeTime(index: number) {
if (index === data.value.length - 1) {
return ''
function getCharNumber(number: number): string {
switch (number) {
case 1:
return '一'
case 2:
return '二'
case 3:
return '三'
case 4:
return '四'
case 5:
return '五'
case 6:
return '六'
case 7:
return '七'
case 8:
return '八'
default:
return ''
}
if (data.value.length === 1) {
return ''
}
return computedTimeDifference(data.value[index].b_sh_date, data.value[index + 1].b_sh_date)
}
function computedTimeDifference(date1: string, date2: string): string {
// 将日期字符串转换为 Date 对象
@@ -63,46 +153,165 @@ function computedTimeDifference(date1: string, date2: string): string {
// 如果没有任何值,说明两个日期相等,返回"0秒"
return result.length > 0 ? result.join('') : '0秒'
}
// 计算步骤的颜色
function getTimelineStyle(item: any) {
const colorMap: Record<number, string> = {
0: '#B0B0B0',
1: '#6A9EF1',
2: '#70C070',
3: '#F17A7A',
}
const color = colorMap[item.type] || '#B0B0B0'
return {
'--dot-color': color,
'--line-color': color,
}
}
function computedStepText(item): string {
switch (item.type) {
case 0:
return '未开始' // 未开始,灰色
case 1:
return '进行中'
// 进行中,蓝色
case 2:
return '已完成' // 已完成,绿色
case 3:
return '驳回' // 已驳回,红色
default:
return '未开始' // 默认灰色
}
}
function formatDateTime(dateTime: string): string {
if (!dateTime) {
return ''
}
return dateTime.replace(/\.\d+$/, '')
}
</script>
<template>
<LoadingPage v-if="loading" />
<view v-else-if="data.length > 0" class="timeline">
<view v-for="(item, index) in data" :key="index" class="timeline-item">
<view class="timeline-col left-col">
<view class="timeline-content left">
{{ item.b_name }}
</view>
<view v-else-if="stepsData.length > 0" class="">
<view style="display: flex;align-items: center;">
<text style="font-size: 28rpx; margin-right: 8rpx;">
当前进度 -
</text>
<view style="flex:1">
<sar-progress-bar
:percent="computedProgress"
inside
striped
animated
/>
</view>
<view class="timeline-col right-col">
<view class="timeline-content right">
<view v-if="item.v_user_name" class="title">
{{ item.v_user_name }}
<text v-if="item.v_tosh_user_name">
> {{ item.v_tosh_user_name }}
</text>
</view>
<view class="timeline">
<view
v-for="(item, index) in stepsData"
:key="index"
class="timeline-item"
:style="getTimelineStyle(item)"
>
<view class="timeline-col left-col">
<view class="timeline-content left">
<text>
{{ item.b_name }}
</text>
<view style="margin-top: 8rpx">
<sar-tag v-if="item.type === 1" theme="primary" size="small">
{{ computedStepText(item) }}
</sar-tag>
<sar-tag v-else-if="item.type === 2" theme="success" size="small">
{{ computedStepText(item) }}
</sar-tag>
<sar-tag v-else-if="item.type === 3" theme="danger" size="small">
{{ computedStepText(item) }}
</sar-tag>
<sar-tag v-else theme="secondary" size="small">
{{ computedStepText(item) }}
</sar-tag>
</view>
</view>
<view>{{ item.b_sh_date }}</view>
<view v-if="computedConsumeTime(index)">
审批耗时:{{ computedConsumeTime(index) }}
</view>
<view>{{ item.b_sh_memo }}</view>
</view>
</view>
<view class="timeline-center">
<view class="timeline-dot" />
<view class="timeline-col right-col">
<view class="timeline-content right">
<view v-if="item.b_level === 0">
<view>
<text>操作人:</text>
<text>{{ item.v_user_name }}</text>
</view>
<view>
<text>操作时间:</text>
<text>{{ formatDateTime(item.b_sh_datetime) }}</text>
</view>
</view>
<view v-else>
<view v-if="item.type === 0 || item.type === 1">
<view>
<text>审核人:</text>
<text>{{ item.v_user_name }}</text>
</view>
</view>
<view v-else-if="item.type === 3">
<view>
<text>审核人:</text>
<text>{{ item.v_user_name }}</text>
</view>
<view>
<text>审核时间:</text>
<text>{{ formatDateTime(item.b_sh_datetime) }}</text>
</view>
<view>
<text>审核耗时:</text>
<text>{{ item.consume_time }}</text>
</view>
</view>
<view v-else>
<view>
<text>审核人:</text>
<text>{{ item.v_user_name }}</text>
</view>
<view>
<text>审核时间:</text>
<text>{{ formatDateTime(item.b_sh_datetime) }}</text>
</view>
<view>
<text>审核意见:</text>
<text>{{ item.b_sh_memo }}</text>
</view>
</view>
</view>
</view>
</view>
<view class="timeline-center">
<view class="timeline-dot" />
</view>
</view>
</view>
</view>
<sar-result v-else status="info" title="空空如也" style="margin-top: 64rpx;"/>
<sar-result v-else status="info" title="空空如也" style="margin-top: 64rpx;" />
</template>
<style lang="scss" scoped>
/* 整个容器 */
.timeline {
display: flex;
flex-direction: column;
}
/* 每个步骤项 */
.timeline-item {
position: relative;
@@ -110,6 +319,7 @@ function computedTimeDifference(date1: string, date2: string): string {
align-items: center;
padding: 32rpx 0;
}
/* 左右列容器,用来固定左右比例,不受内容内 padding 影响 */
.timeline-col {
box-sizing: border-box;
@@ -127,34 +337,30 @@ function computedTimeDifference(date1: string, date2: string): string {
padding-left: 32rpx; /* 给中间圆点留出空间 */
text-align: left;
}
/* 左右内容,可以自由添加 padding */
.timeline-content {
font-size: 28rpx;
word-break: break-all;
border-radius: 12rpx;
}
.timeline-content.left {
padding: 8rpx;
font-weight: bold;
text-align: center;
background-color: #f0f2f5; /* 轻柔的灰色背景,避免视觉突兀 */
border-radius: 12rpx; /* 让边角更柔和 */
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.1); /* 轻微阴影,增加层次感 */
}
.timeline-content.right {
display: flex;
flex-direction: column;
gap: 4rpx;
padding: 16rpx 32rpx;
color: #666;
background: #f5f5f5;
.title {
&.left {
padding: 8rpx;
font-weight: bold;
color: black;
text-align: center;
// background-color: #f0f2f5; /* 轻柔的灰色背景,避免视觉突兀 */
// border-radius: 12rpx; /* 让边角更柔和 */
// box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.1); /* 轻微阴影,增加层次感 */
}
&.right {
display: flex;
flex-direction: column;
gap: 4rpx;
padding: 16rpx 32rpx;
background: #f5f5f5;
}
}
/* 中间圆点容器,绝对定位在左右列分界处 */
.timeline-center {
position: absolute;
@@ -169,8 +375,9 @@ function computedTimeDifference(date1: string, date2: string): string {
.timeline-dot {
width: 24rpx;
height: 24rpx;
background: #007aff;
background: var(--dot-color, #007aff);
border-radius: 50%;
box-shadow: 0 0 3px 2px rgba(0, 0, 0, 0.5);
}
/* 使用每个步骤项的伪元素绘制竖线 */
.timeline-item::before {
@@ -180,7 +387,7 @@ function computedTimeDifference(date1: string, date2: string): string {
left: calc(30%);
width: 4rpx; /* 可随意调整宽度 */
content: '';
background: #409eff; /* 竖线颜色 */
background: #dddddd; /* 竖线颜色 */
transform: translateX(-50%);
}
/* 第一项:只显示圆点下方的线 */
@@ -0,0 +1,201 @@
<script lang="ts" setup>
import LoadingPage from '@/components/loading-page/index.vue'
import { loaddataApi } from '@/request/api'
const props = defineProps({
componentData: {
type: Object,
},
})
const data = ref<any[]>([])
const loading = ref<boolean>(true)
onLoad(() => {
initData()
})
async function initData() {
loading.value = true
await loaddataApi({
viewname: 'v_sh_record',
orderfield: 'b_id desc',
searchcondition: `b_module_id = '${props.componentData.b_module_id}' and b_dj_id = '${props.componentData.b_id}'`,
soption: '',
}).then((res) => {
data.value = res.data
})
loading.value = false
}
function computedConsumeTime(index: number) {
if (index === data.value.length - 1) {
return ''
}
if (data.value.length === 1) {
return ''
}
return computedTimeDifference(data.value[index].b_sh_date, data.value[index + 1].b_sh_date)
}
function computedTimeDifference(date1: string, date2: string): string {
// 将日期字符串转换为 Date 对象
const d1 = new Date(date1)
const d2 = new Date(date2)
// 计算两个日期之间的毫秒差值
const diffInMilliseconds = Math.abs(d2.getTime() - d1.getTime())
// 将毫秒差值转换为对应的天、小时、分钟和秒
const days = Math.floor(diffInMilliseconds / (1000 * 60 * 60 * 24))
const hours = Math.floor((diffInMilliseconds % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
const minutes = Math.floor((diffInMilliseconds % (1000 * 60 * 60)) / (1000 * 60))
const seconds = Math.floor((diffInMilliseconds % (1000 * 60)) / 1000)
// 根据条件组合输出结果
const result: string[] = []
if (days > 0)
result.push(`${days}天`)
if (hours > 0)
result.push(`${hours}时`)
if (minutes > 0)
result.push(`${minutes}分`)
if (seconds > 0)
result.push(`${seconds}秒`)
// 如果没有任何值,说明两个日期相等,返回"0秒"
return result.length > 0 ? result.join('') : '0秒'
}
</script>
<template>
<LoadingPage v-if="loading" />
<view v-else-if="data.length > 0" class="timeline">
<view v-for="(item, index) in data" :key="index" class="timeline-item">
<view class="timeline-col left-col">
<view class="timeline-content left">
{{ item.b_name }}
</view>
</view>
<view class="timeline-col right-col">
<view class="timeline-content right">
<view v-if="item.v_user_name" class="title">
{{ item.v_user_name }}
<text v-if="item.v_tosh_user_name">
> {{ item.v_tosh_user_name }}
</text>
</view>
<view>{{ item.b_sh_date }}</view>
<view v-if="computedConsumeTime(index)">
审批耗时:{{ computedConsumeTime(index) }}
</view>
<view>{{ item.b_sh_memo }}</view>
</view>
</view>
<view class="timeline-center">
<view class="timeline-dot" />
</view>
</view>
</view>
<sar-result v-else status="info" title="空空如也" style="margin-top: 64rpx;" />
</template>
<style lang="scss" scoped>
/* 整个容器 */
.timeline {
display: flex;
flex-direction: column;
}
/* 每个步骤项 */
.timeline-item {
position: relative;
display: flex;
align-items: center;
padding: 32rpx 0;
}
.timeline-item:first-child {
padding-top: 0;
}
/* 左右列容器,用来固定左右比例,不受内容内 padding 影响 */
.timeline-col {
box-sizing: border-box;
}
.left-col {
width: 30%;
padding-right: 32rpx; /* 给中间圆点留出空间 */
font-size: 28rpx;
text-align: right;
}
.right-col {
width: 70%;
padding-left: 32rpx; /* 给中间圆点留出空间 */
text-align: left;
}
/* 左右内容,可以自由添加 padding */
.timeline-content {
font-size: 28rpx;
word-break: break-all;
border-radius: 12rpx;
}
.timeline-content.left {
padding: 8rpx;
font-weight: bold;
text-align: center;
background-color: #f0f2f5; /* 轻柔的灰色背景,避免视觉突兀 */
border-radius: 12rpx; /* 让边角更柔和 */
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.1); /* 轻微阴影,增加层次感 */
}
.timeline-content.right {
display: flex;
flex-direction: column;
gap: 4rpx;
padding: 16rpx 32rpx;
color: #666;
background: #f5f5f5;
.title {
font-weight: bold;
color: black;
}
}
/* 中间圆点容器,绝对定位在左右列分界处 */
.timeline-center {
position: absolute;
left: calc(30%);
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
transform: translateX(-50%);
}
/* 圆点 */
.timeline-dot {
width: 24rpx;
height: 24rpx;
background: #007aff;
border-radius: 50%;
}
/* 使用每个步骤项的伪元素绘制竖线 */
.timeline-item::before {
position: absolute;
top: 0;
bottom: 0;
left: calc(30%);
width: 4rpx; /* 可随意调整宽度 */
content: '';
background: #409eff; /* 竖线颜色 */
transform: translateX(-50%);
}
/* 第一项:只显示圆点下方的线 */
.timeline-item:first-child::before {
top: 50%;
}
/* 最后一项:只显示圆点上方的线 */
.timeline-item:last-child::before {
bottom: 50%;
}
</style>
@@ -36,7 +36,7 @@ async function onSubmit() {
const searchcondition = `b_id = '${userStore.userInfo.userid}' AND ISNULL(b_password, '') = ISNULL('${formModel.oldPassword}', '')`
const searchRes = await loaddataApi({
viewname: 'v_mini_loginid',
viewname: 'b_user',
searchcondition: `${searchcondition}`,
orderfield: 'b_id',
soption: '',
@@ -58,7 +58,7 @@ async function onSubmit() {
const delValues = []
const keys = []
tables.push('b_truck')
tables.push('b_user')
insertValues.push('[]')
updateValues.push(JSON.stringify([updateObj]))
delValues.push('[]')
+72 -42
View File
@@ -26,50 +26,80 @@ export const useCountsStore = defineStore('taskCounts', () => {
// 请求的 SQL 查询列表
const sqlQueries = {
fukuan: `
SELECT COUNT(*) AS all_cnt,
SUM(CASE WHEN rs.b_level >= st.b_level THEN 1 ELSE 0 END) AS v_ash,
SUM(CASE WHEN rs.b_level = st.b_level - 1 THEN 1 ELSE 0 END) AS v_dsh
FROM (
SELECT r.*
FROM (
SELECT b_dj_id, b_module_id, MAX(b_id) AS b_maxid
FROM dbo.sh_record
GROUP BY b_dj_id, b_module_id
) AS a
JOIN sh_record r ON a.b_maxid = r.b_id
WHERE r.b_sh = '1'
) AS rs
LEFT JOIN (
SELECT b_module_id, b_level
FROM sh_b_set
WHERE b_user_id = '${userStore.userInfo.userid}'
) AS st
ON rs.b_module_id = st.b_module_id
AND (rs.b_level = st.b_level - 1 OR rs.b_level = st.b_level)
WHERE rs.b_module_id = 'cw_zdorder_f';
SELECT
COUNT(1) AS all_cnt,
SUM(CASE
WHEN rs.b_level >= st.b_level THEN 1
ELSE 0
END) AS v_ash,
SUM(CASE
WHEN rs.b_level = st.b_level - 1 THEN 1
ELSE 0
END) AS v_dsh
FROM
(
-- 只取每个单据的最新一条
SELECT r.*
FROM
(
SELECT b_dj_id, b_module_id, MAX(b_id) AS b_maxid
FROM dbo.sh_record
GROUP BY b_dj_id, b_module_id
) a
INNER JOIN sh_record r ON a.b_maxid = r.b_id
WHERE r.b_sh = '1'
) rs
CROSS JOIN
(
SELECT TOP 1 b_level
FROM sh_b_set
WHERE b_user_id = '${userStore.userInfo.userid}'
AND b_module_id = 'cw_zdorder_f'
ORDER BY b_level DESC
) st
WHERE rs.b_module_id = 'cw_zdorder_f';
`,
baoxiao: `
SELECT COUNT(*) AS all_cnt,
SUM(CASE WHEN rs.b_level >= st.b_level THEN 1 ELSE 0 END) AS v_ash,
SUM(CASE WHEN rs.b_level = st.b_level - 1 THEN 1 ELSE 0 END) AS v_dsh
FROM (
SELECT r.*
FROM (
SELECT b_dj_id, b_module_id, MAX(b_id) AS b_maxid
FROM dbo.sh_record
GROUP BY b_dj_id, b_module_id
) AS a
JOIN sh_record r ON a.b_maxid = r.b_id
WHERE r.b_sh = '1'
) AS rs
LEFT JOIN (
SELECT b_module_id, b_level
FROM sh_b_set
WHERE b_user_id = '${userStore.userInfo.userid}'
) AS st
ON rs.b_module_id = st.b_module_id
AND (rs.b_level = st.b_level - 1 OR rs.b_level = st.b_level)
WHERE rs.b_module_id = 'car_daily';
SELECT
COUNT(1) AS all_cnt,
SUM(CASE
WHEN rs.b_level >= st.b_level THEN 1
ELSE 0
END) AS v_ash,
SUM(CASE
WHEN rs.b_level = st.b_level - 1 THEN 1
ELSE 0
END) AS v_dsh
FROM
(
SELECT r.*
FROM
(
SELECT b_dj_id, b_module_id, MAX(b_id) AS b_maxid
FROM dbo.sh_record
GROUP BY b_dj_id, b_module_id
) a
INNER JOIN sh_record r ON a.b_maxid = r.b_id
WHERE r.b_sh = '1'
AND r.b_module_id = 'car_daily'
) rs
CROSS JOIN
(
SELECT TOP 1 b_level
FROM sh_b_set
WHERE b_user_id = '${userStore.userInfo.userid}'
AND b_module_id = 'car_daily'
ORDER BY b_level DESC
) st
`,
}
+8 -24
View File
@@ -5,30 +5,14 @@ import { getModuleQxApi } from '@/request/api'
import { useUserStore } from './user'
const PERMISSION_PATH_MAP = [
// {
// moduleId: 'xc_consite_checkcon',
// path: '/pages-sub/jiaoyan',
// },
// {
// moduleId: 'xc_consite_clearcon',
// path: '/pages-sub/qingxi',
// },
// {
// moduleId: 'xc_consite_jiarecon',
// path: '/pages-sub/jiare',
// },
// {
// moduleId: 'xc_consite_wxcon',
// path: '/pages-sub/weixiu',
// },
// {
// moduleId: 'xc_conyard_in',
// path: '/pages-sub/jinchang',
// },
// {
// moduleId: 'xc_conyard_out',
// path: '/pages-sub/chuchang',
// },
{
moduleId: 'cw_zdorder_f',
path: '/pages-sub/fukuan',
},
{
moduleId: 'car_daily',
path: '/pages-sub/baoxiao',
}
]
export const usePermissionStore = defineStore(