固定资产类别使用新api
This commit is contained in:
parent
38a551da22
commit
74ad0b29e2
@ -155,12 +155,6 @@
|
|||||||
"navigationBarTitleText": "审批详情"
|
"navigationBarTitleText": "审批详情"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"path": "pages/consumables/temp-catalog",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "耗材种类管理"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"path": "pages/fixed-assets/scan",
|
"path": "pages/fixed-assets/scan",
|
||||||
"style": {
|
"style": {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -47,14 +47,28 @@ export interface ConsumableRequest {
|
|||||||
items: RequestItem[]
|
items: RequestItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CatalogItem {
|
export interface SKUResponse {
|
||||||
id?: string
|
id: number
|
||||||
name: string
|
skuCode?: string
|
||||||
spec?: string
|
specification?: string
|
||||||
|
barcode: string
|
||||||
|
supplierId?: number
|
||||||
|
purchasePrice?: number
|
||||||
|
sellingPrice?: number
|
||||||
unit?: string
|
unit?: string
|
||||||
isActive?: boolean
|
minStock?: number
|
||||||
createdAt?: string
|
maxStock?: number
|
||||||
updatedAt?: string
|
status?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GoodsWithSKU {
|
||||||
|
id: number
|
||||||
|
goodsName: string
|
||||||
|
categoryId?: number
|
||||||
|
brand?: string
|
||||||
|
status?: number
|
||||||
|
hospId?: string
|
||||||
|
skus: SKUResponse[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ListQuery {
|
export interface ListQuery {
|
||||||
@ -66,10 +80,12 @@ export interface ListQuery {
|
|||||||
to?: string
|
to?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CatalogQuery {
|
export interface GoodsQuery {
|
||||||
page?: number
|
page?: number
|
||||||
pageSize?: number
|
pageSize?: number
|
||||||
isActive?: boolean
|
status?: number
|
||||||
|
hospId?: string
|
||||||
|
categoryId?: number
|
||||||
q?: string
|
q?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,41 +164,18 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// 创建耗材目录
|
// 查询易耗品种类列表(含规格/价格)
|
||||||
createCatalog(data: Partial<CatalogItem>) {
|
getConsumableGoods(params?: GoodsQuery) {
|
||||||
return request('/api/consumable-temp-catalog', {
|
return request('/api/consumable-goods', {
|
||||||
method: 'POST',
|
|
||||||
data
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
// 查询目录列表
|
|
||||||
getCatalogList(params?: CatalogQuery) {
|
|
||||||
return request('/api/consumable-temp-catalog', {
|
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
data: params
|
data: params
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获取目录详情
|
// 获取易耗品种类详情(含规格/价格)
|
||||||
getCatalogDetail(id: string) {
|
getConsumableGoodsDetail(id: number) {
|
||||||
return request(`/api/consumable-temp-catalog/${id}`, {
|
return request(`/api/consumable-goods/${id}`, {
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
})
|
})
|
||||||
},
|
|
||||||
|
|
||||||
// 更新目录
|
|
||||||
updateCatalog(id: string, data: Partial<CatalogItem>) {
|
|
||||||
return request(`/api/consumable-temp-catalog/${id}`, {
|
|
||||||
method: 'PUT',
|
|
||||||
data
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
// 停用目录
|
|
||||||
deleteCatalog(id: string) {
|
|
||||||
return request(`/api/consumable-temp-catalog/${id}`, {
|
|
||||||
method: 'DELETE'
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,9 @@ import { formatTime, formatDate, formatDateTime, BASE_URL, getHeaders } from './
|
|||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
import md5 from '@/utils/md5.js'
|
import md5 from '@/utils/md5.js'
|
||||||
|
import util from '@/utils/util.js'
|
||||||
|
|
||||||
|
import config from '@/config'
|
||||||
|
|
||||||
export function login(username: string, password: string): Promise<{
|
export function login(username: string, password: string): Promise<{
|
||||||
code: number;
|
code: number;
|
||||||
@ -39,9 +42,36 @@ export function login(username: string, password: string): Promise<{
|
|||||||
|
|
||||||
header: getHeaders(),
|
header: getHeaders(),
|
||||||
|
|
||||||
success: (res) => {
|
success: async (res) => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (res.statusCode === 200) {
|
if (res.statusCode === 200) {
|
||||||
|
try {
|
||||||
|
const res_ = await util.request({
|
||||||
|
url: config.urls.login,
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
username: username,
|
||||||
|
password: md5.hexMD5(username + password),
|
||||||
|
typecode: '007'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (res_?.data?.code === 0) {
|
||||||
|
const staff = res_.data.data.userLoginInfo
|
||||||
|
// 兼容旧逻辑,附加 role 别名
|
||||||
|
uni.setStorageSync('staff', { ...staff, role: staff.role_ids })
|
||||||
|
uni.setStorageSync('token', res_.data.token)
|
||||||
resolve(res.data as any);
|
resolve(res.data as any);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: res_?.data?.msg || '登录失败', icon: 'error', duration: 3000 })
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
// util.request 已有统一错误提示
|
||||||
|
console.warn('login error', err)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
reject(res);
|
reject(res);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,43 +1,93 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="page">
|
<view class="page">
|
||||||
<!-- 顶部标题栏 -->
|
<!-- 顶部统一卡片 -->
|
||||||
<view class="header">
|
<view class="top-card card">
|
||||||
<text class="title">临时易耗品审批</text>
|
<!-- 统计信息 -->
|
||||||
|
<view class="stats-row">
|
||||||
|
<view class="stat-item">
|
||||||
|
<text class="stat-value">{{ totalCount }}</text>
|
||||||
|
<text class="stat-label">申请总数</text>
|
||||||
|
</view>
|
||||||
|
<view class="stat-item">
|
||||||
|
<text class="stat-value highlight">{{ pendingCount }}</text>
|
||||||
|
<text class="stat-label">待审批</text>
|
||||||
|
</view>
|
||||||
|
<view class="stat-item">
|
||||||
|
<text class="stat-value warning">{{ approvedCount }}</text>
|
||||||
|
<text class="stat-label">待发放</text>
|
||||||
|
</view>
|
||||||
|
<view class="stat-item">
|
||||||
|
<text class="stat-value success">{{ completedCount }}</text>
|
||||||
|
<text class="stat-label">已完成</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 筛选区 -->
|
<!-- 筛选标签 -->
|
||||||
<view class="filter-bar">
|
<view class="filter-tabs">
|
||||||
<view class="switch-item">
|
<!-- <view
|
||||||
<text class="switch-label">显示已完成</text>
|
class="filter-tab"
|
||||||
<switch :checked="showCompleted" @change="onShowCompletedChange" color="#3a5ddd" />
|
:class="{ active: currentStatus === '' }"
|
||||||
|
@tap="changeFilter('')"
|
||||||
|
>
|
||||||
|
全部
|
||||||
|
</view> -->
|
||||||
|
<view class="filter-tab"
|
||||||
|
:class="{ active: currentStatus === 'PENDING' }"
|
||||||
|
@tap="changeFilter('PENDING')">
|
||||||
|
待审批
|
||||||
|
</view>
|
||||||
|
<view class="filter-tab"
|
||||||
|
:class="{ active: currentStatus === 'APPROVED' }"
|
||||||
|
@tap="changeFilter('APPROVED')">
|
||||||
|
待发放
|
||||||
|
</view>
|
||||||
|
<view class="filter-tab"
|
||||||
|
:class="{ active: currentStatus === 'COMPLETED' }"
|
||||||
|
@tap="changeFilter('COMPLETED')">
|
||||||
|
已完成
|
||||||
|
</view>
|
||||||
|
<view class="filter-tab"
|
||||||
|
:class="{ active: currentStatus === 'REJECTED' }"
|
||||||
|
@tap="changeFilter('REJECTED')">
|
||||||
|
已拒绝
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 申请列表 -->
|
<!-- 申请列表 -->
|
||||||
<scroll-view scroll-y class="list-container" @scrolltolower="loadMore">
|
<scroll-view scroll-y class="list-container" @scrolltolower="loadMore">
|
||||||
<view v-if="list.length === 0 && !loading" class="empty">
|
<view v-if="list.length === 0 && !loading" class="empty">
|
||||||
<image class="empty-icon" src="/static/icons/consumables-temp-approve.png" mode="aspectFit" />
|
<image class="empty-icon"
|
||||||
|
src="/static/icons/consumables-temp-approve.png" mode="aspectFit" />
|
||||||
<text class="empty-text">暂无审批记录</text>
|
<text class="empty-text">暂无审批记录</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-for="item in list" :key="item.id" class="card" @tap="goToApprove(item.id)">
|
<view v-for="item in list" :key="item.id" class="card"
|
||||||
|
@tap="goToApprove(item.id)">
|
||||||
<!-- 进度条 -->
|
<!-- 进度条 -->
|
||||||
<view class="progress-bar">
|
<view class="progress-bar">
|
||||||
<view class="progress-step" :class="{ active: true, completed: ['APPROVED', 'COMPLETED', 'REJECTED', 'CANCELLED'].includes(item.status || '') }">
|
<view class="progress-step"
|
||||||
|
:class="{ active: true, completed: ['APPROVED', 'COMPLETED', 'REJECTED', 'CANCELLED'].includes(item.status || '') }">
|
||||||
<view class="step-circle">
|
<view class="step-circle">
|
||||||
<text class="step-icon">{{ ['REJECTED', 'CANCELLED'].includes(item.status || '') ? '✕' : '✓' }}</text>
|
<text class="step-icon">{{ ['REJECTED',
|
||||||
|
'CANCELLED'].includes(item.status || '') ? '✕' : '✓' }}</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="step-label">待审批</text>
|
<text class="step-label">待审批</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="progress-line" :class="{ completed: ['APPROVED', 'COMPLETED'].includes(item.status || '') }"></view>
|
<view class="progress-line"
|
||||||
<view class="progress-step" :class="{ active: ['APPROVED', 'COMPLETED'].includes(item.status || ''), completed: item.status === 'COMPLETED' }">
|
:class="{ completed: ['APPROVED', 'COMPLETED'].includes(item.status || '') }">
|
||||||
|
</view>
|
||||||
|
<view class="progress-step"
|
||||||
|
:class="{ active: ['APPROVED', 'COMPLETED'].includes(item.status || ''), completed: item.status === 'COMPLETED' }">
|
||||||
<view class="step-circle">
|
<view class="step-circle">
|
||||||
<text class="step-icon">✓</text>
|
<text class="step-icon">✓</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="step-label">待发放</text>
|
<text class="step-label">待发放</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="progress-line" :class="{ completed: item.status === 'COMPLETED' }"></view>
|
<view class="progress-line"
|
||||||
<view class="progress-step" :class="{ active: item.status === 'COMPLETED', completed: item.status === 'COMPLETED' }">
|
:class="{ completed: item.status === 'COMPLETED' }"></view>
|
||||||
|
<view class="progress-step"
|
||||||
|
:class="{ active: item.status === 'COMPLETED', completed: item.status === 'COMPLETED' }">
|
||||||
<view class="step-circle">
|
<view class="step-circle">
|
||||||
<text class="step-icon">✓</text>
|
<text class="step-icon">✓</text>
|
||||||
</view>
|
</view>
|
||||||
@ -46,7 +96,8 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="card-header">
|
<view class="card-header">
|
||||||
<view class="status-badge" :class="`status-${item.status?.toLowerCase()}`">
|
<view class="status-badge"
|
||||||
|
:class="`status-${item.status?.toLowerCase()}`">
|
||||||
{{ getStatusText(item.status) }}
|
{{ getStatusText(item.status) }}
|
||||||
</view>
|
</view>
|
||||||
<text class="time">{{ formatTime(item.createdAt) }}</text>
|
<text class="time">{{ formatTime(item.createdAt) }}</text>
|
||||||
@ -55,7 +106,8 @@
|
|||||||
<view class="card-body">
|
<view class="card-body">
|
||||||
<view class="applicant-info">
|
<view class="applicant-info">
|
||||||
<view class="avatar">
|
<view class="avatar">
|
||||||
<text class="avatar-text">{{ getAvatarText(item.applicantName) }}</text>
|
<text class="avatar-text">{{ getAvatarText(item.applicantName)
|
||||||
|
}}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="applicant-details">
|
<view class="applicant-details">
|
||||||
<text class="name">{{ item.applicantName || '未填写' }}</text>
|
<text class="name">{{ item.applicantName || '未填写' }}</text>
|
||||||
@ -70,7 +122,10 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="grid-item">
|
<view class="grid-item">
|
||||||
<text class="grid-label">物料数量</text>
|
<text class="grid-label">物料数量</text>
|
||||||
<text class="grid-value highlight">{{ item.items?.length || 0 }} 项</text>
|
<text class="grid-value highlight">{{ item.items?.length || 0 }} 项
|
||||||
|
<text class="grid-value">({{item.items.map(it => it.name).join(', ')}})</text>
|
||||||
|
|
||||||
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -81,11 +136,14 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="card-footer" v-if="item.status === 'PENDING'">
|
<view class="card-footer" v-if="item.status === 'PENDING'">
|
||||||
<button class="btn-reject" @tap.stop="handleReject(item.id)">拒绝</button>
|
<button class="btn-reject"
|
||||||
<button class="btn-approve" @tap.stop="handleApprove(item.id)">通过</button>
|
@tap.stop="handleReject(item.id)">拒绝</button>
|
||||||
|
<button class="btn-approve"
|
||||||
|
@tap.stop="handleApprove(item.id)">通过</button>
|
||||||
</view>
|
</view>
|
||||||
<view class="card-footer" v-else-if="item.status === 'APPROVED'">
|
<view class="card-footer" v-else-if="item.status === 'APPROVED'">
|
||||||
<button class="btn-deliver" @tap.stop="handleDeliver(item.id)">确认发放</button>
|
<button class="btn-deliver"
|
||||||
|
@tap.stop="handleDeliver(item.id)">确认发放</button>
|
||||||
</view>
|
</view>
|
||||||
<view class="card-footer" v-else>
|
<view class="card-footer" v-else>
|
||||||
<text class="detail-link">查看详情 →</text>
|
<text class="detail-link">查看详情 →</text>
|
||||||
@ -105,7 +163,8 @@
|
|||||||
<view v-if="showApproveModal" class="modal-mask" @tap="closeApproveModal">
|
<view v-if="showApproveModal" class="modal-mask" @tap="closeApproveModal">
|
||||||
<view class="modal-content" @tap.stop>
|
<view class="modal-content" @tap.stop>
|
||||||
<view class="modal-header">
|
<view class="modal-header">
|
||||||
<text class="modal-title">{{ approveType === 'APPROVED' ? '通过申请' : '拒绝申请' }}</text>
|
<text class="modal-title">{{ approveType === 'APPROVED' ? '通过申请' :
|
||||||
|
'拒绝申请' }}</text>
|
||||||
<text class="modal-close" @tap="closeApproveModal">✕</text>
|
<text class="modal-close" @tap="closeApproveModal">✕</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="modal-body">
|
<view class="modal-body">
|
||||||
@ -120,8 +179,7 @@
|
|||||||
<button class="btn-cancel" @tap="closeApproveModal">取消</button>
|
<button class="btn-cancel" @tap="closeApproveModal">取消</button>
|
||||||
<button class="btn-confirm"
|
<button class="btn-confirm"
|
||||||
:class="approveType === 'REJECTED' ? 'btn-danger' : ''"
|
:class="approveType === 'REJECTED' ? 'btn-danger' : ''"
|
||||||
@tap="confirmApprove"
|
@tap="confirmApprove" :disabled="submitting">
|
||||||
:disabled="submitting">
|
|
||||||
{{ submitting ? '提交中...' : '确认' }}
|
{{ submitting ? '提交中...' : '确认' }}
|
||||||
</button>
|
</button>
|
||||||
</view>
|
</view>
|
||||||
@ -131,20 +189,28 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { onLoad, onShow, onUnload } from '@dcloudio/uni-app'
|
import { onLoad, onShow, onUnload } from '@dcloudio/uni-app'
|
||||||
import consumableTempAPI, { type ConsumableRequest } from '@/pages/api/consumable-temp'
|
import consumableTempAPI, { type ConsumableRequest } from '@/pages/api/consumable-temp'
|
||||||
import { refreshNow } from '@/utils/messageManager'
|
import { refreshNow } from '@/utils/messageManager'
|
||||||
|
|
||||||
// 数据状态
|
// 数据状态
|
||||||
const list = ref<ConsumableRequest[]>([])
|
const list = ref<ConsumableRequest[]>([])
|
||||||
|
const allList = ref<ConsumableRequest[]>([]) // 所有数据,用于统计
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const showCompleted = ref(false) // 是否显示已完成
|
const currentStatus = ref('PENDING') // 当前筛选状态
|
||||||
const page = ref(1)
|
const page = ref(1)
|
||||||
const pageSize = ref(10)
|
const pageSize = ref(10)
|
||||||
const hasMore = ref(true)
|
const hasMore = ref(true)
|
||||||
const needRefresh = ref(false) // 是否需要刷新
|
const needRefresh = ref(false) // 是否需要刷新
|
||||||
|
|
||||||
|
// 统计数据(基于所有数据)
|
||||||
|
const totalCount = computed(() => allList.value.length)
|
||||||
|
const pendingCount = computed(() => allList.value.filter(item => item.status === 'PENDING').length)
|
||||||
|
const approvedCount = computed(() => allList.value.filter(item => item.status === 'APPROVED').length)
|
||||||
|
const completedCount = computed(() => allList.value.filter(item => item.status === 'COMPLETED').length)
|
||||||
|
const rejectedCount = computed(() => allList.value.filter(item => item.status === 'REJECTED').length)
|
||||||
|
|
||||||
// 审批相关
|
// 审批相关
|
||||||
const showApproveModal = ref(false)
|
const showApproveModal = ref(false)
|
||||||
const approveType = ref<'APPROVED' | 'REJECTED' | 'COMPLETED'>('APPROVED')
|
const approveType = ref<'APPROVED' | 'REJECTED' | 'COMPLETED'>('APPROVED')
|
||||||
@ -167,19 +233,27 @@ async function loadList(reset = false) {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 如果是重置,先加载所有数据用于统计
|
||||||
|
if (reset) {
|
||||||
|
const allRes = await consumableTempAPI.getRequestList({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 99 // 获取所有数据用于统计
|
||||||
|
})
|
||||||
|
|
||||||
|
if (allRes.statusCode === 200 && (allRes.data as any)?.data) {
|
||||||
|
allList.value = (allRes.data as any).data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载当前筛选条件的数据
|
||||||
const res = await consumableTempAPI.getRequestList({
|
const res = await consumableTempAPI.getRequestList({
|
||||||
page: page.value,
|
page: page.value,
|
||||||
pageSize: pageSize.value
|
pageSize: pageSize.value,
|
||||||
|
status: currentStatus.value || undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
if (res.statusCode === 200 && (res.data as any)?.data) {
|
if (res.statusCode === 200 && (res.data as any)?.data) {
|
||||||
let newData = (res.data as any).data
|
const newData = (res.data as any).data
|
||||||
|
|
||||||
// 根据开关过滤数据:默认不显示 COMPLETED
|
|
||||||
if (!showCompleted.value) {
|
|
||||||
newData = newData.filter((item: ConsumableRequest) => item.status !== 'COMPLETED' && item.status !== 'REJECTED')
|
|
||||||
}
|
|
||||||
|
|
||||||
list.value = reset ? newData : [...list.value, ...newData]
|
list.value = reset ? newData : [...list.value, ...newData]
|
||||||
hasMore.value = newData.length >= pageSize.value
|
hasMore.value = newData.length >= pageSize.value
|
||||||
} else {
|
} else {
|
||||||
@ -193,9 +267,9 @@ async function loadList(reset = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示已完成开关改变
|
// 切换筛选
|
||||||
function onShowCompletedChange(e: any) {
|
function changeFilter(status: string) {
|
||||||
showCompleted.value = e.detail.value
|
currentStatus.value = status
|
||||||
loadList(true)
|
loadList(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,35 +439,81 @@ onUnload(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
padding-bottom: 32rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.card {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 32rpx;
|
border-radius: 20rpx;
|
||||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
/* 顶部统一卡片 */
|
||||||
font-size: 36rpx;
|
.top-card {
|
||||||
font-weight: 600;
|
margin: 24rpx 24rpx 16rpx;
|
||||||
color: #111;
|
padding: 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-bar {
|
/* 统计信息行 */
|
||||||
background: #fff;
|
.stats-row {
|
||||||
padding: 24rpx 32rpx;
|
|
||||||
margin-bottom: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-item {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-around;
|
||||||
font-size: 28rpx;
|
margin-bottom: 24rpx;
|
||||||
|
padding-bottom: 20rpx;
|
||||||
|
border-bottom: 2rpx solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 600;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.switch-label {
|
.stat-value.highlight {
|
||||||
|
color: #ff6b6b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value.warning {
|
||||||
|
color: #ffa726;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value.success {
|
||||||
|
color: #51cf66;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 筛选标签 */
|
||||||
|
.filter-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-tab {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 12rpx 8rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
background: #f8f9fa;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-tab.active {
|
||||||
|
background: #3a5ddd;
|
||||||
|
color: #fff;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,588 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="page">
|
|
||||||
<!-- 顶部标题栏 -->
|
|
||||||
<view class="header">
|
|
||||||
<text class="title">耗材种类管理</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 搜索栏 -->
|
|
||||||
<view class="search-bar">
|
|
||||||
<view class="search-box">
|
|
||||||
<text class="search-icon">🔍</text>
|
|
||||||
<input class="search-input" v-model="searchKeyword" placeholder="搜索物料名称或规格" @confirm="loadList(true)" />
|
|
||||||
</view>
|
|
||||||
<button class="btn-add" @tap="showAddModal">+ 新增</button>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 列表 -->
|
|
||||||
<scroll-view scroll-y class="list-container" @scrolltolower="loadMore">
|
|
||||||
<view v-if="list.length === 0 && !loading" class="empty">
|
|
||||||
<text class="empty-text">暂无耗材种类</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-for="item in list" :key="item.id" class="catalog-card">
|
|
||||||
<view class="card-main">
|
|
||||||
<view class="catalog-info">
|
|
||||||
<text class="catalog-name">{{ item.name }}</text>
|
|
||||||
<view class="catalog-meta">
|
|
||||||
<text v-if="item.spec" class="meta-item">规格: {{ item.spec }}</text>
|
|
||||||
<text v-if="item.unit" class="meta-item">单位: {{ item.unit }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="catalog-status" :class="item.isActive ? 'active' : 'inactive'">
|
|
||||||
{{ item.isActive ? '启用' : '停用' }}
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="card-footer">
|
|
||||||
<text class="time">{{ formatTime(item.updatedAt) }}</text>
|
|
||||||
<view class="actions">
|
|
||||||
<button class="btn-edit" @tap="handleEdit(item)">编辑</button>
|
|
||||||
<button v-if="item.isActive" class="btn-disable" @tap="handleDisable(item.id)">停用</button>
|
|
||||||
<button v-else class="btn-enable" @tap="handleEnable(item.id)">启用</button>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-if="loading" class="loading">
|
|
||||||
<text>加载中...</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-if="!hasMore && list.length > 0" class="no-more">
|
|
||||||
<text>没有更多了</text>
|
|
||||||
</view>
|
|
||||||
</scroll-view>
|
|
||||||
|
|
||||||
<!-- 编辑/新增弹窗 -->
|
|
||||||
<view v-if="showModal" class="modal-mask" @tap="closeModal">
|
|
||||||
<view class="modal-content" @tap.stop>
|
|
||||||
<view class="modal-header">
|
|
||||||
<text class="modal-title">{{ isEdit ? '编辑种类' : '新增种类' }}</text>
|
|
||||||
<text class="modal-close" @tap="closeModal">✕</text>
|
|
||||||
</view>
|
|
||||||
<view class="modal-body">
|
|
||||||
<view class="form-item">
|
|
||||||
<text class="form-label required">物料名称</text>
|
|
||||||
<input class="form-input" v-model="formData.name" placeholder="请输入物料名称" />
|
|
||||||
</view>
|
|
||||||
<view class="form-item">
|
|
||||||
<text class="form-label">规格</text>
|
|
||||||
<input class="form-input" v-model="formData.spec" placeholder="请输入规格(可选)" />
|
|
||||||
</view>
|
|
||||||
<view class="form-item">
|
|
||||||
<text class="form-label">单位</text>
|
|
||||||
<input class="form-input" v-model="formData.unit" placeholder="请输入单位(可选)" />
|
|
||||||
</view>
|
|
||||||
<view class="form-item">
|
|
||||||
<text class="form-label">状态</text>
|
|
||||||
<view class="switch-box">
|
|
||||||
<switch :checked="formData.isActive" @change="onSwitchChange" color="#667eea" />
|
|
||||||
<text class="switch-label">{{ formData.isActive ? '启用' : '停用' }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="modal-footer">
|
|
||||||
<button class="btn-cancel" @tap="closeModal">取消</button>
|
|
||||||
<button class="btn-confirm" @tap="handleSubmit" :disabled="submitting">
|
|
||||||
{{ submitting ? '提交中...' : '确认' }}
|
|
||||||
</button>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted } from 'vue'
|
|
||||||
import consumableTempAPI, { type CatalogItem } from '@/pages/api/consumable-temp'
|
|
||||||
|
|
||||||
// 数据状态
|
|
||||||
const list = ref<CatalogItem[]>([])
|
|
||||||
const loading = ref(false)
|
|
||||||
const searchKeyword = ref('')
|
|
||||||
const page = ref(1)
|
|
||||||
const pageSize = ref(20)
|
|
||||||
const hasMore = ref(true)
|
|
||||||
|
|
||||||
// 弹窗相关
|
|
||||||
const showModal = ref(false)
|
|
||||||
const isEdit = ref(false)
|
|
||||||
const formData = ref<CatalogItem>({
|
|
||||||
name: '',
|
|
||||||
spec: '',
|
|
||||||
unit: '',
|
|
||||||
isActive: true
|
|
||||||
})
|
|
||||||
const submitting = ref(false)
|
|
||||||
|
|
||||||
// 加载列表
|
|
||||||
async function loadList(reset = false) {
|
|
||||||
if (loading.value) return
|
|
||||||
|
|
||||||
if (reset) {
|
|
||||||
page.value = 1
|
|
||||||
hasMore.value = true
|
|
||||||
list.value = []
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasMore.value) return
|
|
||||||
|
|
||||||
loading.value = true
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await consumableTempAPI.getCatalogList({
|
|
||||||
page: page.value,
|
|
||||||
pageSize: pageSize.value,
|
|
||||||
q: searchKeyword.value || undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
if (res.statusCode === 200 && (res.data as any)?.data) {
|
|
||||||
const newData = (res.data as any).data
|
|
||||||
list.value = reset ? newData : [...list.value, ...newData]
|
|
||||||
hasMore.value = newData.length >= pageSize.value
|
|
||||||
} else {
|
|
||||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('加载列表失败:', err)
|
|
||||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载更多
|
|
||||||
function loadMore() {
|
|
||||||
if (!loading.value && hasMore.value) {
|
|
||||||
page.value++
|
|
||||||
loadList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化时间
|
|
||||||
function formatTime(time?: string) {
|
|
||||||
if (!time) return '-'
|
|
||||||
const date = new Date(time)
|
|
||||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示新增弹窗
|
|
||||||
function showAddModal() {
|
|
||||||
isEdit.value = false
|
|
||||||
formData.value = {
|
|
||||||
name: '',
|
|
||||||
spec: '',
|
|
||||||
unit: '',
|
|
||||||
isActive: true
|
|
||||||
}
|
|
||||||
showModal.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 编辑
|
|
||||||
function handleEdit(item: CatalogItem) {
|
|
||||||
isEdit.value = true
|
|
||||||
formData.value = { ...item }
|
|
||||||
showModal.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭弹窗
|
|
||||||
function closeModal() {
|
|
||||||
showModal.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 开关改变
|
|
||||||
function onSwitchChange(e: any) {
|
|
||||||
formData.value.isActive = e.detail.value
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交表单
|
|
||||||
async function handleSubmit() {
|
|
||||||
if (!formData.value.name) {
|
|
||||||
uni.showToast({ title: '请输入物料名称', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (submitting.value) return
|
|
||||||
|
|
||||||
submitting.value = true
|
|
||||||
|
|
||||||
try {
|
|
||||||
let res
|
|
||||||
if (isEdit.value && formData.value.id) {
|
|
||||||
res = await consumableTempAPI.updateCatalog(formData.value.id, formData.value)
|
|
||||||
} else {
|
|
||||||
res = await consumableTempAPI.createCatalog(formData.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.statusCode === 200 || res.statusCode === 201) {
|
|
||||||
uni.showToast({
|
|
||||||
title: isEdit.value ? '更新成功' : '创建成功',
|
|
||||||
icon: 'success'
|
|
||||||
})
|
|
||||||
closeModal()
|
|
||||||
loadList(true)
|
|
||||||
} else {
|
|
||||||
uni.showToast({ title: '操作失败', icon: 'none' })
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('提交失败:', err)
|
|
||||||
uni.showToast({ title: '操作失败', icon: 'none' })
|
|
||||||
} finally {
|
|
||||||
submitting.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 停用
|
|
||||||
function handleDisable(id?: string) {
|
|
||||||
if (!id) return
|
|
||||||
|
|
||||||
uni.showModal({
|
|
||||||
title: '确认停用',
|
|
||||||
content: '停用后该种类将不会出现在申请选择列表中',
|
|
||||||
success: async (res) => {
|
|
||||||
if (res.confirm) {
|
|
||||||
try {
|
|
||||||
const result = await consumableTempAPI.deleteCatalog(id)
|
|
||||||
if (result.statusCode === 204) {
|
|
||||||
uni.showToast({ title: '已停用', icon: 'success' })
|
|
||||||
loadList(true)
|
|
||||||
} else {
|
|
||||||
uni.showToast({ title: '操作失败', icon: 'none' })
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('停用失败:', err)
|
|
||||||
uni.showToast({ title: '操作失败', icon: 'none' })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 启用
|
|
||||||
async function handleEnable(id?: string) {
|
|
||||||
if (!id) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await consumableTempAPI.updateCatalog(id, { isActive: true })
|
|
||||||
if (res.statusCode === 200) {
|
|
||||||
uni.showToast({ title: '已启用', icon: 'success' })
|
|
||||||
loadList(true)
|
|
||||||
} else {
|
|
||||||
uni.showToast({ title: '操作失败', icon: 'none' })
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('启用失败:', err)
|
|
||||||
uni.showToast({ title: '操作失败', icon: 'none' })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadList(true)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.page {
|
|
||||||
min-height: 100vh;
|
|
||||||
background: #f5f7fb;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
background: #fff;
|
|
||||||
padding: 32rpx;
|
|
||||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 36rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #111;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-bar {
|
|
||||||
background: #fff;
|
|
||||||
padding: 24rpx 32rpx;
|
|
||||||
margin-bottom: 12rpx;
|
|
||||||
display: flex;
|
|
||||||
gap: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 20rpx;
|
|
||||||
background: #f5f7fb;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
height: 72rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-icon {
|
|
||||||
font-size: 28rpx;
|
|
||||||
margin-right: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-input {
|
|
||||||
flex: 1;
|
|
||||||
height: 72rpx;
|
|
||||||
line-height: 72rpx;
|
|
||||||
font-size: 28rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-add {
|
|
||||||
padding: 0 24rpx;
|
|
||||||
height: 72rpx;
|
|
||||||
line-height: 72rpx;
|
|
||||||
background: #3a5ddd;
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
font-size: 28rpx;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-container {
|
|
||||||
flex: 1;
|
|
||||||
padding: 0 32rpx 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty {
|
|
||||||
padding: 120rpx 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-text {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.catalog-card {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
padding: 24rpx;
|
|
||||||
margin-bottom: 16rpx;
|
|
||||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-main {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-bottom: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.catalog-info {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.catalog-name {
|
|
||||||
font-size: 30rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #111;
|
|
||||||
}
|
|
||||||
|
|
||||||
.catalog-meta {
|
|
||||||
display: flex;
|
|
||||||
gap: 16rpx;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta-item {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #666;
|
|
||||||
padding: 4rpx 12rpx;
|
|
||||||
background: #f5f7fb;
|
|
||||||
border-radius: 6rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.catalog-status {
|
|
||||||
padding: 8rpx 16rpx;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.catalog-status.active {
|
|
||||||
background: #e8f5e9;
|
|
||||||
color: #2e7d32;
|
|
||||||
}
|
|
||||||
|
|
||||||
.catalog-status.inactive {
|
|
||||||
background: #f5f5f5;
|
|
||||||
color: #757575;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding-top: 16rpx;
|
|
||||||
border-top: 1rpx solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 8rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-edit,
|
|
||||||
.btn-disable,
|
|
||||||
.btn-enable {
|
|
||||||
padding: 8rpx 20rpx;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-edit {
|
|
||||||
background: #e3f2fd;
|
|
||||||
color: #1976d2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-disable {
|
|
||||||
background: #fff3e0;
|
|
||||||
color: #f57c00;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-enable {
|
|
||||||
background: #e8f5e9;
|
|
||||||
color: #2e7d32;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading,
|
|
||||||
.no-more {
|
|
||||||
padding: 32rpx;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 弹窗样式 */
|
|
||||||
.modal-mask {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
width: 600rpx;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-header {
|
|
||||||
padding: 32rpx;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
border-bottom: 1rpx solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-title {
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #111;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-close {
|
|
||||||
font-size: 40rpx;
|
|
||||||
color: #999;
|
|
||||||
width: 60rpx;
|
|
||||||
height: 60rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-body {
|
|
||||||
padding: 32rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-item {
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-item:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-label {
|
|
||||||
display: block;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-label.required::before {
|
|
||||||
content: '*';
|
|
||||||
color: #ff4d4f;
|
|
||||||
margin-right: 4rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-input {
|
|
||||||
width: 100%;
|
|
||||||
height: 72rpx;
|
|
||||||
line-height: 72rpx;
|
|
||||||
padding: 0 20rpx;
|
|
||||||
background: #f5f7fb;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
font-size: 28rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-box {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-label {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-footer {
|
|
||||||
padding: 24rpx 32rpx;
|
|
||||||
border-top: 1rpx solid #f0f0f0;
|
|
||||||
display: flex;
|
|
||||||
gap: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-cancel,
|
|
||||||
.btn-confirm {
|
|
||||||
flex: 1;
|
|
||||||
height: 80rpx;
|
|
||||||
line-height: 80rpx;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
font-size: 30rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-cancel {
|
|
||||||
background: #f5f5f5;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-confirm {
|
|
||||||
background: #3a5ddd;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-confirm[disabled] {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -9,28 +9,33 @@
|
|||||||
<view class="form-card">
|
<view class="form-card">
|
||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
<text class="form-label required">申请人姓名</text>
|
<text class="form-label required">申请人姓名</text>
|
||||||
<input class="form-input" v-model="formData.applicantName" placeholder="请输入申请人姓名" />
|
<input class="form-input" v-model="formData.applicantName"
|
||||||
|
placeholder="请输入申请人姓名" />
|
||||||
</view>
|
</view>
|
||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
<text class="form-label required">联系电话</text>
|
<text class="form-label required">联系电话</text>
|
||||||
<input class="form-input" v-model="formData.applicantPhone" type="number" placeholder="请输入联系电话" />
|
<input class="form-input" v-model="formData.applicantPhone"
|
||||||
|
type="number" placeholder="请输入联系电话" />
|
||||||
</view>
|
</view>
|
||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
<text class="form-label">部门</text>
|
<text class="form-label required">需要时间</text>
|
||||||
<input class="form-input" v-model="formData.department" placeholder="请输入部门" />
|
<picker mode="date" :value="formData.neededAt"
|
||||||
</view>
|
@change="onDateChange">
|
||||||
<view class="form-item">
|
|
||||||
<text class="form-label">申请原因</text>
|
|
||||||
<textarea class="form-textarea" v-model="formData.reason" placeholder="请输入申请原因" maxlength="200" />
|
|
||||||
</view>
|
|
||||||
<view class="form-item">
|
|
||||||
<text class="form-label">需要时间</text>
|
|
||||||
<picker mode="date" :value="formData.neededAt" @change="onDateChange">
|
|
||||||
<view class="form-picker">
|
<view class="form-picker">
|
||||||
<text>{{ formData.neededAt || '请选择需要时间' }}</text>
|
<text>{{ formData.neededAt || '请选择需要时间' }}</text>
|
||||||
</view>
|
</view>
|
||||||
</picker>
|
</picker>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="form-label">部门</text>
|
||||||
|
<input class="form-input" v-model="formData.department"
|
||||||
|
placeholder="请输入部门" />
|
||||||
|
</view>
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="form-label">申请原因</text>
|
||||||
|
<textarea class="form-textarea" v-model="formData.reason"
|
||||||
|
placeholder="请输入申请原因" maxlength="200" />
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -38,51 +43,50 @@
|
|||||||
<view class="section">
|
<view class="section">
|
||||||
<view class="section-title">
|
<view class="section-title">
|
||||||
<text>物料明细</text>
|
<text>物料明细</text>
|
||||||
<button class="btn-add" @tap="addItem">+ 添加物料</button>
|
<button class="btn-add" @tap="showGoodsModal">+ 添加物料</button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="formData.items.length === 0" class="empty-items">
|
<view v-if="formData.items.length === 0" class="empty-items">
|
||||||
<text>暂无物料,请点击上方按钮添加</text>
|
<text>暂无物料,请点击上方按钮添加</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-for="(item, index) in formData.items" :key="index" class="item-card">
|
<view v-for="(item, index) in formData.items" :key="index"
|
||||||
|
class="item-card">
|
||||||
<view class="item-header">
|
<view class="item-header">
|
||||||
<text class="item-number">#{{ index + 1 }}</text>
|
<text class="item-number">#{{ index + 1 }}</text>
|
||||||
<button class="btn-delete" @tap="deleteItem(index)">删除</button>
|
<button class="btn-delete" @tap="deleteItem(index)">删除</button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="form-item">
|
<view class="info-row">
|
||||||
<text class="form-label required">物料名称</text>
|
<text class="info-label">物料名称:</text>
|
||||||
<view class="form-row">
|
<text class="info-value">{{ item.name }}</text>
|
||||||
<input class="form-input flex-1" v-model="item.name" placeholder="请输入物料名称" />
|
|
||||||
<button class="btn-catalog" @tap="selectFromCatalog(index)">从目录选择</button>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<view class="info-row" v-if="item.spec">
|
||||||
|
<text class="info-label">规格:</text>
|
||||||
|
<text class="info-value">{{ item.spec }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="info-row" v-if="item.unit">
|
||||||
|
<text class="info-label">单位:</text>
|
||||||
|
<text class="info-value">{{ item.unit }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="info-row" v-if="item.estimatedUnitCost">
|
||||||
|
<text class="info-label">单价:</text>
|
||||||
|
<text class="info-value price">¥{{ item.estimatedUnitCost }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
<text class="form-label">规格</text>
|
|
||||||
<input class="form-input" v-model="item.spec" placeholder="请输入规格" />
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="form-row-group">
|
|
||||||
<view class="form-item flex-1">
|
|
||||||
<text class="form-label required">数量</text>
|
<text class="form-label required">数量</text>
|
||||||
<input class="form-input" v-model.number="item.quantity" type="digit" placeholder="数量" />
|
<input class="form-input" v-model.number="item.quantity"
|
||||||
</view>
|
type="digit" placeholder="请输入数量" />
|
||||||
<view class="form-item flex-1">
|
|
||||||
<text class="form-label">单位</text>
|
|
||||||
<input class="form-input" v-model="item.unit" placeholder="单位" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="form-item">
|
|
||||||
<text class="form-label">预估单价</text>
|
|
||||||
<input class="form-input" v-model.number="item.estimatedUnitCost" type="digit" placeholder="请输入预估单价" />
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
<text class="form-label">备注</text>
|
<text class="form-label">备注</text>
|
||||||
<textarea class="form-textarea" v-model="item.remark" placeholder="请输入备注" maxlength="100" />
|
<textarea class="form-textarea" v-model="item.remark"
|
||||||
|
placeholder="请输入备注" maxlength="100" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -96,28 +100,50 @@
|
|||||||
</button>
|
</button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 目录选择弹窗 -->
|
<!-- 商品选择弹窗 -->
|
||||||
<view v-if="showCatalogModal" class="modal-mask" @tap="closeCatalog">
|
<view v-if="showModal" class="modal-mask" @tap="closeGoodsModal">
|
||||||
<view class="modal-content" @tap.stop>
|
<view class="modal-content" @tap.stop>
|
||||||
<view class="modal-header">
|
<view class="modal-header">
|
||||||
<text class="modal-title">选择物料</text>
|
<text class="modal-title">选择物料</text>
|
||||||
<text class="modal-close" @tap="closeCatalog">✕</text>
|
<text class="modal-close" @tap="closeGoodsModal">✕</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="modal-search">
|
<view class="modal-search">
|
||||||
<input class="search-input" v-model="catalogSearch" placeholder="搜索物料名称" />
|
<input class="search-input" v-model="goodsSearch" placeholder="搜索物料名称"
|
||||||
|
@confirm="searchGoods" />
|
||||||
|
<button class="search-btn" @tap="searchGoods">搜索</button>
|
||||||
</view>
|
</view>
|
||||||
<scroll-view scroll-y class="modal-list">
|
<scroll-view scroll-y class="modal-list" @scrolltolower="loadMoreGoods">
|
||||||
<view v-for="catalog in filteredCatalog" :key="catalog.id"
|
<view v-for="goods in goodsList" :key="goods.id" class="goods-item">
|
||||||
class="catalog-item"
|
<view class="goods-name">{{ goods.goodsName }}</view>
|
||||||
@tap="selectCatalog(catalog)">
|
<view v-if="goods.brand" class="goods-brand">品牌:{{ goods.brand }}
|
||||||
<view class="catalog-name">{{ catalog.name }}</view>
|
</view>
|
||||||
<view class="catalog-info">
|
|
||||||
<text v-if="catalog.spec">{{ catalog.spec }}</text>
|
<!-- SKU列表 -->
|
||||||
<text v-if="catalog.unit">{{ catalog.unit }}</text>
|
<view class="sku-list">
|
||||||
|
<view v-for="sku in goods.skus" :key="sku.id" class="sku-item"
|
||||||
|
@tap="selectSKU(goods, sku)">
|
||||||
|
<view class="sku-info">
|
||||||
|
<text v-if="sku.specification" class="sku-spec">{{
|
||||||
|
sku.specification }}</text>
|
||||||
|
<text v-if="sku.unit" class="sku-unit">{{ sku.unit }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="sku-price" v-if="sku.purchasePrice">
|
||||||
|
¥{{ sku.purchasePrice }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="filteredCatalog.length === 0" class="empty">
|
</view>
|
||||||
<text>暂无数据</text>
|
</view>
|
||||||
|
|
||||||
|
<view v-if="goodsLoading" class="loading">
|
||||||
|
<text>加载中...</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="!hasMoreGoods && goodsList.length > 0" class="no-more">
|
||||||
|
<text>没有更多了</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="goodsList.length === 0 && !goodsLoading" class="empty">
|
||||||
|
<text>暂无数据,请尝试搜索</text>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
@ -126,67 +152,119 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import consumableTempAPI, { type RequestItem, type CatalogItem } from '@/pages/api/consumable-temp'
|
import consumableTempAPI, { type RequestItem, type GoodsWithSKU, type SKUResponse } from '@/pages/api/consumable-temp'
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
applicantName: '',
|
applicantName: uni.getStorageSync('staff')?.user_name || uni.getStorageSync('userName') || '',
|
||||||
applicantPhone: '',
|
applicantPhone: uni.getStorageSync('userName') || '',
|
||||||
department: '',
|
department: uni.getStorageSync('staff')?.hospName || '',
|
||||||
reason: '',
|
reason: '',
|
||||||
neededAt: '',
|
neededAt: new Date().toISOString().split('T')[0], // 默认今天
|
||||||
items: [] as RequestItem[]
|
items: [] as RequestItem[]
|
||||||
})
|
})
|
||||||
|
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
const currentItemIndex = ref(-1)
|
const showModal = ref(false)
|
||||||
const showCatalogModal = ref(false)
|
const goodsList = ref<GoodsWithSKU[]>([])
|
||||||
const catalogList = ref<CatalogItem[]>([])
|
const goodsSearch = ref('')
|
||||||
const catalogSearch = ref('')
|
const goodsLoading = ref(false)
|
||||||
|
const goodsPage = ref(1)
|
||||||
|
const goodsPageSize = ref(10)
|
||||||
|
const hasMoreGoods = ref(true)
|
||||||
|
|
||||||
// 过滤后的目录
|
// 显示商品选择弹窗
|
||||||
const filteredCatalog = computed(() => {
|
function showGoodsModal() {
|
||||||
if (!catalogSearch.value) return catalogList.value
|
showModal.value = true
|
||||||
const keyword = catalogSearch.value.toLowerCase()
|
goodsPage.value = 1
|
||||||
return catalogList.value.filter(item =>
|
hasMoreGoods.value = true
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
goodsList.value = []
|
||||||
item.spec?.toLowerCase().includes(keyword)
|
goodsSearch.value = ''
|
||||||
)
|
loadGoods()
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// 关闭商品选择弹窗
|
||||||
|
function closeGoodsModal() {
|
||||||
|
showModal.value = false
|
||||||
|
goodsList.value = []
|
||||||
|
goodsSearch.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载商品列表
|
||||||
|
async function loadGoods(reset = false) {
|
||||||
|
if (goodsLoading.value) return
|
||||||
|
|
||||||
|
if (reset) {
|
||||||
|
goodsPage.value = 1
|
||||||
|
hasMoreGoods.value = true
|
||||||
|
goodsList.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasMoreGoods.value) return
|
||||||
|
|
||||||
|
goodsLoading.value = true
|
||||||
|
|
||||||
// 加载目录
|
|
||||||
async function loadCatalog() {
|
|
||||||
try {
|
try {
|
||||||
const res = await consumableTempAPI.getCatalogList({
|
const res = await consumableTempAPI.getConsumableGoods({
|
||||||
isActive: true,
|
page: goodsPage.value,
|
||||||
pageSize: 99
|
pageSize: goodsPageSize.value,
|
||||||
|
q: goodsSearch.value || undefined,
|
||||||
|
status: 0 // 只查询正常状态的商品
|
||||||
})
|
})
|
||||||
|
|
||||||
if (res.statusCode === 200 && (res.data as any)?.data) {
|
if (res.statusCode === 200 && (res.data as any)?.data) {
|
||||||
catalogList.value = (res.data as any).data
|
const newData = (res.data as any).data
|
||||||
|
goodsList.value = reset ? newData : [...goodsList.value, ...newData]
|
||||||
|
hasMoreGoods.value = newData.length >= goodsPageSize.value
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('加载目录失败:', err)
|
console.error('加载商品列表失败:', err)
|
||||||
|
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
goodsLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 搜索商品
|
||||||
|
function searchGoods() {
|
||||||
|
loadGoods(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载更多商品
|
||||||
|
function loadMoreGoods() {
|
||||||
|
if (!goodsLoading.value && hasMoreGoods.value) {
|
||||||
|
goodsPage.value++
|
||||||
|
loadGoods()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择SKU
|
||||||
|
function selectSKU(goods: GoodsWithSKU, sku: SKUResponse) {
|
||||||
|
const newItem: RequestItem = {
|
||||||
|
name: goods.goodsName,
|
||||||
|
spec: sku.specification || '',
|
||||||
|
quantity: 1,
|
||||||
|
unit: sku.unit || '',
|
||||||
|
estimatedUnitCost: sku.purchasePrice || 0,
|
||||||
|
remark: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
formData.value.items.push(newItem)
|
||||||
|
closeGoodsModal()
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: '已添加',
|
||||||
|
icon: 'success',
|
||||||
|
duration: 1000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 日期改变
|
// 日期改变
|
||||||
function onDateChange(e: any) {
|
function onDateChange(e: any) {
|
||||||
formData.value.neededAt = e.detail.value
|
formData.value.neededAt = e.detail.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加物料
|
|
||||||
function addItem() {
|
|
||||||
formData.value.items.push({
|
|
||||||
name: '',
|
|
||||||
spec: '',
|
|
||||||
quantity: 1,
|
|
||||||
unit: '',
|
|
||||||
estimatedUnitCost: 0,
|
|
||||||
remark: ''
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除物料
|
// 删除物料
|
||||||
function deleteItem(index: number) {
|
function deleteItem(index: number) {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
@ -200,31 +278,6 @@ function deleteItem(index: number) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从目录选择
|
|
||||||
function selectFromCatalog(index: number) {
|
|
||||||
currentItemIndex.value = index
|
|
||||||
showCatalogModal.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 选择目录项
|
|
||||||
function selectCatalog(catalog: CatalogItem) {
|
|
||||||
if (currentItemIndex.value >= 0) {
|
|
||||||
const item = formData.value.items[currentItemIndex.value]
|
|
||||||
item.name = catalog.name
|
|
||||||
item.spec = catalog.spec || ''
|
|
||||||
item.unit = catalog.unit || ''
|
|
||||||
item.catalogId = catalog.id
|
|
||||||
}
|
|
||||||
closeCatalog()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭目录弹窗
|
|
||||||
function closeCatalog() {
|
|
||||||
showCatalogModal.value = false
|
|
||||||
catalogSearch.value = ''
|
|
||||||
currentItemIndex.value = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// 表单验证
|
// 表单验证
|
||||||
function validateForm() {
|
function validateForm() {
|
||||||
if (!formData.value.applicantPhone) {
|
if (!formData.value.applicantPhone) {
|
||||||
@ -244,10 +297,6 @@ function validateForm() {
|
|||||||
|
|
||||||
for (let i = 0; i < formData.value.items.length; i++) {
|
for (let i = 0; i < formData.value.items.length; i++) {
|
||||||
const item = formData.value.items[i]
|
const item = formData.value.items[i]
|
||||||
if (!item.name) {
|
|
||||||
uni.showToast({ title: `第${i + 1}项物料名称不能为空`, icon: 'none' })
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!item.quantity || item.quantity <= 0) {
|
if (!item.quantity || item.quantity <= 0) {
|
||||||
uni.showToast({ title: `第${i + 1}项物料数量必须大于0`, icon: 'none' })
|
uni.showToast({ title: `第${i + 1}项物料数量必须大于0`, icon: 'none' })
|
||||||
return false
|
return false
|
||||||
@ -309,13 +358,6 @@ function goBack() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadCatalog()
|
|
||||||
// 自动填充当前用户信息
|
|
||||||
const staff = uni.getStorageSync('staff')
|
|
||||||
if (staff) {
|
|
||||||
formData.value.applicantName = staff.name || ''
|
|
||||||
formData.value.applicantPhone = staff.phone || ''
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -397,6 +439,28 @@ onMounted(() => {
|
|||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.info-row {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
color: #666;
|
||||||
|
width: 160rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
color: #333;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value.price {
|
||||||
|
color: #f57c00;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.form-item {
|
.form-item {
|
||||||
margin-bottom: 24rpx;
|
margin-bottom: 24rpx;
|
||||||
}
|
}
|
||||||
@ -454,17 +518,6 @@ onMounted(() => {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-catalog {
|
|
||||||
padding: 0 20rpx;
|
|
||||||
height: 80rpx;
|
|
||||||
line-height: 80rpx;
|
|
||||||
background: #e3f2fd;
|
|
||||||
color: #1976d2;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row-group {
|
.form-row-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16rpx;
|
gap: 16rpx;
|
||||||
@ -569,10 +622,12 @@ onMounted(() => {
|
|||||||
.modal-search {
|
.modal-search {
|
||||||
padding: 24rpx 32rpx;
|
padding: 24rpx 32rpx;
|
||||||
border-bottom: 1rpx solid #f0f0f0;
|
border-bottom: 1rpx solid #f0f0f0;
|
||||||
|
display: flex;
|
||||||
|
gap: 12rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-input {
|
.search-input {
|
||||||
width: 100%;
|
flex: 1;
|
||||||
height: 72rpx;
|
height: 72rpx;
|
||||||
line-height: 72rpx;
|
line-height: 72rpx;
|
||||||
padding: 0 24rpx;
|
padding: 0 24rpx;
|
||||||
@ -581,27 +636,82 @@ onMounted(() => {
|
|||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-btn {
|
||||||
|
padding: 0 32rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
line-height: 72rpx;
|
||||||
|
background: #3a5ddd;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-list {
|
.modal-list {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 12rpx 0;
|
padding: 12rpx 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.catalog-item {
|
.goods-item {
|
||||||
padding: 24rpx 32rpx;
|
padding: 24rpx 32rpx;
|
||||||
border-bottom: 1rpx solid #f5f5f5;
|
border-bottom: 1rpx solid #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.catalog-name {
|
.goods-name {
|
||||||
font-size: 30rpx;
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
color: #333;
|
color: #333;
|
||||||
margin-bottom: 8rpx;
|
margin-bottom: 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.catalog-info {
|
.goods-brand {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #999;
|
color: #999;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16rpx;
|
flex-direction: column;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12rpx 16rpx;
|
||||||
|
background: #f5f7fb;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-info {
|
||||||
|
display: flex;
|
||||||
|
gap: 12rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-spec {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-unit {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-price {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #f57c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading,
|
||||||
|
.no-more {
|
||||||
|
padding: 32rpx;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
|
|||||||
@ -19,10 +19,6 @@
|
|||||||
<text class="form-label">部门</text>
|
<text class="form-label">部门</text>
|
||||||
<input class="form-input" v-model="formData.department" placeholder="请输入部门" />
|
<input class="form-input" v-model="formData.department" placeholder="请输入部门" />
|
||||||
</view>
|
</view>
|
||||||
<view class="form-item">
|
|
||||||
<text class="form-label">申请原因</text>
|
|
||||||
<textarea class="form-textarea" v-model="formData.reason" placeholder="请输入申请原因" maxlength="200" />
|
|
||||||
</view>
|
|
||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
<text class="form-label">需要时间</text>
|
<text class="form-label">需要时间</text>
|
||||||
<picker mode="date" :value="formData.neededAt" @change="onDateChange">
|
<picker mode="date" :value="formData.neededAt" @change="onDateChange">
|
||||||
@ -31,6 +27,10 @@
|
|||||||
</view>
|
</view>
|
||||||
</picker>
|
</picker>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="form-label">申请原因</text>
|
||||||
|
<textarea class="form-textarea" v-model="formData.reason" placeholder="请输入申请原因" maxlength="200" />
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -38,7 +38,7 @@
|
|||||||
<view class="section">
|
<view class="section">
|
||||||
<view class="section-title">
|
<view class="section-title">
|
||||||
<text>物料明细</text>
|
<text>物料明细</text>
|
||||||
<button class="btn-add" @tap="addItem">+ 添加物料</button>
|
<button class="btn-add" @tap="showGoodsModal">+ 添加物料</button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="formData.items.length === 0" class="empty-items">
|
<view v-if="formData.items.length === 0" class="empty-items">
|
||||||
@ -51,33 +51,29 @@
|
|||||||
<button class="btn-delete" @tap="deleteItem(index)">删除</button>
|
<button class="btn-delete" @tap="deleteItem(index)">删除</button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="form-item">
|
<view class="info-row">
|
||||||
<text class="form-label required">物料名称</text>
|
<text class="info-label">物料名称:</text>
|
||||||
<view class="form-row">
|
<text class="info-value">{{ item.name }}</text>
|
||||||
<input class="form-input flex-1" v-model="item.name" placeholder="请输入物料名称" />
|
|
||||||
<button class="btn-catalog" @tap="selectFromCatalog(index)">从目录选择</button>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<view class="info-row" v-if="item.spec">
|
||||||
|
<text class="info-label">规格:</text>
|
||||||
|
<text class="info-value">{{ item.spec }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="info-row" v-if="item.unit">
|
||||||
|
<text class="info-label">单位:</text>
|
||||||
|
<text class="info-value">{{ item.unit }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="info-row" v-if="item.estimatedUnitCost">
|
||||||
|
<text class="info-label">单价:</text>
|
||||||
|
<text class="info-value price">¥{{ item.estimatedUnitCost }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
<text class="form-label">规格</text>
|
|
||||||
<input class="form-input" v-model="item.spec" placeholder="请输入规格" />
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="form-row-group">
|
|
||||||
<view class="form-item flex-1">
|
|
||||||
<text class="form-label required">数量</text>
|
<text class="form-label required">数量</text>
|
||||||
<input class="form-input" v-model.number="item.quantity" type="digit" placeholder="数量" />
|
<input class="form-input" v-model.number="item.quantity" type="digit" placeholder="请输入数量" />
|
||||||
</view>
|
|
||||||
<view class="form-item flex-1">
|
|
||||||
<text class="form-label">单位</text>
|
|
||||||
<input class="form-input" v-model="item.unit" placeholder="单位" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="form-item">
|
|
||||||
<text class="form-label">预估单价</text>
|
|
||||||
<input class="form-input" v-model.number="item.estimatedUnitCost" type="digit" placeholder="请输入预估单价" />
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="form-item">
|
<view class="form-item">
|
||||||
@ -100,28 +96,48 @@
|
|||||||
</button>
|
</button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 目录选择弹窗 -->
|
<!-- 商品选择弹窗 -->
|
||||||
<view v-if="showCatalogModal" class="modal-mask" @tap="closeCatalog">
|
<view v-if="showModal" class="modal-mask" @tap="closeGoodsModal">
|
||||||
<view class="modal-content" @tap.stop>
|
<view class="modal-content" @tap.stop>
|
||||||
<view class="modal-header">
|
<view class="modal-header">
|
||||||
<text class="modal-title">选择物料</text>
|
<text class="modal-title">选择物料</text>
|
||||||
<text class="modal-close" @tap="closeCatalog">✕</text>
|
<text class="modal-close" @tap="closeGoodsModal">✕</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="modal-search">
|
<view class="modal-search">
|
||||||
<input class="search-input" v-model="catalogSearch" placeholder="搜索物料名称" />
|
<input class="search-input" v-model="goodsSearch" placeholder="搜索物料名称" @confirm="searchGoods" />
|
||||||
|
<button class="search-btn" @tap="searchGoods">搜索</button>
|
||||||
</view>
|
</view>
|
||||||
<scroll-view scroll-y class="modal-list">
|
<scroll-view scroll-y class="modal-list" @scrolltolower="loadMoreGoods">
|
||||||
<view v-for="catalog in filteredCatalog" :key="catalog.id"
|
<view v-for="goods in goodsList" :key="goods.id" class="goods-item">
|
||||||
class="catalog-item"
|
<view class="goods-name">{{ goods.goodsName }}</view>
|
||||||
@tap="selectCatalog(catalog)">
|
<view v-if="goods.brand" class="goods-brand">品牌:{{ goods.brand }}</view>
|
||||||
<view class="catalog-name">{{ catalog.name }}</view>
|
|
||||||
<view class="catalog-info">
|
<!-- SKU列表 -->
|
||||||
<text v-if="catalog.spec">{{ catalog.spec }}</text>
|
<view class="sku-list">
|
||||||
<text v-if="catalog.unit">{{ catalog.unit }}</text>
|
<view v-for="sku in goods.skus" :key="sku.id"
|
||||||
|
class="sku-item"
|
||||||
|
@tap="selectSKU(goods, sku)">
|
||||||
|
<view class="sku-info">
|
||||||
|
<text v-if="sku.specification" class="sku-spec">{{ sku.specification }}</text>
|
||||||
|
<text v-if="sku.unit" class="sku-unit">{{ sku.unit }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="sku-price" v-if="sku.purchasePrice">
|
||||||
|
¥{{ sku.purchasePrice }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="filteredCatalog.length === 0" class="empty">
|
</view>
|
||||||
<text>暂无数据</text>
|
</view>
|
||||||
|
|
||||||
|
<view v-if="goodsLoading" class="loading">
|
||||||
|
<text>加载中...</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="!hasMoreGoods && goodsList.length > 0" class="no-more">
|
||||||
|
<text>没有更多了</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="goodsList.length === 0 && !goodsLoading" class="empty">
|
||||||
|
<text>暂无数据,请尝试搜索</text>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
@ -130,8 +146,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import consumableTempAPI, { type RequestItem, type CatalogItem } from '@/pages/api/consumable-temp'
|
import consumableTempAPI, { type RequestItem, type GoodsWithSKU, type SKUResponse } from '@/pages/api/consumable-temp'
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
@ -146,20 +162,13 @@ const formData = ref({
|
|||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
const requestId = ref('')
|
const requestId = ref('')
|
||||||
const currentItemIndex = ref(-1)
|
const showModal = ref(false)
|
||||||
const showCatalogModal = ref(false)
|
const goodsList = ref<GoodsWithSKU[]>([])
|
||||||
const catalogList = ref<CatalogItem[]>([])
|
const goodsSearch = ref('')
|
||||||
const catalogSearch = ref('')
|
const goodsLoading = ref(false)
|
||||||
|
const goodsPage = ref(1)
|
||||||
// 过滤后的目录
|
const goodsPageSize = ref(10)
|
||||||
const filteredCatalog = computed(() => {
|
const hasMoreGoods = ref(true)
|
||||||
if (!catalogSearch.value) return catalogList.value
|
|
||||||
const keyword = catalogSearch.value.toLowerCase()
|
|
||||||
return catalogList.value.filter(item =>
|
|
||||||
item.name.toLowerCase().includes(keyword) ||
|
|
||||||
item.spec?.toLowerCase().includes(keyword)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 加载详情
|
// 加载详情
|
||||||
async function loadDetail() {
|
async function loadDetail() {
|
||||||
@ -187,8 +196,7 @@ async function loadDetail() {
|
|||||||
quantity: item.quantity || 1,
|
quantity: item.quantity || 1,
|
||||||
unit: item.unit || '',
|
unit: item.unit || '',
|
||||||
estimatedUnitCost: item.estimatedUnitCost || 0,
|
estimatedUnitCost: item.estimatedUnitCost || 0,
|
||||||
remark: item.remark || '',
|
remark: item.remark || ''
|
||||||
catalogId: item.catalogId
|
|
||||||
})) || []
|
})) || []
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||||
@ -203,38 +211,97 @@ async function loadDetail() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载目录
|
// 显示商品选择弹窗
|
||||||
async function loadCatalog() {
|
function showGoodsModal() {
|
||||||
|
showModal.value = true
|
||||||
|
goodsPage.value = 1
|
||||||
|
hasMoreGoods.value = true
|
||||||
|
goodsList.value = []
|
||||||
|
goodsSearch.value = ''
|
||||||
|
loadGoods()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭商品选择弹窗
|
||||||
|
function closeGoodsModal() {
|
||||||
|
showModal.value = false
|
||||||
|
goodsList.value = []
|
||||||
|
goodsSearch.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载商品列表
|
||||||
|
async function loadGoods(reset = false) {
|
||||||
|
if (goodsLoading.value) return
|
||||||
|
|
||||||
|
if (reset) {
|
||||||
|
goodsPage.value = 1
|
||||||
|
hasMoreGoods.value = true
|
||||||
|
goodsList.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasMoreGoods.value) return
|
||||||
|
|
||||||
|
goodsLoading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await consumableTempAPI.getCatalogList({
|
const res = await consumableTempAPI.getConsumableGoods({
|
||||||
isActive: true,
|
page: goodsPage.value,
|
||||||
pageSize: 99
|
pageSize: goodsPageSize.value,
|
||||||
|
q: goodsSearch.value || undefined,
|
||||||
|
status: 0 // 只查询正常状态的商品
|
||||||
})
|
})
|
||||||
|
|
||||||
if (res.statusCode === 200 && (res.data as any)?.data) {
|
if (res.statusCode === 200 && (res.data as any)?.data) {
|
||||||
catalogList.value = (res.data as any).data
|
const newData = (res.data as any).data
|
||||||
|
goodsList.value = reset ? newData : [...goodsList.value, ...newData]
|
||||||
|
hasMoreGoods.value = newData.length >= goodsPageSize.value
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('加载目录失败:', err)
|
console.error('加载商品列表失败:', err)
|
||||||
|
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
goodsLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 搜索商品
|
||||||
|
function searchGoods() {
|
||||||
|
loadGoods(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载更多商品
|
||||||
|
function loadMoreGoods() {
|
||||||
|
if (!goodsLoading.value && hasMoreGoods.value) {
|
||||||
|
goodsPage.value++
|
||||||
|
loadGoods()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择SKU
|
||||||
|
function selectSKU(goods: GoodsWithSKU, sku: SKUResponse) {
|
||||||
|
const newItem: RequestItem = {
|
||||||
|
name: goods.goodsName,
|
||||||
|
spec: sku.specification || '',
|
||||||
|
quantity: 1,
|
||||||
|
unit: sku.unit || '',
|
||||||
|
estimatedUnitCost: sku.purchasePrice || 0,
|
||||||
|
remark: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
formData.value.items.push(newItem)
|
||||||
|
closeGoodsModal()
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: '已添加',
|
||||||
|
icon: 'success',
|
||||||
|
duration: 1000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 日期改变
|
// 日期改变
|
||||||
function onDateChange(e: any) {
|
function onDateChange(e: any) {
|
||||||
formData.value.neededAt = e.detail.value
|
formData.value.neededAt = e.detail.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加物料
|
|
||||||
function addItem() {
|
|
||||||
formData.value.items.push({
|
|
||||||
name: '',
|
|
||||||
spec: '',
|
|
||||||
quantity: 1,
|
|
||||||
unit: '',
|
|
||||||
estimatedUnitCost: 0,
|
|
||||||
remark: ''
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除物料
|
// 删除物料
|
||||||
function deleteItem(index: number) {
|
function deleteItem(index: number) {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
@ -248,31 +315,6 @@ function deleteItem(index: number) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从目录选择
|
|
||||||
function selectFromCatalog(index: number) {
|
|
||||||
currentItemIndex.value = index
|
|
||||||
showCatalogModal.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 选择目录项
|
|
||||||
function selectCatalog(catalog: CatalogItem) {
|
|
||||||
if (currentItemIndex.value >= 0) {
|
|
||||||
const item = formData.value.items[currentItemIndex.value]
|
|
||||||
item.name = catalog.name
|
|
||||||
item.spec = catalog.spec || ''
|
|
||||||
item.unit = catalog.unit || ''
|
|
||||||
item.catalogId = catalog.id
|
|
||||||
}
|
|
||||||
closeCatalog()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭目录弹窗
|
|
||||||
function closeCatalog() {
|
|
||||||
showCatalogModal.value = false
|
|
||||||
catalogSearch.value = ''
|
|
||||||
currentItemIndex.value = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// 表单验证
|
// 表单验证
|
||||||
function validateForm() {
|
function validateForm() {
|
||||||
if (!formData.value.applicantPhone) {
|
if (!formData.value.applicantPhone) {
|
||||||
@ -292,10 +334,6 @@ function validateForm() {
|
|||||||
|
|
||||||
for (let i = 0; i < formData.value.items.length; i++) {
|
for (let i = 0; i < formData.value.items.length; i++) {
|
||||||
const item = formData.value.items[i]
|
const item = formData.value.items[i]
|
||||||
if (!item.name) {
|
|
||||||
uni.showToast({ title: `第${i + 1}项物料名称不能为空`, icon: 'none' })
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!item.quantity || item.quantity <= 0) {
|
if (!item.quantity || item.quantity <= 0) {
|
||||||
uni.showToast({ title: `第${i + 1}项物料数量必须大于0`, icon: 'none' })
|
uni.showToast({ title: `第${i + 1}项物料数量必须大于0`, icon: 'none' })
|
||||||
return false
|
return false
|
||||||
@ -360,7 +398,6 @@ onMounted(() => {
|
|||||||
if (options.id) {
|
if (options.id) {
|
||||||
requestId.value = options.id
|
requestId.value = options.id
|
||||||
loadDetail()
|
loadDetail()
|
||||||
loadCatalog()
|
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({ title: '参数错误', icon: 'none' })
|
uni.showToast({ title: '参数错误', icon: 'none' })
|
||||||
setTimeout(() => uni.navigateBack(), 1500)
|
setTimeout(() => uni.navigateBack(), 1500)
|
||||||
@ -411,7 +448,7 @@ onMounted(() => {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
height: 56rpx;
|
height: 56rpx;
|
||||||
line-height: 40rpx;
|
line-height: 40rpx;
|
||||||
background: #007aff;
|
background: #3a5ddd;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-radius: 8rpx;
|
border-radius: 8rpx;
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
@ -442,20 +479,41 @@ onMounted(() => {
|
|||||||
.item-number {
|
.item-number {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #007aff;
|
color: #4b7aff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-delete {
|
.btn-delete {
|
||||||
padding: 8rpx 16rpx;
|
padding: 8rpx 16rpx;
|
||||||
height: 48rpx;
|
height: 48rpx;
|
||||||
line-height: 32rpx;
|
line-height: 32rpx;
|
||||||
background: #fff;
|
background: #ffebee;
|
||||||
color: #f44336;
|
color: #c62828;
|
||||||
border: 2rpx solid #f44336;
|
|
||||||
border-radius: 8rpx;
|
border-radius: 8rpx;
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.info-row {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
color: #666;
|
||||||
|
width: 160rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
color: #333;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value.price {
|
||||||
|
color: #f57c00;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.form-item {
|
.form-item {
|
||||||
margin-bottom: 24rpx;
|
margin-bottom: 24rpx;
|
||||||
}
|
}
|
||||||
@ -503,36 +561,6 @@ onMounted(() => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-row {
|
|
||||||
display: flex;
|
|
||||||
gap: 12rpx;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row .flex-1 {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-catalog {
|
|
||||||
padding: 0 20rpx;
|
|
||||||
height: 80rpx;
|
|
||||||
line-height: 80rpx;
|
|
||||||
background: #e3f2fd;
|
|
||||||
color: #1976d2;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row-group {
|
|
||||||
display: flex;
|
|
||||||
gap: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row-group .form-item {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-items {
|
.empty-items {
|
||||||
padding: 80rpx 0;
|
padding: 80rpx 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -571,7 +599,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background: #007aff;
|
background: #3a5ddd;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -628,10 +656,12 @@ onMounted(() => {
|
|||||||
.modal-search {
|
.modal-search {
|
||||||
padding: 24rpx 32rpx;
|
padding: 24rpx 32rpx;
|
||||||
border-bottom: 1rpx solid #f0f0f0;
|
border-bottom: 1rpx solid #f0f0f0;
|
||||||
|
display: flex;
|
||||||
|
gap: 12rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-input {
|
.search-input {
|
||||||
width: 100%;
|
flex: 1;
|
||||||
height: 72rpx;
|
height: 72rpx;
|
||||||
line-height: 72rpx;
|
line-height: 72rpx;
|
||||||
padding: 0 24rpx;
|
padding: 0 24rpx;
|
||||||
@ -640,27 +670,82 @@ onMounted(() => {
|
|||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-btn {
|
||||||
|
padding: 0 32rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
line-height: 72rpx;
|
||||||
|
background: #3a5ddd;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-list {
|
.modal-list {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 12rpx 0;
|
padding: 12rpx 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.catalog-item {
|
.goods-item {
|
||||||
padding: 24rpx 32rpx;
|
padding: 24rpx 32rpx;
|
||||||
border-bottom: 1rpx solid #f5f5f5;
|
border-bottom: 1rpx solid #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.catalog-name {
|
.goods-name {
|
||||||
font-size: 30rpx;
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
color: #333;
|
color: #333;
|
||||||
margin-bottom: 8rpx;
|
margin-bottom: 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.catalog-info {
|
.goods-brand {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #999;
|
color: #999;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16rpx;
|
flex-direction: column;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12rpx 16rpx;
|
||||||
|
background: #f5f7fb;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-info {
|
||||||
|
display: flex;
|
||||||
|
gap: 12rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-spec {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-unit {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sku-price {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #f57c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading,
|
||||||
|
.no-more {
|
||||||
|
padding: 32rpx;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
|
|||||||
@ -3,9 +3,6 @@
|
|||||||
<!-- 顶部标题栏 -->
|
<!-- 顶部标题栏 -->
|
||||||
<view class="header">
|
<view class="header">
|
||||||
<text class="title">临时易耗品申请</text>
|
<text class="title">临时易耗品申请</text>
|
||||||
<view class="actions">
|
|
||||||
<button class="btn-outline" @tap="goToCatalog">种类管理</button>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 筛选区 -->
|
<!-- 筛选区 -->
|
||||||
@ -183,13 +180,6 @@ function goToCreate() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 跳转到种类管理
|
|
||||||
function goToCatalog() {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: '/pages/consumables/temp-catalog'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生命周期
|
// 生命周期
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
loadList(true)
|
loadList(true)
|
||||||
@ -223,9 +213,6 @@ onUnload(() => {
|
|||||||
.header {
|
.header {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 32rpx;
|
padding: 32rpx;
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,20 +222,6 @@ onUnload(() => {
|
|||||||
color: #111;
|
color: #111;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-outline {
|
|
||||||
padding: 0rpx 24rpx;
|
|
||||||
background: #fff;
|
|
||||||
color: #4b7aff;
|
|
||||||
border: 2rpx solid #4b7aff;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
font-size: 26rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-bar {
|
.filter-bar {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 24rpx 32rpx;
|
padding: 24rpx 32rpx;
|
||||||
|
|||||||
@ -94,6 +94,10 @@
|
|||||||
|
|
||||||
<!-- 信息行 -->
|
<!-- 信息行 -->
|
||||||
<view class="item-info">
|
<view class="item-info">
|
||||||
|
<view class="info-row">
|
||||||
|
<text class="info-label">资产SN:</text>
|
||||||
|
<text class="info-value">{{ item.assetId || '-' }}</text>
|
||||||
|
</view>
|
||||||
<view class="info-row">
|
<view class="info-row">
|
||||||
<text class="info-label">借用人:</text>
|
<text class="info-label">借用人:</text>
|
||||||
<text class="info-value">{{ item.borrowerName || '-' }}</text>
|
<text class="info-value">{{ item.borrowerName || '-' }}</text>
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 顶部滑动轮播图 -->
|
<!-- 顶部滑动轮播图 -->
|
||||||
<view class="swiper-card card">
|
<!-- <view class="swiper-card card">
|
||||||
<swiper class="swiper" :current="swiperIndex" @change="changeSwiper" previous-margin="50rpx" next-margin="50rpx"
|
<swiper class="swiper" :current="swiperIndex" @change="changeSwiper" previous-margin="50rpx" next-margin="50rpx"
|
||||||
:indicator-dots="true" indicator-color="rgba(51,51,51,0.25)" indicator-active-color="#4b7aff" autoplay
|
:indicator-dots="true" indicator-color="rgba(51,51,51,0.25)" indicator-active-color="#4b7aff" autoplay
|
||||||
interval="3000" circular>
|
interval="3000" circular>
|
||||||
@ -28,7 +28,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</swiper-item>
|
</swiper-item>
|
||||||
</swiper>
|
</swiper>
|
||||||
</view>
|
</view> -->
|
||||||
|
|
||||||
<!-- 主操作 -->
|
<!-- 主操作 -->
|
||||||
<view class="ops">
|
<view class="ops">
|
||||||
|
|||||||
@ -29,6 +29,8 @@
|
|||||||
import { reactive, ref, onMounted } from 'vue'
|
import { reactive, ref, onMounted } from 'vue'
|
||||||
import { login } from '../api/user'
|
import { login } from '../api/user'
|
||||||
import { fetchPendingCount } from '@/utils/messageManager'
|
import { fetchPendingCount } from '@/utils/messageManager'
|
||||||
|
// @ts-ignore
|
||||||
|
import util from '@/utils/util'
|
||||||
|
|
||||||
const usernameFocus = ref(true)
|
const usernameFocus = ref(true)
|
||||||
const loginRequest = reactive({
|
const loginRequest = reactive({
|
||||||
@ -80,7 +82,7 @@ async function onLogin() {
|
|||||||
}
|
}
|
||||||
const staff = res.data.userLoginRequestVO
|
const staff = res.data.userLoginRequestVO
|
||||||
// 兼容旧逻辑,附加 role 别名
|
// 兼容旧逻辑,附加 role 别名
|
||||||
uni.setStorageSync('token', res.data.token)
|
|
||||||
uni.showToast({ title: '登录成功', icon: 'success', duration: 1000 })
|
uni.showToast({ title: '登录成功', icon: 'success', duration: 1000 })
|
||||||
uni.setStorageSync('assetsRole', staff.assetsRole) // 保存用户信息到本地缓存
|
uni.setStorageSync('assetsRole', staff.assetsRole) // 保存用户信息到本地缓存
|
||||||
// 跳转到首页(tabBar)
|
// 跳转到首页(tabBar)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user