384 lines
9.2 KiB
Vue
384 lines
9.2 KiB
Vue
<template>
|
||
<business-page-layout :title="pageTitle" :show-back="true">
|
||
<!-- 搜索栏优化:胶囊式设计,增加视觉呼吸感 -->
|
||
<view class="search-wrapper">
|
||
<view class="search-bar">
|
||
<input class="search-input" v-model="keyword" placeholder="搜索药品名称" placeholder-class="search-placeholder" @confirm="reload" />
|
||
<view class="search-btn" hover-class="btn-hover" @click="reload">搜索</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="list-container">
|
||
<view
|
||
v-for="item in list"
|
||
:key="item._wxKey"
|
||
class="item-card"
|
||
hover-class="card-hover"
|
||
@click="goDetail(item)"
|
||
>
|
||
<!-- 左侧缩略图 -->
|
||
<image class="thumb" :src="rowDisplay(item).imageUrl" mode="aspectFill" />
|
||
|
||
<!-- 右侧内容区 -->
|
||
<view class="content">
|
||
<!-- 顶部:标题与状态标签对齐 -->
|
||
<view class="header-row">
|
||
<text class="title">{{ rowDisplay(item).drugName }}</text>
|
||
<view class="status-tag" :class="rowDisplay(item).isOnShelf ? 'tag-on' : 'tag-off'">
|
||
{{ rowDisplay(item).statusText }}
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 中部:详细信息 -->
|
||
<view class="info-group">
|
||
<text class="info-text">供货 {{ rowDisplay(item).buyPrice }} · 售价 {{ rowDisplay(item).price }}</text>
|
||
<text class="info-text">库存 {{ rowDisplay(item).stock }}</text>
|
||
</view>
|
||
|
||
<!-- 底部:操作按钮横向排列,增加点击区域与反馈 -->
|
||
<view class="action-row" @click.stop>
|
||
<view class="action-btn outline" hover-class="btn-hover-opacity" @click.stop="openPriceEdit(item)">
|
||
改价
|
||
</view>
|
||
<view class="action-btn" :class="rowDisplay(item).isOnShelf ? 'btn-danger' : 'btn-primary'" hover-class="btn-hover-opacity" @click.stop="toggleStatus(item)">
|
||
{{ rowDisplay(item).isOnShelf ? '下架' : '上架' }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 空状态优化 -->
|
||
<view v-if="!loading && !list.length" class="empty-state">
|
||
<view class="empty-icon"></view>
|
||
<text class="empty-text">暂无药品数据</text>
|
||
</view>
|
||
|
||
<price-edit-modal
|
||
:visible="priceModalVisible"
|
||
:item="priceEditItem"
|
||
@close="closePriceEdit"
|
||
@confirm="onPriceConfirm"
|
||
/>
|
||
</business-page-layout>
|
||
</template>
|
||
|
||
<script>
|
||
import BusinessPageLayout from '@/subPackages/sub_business_shared/components/business-page-layout.vue'
|
||
import PriceEditModal from './components/PriceEditModal.vue'
|
||
import { withWxKey } from '@/utils/wxListKey.js'
|
||
import { mapWarehouseRow } from '@/subPackages/sub_business_shared/common/display.js'
|
||
|
||
import businessMixin from '@/subPackages/sub_business_shared/common/businessMixin.js'
|
||
|
||
export default {
|
||
mixins: [businessMixin],
|
||
components: { BusinessPageLayout, PriceEditModal },
|
||
data() {
|
||
return {
|
||
pageTitle: '仓库管理',
|
||
warehouseType: '',
|
||
keyword: '',
|
||
list: [],
|
||
page: 1,
|
||
loading: false,
|
||
priceModalVisible: false,
|
||
priceEditItem: null,
|
||
priceSubmitting: false,
|
||
}
|
||
},
|
||
onLoad(q) {
|
||
if (q.label) {
|
||
this.pageTitle = decodeURIComponent(q.label)
|
||
}
|
||
if (q.type != null && q.type !== '') {
|
||
this.warehouseType = q.type
|
||
}
|
||
this.reload()
|
||
},
|
||
onReachBottom() {
|
||
this.load(this.page + 1, true)
|
||
},
|
||
methods: {
|
||
rowDisplay(item) {
|
||
return mapWarehouseRow(item, this.bizCap.warehouseType || 'store')
|
||
},
|
||
reload() {
|
||
this.page = 1
|
||
this.load(1, false)
|
||
},
|
||
load(page, append) {
|
||
this.loading = true
|
||
const params = { page, pageSize: 20 }
|
||
if (this.keyword) params.name = this.keyword
|
||
if (this.warehouseType !== '' && this.warehouseType != null) {
|
||
params.type = this.warehouseType
|
||
}
|
||
this.bizApi.getWarehouseList(params).then(w => {
|
||
const items = withWxKey((w.data && w.data.items) || [], 'id', 'wh')
|
||
this.list = append ? this.list.concat(items) : items
|
||
this.page = page
|
||
}).finally(() => { this.loading = false })
|
||
},
|
||
goDetail(item) {
|
||
uni.navigateTo({ url: `${this.bizRoot}/warehouse/detail?id=${item.id}` })
|
||
},
|
||
openPriceEdit(item) {
|
||
this.priceEditItem = item
|
||
this.priceModalVisible = true
|
||
},
|
||
closePriceEdit() {
|
||
this.priceModalVisible = false
|
||
this.priceEditItem = null
|
||
},
|
||
onPriceConfirm(payload) {
|
||
if (!this.priceEditItem || this.priceSubmitting) return
|
||
this.priceSubmitting = true
|
||
const data = typeof payload === 'object' && payload !== null
|
||
? { id: this.priceEditItem.id, ...payload }
|
||
: { id: this.priceEditItem.id, price: payload }
|
||
this.bizApi.updateWarehousePrice(data).then(w => {
|
||
if (w.ok) {
|
||
uni.showToast({ title: '修改成功' })
|
||
this.closePriceEdit()
|
||
this.reload()
|
||
}
|
||
}).finally(() => { this.priceSubmitting = false })
|
||
},
|
||
toggleStatus(item) {
|
||
const action = item.status === 2 ? '下架' : '上架'
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '确定' + action + '「' + ((item.drug && item.drug.drug_name) || '') + '」?',
|
||
success: (res) => {
|
||
if (!res.confirm) return
|
||
this.bizApi.updateWarehouseStatus(item.id).then(w => {
|
||
if (w.ok) {
|
||
uni.showToast({ title: '操作成功' })
|
||
item.status = item.status === 2 ? 1 : 2
|
||
}
|
||
})
|
||
},
|
||
})
|
||
},
|
||
},
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
/* 核心主题色变量提取,方便后续统一管理 */
|
||
$theme-color: #6acdbb;
|
||
$theme-color-light: rgba(106, 205, 187, 0.12);
|
||
$text-primary: #2c3e50;
|
||
$text-secondary: #8a97a3;
|
||
$text-light: #b0b8c1;
|
||
$bg-color: #f7f9fa;
|
||
$border-color: #ebedf0;
|
||
$danger-color: #ff6b6b;
|
||
$danger-color-light: rgba(255, 107, 107, 0.1);
|
||
|
||
.search-wrapper {
|
||
padding: 16rpx 24rpx;
|
||
background-color: transparent;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 10;
|
||
}
|
||
|
||
.search-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
background: #ffffff;
|
||
padding: 12rpx 16rpx 12rpx 32rpx;
|
||
border-radius: 40rpx;
|
||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||
}
|
||
|
||
.search-input {
|
||
flex: 1;
|
||
font-size: 28rpx;
|
||
color: $text-primary;
|
||
height: 60rpx;
|
||
}
|
||
|
||
.search-placeholder {
|
||
color: $text-light;
|
||
}
|
||
|
||
.search-btn {
|
||
background: $theme-color;
|
||
color: #ffffff;
|
||
font-size: 26rpx;
|
||
font-weight: 500;
|
||
padding: 12rpx 36rpx;
|
||
border-radius: 30rpx;
|
||
margin-left: 16rpx;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.list-container {
|
||
padding: 0 24rpx 24rpx;
|
||
}
|
||
|
||
.item-card {
|
||
display: flex;
|
||
background: #ffffff;
|
||
padding: 24rpx;
|
||
border-radius: 24rpx;
|
||
margin-bottom: 20rpx;
|
||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.03);
|
||
transition: transform 0.2s, box-shadow 0.2s;
|
||
}
|
||
|
||
.card-hover {
|
||
transform: scale(0.99);
|
||
background-color: #fafafa;
|
||
}
|
||
|
||
.thumb {
|
||
width: 140rpx;
|
||
height: 140rpx;
|
||
border-radius: 16rpx;
|
||
flex-shrink: 0;
|
||
background: $bg-color;
|
||
border: 1rpx solid $border-color;
|
||
}
|
||
|
||
.content {
|
||
flex: 1;
|
||
margin-left: 24rpx;
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.header-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.title {
|
||
font-size: 30rpx;
|
||
color: $text-primary;
|
||
font-weight: 600;
|
||
line-height: 1.4;
|
||
flex: 1;
|
||
margin-right: 16rpx;
|
||
/* 单行截断 */
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.status-tag {
|
||
font-size: 20rpx;
|
||
font-weight: 500;
|
||
padding: 4rpx 14rpx;
|
||
border-radius: 8rpx;
|
||
flex-shrink: 0;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.tag-on {
|
||
color: $theme-color;
|
||
background: $theme-color-light;
|
||
}
|
||
|
||
.tag-off {
|
||
color: $text-secondary;
|
||
background: $bg-color;
|
||
}
|
||
|
||
.info-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6rpx;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.info-text {
|
||
font-size: 24rpx;
|
||
color: $text-secondary;
|
||
}
|
||
|
||
.action-row {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
margin-top: auto;
|
||
}
|
||
|
||
.action-btn {
|
||
font-size: 24rpx;
|
||
padding: 10rpx 28rpx;
|
||
border-radius: 12rpx;
|
||
font-weight: 500;
|
||
line-height: 1.5;
|
||
transition: opacity 0.2s;
|
||
}
|
||
|
||
.action-btn.outline {
|
||
color: $text-primary;
|
||
background: #ffffff;
|
||
border: 1rpx solid $border-color;
|
||
}
|
||
|
||
.action-btn.btn-primary {
|
||
color: #ffffff;
|
||
background: $theme-color;
|
||
border: 1rpx solid $theme-color;
|
||
}
|
||
|
||
.action-btn.btn-danger {
|
||
color: $danger-color;
|
||
background: $danger-color-light;
|
||
border: 1rpx solid transparent;
|
||
}
|
||
|
||
.btn-hover {
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.btn-hover-opacity {
|
||
opacity: 0.6;
|
||
}
|
||
|
||
.empty-state {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 120rpx 0;
|
||
}
|
||
|
||
.empty-icon {
|
||
width: 200rpx;
|
||
height: 200rpx;
|
||
background-color: $bg-color;
|
||
border-radius: 50%;
|
||
margin-bottom: 30rpx;
|
||
position: relative;
|
||
}
|
||
|
||
/* 用伪元素做一个简单的空状态图形占位,有条件可替换为真实的Image/SVG */
|
||
.empty-icon::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border: 8rpx solid $border-color;
|
||
border-radius: 16rpx;
|
||
}
|
||
|
||
.empty-text {
|
||
color: $text-light;
|
||
font-size: 28rpx;
|
||
}
|
||
</style> |