|
|
@@ -1,13 +1,509 @@
|
|
|
<template>
|
|
|
- <view>
|
|
|
-
|
|
|
+ <uni-navbar-lite @rightClick="handleRight" :show-right="showRight" title="采购订单"></uni-navbar-lite>
|
|
|
+ <view class="list-page">
|
|
|
+ <view class="search-block">
|
|
|
+ <view class="search-bar">
|
|
|
+ <view class="search-box">
|
|
|
+ <image class="search-icon" src="/static/images/workbench/list/1.png" mode="aspectFit"></image>
|
|
|
+ <input class="search-input" type="text" placeholder="请输入供应商名称" v-model="keyword" @input="handleSearch" />
|
|
|
+ <view v-if="keyword.length > 0" class="clear-btn" @tap="clearKeyword">
|
|
|
+ <text class="clear-btn-text">×</text>
|
|
|
+ </view>
|
|
|
+ <view class="search-btn" @tap="handleSearch">
|
|
|
+ <text class="search-btn-text">搜索</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <scroll-view class="status-tabs" scroll-x="true">
|
|
|
+ <view class="status-tab" :class="{ 'active': currentStatus === '' }" @tap="handleStatusChange('')">
|
|
|
+ <text class="status-tab-text" :class="{ 'active-text': currentStatus === '' }">全部</text>
|
|
|
+ </view>
|
|
|
+ <view class="status-tab" :class="{ 'active': currentStatus === 'PREPARE' }" @tap="handleStatusChange('PREPARE')">
|
|
|
+ <text class="status-tab-text" :class="{ 'active-text': currentStatus === 'PREPARE' }">草稿</text>
|
|
|
+ </view>
|
|
|
+ <view class="status-tab" :class="{ 'active': currentStatus === 'CONFIRMED' }" @tap="handleStatusChange('CONFIRMED')">
|
|
|
+ <text class="status-tab-text" :class="{ 'active-text': currentStatus === 'CONFIRMED' }">已确认</text>
|
|
|
+ </view>
|
|
|
+ <view class="status-tab" :class="{ 'active': currentStatus === 'FINISHED' }" @tap="handleStatusChange('FINISHED')">
|
|
|
+ <text class="status-tab-text" :class="{ 'active-text': currentStatus === 'FINISHED' }">已完成</text>
|
|
|
+ </view>
|
|
|
+ <view class="status-tab" :class="{ 'active': currentStatus === 'CANCEL' }" @tap="handleStatusChange('CANCEL')">
|
|
|
+ <text class="status-tab-text" :class="{ 'active-text': currentStatus === 'CANCEL' }">已取消</text>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <common-list
|
|
|
+ :dataList="dataList"
|
|
|
+ :loading="loading"
|
|
|
+ :refreshing="refreshing"
|
|
|
+ :hasMore="hasMore"
|
|
|
+ @refresh="handleRefresh"
|
|
|
+ @loadMore="loadMore"
|
|
|
+ >
|
|
|
+ <template #default="{ item, index }">
|
|
|
+ <view class="list-item" @tap="handleItemClick(item, index)">
|
|
|
+ <view class="item-container">
|
|
|
+ <view class="item-header">
|
|
|
+ <text class="item-title">{{ getPurchaseCode(item) }}</text>
|
|
|
+ <text class="item-status" :class="'status-' + getStatus(item)">{{ getStatusText(item) }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="item-info">
|
|
|
+ <view class="info-row">
|
|
|
+ <view class="info-item">
|
|
|
+ <text class="info-label">供应商</text>
|
|
|
+ <text class="info-value">{{ getVendorName(item) }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="info-row">
|
|
|
+ <view class="info-item">
|
|
|
+ <text class="info-label">创建人</text>
|
|
|
+ <text class="info-value">{{ getNickName(item) }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="info-item">
|
|
|
+ <text class="info-label">创建时间</text>
|
|
|
+ <text class="info-value">{{ getCreateTime(item) }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="info-row">
|
|
|
+ <view class="info-item">
|
|
|
+ <text class="info-label">金额</text>
|
|
|
+ <text class="info-value warning">{{ getTotalAmount(item) }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="info-item">
|
|
|
+ <text class="info-label">合同</text>
|
|
|
+ <text class="info-value">{{ getContractNumber(item) || '-' }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="item-footer">
|
|
|
+ <text class="item-name">{{ getPurchaseName(item) }}</text>
|
|
|
+ <text class="arrow">›</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </template>
|
|
|
+ </common-list>
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
-<script setup>
|
|
|
-
|
|
|
+<script setup lang="uts">
|
|
|
+ import { ref, onMounted } from 'vue'
|
|
|
+ import { onShow } from '@dcloudio/uni-app'
|
|
|
+ import { listPurchase } from '../../api/purchase/index'
|
|
|
+
|
|
|
+ const dataList = ref<any[]>([])
|
|
|
+ const keyword = ref<string>("")
|
|
|
+ let searchTimer: number | null = null
|
|
|
+ const currentStatus = ref<string>("")
|
|
|
+ const page = ref<number>(1)
|
|
|
+ const pageSize: number = 20
|
|
|
+ const hasMore = ref<boolean>(true)
|
|
|
+ const loading = ref<boolean>(false)
|
|
|
+ const refreshing = ref<boolean>(false)
|
|
|
+ const showRight = ref<boolean>(false)
|
|
|
+
|
|
|
+ const getPurchaseCode = (item: any | null): string => {
|
|
|
+ if (item == null) return ''
|
|
|
+ const jsonItem = item as UTSJSONObject
|
|
|
+ const val = jsonItem['purchaseCode']
|
|
|
+ return val != null ? val.toString() : ''
|
|
|
+ }
|
|
|
+
|
|
|
+ const getPurchaseName = (item: any | null): string => {
|
|
|
+ if (item == null) return ''
|
|
|
+ const jsonItem = item as UTSJSONObject
|
|
|
+ const val = jsonItem['purchaseName']
|
|
|
+ return val != null ? val.toString() : ''
|
|
|
+ }
|
|
|
+
|
|
|
+ const getStatus = (item: any | null): string => {
|
|
|
+ if (item == null) return ''
|
|
|
+ const jsonItem = item as UTSJSONObject
|
|
|
+ const val = jsonItem['status']
|
|
|
+ return val != null ? val.toString() : ''
|
|
|
+ }
|
|
|
+
|
|
|
+ const getStatusText = (item: any | null): string => {
|
|
|
+ if (item == null) return ''
|
|
|
+ const jsonItem = item as UTSJSONObject
|
|
|
+ const val = jsonItem['status']
|
|
|
+ const status = val != null ? val.toString() : ''
|
|
|
+ switch (status) {
|
|
|
+ case 'PREPARE': return '草稿'
|
|
|
+ case 'CONFIRMED': return '已确认'
|
|
|
+ case 'FINISHED': return '已完成'
|
|
|
+ case 'CANCEL': return '已取消'
|
|
|
+ default: return status
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const getVendorName = (item: any | null): string => {
|
|
|
+ if (item == null) return ''
|
|
|
+ const jsonItem = item as UTSJSONObject
|
|
|
+ const val = jsonItem['vendorName']
|
|
|
+ return val != null ? val.toString() : '-'
|
|
|
+ }
|
|
|
+
|
|
|
+ const getTotalAmount = (item: any | null): string => {
|
|
|
+ if (item == null) return '0.00'
|
|
|
+ const jsonItem = item as UTSJSONObject
|
|
|
+ const val = jsonItem['totalAmount']
|
|
|
+ if (val == null) return '0.00'
|
|
|
+ const num = Number(val)
|
|
|
+ if (isNaN(num)) return '0.00'
|
|
|
+ return num.toFixed(2)
|
|
|
+ }
|
|
|
+
|
|
|
+ const getNickName = (item: any | null): string => {
|
|
|
+ if (item == null) return ''
|
|
|
+ const jsonItem = item as UTSJSONObject
|
|
|
+ const val = jsonItem['nickName']
|
|
|
+ return val != null ? val.toString() : '-'
|
|
|
+ }
|
|
|
+
|
|
|
+ const getCreateTime = (item: any | null): string => {
|
|
|
+ if (item == null) return ''
|
|
|
+ const jsonItem = item as UTSJSONObject
|
|
|
+ const val = jsonItem['createTime']
|
|
|
+ if (val == null) return '-'
|
|
|
+ const dateStr = val.toString()
|
|
|
+ if (dateStr.length >= 16) {
|
|
|
+ return dateStr.substring(0, 16)
|
|
|
+ }
|
|
|
+ return dateStr
|
|
|
+ }
|
|
|
+
|
|
|
+ const getContractNumber = (item: any | null): string => {
|
|
|
+ if (item == null) return ''
|
|
|
+ const jsonItem = item as UTSJSONObject
|
|
|
+ const val = jsonItem['contractNumber']
|
|
|
+ return val != null ? val.toString() : ''
|
|
|
+ }
|
|
|
+
|
|
|
+ const getFromSys = (item: any | null): string => {
|
|
|
+ if (item == null) return ''
|
|
|
+ const jsonItem = item as UTSJSONObject
|
|
|
+ const val = jsonItem['fromSys']
|
|
|
+ const sys = val != null ? val.toString() : ''
|
|
|
+ switch (sys) {
|
|
|
+ case 'oa': return 'OA'
|
|
|
+ case 'self': return '本系统'
|
|
|
+ default: return sys
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const loadData = async (isRefresh: boolean): Promise<void> => {
|
|
|
+ if (loading.value) return
|
|
|
+ try {
|
|
|
+ loading.value = true
|
|
|
+ if (isRefresh) {
|
|
|
+ page.value = 1
|
|
|
+ }
|
|
|
+ const searchKeyword = keyword.value != null ? keyword.value : ''
|
|
|
+ const statusParam = currentStatus.value != null ? currentStatus.value : ''
|
|
|
+ const query = new UTSJSONObject()
|
|
|
+ query['pageNum'] = page.value
|
|
|
+ query['pageSize'] = pageSize
|
|
|
+ if (searchKeyword.length > 0) {
|
|
|
+ query['vendorName'] = searchKeyword
|
|
|
+ }
|
|
|
+ if (statusParam.length > 0) {
|
|
|
+ query['status'] = statusParam
|
|
|
+ }
|
|
|
+ const result = await listPurchase(query)
|
|
|
+ const resultObj = result as UTSJSONObject
|
|
|
+ const rows = resultObj['rows']
|
|
|
+ const total = resultObj['total'] as number
|
|
|
+ if (rows != null) {
|
|
|
+ const newData = rows as any[]
|
|
|
+ if (isRefresh) {
|
|
|
+ dataList.value = newData
|
|
|
+ } else {
|
|
|
+ dataList.value = [...dataList.value, ...newData]
|
|
|
+ }
|
|
|
+ hasMore.value = dataList.value.length < total
|
|
|
+ } else {
|
|
|
+ if (isRefresh) {
|
|
|
+ dataList.value = []
|
|
|
+ }
|
|
|
+ hasMore.value = false
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载失败:', e)
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ refreshing.value = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleRefresh = (): void => {
|
|
|
+ refreshing.value = true
|
|
|
+ loadData(true)
|
|
|
+ }
|
|
|
+
|
|
|
+ const loadMore = (): void => {
|
|
|
+ if (!hasMore.value || loading.value) return
|
|
|
+ page.value++
|
|
|
+ loadData(false)
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleSearch = (): void => {
|
|
|
+ const timer = searchTimer
|
|
|
+ if (timer != null) {
|
|
|
+ clearTimeout(timer)
|
|
|
+ }
|
|
|
+ searchTimer = setTimeout(() => {
|
|
|
+ loadData(true)
|
|
|
+ }, 300)
|
|
|
+ }
|
|
|
+
|
|
|
+ const clearKeyword = (): void => {
|
|
|
+ keyword.value = ''
|
|
|
+ loadData(true)
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleStatusChange = (status: string): void => {
|
|
|
+ currentStatus.value = status
|
|
|
+ loadData(true)
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleRight = (): void => {
|
|
|
+ uni.navigateTo({
|
|
|
+ url: `/pages/purchase/createByApply`
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleItemClick = (item: any | null, index: number): void => {
|
|
|
+ if (item == null) return
|
|
|
+ const jsonItem = item as UTSJSONObject
|
|
|
+ const purchaseId = jsonItem['purchaseId']
|
|
|
+ if (purchaseId == null) return
|
|
|
+ const status = getStatus(item)
|
|
|
+ uni.navigateTo({
|
|
|
+ url: `/pages/purchase/detail?id=${purchaseId}`
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ onMounted(() => {
|
|
|
+ loadData(true)
|
|
|
+ })
|
|
|
+
|
|
|
+ onShow(() => {
|
|
|
+ const needRefresh = uni.getStorageSync('needRefresh')
|
|
|
+ if (needRefresh === 'true') {
|
|
|
+ uni.removeStorageSync('needRefresh')
|
|
|
+ loadData(true)
|
|
|
+ }
|
|
|
+ })
|
|
|
</script>
|
|
|
|
|
|
-<style>
|
|
|
+<style lang="scss">
|
|
|
+ .list-page {
|
|
|
+ flex: 1;
|
|
|
+ background-color: #e8f0f9;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-block {
|
|
|
+ background-color: #ffffff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-bar {
|
|
|
+ padding: 20rpx 30rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-box {
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ height: 72rpx;
|
|
|
+ padding: 0 24rpx;
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ border-radius: 36rpx;
|
|
|
+
|
|
|
+ .search-icon {
|
|
|
+ width: 32rpx;
|
|
|
+ height: 32rpx;
|
|
|
+ margin-right: 12rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-input {
|
|
|
+ flex: 1;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333333;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-btn {
|
|
|
+ padding: 10rpx 20rpx;
|
|
|
+ background-color: #007aff;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ margin-left: 10rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-btn-text {
|
|
|
+ color: #ffffff;
|
|
|
+ font-size: 24rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .clear-btn {
|
|
|
+ width: 36rpx;
|
|
|
+ height: 36rpx;
|
|
|
+ border-radius: 18rpx;
|
|
|
+ background-color: #cccccc;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-left: 10rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .clear-btn-text {
|
|
|
+ color: #ffffff;
|
|
|
+ font-size: 24rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-tabs {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ padding: 0rpx 30rpx 20rpx;
|
|
|
+ background-color: #ffffff;
|
|
|
+ white-space: nowrap;
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-tab {
|
|
|
+ display: inline-flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ padding: 16rpx 24rpx;
|
|
|
+ text-align: center;
|
|
|
+ position: relative;
|
|
|
+ margin-right: 16rpx;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ background-color: #007aff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .status-tab-text {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #666666;
|
|
|
+ text-align: center;
|
|
|
+
|
|
|
+ &.active-text {
|
|
|
+ color: #ffffff;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .list-item {
|
|
|
+ margin: 10rpx 20rpx;
|
|
|
+ background-color: #ffffff;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-container {
|
|
|
+ padding: 30rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-header {
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+
|
|
|
+ .item-title {
|
|
|
+ flex: 1;
|
|
|
+ font-size: 32rpx;
|
|
|
+ color: #333333;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-status {
|
|
|
+ font-size: 26rpx;
|
|
|
+ padding: 6rpx 16rpx;
|
|
|
+ border-radius: 6rpx;
|
|
|
+
|
|
|
+ &.status-PREPARE {
|
|
|
+ background-color: #f0f0f0;
|
|
|
+ color: #666666;
|
|
|
+ }
|
|
|
+ &.status-CONFIRMED {
|
|
|
+ background-color: #e6f7ff;
|
|
|
+ color: #1890ff;
|
|
|
+ }
|
|
|
+ &.status-FINISHED {
|
|
|
+ background-color: #f6ffed;
|
|
|
+ color: #52c41a;
|
|
|
+ }
|
|
|
+ &.status-CANCEL {
|
|
|
+ background-color: #fff1f0;
|
|
|
+ color: #ff4d4f;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-info {
|
|
|
+ padding: 20rpx;
|
|
|
+ background-color: #f8f9fa;
|
|
|
+ border-radius: 8rpx;
|
|
|
+
|
|
|
+ .info-row {
|
|
|
+ flex-direction: row;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info-item {
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ flex: 1;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ flex: 1;
|
|
|
+ justify-content: flex-end;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info-label {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #666666;
|
|
|
+ margin-right: 8rpx;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info-value {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #333333;
|
|
|
+ flex: 1;
|
|
|
+
|
|
|
+ &.warning {
|
|
|
+ color: #faad14;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-footer {
|
|
|
+ flex-direction: row;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-top: 20rpx;
|
|
|
+
|
|
|
+ .item-name {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #999999;
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
|
|
|
-</style>
|
|
|
+ .arrow {
|
|
|
+ font-size: 36rpx;
|
|
|
+ color: #cccccc;
|
|
|
+ }
|
|
|
+ }
|
|
|
+</style>
|