Commit 873e7a7d474b004bafe0bd0cda64a6e3165e60f2

Authored by “wangming”
2 parents 2bea36e2 887a79d6

Merge branch 'master' of http://39.98.150.180/antissoft/lvqianmeiye_ERP

Showing 37 changed files with 9891 additions and 467 deletions
antis-ncc-admin/src/views/LqLaundryFlow/detail-dialog.vue 0 → 100644
  1 +<template>
  2 + <el-dialog title="流水详情" :visible.sync="visible" width="800px" :close-on-click-modal="false">
  3 + <div v-loading="loading" class="detail-container">
  4 + <!-- 基本信息 -->
  5 + <div class="detail-section">
  6 + <div class="section-title">
  7 + <i class="el-icon-info section-icon"></i>
  8 + <span>基本信息</span>
  9 + </div>
  10 + <div class="detail-content">
  11 + <div class="detail-row">
  12 + <div class="detail-label">
  13 + <i class="el-icon-upload2 detail-icon" :class="form.flowType === 0 ? 'send-icon' : 'return-icon'"></i>
  14 + <span>流水类型</span>
  15 + </div>
  16 + <div class="detail-value">
  17 + <el-tag :type="form.flowType === 0 ? 'primary' : 'success'" size="small">
  18 + <i :class="form.flowType === 0 ? 'el-icon-upload2' : 'el-icon-download'" style="margin-right: 4px;"></i>
  19 + {{ form.flowTypeName || '无' }}
  20 + </el-tag>
  21 + </div>
  22 + </div>
  23 + <div class="detail-row">
  24 + <div class="detail-label">
  25 + <i class="el-icon-tickets detail-icon batch-icon"></i>
  26 + <span>批次号</span>
  27 + </div>
  28 + <div class="detail-value">
  29 + <span>{{ form.batchNumber || '无' }}</span>
  30 + </div>
  31 + </div>
  32 + <div class="detail-row">
  33 + <div class="detail-label">
  34 + <i class="el-icon-office-building detail-icon store-icon"></i>
  35 + <span>门店名称</span>
  36 + </div>
  37 + <div class="detail-value">
  38 + <span>{{ form.storeName || '无' }}</span>
  39 + </div>
  40 + </div>
  41 + <div class="detail-row">
  42 + <div class="detail-label">
  43 + <i class="el-icon-goods detail-icon product-icon"></i>
  44 + <span>产品类型</span>
  45 + </div>
  46 + <div class="detail-value">
  47 + <span>{{ form.productType || '无' }}</span>
  48 + </div>
  49 + </div>
  50 + <div class="detail-row">
  51 + <div class="detail-label">
  52 + <i class="el-icon-s-shop detail-icon supplier-icon"></i>
  53 + <span>清洗商名称</span>
  54 + </div>
  55 + <div class="detail-value">
  56 + <span>{{ form.laundrySupplierName || '无' }}</span>
  57 + </div>
  58 + </div>
  59 + <div class="detail-row">
  60 + <div class="detail-label">
  61 + <i class="el-icon-s-data detail-icon quantity-icon"></i>
  62 + <span>数量</span>
  63 + </div>
  64 + <div class="detail-value">
  65 + <span class="value-number">{{ form.quantity || 0 }}</span>
  66 + </div>
  67 + </div>
  68 + </div>
  69 + </div>
  70 +
  71 + <!-- 费用信息 -->
  72 + <div class="detail-section">
  73 + <div class="section-title">
  74 + <i class="el-icon-coin section-icon"></i>
  75 + <span>费用信息</span>
  76 + </div>
  77 + <div class="detail-content">
  78 + <div class="detail-row">
  79 + <div class="detail-label">
  80 + <i class="el-icon-coin detail-icon price-icon"></i>
  81 + <span>清洗单价</span>
  82 + </div>
  83 + <div class="detail-value">
  84 + <span class="value-number">¥{{ form.laundryPrice || 0 }}</span>
  85 + </div>
  86 + </div>
  87 + <div class="detail-row">
  88 + <div class="detail-label">
  89 + <i class="el-icon-money detail-icon total-price-icon"></i>
  90 + <span>总费用</span>
  91 + </div>
  92 + <div class="detail-value">
  93 + <span class="value-number value-total">¥{{ form.totalPrice || 0 }}</span>
  94 + </div>
  95 + </div>
  96 + </div>
  97 + </div>
  98 +
  99 + <!-- 其他信息 -->
  100 + <div class="detail-section">
  101 + <div class="section-title">
  102 + <i class="el-icon-document section-icon"></i>
  103 + <span>其他信息</span>
  104 + </div>
  105 + <div class="detail-content">
  106 + <div class="detail-row">
  107 + <div class="detail-label">
  108 + <i class="el-icon-document detail-icon remark-icon"></i>
  109 + <span>备注</span>
  110 + </div>
  111 + <div class="detail-value">
  112 + <span>{{ form.remark || '无' }}</span>
  113 + </div>
  114 + </div>
  115 + <div class="detail-row">
  116 + <div class="detail-label">
  117 + <i class="el-icon-success detail-icon status-icon"></i>
  118 + <span>是否有效</span>
  119 + </div>
  120 + <div class="detail-value">
  121 + <el-tag :type="form.isEffective === 1 ? 'success' : 'info'" size="small">
  122 + {{ form.isEffective === 1 ? '有效' : '无效' }}
  123 + </el-tag>
  124 + </div>
  125 + </div>
  126 + <div class="detail-row">
  127 + <div class="detail-label">
  128 + <i class="el-icon-user detail-icon user-icon"></i>
  129 + <span>创建人</span>
  130 + </div>
  131 + <div class="detail-value">
  132 + <span>{{ form.createUserName || '无' }}</span>
  133 + </div>
  134 + </div>
  135 + <div class="detail-row">
  136 + <div class="detail-label">
  137 + <i class="el-icon-time detail-icon time-icon"></i>
  138 + <span>创建时间</span>
  139 + </div>
  140 + <div class="detail-value">
  141 + <span>{{ formatDateTime(form.createTime) || '无' }}</span>
  142 + </div>
  143 + </div>
  144 + </div>
  145 + </div>
  146 + </div>
  147 + <div slot="footer" class="dialog-footer">
  148 + <el-button @click="visible = false">关闭</el-button>
  149 + </div>
  150 + </el-dialog>
  151 +</template>
  152 +
  153 +<script>
  154 +import request from '@/utils/request'
  155 +
  156 +export default {
  157 + name: 'DetailDialog',
  158 + data() {
  159 + return {
  160 + visible: false,
  161 + loading: false,
  162 + form: {
  163 + id: '',
  164 + flowType: 0,
  165 + flowTypeName: '',
  166 + batchNumber: '',
  167 + storeId: '',
  168 + storeName: '',
  169 + productType: '',
  170 + laundrySupplierId: '',
  171 + laundrySupplierName: '',
  172 + quantity: 0,
  173 + laundryPrice: 0,
  174 + totalPrice: 0,
  175 + remark: '',
  176 + isEffective: 1,
  177 + createUser: '',
  178 + createUserName: '',
  179 + createTime: ''
  180 + }
  181 + }
  182 + },
  183 + methods: {
  184 + // 初始化
  185 + init(id) {
  186 + this.visible = true
  187 + this.loading = true
  188 + request({
  189 + url: `/api/Extend/LqLaundryFlow/${id}`,
  190 + method: 'GET'
  191 + }).then(res => {
  192 + this.loading = false
  193 + if (res.code == 200 && res.data) {
  194 + this.form = res.data
  195 + } else {
  196 + this.$message.error(res.msg || '获取详情失败')
  197 + this.visible = false
  198 + }
  199 + }).catch(() => {
  200 + this.loading = false
  201 + this.visible = false
  202 + })
  203 + },
  204 + // 格式化日期时间
  205 + formatDateTime(dateTime) {
  206 + if (!dateTime) return ''
  207 + const date = new Date(dateTime)
  208 + const year = date.getFullYear()
  209 + const month = String(date.getMonth() + 1).padStart(2, '0')
  210 + const day = String(date.getDate()).padStart(2, '0')
  211 + const hours = String(date.getHours()).padStart(2, '0')
  212 + const minutes = String(date.getMinutes()).padStart(2, '0')
  213 + const seconds = String(date.getSeconds()).padStart(2, '0')
  214 + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
  215 + }
  216 + }
  217 +}
  218 +</script>
  219 +
  220 +<style lang="scss" scoped>
  221 +.detail-container {
  222 + padding: 0;
  223 +}
  224 +
  225 +.detail-section {
  226 + margin-bottom: 24px;
  227 + background: #fff;
  228 + border-radius: 8px;
  229 + overflow: hidden;
  230 +
  231 + &:last-child {
  232 + margin-bottom: 0;
  233 + }
  234 +}
  235 +
  236 +.section-title {
  237 + display: flex;
  238 + align-items: center;
  239 + padding: 16px 20px;
  240 + background: #f5f7fa;
  241 + border-bottom: 1px solid #e4e7ed;
  242 + font-size: 16px;
  243 + font-weight: 600;
  244 + color: #303133;
  245 +}
  246 +
  247 +.section-icon {
  248 + margin-right: 8px;
  249 + color: #409EFF;
  250 + font-size: 18px;
  251 +}
  252 +
  253 +.detail-content {
  254 + padding: 20px;
  255 +}
  256 +
  257 +.detail-row {
  258 + display: flex;
  259 + align-items: flex-start;
  260 + margin-bottom: 16px;
  261 + padding-bottom: 16px;
  262 + border-bottom: 1px solid #f0f2f5;
  263 +
  264 + &:last-child {
  265 + margin-bottom: 0;
  266 + padding-bottom: 0;
  267 + border-bottom: none;
  268 + }
  269 +}
  270 +
  271 +.detail-label {
  272 + display: flex;
  273 + align-items: center;
  274 + width: 140px;
  275 + flex-shrink: 0;
  276 + font-weight: 500;
  277 + color: #606266;
  278 +}
  279 +
  280 +.detail-icon {
  281 + margin-right: 8px;
  282 + font-size: 16px;
  283 +}
  284 +
  285 +.detail-value {
  286 + flex: 1;
  287 + color: #303133;
  288 + word-break: break-all;
  289 +}
  290 +
  291 +.value-number {
  292 + font-weight: 600;
  293 + color: #E6A23C;
  294 +}
  295 +
  296 +.value-total {
  297 + font-size: 18px;
  298 + color: #F56C6C;
  299 +}
  300 +
  301 +// 图标颜色
  302 +.batch-icon {
  303 + color: #409EFF;
  304 +}
  305 +
  306 +.store-icon {
  307 + color: #409EFF;
  308 +}
  309 +
  310 +.product-icon {
  311 + color: #67C23A;
  312 +}
  313 +
  314 +.supplier-icon {
  315 + color: #409EFF;
  316 +}
  317 +
  318 +.quantity-icon {
  319 + color: #E6A23C;
  320 +}
  321 +
  322 +.price-icon {
  323 + color: #E6A23C;
  324 +}
  325 +
  326 +.total-price-icon {
  327 + color: #F56C6C;
  328 +}
  329 +
  330 +.remark-icon {
  331 + color: #909399;
  332 +}
  333 +
  334 +.status-icon {
  335 + color: #67C23A;
  336 +}
  337 +
  338 +.user-icon {
  339 + color: #909399;
  340 +}
  341 +
  342 +.time-icon {
  343 + color: #909399;
  344 +}
  345 +
  346 +.send-icon {
  347 + color: #409EFF;
  348 +}
  349 +
  350 +.return-icon {
  351 + color: #67C23A;
  352 +}
  353 +
  354 +.dialog-footer {
  355 + text-align: right;
  356 + padding-top: 20px;
  357 +}
  358 +</style>
  359 +
... ...
antis-ncc-admin/src/views/LqLaundryFlow/difference-dialog.vue 0 → 100644
  1 +<template>
  2 + <el-dialog title="差异记录(送出数量 > 送回数量)" :visible.sync="visible" width="1200px" :close-on-click-modal="false">
  3 + <!-- 查询条件 -->
  4 + <el-row class="search-box" :gutter="16">
  5 + <el-form @submit.native.prevent label-width="90px">
  6 + <el-col :span="8">
  7 + <el-form-item label="门店">
  8 + <el-select style="width: 100%" v-model="query.storeId" placeholder="请选择门店" clearable filterable>
  9 + <el-option v-for="item in storeList" :key="item.id" :label="item.fullName" :value="item.id" />
  10 + </el-select>
  11 + </el-form-item>
  12 + </el-col>
  13 + <el-col :span="8">
  14 + <el-form-item label="产品类型">
  15 + <el-input v-model="query.productType" placeholder="请输入产品类型" clearable />
  16 + </el-form-item>
  17 + </el-col>
  18 + <el-col :span="8">
  19 + <el-form-item label="开始时间">
  20 + <el-date-picker v-model="query.startTime" type="datetime" placeholder="选择开始时间" value-format="yyyy-MM-dd HH:mm:ss" style="width: 100%" />
  21 + </el-form-item>
  22 + </el-col>
  23 + <el-col :span="8" style="margin-top: 20px;">
  24 + <el-form-item label="结束时间">
  25 + <el-date-picker v-model="query.endTime" type="datetime" placeholder="选择结束时间" value-format="yyyy-MM-dd HH:mm:ss" style="width: 100%" />
  26 + </el-form-item>
  27 + </el-col>
  28 + <el-col :span="8" style="margin-top: 20px;">
  29 + <el-form-item>
  30 + <el-button type="primary" icon="el-icon-search" @click="search()">查询</el-button>
  31 + <el-button icon="el-icon-refresh-right" @click="reset()">重置</el-button>
  32 + </el-form-item>
  33 + </el-col>
  34 + </el-form>
  35 + </el-row>
  36 + <!-- 列表 -->
  37 + <NCC-table v-loading="loading" :data="list"
  38 + :header-cell-style="{ background: '#f5f7fa', color: '#606266' }">
  39 + <el-table-column label="批次号" align="center" width="180">
  40 + <template slot-scope="scope">
  41 + <div class="batch-info">
  42 + <i class="el-icon-tickets batch-icon"></i>
  43 + <span class="text-nowrap">{{ scope.row.batchNumber || '无' }}</span>
  44 + </div>
  45 + </template>
  46 + </el-table-column>
  47 + <el-table-column label="门店名称" align="center">
  48 + <template slot-scope="scope">
  49 + <div class="store-info">
  50 + <i class="el-icon-office-building store-icon"></i>
  51 + <span class="text-nowrap">{{ scope.row.storeName || '无' }}</span>
  52 + </div>
  53 + </template>
  54 + </el-table-column>
  55 + <el-table-column label="产品类型" align="center">
  56 + <template slot-scope="scope">
  57 + <div class="product-type-info">
  58 + <i class="el-icon-goods product-type-icon"></i>
  59 + <span class="text-nowrap">{{ scope.row.productType || '无' }}</span>
  60 + </div>
  61 + </template>
  62 + </el-table-column>
  63 + <el-table-column label="送出数量" align="center" width="120">
  64 + <template slot-scope="scope">
  65 + <div class="quantity-info">
  66 + <i class="el-icon-upload2 quantity-icon"></i>
  67 + <span>{{ scope.row.sendQuantity || 0 }}</span>
  68 + </div>
  69 + </template>
  70 + </el-table-column>
  71 + <el-table-column label="送回数量" align="center" width="120">
  72 + <template slot-scope="scope">
  73 + <div class="quantity-info">
  74 + <i class="el-icon-download quantity-icon"></i>
  75 + <span>{{ scope.row.returnQuantity || 0 }}</span>
  76 + </div>
  77 + </template>
  78 + </el-table-column>
  79 + <el-table-column label="差异数量" align="center" width="120">
  80 + <template slot-scope="scope">
  81 + <el-tag type="warning" size="small">
  82 + <i class="el-icon-warning" style="margin-right: 4px;"></i>
  83 + {{ scope.row.differenceQuantity || 0 }}
  84 + </el-tag>
  85 + </template>
  86 + </el-table-column>
  87 + <el-table-column label="差异备注" align="center" min-width="150">
  88 + <template slot-scope="scope">
  89 + <div class="remark-info">
  90 + <i class="el-icon-document remark-icon"></i>
  91 + <span class="text-nowrap">{{ scope.row.differenceRemark || '无' }}</span>
  92 + </div>
  93 + </template>
  94 + </el-table-column>
  95 + <el-table-column label="送出时间" align="center" width="180">
  96 + <template slot-scope="scope">
  97 + <div class="time-info">
  98 + <i class="el-icon-time time-icon"></i>
  99 + <span class="text-nowrap">{{ formatDateTime(scope.row.sendTime) || '无' }}</span>
  100 + </div>
  101 + </template>
  102 + </el-table-column>
  103 + <el-table-column label="送回时间" align="center" width="180">
  104 + <template slot-scope="scope">
  105 + <div class="time-info">
  106 + <i class="el-icon-time time-icon"></i>
  107 + <span class="text-nowrap">{{ formatDateTime(scope.row.returnTime) || '无' }}</span>
  108 + </div>
  109 + </template>
  110 + </el-table-column>
  111 + </NCC-table>
  112 + <pagination :total="total" :page.sync="query.currentPage"
  113 + :limit.sync="query.pageSize" @pagination="initData" />
  114 + <div slot="footer" class="dialog-footer">
  115 + <el-button @click="visible = false">关闭</el-button>
  116 + </div>
  117 + </el-dialog>
  118 +</template>
  119 +
  120 +<script>
  121 +import request from '@/utils/request'
  122 +import { getStoreSelector } from '@/api/extend/store'
  123 +
  124 +export default {
  125 + name: 'DifferenceDialog',
  126 + data() {
  127 + return {
  128 + visible: false,
  129 + loading: false,
  130 + list: [],
  131 + total: 0,
  132 + query: {
  133 + currentPage: 1,
  134 + pageSize: 20,
  135 + storeId: undefined,
  136 + productType: undefined,
  137 + startTime: undefined,
  138 + endTime: undefined
  139 + },
  140 + storeList: []
  141 + }
  142 + },
  143 + mounted() {
  144 + this.initStoreList()
  145 + },
  146 + methods: {
  147 + // 初始化
  148 + init() {
  149 + this.visible = true
  150 + this.reset()
  151 + this.initData()
  152 + },
  153 + // 初始化门店列表
  154 + initStoreList() {
  155 + getStoreSelector().then(res => {
  156 + if (res.code == 200 && res.data && res.data.list) {
  157 + this.storeList = res.data.list
  158 + }
  159 + }).catch(() => {
  160 + this.storeList = []
  161 + })
  162 + },
  163 + // 初始化数据
  164 + initData() {
  165 + this.loading = true
  166 + let query = { ...this.query }
  167 + // 移除空值
  168 + Object.keys(query).forEach(key => {
  169 + if (query[key] === undefined || query[key] === null || query[key] === '') {
  170 + delete query[key]
  171 + }
  172 + })
  173 + request({
  174 + url: '/api/Extend/LqLaundryFlow/GetDifferenceList',
  175 + method: 'POST',
  176 + data: query
  177 + }).then(res => {
  178 + if (res.code == 200 && res.data) {
  179 + this.list = res.data.list || []
  180 + this.total = res.data.pagination ? res.data.pagination.total : 0
  181 + } else {
  182 + this.list = []
  183 + this.total = 0
  184 + }
  185 + this.loading = false
  186 + }).catch(() => {
  187 + this.loading = false
  188 + this.list = []
  189 + this.total = 0
  190 + })
  191 + },
  192 + // 查询
  193 + search() {
  194 + this.query.currentPage = 1
  195 + this.initData()
  196 + },
  197 + // 重置
  198 + reset() {
  199 + this.query = {
  200 + currentPage: 1,
  201 + pageSize: 20,
  202 + storeId: undefined,
  203 + productType: undefined,
  204 + startTime: undefined,
  205 + endTime: undefined
  206 + }
  207 + },
  208 + // 格式化日期时间
  209 + formatDateTime(dateTime) {
  210 + if (!dateTime) return ''
  211 + const date = new Date(dateTime)
  212 + const year = date.getFullYear()
  213 + const month = String(date.getMonth() + 1).padStart(2, '0')
  214 + const day = String(date.getDate()).padStart(2, '0')
  215 + const hours = String(date.getHours()).padStart(2, '0')
  216 + const minutes = String(date.getMinutes()).padStart(2, '0')
  217 + const seconds = String(date.getSeconds()).padStart(2, '0')
  218 + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
  219 + }
  220 + }
  221 +}
  222 +</script>
  223 +
  224 +<style lang="scss" scoped>
  225 +.search-box {
  226 + margin-bottom: 16px;
  227 + padding: 16px;
  228 + background: #f5f7fa;
  229 + border-radius: 4px;
  230 +
  231 + ::v-deep .el-form-item {
  232 + margin-bottom: 0;
  233 + }
  234 +
  235 + ::v-deep .el-button {
  236 + cursor: pointer;
  237 + }
  238 +}
  239 +
  240 +// 通用文本不换行样式
  241 +.text-nowrap {
  242 + white-space: nowrap;
  243 + overflow: hidden;
  244 + text-overflow: ellipsis;
  245 + max-width: 100%;
  246 +}
  247 +
  248 +// 信息显示样式
  249 +.batch-info,
  250 +.store-info,
  251 +.product-type-info,
  252 +.quantity-info,
  253 +.remark-info,
  254 +.time-info {
  255 + display: flex;
  256 + align-items: center;
  257 + justify-content: center;
  258 + gap: 6px;
  259 +}
  260 +
  261 +.batch-icon {
  262 + color: #409EFF;
  263 + font-size: 16px;
  264 +}
  265 +
  266 +.store-icon {
  267 + color: #409EFF;
  268 + font-size: 16px;
  269 +}
  270 +
  271 +.product-type-icon {
  272 + color: #67C23A;
  273 + font-size: 16px;
  274 +}
  275 +
  276 +.quantity-icon {
  277 + color: #E6A23C;
  278 + font-size: 16px;
  279 +}
  280 +
  281 +.remark-icon {
  282 + color: #909399;
  283 + font-size: 16px;
  284 +}
  285 +
  286 +.time-icon {
  287 + color: #909399;
  288 + font-size: 16px;
  289 +}
  290 +
  291 +.dialog-footer {
  292 + text-align: right;
  293 +}
  294 +</style>
  295 +
... ...
antis-ncc-admin/src/views/LqLaundryFlow/index.vue 0 → 100644
  1 +<template>
  2 + <div class="NCC-common-layout">
  3 + <div class="NCC-common-layout-center">
  4 + <!-- 查询条件 -->
  5 + <el-row class="NCC-common-search-box" :gutter="16">
  6 + <el-form @submit.native.prevent>
  7 + <el-col :span="6">
  8 + <el-form-item label="流水类型">
  9 + <el-select v-model="query.flowType" placeholder="请选择流水类型" clearable>
  10 + <el-option label="送出" :value="0" />
  11 + <el-option label="送回" :value="1" />
  12 + </el-select>
  13 + </el-form-item>
  14 + </el-col>
  15 + <el-col :span="6">
  16 + <el-form-item label="批次号">
  17 + <el-input v-model="query.batchNumber" placeholder="请输入批次号" clearable />
  18 + </el-form-item>
  19 + </el-col>
  20 + <el-col :span="6">
  21 + <el-form-item label="门店">
  22 + <el-select v-model="query.storeId" placeholder="请选择门店" clearable filterable>
  23 + <el-option v-for="item in storeList" :key="item.id" :label="item.fullName" :value="item.id" />
  24 + </el-select>
  25 + </el-form-item>
  26 + </el-col>
  27 + <el-col :span="6">
  28 + <el-form-item label="产品类型">
  29 + <el-input v-model="query.productType" placeholder="请输入产品类型" clearable />
  30 + </el-form-item>
  31 + </el-col>
  32 + <el-col :span="6">
  33 + <el-form-item label="清洗商">
  34 + <el-select v-model="query.laundrySupplierId" placeholder="请选择清洗商" clearable filterable>
  35 + <el-option v-for="item in supplierList" :key="item.id" :label="item.supplierName" :value="item.id" />
  36 + </el-select>
  37 + </el-form-item>
  38 + </el-col>
  39 + <el-col :span="6">
  40 + <el-form-item label="开始时间">
  41 + <el-date-picker v-model="query.startTime" type="datetime" placeholder="选择开始时间" value-format="yyyy-MM-dd HH:mm:ss" style="width: 100%" />
  42 + </el-form-item>
  43 + </el-col>
  44 + <el-col :span="6">
  45 + <el-form-item label="结束时间">
  46 + <el-date-picker v-model="query.endTime" type="datetime" placeholder="选择结束时间" value-format="yyyy-MM-dd HH:mm:ss" style="width: 100%" />
  47 + </el-form-item>
  48 + </el-col>
  49 + <el-col :span="6">
  50 + <el-form-item label="是否有效">
  51 + <el-select v-model="query.isEffective" placeholder="是否有效" clearable>
  52 + <el-option label="有效" :value="1" />
  53 + <el-option label="无效" :value="0" />
  54 + </el-select>
  55 + </el-form-item>
  56 + </el-col>
  57 + <el-col :span="6">
  58 + <el-form-item>
  59 + <el-button type="primary" icon="el-icon-search" @click="search()">查询</el-button>
  60 + <el-button icon="el-icon-refresh-right" @click="reset()">重置</el-button>
  61 + </el-form-item>
  62 + </el-col>
  63 + </el-form>
  64 + </el-row>
  65 + <!-- 列表 -->
  66 + <div class="NCC-common-layout-main NCC-flex-main">
  67 + <div class="NCC-common-head">
  68 + <div>
  69 + <el-button type="primary" icon="el-icon-plus" @click="addSend()">创建送出记录</el-button>
  70 + <el-button type="success" icon="el-icon-refresh" @click="addReturn()">创建送回记录</el-button>
  71 + <el-button type="warning" icon="el-icon-warning" @click="viewDifference()">查看差异记录</el-button>
  72 + </div>
  73 + <div class="NCC-common-head-right">
  74 + <el-tooltip effect="dark" content="刷新" placement="top">
  75 + <el-link icon="icon-ym icon-ym-Refresh NCC-common-head-icon" :underline="false"
  76 + @click="initData()" />
  77 + </el-tooltip>
  78 + <screenfull isContainer />
  79 + </div>
  80 + </div>
  81 + <NCC-table v-loading="loading" :data="list"
  82 + :header-cell-style="{ background: '#f5f7fa', color: '#606266' }">
  83 + <el-table-column label="流水类型" align="center" width="100">
  84 + <template slot-scope="scope">
  85 + <el-tag :type="scope.row.flowType === 0 ? 'primary' : 'success'" size="small">
  86 + <i :class="scope.row.flowType === 0 ? 'el-icon-upload2' : 'el-icon-download'" style="margin-right: 4px;"></i>
  87 + {{ scope.row.flowTypeName || '无' }}
  88 + </el-tag>
  89 + </template>
  90 + </el-table-column>
  91 + <el-table-column label="批次号" align="center" width="180">
  92 + <template slot-scope="scope">
  93 + <div class="batch-info">
  94 + <i class="el-icon-tickets batch-icon"></i>
  95 + <span class="text-nowrap">{{ scope.row.batchNumber || '无' }}</span>
  96 + </div>
  97 + </template>
  98 + </el-table-column>
  99 + <el-table-column label="门店名称" align="center">
  100 + <template slot-scope="scope">
  101 + <div class="store-info">
  102 + <i class="el-icon-office-building store-icon"></i>
  103 + <span class="text-nowrap">{{ scope.row.storeName || '无' }}</span>
  104 + </div>
  105 + </template>
  106 + </el-table-column>
  107 + <el-table-column label="产品类型" align="center">
  108 + <template slot-scope="scope">
  109 + <div class="product-type-info">
  110 + <i class="el-icon-goods product-type-icon"></i>
  111 + <span class="text-nowrap">{{ scope.row.productType || '无' }}</span>
  112 + </div>
  113 + </template>
  114 + </el-table-column>
  115 + <el-table-column label="清洗商名称" align="center">
  116 + <template slot-scope="scope">
  117 + <div class="supplier-info">
  118 + <i class="el-icon-s-shop supplier-icon"></i>
  119 + <span class="text-nowrap">{{ scope.row.laundrySupplierName || '无' }}</span>
  120 + </div>
  121 + </template>
  122 + </el-table-column>
  123 + <el-table-column label="数量" align="center" width="100">
  124 + <template slot-scope="scope">
  125 + <div class="quantity-info">
  126 + <i class="el-icon-s-data quantity-icon"></i>
  127 + <span>{{ scope.row.quantity || 0 }}</span>
  128 + </div>
  129 + </template>
  130 + </el-table-column>
  131 + <el-table-column label="清洗单价" align="center" width="120">
  132 + <template slot-scope="scope">
  133 + <div class="price-info">
  134 + <i class="el-icon-coin price-icon"></i>
  135 + <span>{{ scope.row.laundryPrice || 0 }}</span>
  136 + </div>
  137 + </template>
  138 + </el-table-column>
  139 + <el-table-column label="总费用" align="center" width="120">
  140 + <template slot-scope="scope">
  141 + <div class="total-price-info">
  142 + <i class="el-icon-money total-price-icon"></i>
  143 + <span>{{ scope.row.totalPrice || 0 }}</span>
  144 + </div>
  145 + </template>
  146 + </el-table-column>
  147 + <el-table-column label="备注" align="center" min-width="150">
  148 + <template slot-scope="scope">
  149 + <div class="remark-info">
  150 + <i class="el-icon-document remark-icon"></i>
  151 + <span class="text-nowrap">{{ scope.row.remark || '无' }}</span>
  152 + </div>
  153 + </template>
  154 + </el-table-column>
  155 + <el-table-column label="是否有效" align="center" width="100">
  156 + <template slot-scope="scope">
  157 + <el-tag :type="scope.row.isEffective === 1 ? 'success' : 'info'" size="small">
  158 + {{ scope.row.isEffective === 1 ? '有效' : '无效' }}
  159 + </el-tag>
  160 + </template>
  161 + </el-table-column>
  162 + <el-table-column label="创建人" align="center">
  163 + <template slot-scope="scope">
  164 + <div class="user-info">
  165 + <i class="el-icon-user user-icon"></i>
  166 + <span class="text-nowrap">{{ scope.row.createUserName || '无' }}</span>
  167 + </div>
  168 + </template>
  169 + </el-table-column>
  170 + <el-table-column label="创建时间" align="center" width="180" prop="createTime">
  171 + <template slot-scope="scope">
  172 + <div class="time-info">
  173 + <i class="el-icon-time time-icon"></i>
  174 + <span class="text-nowrap">{{ formatDateTime(scope.row.createTime) || '无' }}</span>
  175 + </div>
  176 + </template>
  177 + </el-table-column>
  178 + <el-table-column label="操作" width="150" align="left" fixed="right">
  179 + <template slot-scope="scope">
  180 + <div class="action-buttons">
  181 + <el-button type="text" icon="el-icon-view" @click="viewDetail(scope.row)" class="view-btn">
  182 + 详情
  183 + </el-button>
  184 + <!-- <el-button v-if="scope.row.flowTypeName == '送出'" type="text" icon="el-icon-edit" class="edit-btn">
  185 + 创建送回
  186 + </el-button> -->
  187 + </div>
  188 + </template>
  189 + </el-table-column>
  190 + </NCC-table>
  191 + <pagination :total="total" :page.sync="query.currentPage"
  192 + :limit.sync="query.pageSize" @pagination="initData" />
  193 + </div>
  194 + </div>
  195 + <!-- 送出记录弹窗 -->
  196 + <SendDialog v-if="sendDialogVisible" ref="SendDialog" @refresh="refresh" />
  197 + <!-- 送回记录弹窗 -->
  198 + <ReturnDialog v-if="returnDialogVisible" ref="ReturnDialog" @refresh="refresh" />
  199 + <!-- 详情弹窗 -->
  200 + <DetailDialog v-if="detailDialogVisible" ref="DetailDialog" />
  201 + <!-- 差异记录弹窗 -->
  202 + <DifferenceDialog v-if="differenceDialogVisible" ref="DifferenceDialog" />
  203 + </div>
  204 +</template>
  205 +
  206 +<script>
  207 +import request from '@/utils/request'
  208 +import { getStoreSelector } from '@/api/extend/store'
  209 +import SendDialog from './send-dialog.vue'
  210 +import ReturnDialog from './return-dialog.vue'
  211 +import DetailDialog from './detail-dialog.vue'
  212 +import DifferenceDialog from './difference-dialog.vue'
  213 +
  214 +export default {
  215 + components: { SendDialog, ReturnDialog, DetailDialog, DifferenceDialog },
  216 + data() {
  217 + return {
  218 + list: [],
  219 + loading: false,
  220 + total: 0,
  221 + query: {
  222 + currentPage: 1,
  223 + pageSize: 20,
  224 + flowType: undefined,
  225 + batchNumber: undefined,
  226 + storeId: undefined,
  227 + productType: undefined,
  228 + laundrySupplierId: undefined,
  229 + startTime: undefined,
  230 + endTime: undefined,
  231 + isEffective: undefined
  232 + },
  233 + storeList: [],
  234 + supplierList: [],
  235 + sendDialogVisible: false,
  236 + returnDialogVisible: false,
  237 + detailDialogVisible: false,
  238 + differenceDialogVisible: false
  239 + }
  240 + },
  241 + created() {
  242 + this.initStoreList()
  243 + this.initSupplierList()
  244 + this.initData()
  245 + },
  246 + methods: {
  247 + // 初始化门店列表
  248 + initStoreList() {
  249 + getStoreSelector().then(res => {
  250 + if (res.code == 200 && res.data && res.data.list) {
  251 + this.storeList = res.data.list
  252 + }
  253 + }).catch(() => {
  254 + this.storeList = []
  255 + })
  256 + },
  257 + // 初始化清洗商列表
  258 + initSupplierList() {
  259 + request({
  260 + url: '/api/Extend/LqLaundrySupplier/GetList',
  261 + method: 'GET',
  262 + data: { currentPage: 1, pageSize: 1000, isEffective: 1 }
  263 + }).then(res => {
  264 + if (res.code == 200 && res.data && res.data.list) {
  265 + this.supplierList = res.data.list
  266 + }
  267 + }).catch(() => {
  268 + this.supplierList = []
  269 + })
  270 + },
  271 + // 初始化数据
  272 + initData() {
  273 + this.loading = true
  274 + let query = { ...this.query }
  275 + // 移除空值
  276 + Object.keys(query).forEach(key => {
  277 + if (query[key] === undefined || query[key] === null || query[key] === '') {
  278 + delete query[key]
  279 + }
  280 + })
  281 + request({
  282 + url: '/api/Extend/LqLaundryFlow/GetList',
  283 + method: 'GET',
  284 + data: query
  285 + }).then(res => {
  286 + if (res.code == 200 && res.data) {
  287 + this.list = res.data.list || []
  288 + this.total = res.data.pagination ? res.data.pagination.total : 0
  289 + } else {
  290 + this.list = []
  291 + this.total = 0
  292 + }
  293 + this.loading = false
  294 + }).catch(() => {
  295 + this.loading = false
  296 + this.list = []
  297 + this.total = 0
  298 + })
  299 + },
  300 + // 查询
  301 + search() {
  302 + this.query.currentPage = 1
  303 + this.initData()
  304 + },
  305 + // 重置
  306 + reset() {
  307 + this.query = {
  308 + currentPage: 1,
  309 + pageSize: 20,
  310 + flowType: undefined,
  311 + batchNumber: undefined,
  312 + storeId: undefined,
  313 + productType: undefined,
  314 + laundrySupplierId: undefined,
  315 + startTime: undefined,
  316 + endTime: undefined,
  317 + isEffective: undefined
  318 + }
  319 + this.initData()
  320 + },
  321 + // 创建送出记录
  322 + addSend() {
  323 + this.sendDialogVisible = true
  324 + this.$nextTick(() => {
  325 + this.$refs.SendDialog.init()
  326 + })
  327 + },
  328 + // 创建送回记录
  329 + addReturn() {
  330 + this.returnDialogVisible = true
  331 + this.$nextTick(() => {
  332 + this.$refs.ReturnDialog.init()
  333 + })
  334 + },
  335 + // 查看详情
  336 + viewDetail(row) {
  337 + this.detailDialogVisible = true
  338 + this.$nextTick(() => {
  339 + this.$refs.DetailDialog.init(row.id)
  340 + })
  341 + },
  342 + // 查看差异记录
  343 + viewDifference() {
  344 + this.differenceDialogVisible = true
  345 + this.$nextTick(() => {
  346 + this.$refs.DifferenceDialog.init()
  347 + })
  348 + },
  349 + // 刷新
  350 + refresh() {
  351 + this.sendDialogVisible = false
  352 + this.returnDialogVisible = false
  353 + this.initData()
  354 + },
  355 + // 格式化日期时间
  356 + formatDateTime(dateTime) {
  357 + if (!dateTime) return ''
  358 + const date = new Date(dateTime)
  359 + const year = date.getFullYear()
  360 + const month = String(date.getMonth() + 1).padStart(2, '0')
  361 + const day = String(date.getDate()).padStart(2, '0')
  362 + const hours = String(date.getHours()).padStart(2, '0')
  363 + const minutes = String(date.getMinutes()).padStart(2, '0')
  364 + const seconds = String(date.getSeconds()).padStart(2, '0')
  365 + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
  366 + }
  367 + }
  368 +}
  369 +</script>
  370 +
  371 +<style lang="scss" scoped>
  372 +// 通用文本不换行样式
  373 +.text-nowrap {
  374 + white-space: nowrap;
  375 + overflow: hidden;
  376 + text-overflow: ellipsis;
  377 + max-width: 100%;
  378 +}
  379 +
  380 +// 信息显示样式
  381 +.batch-info,
  382 +.store-info,
  383 +.product-type-info,
  384 +.supplier-info,
  385 +.quantity-info,
  386 +.price-info,
  387 +.total-price-info,
  388 +.remark-info,
  389 +.user-info,
  390 +.time-info {
  391 + display: flex;
  392 + align-items: center;
  393 + justify-content: center;
  394 + gap: 6px;
  395 +}
  396 +
  397 +.batch-icon {
  398 + color: #409EFF;
  399 + font-size: 16px;
  400 +}
  401 +
  402 +.store-icon {
  403 + color: #409EFF;
  404 + font-size: 16px;
  405 +}
  406 +
  407 +.product-type-icon {
  408 + color: #67C23A;
  409 + font-size: 16px;
  410 +}
  411 +
  412 +.supplier-icon {
  413 + color: #409EFF;
  414 + font-size: 16px;
  415 +}
  416 +
  417 +.quantity-icon {
  418 + color: #E6A23C;
  419 + font-size: 16px;
  420 +}
  421 +
  422 +.price-icon {
  423 + color: #E6A23C;
  424 + font-size: 16px;
  425 +}
  426 +
  427 +.total-price-icon {
  428 + color: #F56C6C;
  429 + font-size: 16px;
  430 +}
  431 +
  432 +.remark-icon {
  433 + color: #909399;
  434 + font-size: 16px;
  435 +}
  436 +
  437 +.user-icon {
  438 + color: #909399;
  439 + font-size: 16px;
  440 +}
  441 +
  442 +.time-icon {
  443 + color: #909399;
  444 + font-size: 16px;
  445 +}
  446 +
  447 +// 操作按钮样式
  448 +.action-buttons {
  449 + display: flex;
  450 + align-items: center;
  451 + gap: 8px;
  452 +}
  453 +
  454 +.view-btn {
  455 + color: #409EFF;
  456 +
  457 + &:hover {
  458 + color: #66b1ff;
  459 + }
  460 +}
  461 +
  462 +// 表格行悬停效果
  463 +::v-deep .el-table__row:hover {
  464 + background-color: #f5f7fa;
  465 +}
  466 +
  467 +// 表格头部样式
  468 +::v-deep .el-table__header-wrapper {
  469 + .el-table__header {
  470 + th {
  471 + background-color: #f5f7fa;
  472 + color: #606266;
  473 + font-weight: 600;
  474 + }
  475 + }
  476 +}
  477 +</style>
  478 +
... ...
antis-ncc-admin/src/views/LqLaundryFlow/return-dialog.vue 0 → 100644
  1 +<template>
  2 + <el-dialog title="创建送回记录" :visible.sync="visible" width="600px" :close-on-click-modal="false">
  3 + <el-form ref="form" :model="form" :rules="rules" label-width="120px">
  4 + <el-form-item label="批次号" prop="batchNumber">
  5 + <el-select v-model="form.batchNumber" placeholder="请选择批次号" filterable style="width: 100%" @change="handleBatchChange">
  6 + <el-option v-for="item in batchList" :key="item.batchNumber" :label="`${item.batchNumber} (${item.storeName} - ${item.productType} - 送出${item.quantity}件)`" :value="item.batchNumber" />
  7 + </el-select>
  8 + </el-form-item>
  9 + <el-form-item label="门店">
  10 + <el-input v-model="sendRecordInfo.storeName" disabled />
  11 + </el-form-item>
  12 + <el-form-item label="产品类型">
  13 + <el-input v-model="sendRecordInfo.productType" disabled />
  14 + </el-form-item>
  15 + <el-form-item label="送出数量">
  16 + <el-input v-model="sendRecordInfo.quantity" disabled />
  17 + </el-form-item>
  18 + <el-form-item label="清洗商" prop="laundrySupplierId">
  19 + <el-select v-model="form.laundrySupplierId" placeholder="请选择清洗商" filterable style="width: 100%" @change="handleSupplierChange">
  20 + <el-option v-for="item in filteredSupplierList" :key="item.id" :label="item.supplierName" :value="item.id" />
  21 + </el-select>
  22 + </el-form-item>
  23 + <el-form-item label="清洗单价" prop="laundryPrice">
  24 + <el-input v-model="form.laundryPrice" placeholder="自动填充" disabled />
  25 + </el-form-item>
  26 + <el-form-item label="送回数量" prop="quantity">
  27 + <el-input-number v-model="form.quantity" :min="0" :precision="0" style="width: 100%" />
  28 + </el-form-item>
  29 + <el-form-item label="预计总费用">
  30 + <el-input v-model="totalPrice" disabled />
  31 + </el-form-item>
  32 + <el-form-item label="备注">
  33 + <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注(可说明差异原因,如损坏、丢失等)" />
  34 + </el-form-item>
  35 + </el-form>
  36 + <div slot="footer" class="dialog-footer">
  37 + <el-button @click="visible = false">取消</el-button>
  38 + <el-button type="primary" @click="submit" :loading="loading">确定</el-button>
  39 + </div>
  40 + </el-dialog>
  41 +</template>
  42 +
  43 +<script>
  44 +import request from '@/utils/request'
  45 +
  46 +export default {
  47 + name: 'ReturnDialog',
  48 + data() {
  49 + return {
  50 + visible: false,
  51 + loading: false,
  52 + form: {
  53 + batchNumber: '',
  54 + laundrySupplierId: '',
  55 + quantity: 0,
  56 + remark: ''
  57 + },
  58 + rules: {
  59 + batchNumber: [{ required: true, message: '请选择批次号', trigger: 'change' }],
  60 + laundrySupplierId: [{ required: true, message: '请选择清洗商', trigger: 'blur' }],
  61 + quantity: [{ required: true, message: '请输入送回数量', trigger: 'blur' }]
  62 + },
  63 + batchList: [],
  64 + supplierList: [],
  65 + allSupplierList: [], // 保存所有清洗商列表
  66 + sendRecordInfo: {
  67 + storeName: '',
  68 + productType: '',
  69 + quantity: 0
  70 + },
  71 + selectedSupplier: null
  72 + }
  73 + },
  74 + computed: {
  75 + totalPrice() {
  76 + if (this.form.quantity && this.form.laundryPrice) {
  77 + return (this.form.quantity * this.form.laundryPrice).toFixed(2)
  78 + }
  79 + return '0.00'
  80 + },
  81 + // 过滤后的清洗商列表(根据产品类型)
  82 + filteredSupplierList() {
  83 + if (!this.sendRecordInfo.productType) {
  84 + return this.allSupplierList
  85 + }
  86 + return this.allSupplierList.filter(supplier => supplier.productType === this.sendRecordInfo.productType)
  87 + }
  88 + },
  89 + mounted() {
  90 + this.initSupplierList()
  91 + this.initBatchList()
  92 + },
  93 + methods: {
  94 + // 初始化
  95 + init() {
  96 + this.visible = true
  97 + this.form = {
  98 + batchNumber: '',
  99 + laundrySupplierId: '',
  100 + quantity: 0,
  101 + remark: ''
  102 + }
  103 + this.sendRecordInfo = {
  104 + storeName: '',
  105 + productType: '',
  106 + quantity: 0
  107 + }
  108 + this.selectedSupplier = null
  109 + this.initBatchList()
  110 + this.$nextTick(() => {
  111 + if (this.$refs.form) {
  112 + this.$refs.form.clearValidate()
  113 + }
  114 + })
  115 + },
  116 + // 初始化批次列表(只显示已送出但未送回或送回数量小于送出数量的记录)
  117 + initBatchList() {
  118 + request({
  119 + url: '/api/Extend/LqLaundryFlow/GetList',
  120 + method: 'GET',
  121 + data: { currentPage: 1, pageSize: 1000, flowType: 0, isEffective: 1 }
  122 + }).then(res => {
  123 + if (res.code == 200 && res.data && res.data.list) {
  124 + // 获取所有送出记录
  125 + const sendRecords = res.data.list
  126 + // 获取所有送回记录
  127 + request({
  128 + url: '/api/Extend/LqLaundryFlow/GetList',
  129 + method: 'GET',
  130 + data: { currentPage: 1, pageSize: 1000, flowType: 1, isEffective: 1 }
  131 + }).then(returnRes => {
  132 + if (returnRes.code == 200 && returnRes.data && returnRes.data.list) {
  133 + const returnRecords = returnRes.data.list
  134 + // 按批次号分组统计送回数量
  135 + const returnMap = {}
  136 + returnRecords.forEach(item => {
  137 + if (!returnMap[item.batchNumber]) {
  138 + returnMap[item.batchNumber] = 0
  139 + }
  140 + returnMap[item.batchNumber] += item.quantity
  141 + })
  142 + // 过滤出可以送回记录的批次(送出数量 > 已送回数量)
  143 + this.batchList = sendRecords.filter(send => {
  144 + const returnedQty = returnMap[send.batchNumber] || 0
  145 + return send.quantity > returnedQty
  146 + }).map(send => ({
  147 + batchNumber: send.batchNumber,
  148 + storeName: send.storeName,
  149 + productType: send.productType,
  150 + quantity: send.quantity,
  151 + returnedQty: returnMap[send.batchNumber] || 0
  152 + }))
  153 + } else {
  154 + // 如果没有送回记录,所有送出记录都可以送回
  155 + this.batchList = sendRecords.map(send => ({
  156 + batchNumber: send.batchNumber,
  157 + storeName: send.storeName,
  158 + productType: send.productType,
  159 + quantity: send.quantity,
  160 + returnedQty: 0
  161 + }))
  162 + }
  163 + }).catch(() => {
  164 + // 如果获取送回记录失败,所有送出记录都可以送回
  165 + this.batchList = sendRecords.map(send => ({
  166 + batchNumber: send.batchNumber,
  167 + storeName: send.storeName,
  168 + productType: send.productType,
  169 + quantity: send.quantity,
  170 + returnedQty: 0
  171 + }))
  172 + })
  173 + } else {
  174 + this.batchList = []
  175 + }
  176 + }).catch(() => {
  177 + this.batchList = []
  178 + })
  179 + },
  180 + // 初始化清洗商列表
  181 + initSupplierList() {
  182 + request({
  183 + url: '/api/Extend/LqLaundrySupplier/GetList',
  184 + method: 'GET',
  185 + data: { currentPage: 1, pageSize: 1000, isEffective: 1 }
  186 + }).then(res => {
  187 + if (res.code == 200 && res.data && res.data.list) {
  188 + this.allSupplierList = res.data.list
  189 + this.supplierList = res.data.list
  190 + }
  191 + }).catch(() => {
  192 + this.allSupplierList = []
  193 + this.supplierList = []
  194 + })
  195 + },
  196 + // 批次号变化
  197 + handleBatchChange(value) {
  198 + const batch = this.batchList.find(item => item.batchNumber === value)
  199 + if (batch) {
  200 + this.sendRecordInfo = {
  201 + storeName: batch.storeName,
  202 + productType: batch.productType,
  203 + quantity: batch.quantity
  204 + }
  205 + // 清空已选择的清洗商
  206 + this.form.laundrySupplierId = ''
  207 + this.form.laundryPrice = 0
  208 + this.selectedSupplier = null
  209 + } else {
  210 + this.sendRecordInfo = {
  211 + storeName: '',
  212 + productType: '',
  213 + quantity: 0
  214 + }
  215 + }
  216 + },
  217 + // 清洗商变化
  218 + handleSupplierChange(value) {
  219 + const supplier = this.filteredSupplierList.find(item => item.id === value)
  220 + if (supplier) {
  221 + this.selectedSupplier = supplier
  222 + this.$set(this.form, 'laundryPrice', supplier.laundryPrice || 0)
  223 + } else {
  224 + this.selectedSupplier = null
  225 + this.$set(this.form, 'laundryPrice', 0)
  226 + }
  227 + },
  228 + // 提交
  229 + submit() {
  230 + this.$refs.form.validate(valid => {
  231 + if (!valid) return
  232 +
  233 + // 验证产品类型是否匹配(通过计算属性已经过滤,这里做二次验证)
  234 + if (this.selectedSupplier && this.sendRecordInfo.productType && this.sendRecordInfo.productType !== this.selectedSupplier.productType) {
  235 + this.$message.error(`清洗商【${this.selectedSupplier.supplierName}】不支持清洗产品类型【${this.sendRecordInfo.productType}】`)
  236 + return
  237 + }
  238 +
  239 + this.loading = true
  240 + request({
  241 + url: '/api/Extend/LqLaundryFlow/Return',
  242 + method: 'POST',
  243 + data: {
  244 + batchNumber: this.form.batchNumber,
  245 + laundrySupplierId: this.form.laundrySupplierId,
  246 + quantity: this.form.quantity,
  247 + remark: this.form.remark
  248 + }
  249 + }).then(res => {
  250 + this.loading = false
  251 + if (res.code == 200) {
  252 + this.$message.success(res.msg || '创建成功')
  253 + this.visible = false
  254 + this.$emit('refresh')
  255 + } else {
  256 + this.$message.error(res.msg || '创建失败')
  257 + }
  258 + }).catch(() => {
  259 + this.loading = false
  260 + })
  261 + })
  262 + }
  263 + },
  264 + watch: {
  265 + visible(val) {
  266 + if (!val) {
  267 + this.$refs.form && this.$refs.form.resetFields()
  268 + }
  269 + }
  270 + }
  271 +}
  272 +</script>
  273 +
  274 +<style lang="scss" scoped>
  275 +.dialog-footer {
  276 + text-align: right;
  277 +}
  278 +</style>
  279 +
... ...
antis-ncc-admin/src/views/LqLaundryFlow/send-dialog.vue 0 → 100644
  1 +<template>
  2 + <el-dialog title="创建送出记录" :visible.sync="visible" width="600px" :close-on-click-modal="false">
  3 + <el-form ref="form" :model="form" :rules="rules" label-width="120px">
  4 + <el-form-item label="门店" prop="storeId">
  5 + <el-select v-model="form.storeId" placeholder="请选择门店" filterable style="width: 100%" @change="handleStoreChange">
  6 + <el-option v-for="item in storeList" :key="item.id" :label="item.fullName" :value="item.id" />
  7 + </el-select>
  8 + </el-form-item>
  9 + <el-form-item label="产品类型" prop="productType">
  10 + <el-select v-model="form.productType" placeholder="请选择产品类型" clearable filterable style="width: 100%">
  11 + <el-option v-for="item in productTypeOptions" :key="item.Value" :label="item.Name" :value="item.Name" />
  12 + </el-select>
  13 + </el-form-item>
  14 + <el-form-item label="清洗商" prop="laundrySupplierId">
  15 + <el-select v-model="form.laundrySupplierId" placeholder="请选择清洗商" filterable style="width: 100%" @change="handleSupplierChange">
  16 + <el-option v-for="item in supplierList" :key="item.id" :label="item.supplierName" :value="item.id" />
  17 + </el-select>
  18 + </el-form-item>
  19 + <el-form-item label="清洗单价" prop="laundryPrice">
  20 + <el-input v-model="form.laundryPrice" placeholder="自动填充" disabled />
  21 + </el-form-item>
  22 + <el-form-item label="送出数量" prop="quantity">
  23 + <el-input-number v-model="form.quantity" :min="1" :precision="0" style="width: 100%" />
  24 + </el-form-item>
  25 + <el-form-item label="备注">
  26 + <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
  27 + </el-form-item>
  28 + </el-form>
  29 + <div slot="footer" class="dialog-footer">
  30 + <el-button @click="visible = false">取消</el-button>
  31 + <el-button type="primary" @click="submit" :loading="loading">确定</el-button>
  32 + </div>
  33 + </el-dialog>
  34 +</template>
  35 +
  36 +<script>
  37 +import request from '@/utils/request'
  38 +import { getStoreSelector } from '@/api/extend/store'
  39 +
  40 +export default {
  41 + name: 'SendDialog',
  42 + data() {
  43 + return {
  44 + visible: false,
  45 + loading: false,
  46 + form: {
  47 + storeId: '',
  48 + productType: '',
  49 + laundrySupplierId: '',
  50 + quantity: 1,
  51 + remark: ''
  52 + },
  53 + rules: {
  54 + storeId: [{ required: true, message: '请选择门店', trigger: 'change' }],
  55 + productType: [{ required: true, message: '请选择产品类型', trigger: 'change' }],
  56 + laundrySupplierId: [{ required: true, message: '请选择清洗商', trigger: 'change' }],
  57 + quantity: [{ required: true, message: '请输入送出数量', trigger: 'blur' }]
  58 + },
  59 + storeList: [],
  60 + supplierList: [],
  61 + selectedSupplier: null,
  62 + productTypeOptions: []
  63 + }
  64 + },
  65 + mounted() {
  66 + this.initStoreList()
  67 + this.initSupplierList()
  68 + this.loadProductTypeOptions()
  69 + },
  70 + methods: {
  71 + // 初始化
  72 + init() {
  73 + this.visible = true
  74 + this.form = {
  75 + storeId: '',
  76 + productType: '',
  77 + laundrySupplierId: '',
  78 + quantity: 1,
  79 + remark: ''
  80 + }
  81 + this.selectedSupplier = null
  82 + this.$nextTick(() => {
  83 + if (this.$refs.form) {
  84 + this.$refs.form.clearValidate()
  85 + }
  86 + })
  87 + },
  88 + // 初始化门店列表
  89 + initStoreList() {
  90 + getStoreSelector().then(res => {
  91 + if (res.code == 200 && res.data && res.data.list) {
  92 + this.storeList = res.data.list
  93 + }
  94 + }).catch(() => {
  95 + this.storeList = []
  96 + })
  97 + },
  98 + // 初始化清洗商列表
  99 + initSupplierList() {
  100 + request({
  101 + url: '/api/Extend/LqLaundrySupplier/GetList',
  102 + method: 'GET',
  103 + data: { currentPage: 1, pageSize: 1000, isEffective: 1 }
  104 + }).then(res => {
  105 + if (res.code == 200 && res.data && res.data.list) {
  106 + this.supplierList = res.data.list
  107 + }
  108 + }).catch(() => {
  109 + this.supplierList = []
  110 + })
  111 + },
  112 + // 加载产品类型选项
  113 + loadProductTypeOptions() {
  114 + request({
  115 + url: '/api/Extend/LqStoreConsumableInventory/consumable-product-type',
  116 + method: 'GET'
  117 + }).then(res => {
  118 + if (res.code == 200 && res.data && Array.isArray(res.data)) {
  119 + this.productTypeOptions = res.data
  120 + } else {
  121 + this.productTypeOptions = []
  122 + }
  123 + }).catch(() => {
  124 + this.productTypeOptions = []
  125 + })
  126 + },
  127 + // 门店变化
  128 + handleStoreChange() {
  129 + // 可以在这里添加逻辑
  130 + },
  131 + // 清洗商变化
  132 + handleSupplierChange(value) {
  133 + const supplier = this.supplierList.find(item => item.id === value)
  134 + if (supplier) {
  135 + this.selectedSupplier = supplier
  136 + this.$set(this.form, 'laundryPrice', supplier.laundryPrice || 0)
  137 + // 如果已选择产品类型,验证是否匹配
  138 + if (this.form.productType && supplier.productType !== this.form.productType) {
  139 + this.$message.warning(`清洗商【${supplier.supplierName}】不支持清洗产品类型【${this.form.productType}】,请重新选择`)
  140 + }
  141 + } else {
  142 + this.selectedSupplier = null
  143 + this.$set(this.form, 'laundryPrice', 0)
  144 + }
  145 + },
  146 + // 提交
  147 + submit() {
  148 + this.$refs.form.validate(valid => {
  149 + if (!valid) return
  150 +
  151 + // 验证产品类型是否匹配
  152 + if (this.selectedSupplier && this.form.productType !== this.selectedSupplier.productType) {
  153 + this.$message.error(`清洗商【${this.selectedSupplier.supplierName}】不支持清洗产品类型【${this.form.productType}】`)
  154 + return
  155 + }
  156 +
  157 + this.loading = true
  158 + request({
  159 + url: '/api/Extend/LqLaundryFlow/Send',
  160 + method: 'POST',
  161 + data: {
  162 + storeId: this.form.storeId,
  163 + productType: this.form.productType,
  164 + laundrySupplierId: this.form.laundrySupplierId,
  165 + quantity: this.form.quantity,
  166 + remark: this.form.remark
  167 + }
  168 + }).then(res => {
  169 + this.loading = false
  170 + if (res.code == 200) {
  171 + this.$message.success(res.msg || '创建成功')
  172 + this.visible = false
  173 + this.$emit('refresh')
  174 + } else {
  175 + this.$message.error(res.msg || '创建失败')
  176 + }
  177 + }).catch(() => {
  178 + this.loading = false
  179 + })
  180 + })
  181 + }
  182 + },
  183 + watch: {
  184 + visible(val) {
  185 + if (!val) {
  186 + this.$refs.form && this.$refs.form.resetFields()
  187 + }
  188 + }
  189 + }
  190 +}
  191 +</script>
  192 +
  193 +<style lang="scss" scoped>
  194 +.dialog-footer {
  195 + text-align: right;
  196 +}
  197 +</style>
  198 +
... ...
antis-ncc-admin/src/views/LqLaundrySupplier/form-dialog.vue 0 → 100644
  1 +<template>
  2 + <el-dialog :title="!dataForm.id ? '新增清洗商' : '编辑清洗商'" :close-on-click-modal="false"
  3 + :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="600px">
  4 + <el-form ref="elForm" :model="dataForm" size="small" label-width="120px" label-position="right" :rules="rules">
  5 + <el-row :gutter="15">
  6 + <el-col :span="24">
  7 + <el-form-item label="清洗商名称" prop="supplierName">
  8 + <el-input v-model="dataForm.supplierName" placeholder="请输入清洗商名称" clearable
  9 + :style='{"width":"100%"}' />
  10 + </el-form-item>
  11 + </el-col>
  12 + <el-col :span="24">
  13 + <el-form-item label="产品类型" prop="productType">
  14 + <el-select v-model="dataForm.productType" placeholder="请选择产品类型" clearable filterable
  15 + :style='{"width":"100%"}'>
  16 + <el-option v-for="item in productTypeOptions" :key="item.Value" :label="item.Name" :value="item.Name" />
  17 + </el-select>
  18 + </el-form-item>
  19 + </el-col>
  20 + <el-col :span="24">
  21 + <el-form-item label="清洗价格" prop="laundryPrice">
  22 + <el-input-number v-model="dataForm.laundryPrice" placeholder="请输入清洗价格" :min="0" :precision="2"
  23 + :style='{"width":"100%"}' />
  24 + </el-form-item>
  25 + </el-col>
  26 + <el-col :span="24">
  27 + <el-form-item label="备注" prop="remark">
  28 + <el-input v-model="dataForm.remark" type="textarea" :rows="4" placeholder="请输入备注信息" clearable
  29 + :style='{"width":"100%"}' />
  30 + </el-form-item>
  31 + </el-col>
  32 + </el-row>
  33 + </el-form>
  34 + <span slot="footer" class="dialog-footer">
  35 + <el-button @click="visible = false">取 消</el-button>
  36 + <el-button type="primary" @click="dataFormSubmit()" :loading="btnLoading">确 定</el-button>
  37 + </span>
  38 + </el-dialog>
  39 +</template>
  40 +
  41 +<script>
  42 +import request from '@/utils/request'
  43 +
  44 +export default {
  45 + data() {
  46 + return {
  47 + visible: false,
  48 + btnLoading: false,
  49 + productTypeOptions: [],
  50 + dataForm: {
  51 + id: undefined,
  52 + supplierName: '',
  53 + productType: '',
  54 + laundryPrice: 0,
  55 + remark: ''
  56 + },
  57 + rules: {
  58 + supplierName: [
  59 + { required: true, message: '请输入清洗商名称', trigger: 'blur' }
  60 + ],
  61 + productType: [
  62 + { required: true, message: '请选择产品类型', trigger: 'change' }
  63 + ],
  64 + laundryPrice: [
  65 + { required: true, message: '请输入清洗价格', trigger: 'blur' },
  66 + { type: 'number', min: 0, message: '清洗价格不能小于0', trigger: 'blur' }
  67 + ]
  68 + }
  69 + }
  70 + },
  71 + methods: {
  72 + init(id) {
  73 + this.visible = true
  74 + this.dataForm = {
  75 + id: undefined,
  76 + supplierName: '',
  77 + productType: '',
  78 + laundryPrice: 0,
  79 + remark: ''
  80 + }
  81 + this.loadProductTypeOptions()
  82 + if (id) {
  83 + // 编辑模式,获取详情
  84 + this.getDetail(id)
  85 + }
  86 + },
  87 + // 加载产品类型选项
  88 + loadProductTypeOptions() {
  89 + request({
  90 + url: '/api/Extend/LqStoreConsumableInventory/consumable-product-type',
  91 + method: 'GET'
  92 + }).then(res => {
  93 + if (res.code == 200 && res.data && Array.isArray(res.data)) {
  94 + this.productTypeOptions = res.data
  95 + } else {
  96 + this.productTypeOptions = []
  97 + }
  98 + }).catch(() => {
  99 + this.productTypeOptions = []
  100 + })
  101 + },
  102 + // 获取详情
  103 + getDetail(id) {
  104 + request({
  105 + url: `/api/Extend/LqLaundrySupplier/${id}`,
  106 + method: 'GET'
  107 + }).then(res => {
  108 + if (res.code == 200 && res.data) {
  109 + const data = res.data
  110 + this.dataForm = {
  111 + id: data.id,
  112 + supplierName: data.supplierName || '',
  113 + productType: data.productType || '',
  114 + laundryPrice: data.laundryPrice || 0,
  115 + remark: data.remark || ''
  116 + }
  117 + }
  118 + }).catch(() => {
  119 + this.$message({
  120 + type: 'error',
  121 + message: '获取详情失败'
  122 + })
  123 + })
  124 + },
  125 + // 提交表单
  126 + dataFormSubmit() {
  127 + this.$refs.elForm.validate((valid) => {
  128 + if (!valid) {
  129 + return false
  130 + }
  131 + this.btnLoading = true
  132 + const url = this.dataForm.id ? '/api/Extend/LqLaundrySupplier/Update' : '/api/Extend/LqLaundrySupplier/Create'
  133 + const method = this.dataForm.id ? 'PUT' : 'POST'
  134 + const data = this.dataForm.id
  135 + ? {
  136 + id: this.dataForm.id,
  137 + supplierName: this.dataForm.supplierName,
  138 + productType: this.dataForm.productType,
  139 + laundryPrice: this.dataForm.laundryPrice,
  140 + remark: this.dataForm.remark
  141 + }
  142 + : {
  143 + supplierName: this.dataForm.supplierName,
  144 + productType: this.dataForm.productType,
  145 + laundryPrice: this.dataForm.laundryPrice,
  146 + remark: this.dataForm.remark
  147 + }
  148 + request({
  149 + url: url,
  150 + method: method,
  151 + data: data
  152 + }).then(res => {
  153 + this.btnLoading = false
  154 + this.$message({
  155 + type: 'success',
  156 + message: res.msg || (this.dataForm.id ? '编辑成功' : '创建成功'),
  157 + onClose: () => {
  158 + this.visible = false
  159 + this.$emit('refresh')
  160 + }
  161 + })
  162 + }).catch(() => {
  163 + this.btnLoading = false
  164 + })
  165 + })
  166 + }
  167 + }
  168 +}
  169 +</script>
  170 +
  171 +<style lang="scss" scoped>
  172 +</style>
  173 +
... ...
antis-ncc-admin/src/views/LqLaundrySupplier/index.vue 0 → 100644
  1 +<template>
  2 + <div class="NCC-common-layout">
  3 + <div class="NCC-common-layout-center">
  4 + <!-- 查询条件 -->
  5 + <el-row class="NCC-common-search-box" :gutter="16">
  6 + <el-form @submit.native.prevent>
  7 + <el-col :span="6">
  8 + <el-form-item label="清洗商名称">
  9 + <el-input v-model="query.supplierName" placeholder="请输入清洗商名称" clearable />
  10 + </el-form-item>
  11 + </el-col>
  12 + <el-col :span="6">
  13 + <el-form-item label="产品类型">
  14 + <el-input v-model="query.productType" placeholder="请输入产品类型" clearable />
  15 + </el-form-item>
  16 + </el-col>
  17 + <el-col :span="6">
  18 + <el-form-item label="是否有效">
  19 + <el-select v-model="query.isEffective" placeholder="是否有效" clearable>
  20 + <el-option label="有效" :value="1" />
  21 + <el-option label="无效" :value="0" />
  22 + </el-select>
  23 + </el-form-item>
  24 + </el-col>
  25 + <el-col :span="6">
  26 + <el-form-item>
  27 + <el-button type="primary" icon="el-icon-search" @click="search()">查询</el-button>
  28 + <el-button icon="el-icon-refresh-right" @click="reset()">重置</el-button>
  29 + </el-form-item>
  30 + </el-col>
  31 + </el-form>
  32 + </el-row>
  33 + <!-- 列表 -->
  34 + <div class="NCC-common-layout-main NCC-flex-main">
  35 + <div class="NCC-common-head">
  36 + <div>
  37 + <el-button type="primary" icon="el-icon-plus" @click="add()">新增</el-button>
  38 + </div>
  39 + <div class="NCC-common-head-right">
  40 + <el-tooltip effect="dark" content="刷新" placement="top">
  41 + <el-link icon="icon-ym icon-ym-Refresh NCC-common-head-icon" :underline="false"
  42 + @click="initData()" />
  43 + </el-tooltip>
  44 + <screenfull isContainer />
  45 + </div>
  46 + </div>
  47 + <NCC-table v-loading="loading" :data="list"
  48 + :header-cell-style="{ background: '#f5f7fa', color: '#606266' }">
  49 + <el-table-column label="清洗商名称" align="center">
  50 + <template slot-scope="scope">
  51 + <div class="supplier-info">
  52 + <i class="el-icon-office-building supplier-icon"></i>
  53 + <span class="text-nowrap">{{ scope.row.supplierName || '无' }}</span>
  54 + </div>
  55 + </template>
  56 + </el-table-column>
  57 + <el-table-column label="产品类型" align="center">
  58 + <template slot-scope="scope">
  59 + <div class="product-type-info">
  60 + <i class="el-icon-goods product-type-icon"></i>
  61 + <span class="text-nowrap">{{ scope.row.productType || '无' }}</span>
  62 + </div>
  63 + </template>
  64 + </el-table-column>
  65 + <el-table-column label="清洗价格" align="center">
  66 + <template slot-scope="scope">
  67 + <div class="price-info">
  68 + <i class="el-icon-coin price-icon"></i>
  69 + <span class="text-nowrap">{{ scope.row.laundryPrice || 0 }}</span>
  70 + </div>
  71 + </template>
  72 + </el-table-column>
  73 + <el-table-column label="备注" align="center" min-width="150">
  74 + <template slot-scope="scope">
  75 + <div class="remark-info">
  76 + <i class="el-icon-document remark-icon"></i>
  77 + <span class="text-nowrap">{{ scope.row.remark || '无' }}</span>
  78 + </div>
  79 + </template>
  80 + </el-table-column>
  81 + <el-table-column label="是否有效" align="center">
  82 + <template slot-scope="scope">
  83 + <el-tag :type="scope.row.isEffective === 1 ? 'success' : 'info'" size="small">
  84 + {{ scope.row.isEffective === 1 ? '有效' : '无效' }}
  85 + </el-tag>
  86 + </template>
  87 + </el-table-column>
  88 + <el-table-column label="创建人" align="center">
  89 + <template slot-scope="scope">
  90 + <div class="user-info">
  91 + <i class="el-icon-user user-icon"></i>
  92 + <span class="text-nowrap">{{ scope.row.createUserName || '无' }}</span>
  93 + </div>
  94 + </template>
  95 + </el-table-column>
  96 + <el-table-column label="创建时间" align="center" width="180" prop="createTime">
  97 + <template slot-scope="scope">
  98 + <div class="time-info">
  99 + <i class="el-icon-time time-icon"></i>
  100 + <span class="text-nowrap">{{ formatDateTime(scope.row.createTime) || '无' }}</span>
  101 + </div>
  102 + </template>
  103 + </el-table-column>
  104 + <el-table-column label="更新人" align="center">
  105 + <template slot-scope="scope">
  106 + <div class="user-info">
  107 + <i class="el-icon-user user-icon"></i>
  108 + <span class="text-nowrap">{{ scope.row.updateUserName || '无' }}</span>
  109 + </div>
  110 + </template>
  111 + </el-table-column>
  112 + <el-table-column label="更新时间" align="center" width="180" prop="updateTime">
  113 + <template slot-scope="scope">
  114 + <div class="time-info">
  115 + <i class="el-icon-time time-icon"></i>
  116 + <span class="text-nowrap">{{ formatDateTime(scope.row.updateTime) || '无' }}</span>
  117 + </div>
  118 + </template>
  119 + </el-table-column>
  120 + <el-table-column label="操作" width="180" align="left" fixed="right">
  121 + <template slot-scope="scope">
  122 + <div class="action-buttons">
  123 + <el-button type="text" icon="el-icon-edit" @click="edit(scope.row)" class="edit-btn">
  124 + 编辑
  125 + </el-button>
  126 + <el-button type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
  127 + class="delete-btn">
  128 + 删除
  129 + </el-button>
  130 + </div>
  131 + </template>
  132 + </el-table-column>
  133 + </NCC-table>
  134 + <pagination :total="total" :page.sync="query.currentPage"
  135 + :limit.sync="query.pageSize" @pagination="initData" />
  136 + </div>
  137 + </div>
  138 + <!-- 表单弹窗 -->
  139 + <FormDialog v-if="formVisible" ref="FormDialog" @refresh="refresh" />
  140 + </div>
  141 +</template>
  142 +
  143 +<script>
  144 +import request from '@/utils/request'
  145 +import FormDialog from './form-dialog.vue'
  146 +
  147 +export default {
  148 + components: { FormDialog },
  149 + data() {
  150 + return {
  151 + list: [],
  152 + loading: false,
  153 + total: 0,
  154 + query: {
  155 + currentPage: 1,
  156 + pageSize: 20,
  157 + supplierName: undefined,
  158 + productType: undefined,
  159 + isEffective: undefined
  160 + },
  161 + formVisible: false
  162 + }
  163 + },
  164 + created() {
  165 + this.initData()
  166 + },
  167 + methods: {
  168 + // 初始化数据
  169 + initData() {
  170 + this.loading = true
  171 + let query = { ...this.query }
  172 + // 移除空值
  173 + Object.keys(query).forEach(key => {
  174 + if (query[key] === undefined || query[key] === null || query[key] === '') {
  175 + delete query[key]
  176 + }
  177 + })
  178 + request({
  179 + url: '/api/Extend/LqLaundrySupplier/GetList',
  180 + method: 'GET',
  181 + data: query
  182 + }).then(res => {
  183 + if (res.code == 200 && res.data) {
  184 + this.list = res.data.list || []
  185 + this.total = res.data.pagination ? res.data.pagination.total : 0
  186 + } else {
  187 + this.list = []
  188 + this.total = 0
  189 + }
  190 + this.loading = false
  191 + }).catch(() => {
  192 + this.loading = false
  193 + this.list = []
  194 + this.total = 0
  195 + })
  196 + },
  197 + // 查询
  198 + search() {
  199 + this.query.currentPage = 1
  200 + this.initData()
  201 + },
  202 + // 重置
  203 + reset() {
  204 + this.query = {
  205 + currentPage: 1,
  206 + pageSize: 20,
  207 + supplierName: undefined,
  208 + productType: undefined,
  209 + isEffective: undefined
  210 + }
  211 + this.initData()
  212 + },
  213 + // 新增
  214 + add() {
  215 + this.formVisible = true
  216 + this.$nextTick(() => {
  217 + this.$refs.FormDialog.init()
  218 + })
  219 + },
  220 + // 编辑
  221 + edit(row) {
  222 + this.formVisible = true
  223 + this.$nextTick(() => {
  224 + this.$refs.FormDialog.init(row.id)
  225 + })
  226 + },
  227 + // 删除
  228 + handleDelete(row) {
  229 + this.$confirm(`确定要删除清洗商【${row.supplierName}】的${row.productType}记录吗?`, '提示', {
  230 + type: 'warning'
  231 + }).then(() => {
  232 + request({
  233 + url: `/api/Extend/LqLaundrySupplier/${row.id}`,
  234 + method: 'DELETE'
  235 + }).then(res => {
  236 + this.$message({
  237 + type: 'success',
  238 + message: res.msg || '删除成功',
  239 + onClose: () => {
  240 + this.initData()
  241 + }
  242 + })
  243 + })
  244 + }).catch(() => { })
  245 + },
  246 + // 刷新
  247 + refresh() {
  248 + this.formVisible = false
  249 + this.initData()
  250 + },
  251 + // 格式化日期时间
  252 + formatDateTime(dateTime) {
  253 + if (!dateTime) return ''
  254 + const date = new Date(dateTime)
  255 + const year = date.getFullYear()
  256 + const month = String(date.getMonth() + 1).padStart(2, '0')
  257 + const day = String(date.getDate()).padStart(2, '0')
  258 + const hours = String(date.getHours()).padStart(2, '0')
  259 + const minutes = String(date.getMinutes()).padStart(2, '0')
  260 + const seconds = String(date.getSeconds()).padStart(2, '0')
  261 + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
  262 + }
  263 + }
  264 +}
  265 +</script>
  266 +
  267 +<style lang="scss" scoped>
  268 +// 通用文本不换行样式
  269 +.text-nowrap {
  270 + white-space: nowrap;
  271 + overflow: hidden;
  272 + text-overflow: ellipsis;
  273 + max-width: 100%;
  274 +}
  275 +
  276 +// 信息显示样式
  277 +.supplier-info,
  278 +.product-type-info,
  279 +.price-info,
  280 +.remark-info,
  281 +.user-info,
  282 +.time-info {
  283 + display: flex;
  284 + align-items: center;
  285 + justify-content: center;
  286 + gap: 6px;
  287 +}
  288 +
  289 +.supplier-icon {
  290 + color: #409EFF;
  291 + font-size: 16px;
  292 +}
  293 +
  294 +.product-type-icon {
  295 + color: #67C23A;
  296 + font-size: 16px;
  297 +}
  298 +
  299 +.price-icon {
  300 + color: #E6A23C;
  301 + font-size: 16px;
  302 +}
  303 +
  304 +.remark-icon {
  305 + color: #909399;
  306 + font-size: 16px;
  307 +}
  308 +
  309 +.user-icon {
  310 + color: #909399;
  311 + font-size: 16px;
  312 +}
  313 +
  314 +.time-icon {
  315 + color: #909399;
  316 + font-size: 16px;
  317 +}
  318 +
  319 +// 操作按钮样式
  320 +.action-buttons {
  321 + display: flex;
  322 + align-items: center;
  323 + gap: 8px;
  324 +}
  325 +
  326 +.edit-btn {
  327 + color: #409EFF;
  328 +
  329 + &:hover {
  330 + color: #66b1ff;
  331 + }
  332 +}
  333 +
  334 +.delete-btn {
  335 + color: #F56C6C !important;
  336 +
  337 + &:hover {
  338 + color: #f56c6c !important;
  339 + opacity: 0.8;
  340 + }
  341 +}
  342 +
  343 +// 表格行悬停效果
  344 +::v-deep .el-table__row:hover {
  345 + background-color: #f5f7fa;
  346 +}
  347 +
  348 +// 表格头部样式
  349 +::v-deep .el-table__header-wrapper {
  350 + .el-table__header {
  351 + th {
  352 + background-color: #f5f7fa;
  353 + color: #606266;
  354 + font-weight: 600;
  355 + }
  356 + }
  357 +}
  358 +</style>
  359 +
... ...
antis-ncc-admin/src/views/LqStoreConsumableInventory/form-dialog.vue 0 → 100644
  1 +<template>
  2 + <el-dialog :title="!dataForm.id ? '新增门店消耗品库存' : '编辑门店消耗品库存'" :close-on-click-modal="false"
  3 + :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="600px">
  4 + <el-form ref="elForm" :model="dataForm" size="small" label-width="120px" label-position="right" :rules="rules">
  5 + <el-row :gutter="15">
  6 + <el-col :span="24">
  7 + <el-form-item label="门店" prop="storeId">
  8 + <el-select v-model="dataForm.storeId" placeholder="请选择门店" clearable filterable
  9 + :style='{"width":"100%"}' :disabled="!!dataForm.id">
  10 + <el-option v-for="store in storeOptions" :key="store.id" :label="store.dm" :value="store.id" />
  11 + </el-select>
  12 + </el-form-item>
  13 + </el-col>
  14 + <el-col :span="24">
  15 + <el-form-item label="产品类型" prop="productType">
  16 + <el-select v-model="dataForm.productType" placeholder="请选择产品类型" clearable filterable
  17 + :style='{"width":"100%"}'>
  18 + <el-option v-for="item in productTypeOptions" :key="item.Value" :label="item.Name" :value="item.Name" />
  19 + </el-select>
  20 + </el-form-item>
  21 + </el-col>
  22 + <el-col :span="24">
  23 + <el-form-item label="库存数量" prop="quantity">
  24 + <el-input-number v-model="dataForm.quantity" placeholder="请输入库存数量" :min="0" :precision="0"
  25 + :style='{"width":"100%"}' />
  26 + </el-form-item>
  27 + </el-col>
  28 + </el-row>
  29 + </el-form>
  30 + <span slot="footer" class="dialog-footer">
  31 + <el-button @click="visible = false">取 消</el-button>
  32 + <el-button type="primary" @click="dataFormSubmit()" :loading="btnLoading">确 定</el-button>
  33 + </span>
  34 + </el-dialog>
  35 +</template>
  36 +
  37 +<script>
  38 +import request from '@/utils/request'
  39 +
  40 +export default {
  41 + data() {
  42 + return {
  43 + visible: false,
  44 + btnLoading: false,
  45 + storeOptions: [],
  46 + productTypeOptions: [],
  47 + dataForm: {
  48 + id: undefined,
  49 + storeId: '',
  50 + productType: '',
  51 + quantity: 0
  52 + },
  53 + rules: {
  54 + storeId: [
  55 + { required: true, message: '请选择门店', trigger: 'change' }
  56 + ],
  57 + productType: [
  58 + { required: true, message: '请选择产品类型', trigger: 'change' }
  59 + ],
  60 + quantity: [
  61 + { required: true, message: '请输入库存数量', trigger: 'blur' },
  62 + { type: 'number', min: 0, message: '库存数量不能小于0', trigger: 'blur' }
  63 + ]
  64 + }
  65 + }
  66 + },
  67 + methods: {
  68 + init(id) {
  69 + this.visible = true
  70 + this.dataForm = {
  71 + id: undefined,
  72 + storeId: '',
  73 + productType: '',
  74 + quantity: 0
  75 + }
  76 + this.loadStoreOptions()
  77 + this.loadProductTypeOptions()
  78 + if (id) {
  79 + // 编辑模式,获取详情
  80 + this.getDetail(id)
  81 + }
  82 + },
  83 + // 加载门店选项
  84 + loadStoreOptions() {
  85 + request({
  86 + url: '/api/Extend/LqMdxx',
  87 + method: 'GET',
  88 + data: {
  89 + currentPage: 1,
  90 + pageSize: 1000
  91 + }
  92 + }).then(res => {
  93 + if (res.code == 200 && res.data && res.data.list) {
  94 + this.storeOptions = res.data.list
  95 + } else {
  96 + this.storeOptions = []
  97 + }
  98 + }).catch(() => {
  99 + this.storeOptions = []
  100 + })
  101 + },
  102 + // 加载产品类型选项
  103 + loadProductTypeOptions() {
  104 + request({
  105 + url: '/api/Extend/LqStoreConsumableInventory/consumable-product-type',
  106 + method: 'GET'
  107 + }).then(res => {
  108 + if (res.code == 200 && res.data && Array.isArray(res.data)) {
  109 + this.productTypeOptions = res.data
  110 + } else {
  111 + this.productTypeOptions = []
  112 + }
  113 + }).catch(() => {
  114 + this.productTypeOptions = []
  115 + })
  116 + },
  117 + // 获取详情
  118 + getDetail(id) {
  119 + request({
  120 + url: `/api/Extend/LqStoreConsumableInventory/${id}`,
  121 + method: 'GET'
  122 + }).then(res => {
  123 + if (res.code == 200 && res.data) {
  124 + const data = res.data
  125 + this.dataForm = {
  126 + id: data.id,
  127 + storeId: data.storeId || '',
  128 + productType: data.productType || '',
  129 + quantity: data.quantity || 0
  130 + }
  131 + }
  132 + }).catch(() => {
  133 + this.$message({
  134 + type: 'error',
  135 + message: '获取详情失败'
  136 + })
  137 + })
  138 + },
  139 + // 提交表单
  140 + dataFormSubmit() {
  141 + this.$refs.elForm.validate((valid) => {
  142 + if (!valid) {
  143 + return false
  144 + }
  145 + this.btnLoading = true
  146 + const url = this.dataForm.id ? '/api/Extend/LqStoreConsumableInventory/Update' : '/api/Extend/LqStoreConsumableInventory/Create'
  147 + const method = this.dataForm.id ? 'PUT' : 'POST'
  148 + const data = this.dataForm.id
  149 + ? {
  150 + id: this.dataForm.id,
  151 + storeId: this.dataForm.storeId,
  152 + productType: this.dataForm.productType,
  153 + quantity: this.dataForm.quantity
  154 + }
  155 + : {
  156 + storeId: this.dataForm.storeId,
  157 + productType: this.dataForm.productType,
  158 + quantity: this.dataForm.quantity
  159 + }
  160 + request({
  161 + url: url,
  162 + method: method,
  163 + data: data
  164 + }).then(res => {
  165 + this.btnLoading = false
  166 + this.$message({
  167 + type: 'success',
  168 + message: res.msg || (this.dataForm.id ? '编辑成功' : '创建成功'),
  169 + onClose: () => {
  170 + this.visible = false
  171 + this.$emit('refresh')
  172 + }
  173 + })
  174 + }).catch(() => {
  175 + this.btnLoading = false
  176 + })
  177 + })
  178 + }
  179 + }
  180 +}
  181 +</script>
  182 +
  183 +<style lang="scss" scoped>
  184 +</style>
  185 +
... ...
antis-ncc-admin/src/views/LqStoreConsumableInventory/index.vue 0 → 100644
  1 +<template>
  2 + <div class="NCC-common-layout">
  3 + <div class="NCC-common-layout-center">
  4 + <!-- 查询条件 -->
  5 + <el-row class="NCC-common-search-box" :gutter="16">
  6 + <el-form @submit.native.prevent>
  7 + <el-col :span="6">
  8 + <el-form-item label="门店名称">
  9 + <el-select v-model="query.storeId" placeholder="请选择门店" clearable filterable>
  10 + <el-option v-for="store in storeOptions" :key="store.id" :label="store.dm" :value="store.id" />
  11 + </el-select>
  12 + </el-form-item>
  13 + </el-col>
  14 + <el-col :span="6">
  15 + <el-form-item label="产品类型">
  16 + <el-input v-model="query.productType" placeholder="产品类型" clearable />
  17 + </el-form-item>
  18 + </el-col>
  19 + <el-col :span="6">
  20 + <el-form-item label="是否有效">
  21 + <el-select v-model="query.isEffective" placeholder="是否有效" clearable>
  22 + <el-option label="有效" :value="1" />
  23 + <el-option label="无效" :value="0" />
  24 + </el-select>
  25 + </el-form-item>
  26 + </el-col>
  27 + <el-col :span="6">
  28 + <el-form-item>
  29 + <el-button type="primary" icon="el-icon-search" @click="search()">查询</el-button>
  30 + <el-button icon="el-icon-refresh-right" @click="reset()">重置</el-button>
  31 + </el-form-item>
  32 + </el-col>
  33 + </el-form>
  34 + </el-row>
  35 + <!-- 列表 -->
  36 + <div class="NCC-common-layout-main NCC-flex-main">
  37 + <div class="NCC-common-head">
  38 + <div>
  39 + <el-button type="primary" icon="el-icon-plus" @click="add()">新增</el-button>
  40 + </div>
  41 + <div class="NCC-common-head-right">
  42 + <el-tooltip effect="dark" content="刷新" placement="top">
  43 + <el-link icon="icon-ym icon-ym-Refresh NCC-common-head-icon" :underline="false"
  44 + @click="initData()" />
  45 + </el-tooltip>
  46 + <screenfull isContainer />
  47 + </div>
  48 + </div>
  49 + <NCC-table v-loading="loading" :data="list"
  50 + :header-cell-style="{ background: '#f5f7fa', color: '#606266' }">
  51 + <el-table-column label="门店名称" align="center" >
  52 + <template slot-scope="scope">
  53 + <div class="store-info">
  54 + <i class="el-icon-office-building store-icon"></i>
  55 + <span class="text-nowrap">{{ scope.row.storeName || '无' }}</span>
  56 + </div>
  57 + </template>
  58 + </el-table-column>
  59 + <el-table-column label="产品类型" align="center" >
  60 + <template slot-scope="scope">
  61 + <div class="product-type-info">
  62 + <i class="el-icon-goods product-type-icon"></i>
  63 + <span class="text-nowrap">{{ scope.row.productType || '无' }}</span>
  64 + </div>
  65 + </template>
  66 + </el-table-column>
  67 + <el-table-column label="库存数量" align="center" >
  68 + <template slot-scope="scope">
  69 + <div class="quantity-info">
  70 + <i class="el-icon-box quantity-icon"></i>
  71 + <span class="text-nowrap">{{ scope.row.quantity || 0 }}</span>
  72 + </div>
  73 + </template>
  74 + </el-table-column>
  75 + <el-table-column label="是否有效" align="center" >
  76 + <template slot-scope="scope">
  77 + <el-tag :type="scope.row.isEffective === 1 ? 'success' : 'info'" size="small">
  78 + {{ scope.row.isEffective === 1 ? '有效' : '无效' }}
  79 + </el-tag>
  80 + </template>
  81 + </el-table-column>
  82 + <el-table-column label="创建人" align="center" >
  83 + <template slot-scope="scope">
  84 + <div class="user-info">
  85 + <i class="el-icon-user user-icon"></i>
  86 + <span class="text-nowrap">{{ scope.row.createUserName || '无' }}</span>
  87 + </div>
  88 + </template>
  89 + </el-table-column>
  90 + <el-table-column label="创建时间" align="center" width="180" prop="createTime">
  91 + <template slot-scope="scope">
  92 + <div class="time-info">
  93 + <i class="el-icon-time time-icon"></i>
  94 + <span class="text-nowrap">{{ formatDateTime(scope.row.createTime) || '无' }}</span>
  95 + </div>
  96 + </template>
  97 + </el-table-column>
  98 + <el-table-column label="更新人" align="center" >
  99 + <template slot-scope="scope">
  100 + <div class="user-info">
  101 + <i class="el-icon-user user-icon"></i>
  102 + <span class="text-nowrap">{{ scope.row.updateUserName || '无' }}</span>
  103 + </div>
  104 + </template>
  105 + </el-table-column>
  106 + <el-table-column label="更新时间" align="center" width="180" prop="updateTime">
  107 + <template slot-scope="scope">
  108 + <div class="time-info">
  109 + <i class="el-icon-time time-icon"></i>
  110 + <span class="text-nowrap">{{ formatDateTime(scope.row.updateTime) || '无' }}</span>
  111 + </div>
  112 + </template>
  113 + </el-table-column>
  114 + <el-table-column label="操作" width="180" align="left" fixed="right">
  115 + <template slot-scope="scope">
  116 + <div class="action-buttons">
  117 + <el-button type="text" icon="el-icon-edit" @click="edit(scope.row)" class="edit-btn">
  118 + 编辑
  119 + </el-button>
  120 + <el-button type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
  121 + class="delete-btn">
  122 + 删除
  123 + </el-button>
  124 + </div>
  125 + </template>
  126 + </el-table-column>
  127 + </NCC-table>
  128 + <pagination :total="total" :page.sync="query.currentPage"
  129 + :limit.sync="query.pageSize" @pagination="initData" />
  130 + </div>
  131 + </div>
  132 + <!-- 表单弹窗 -->
  133 + <FormDialog v-if="formVisible" ref="FormDialog" @refresh="refresh" />
  134 + </div>
  135 +</template>
  136 +
  137 +<script>
  138 +import request from '@/utils/request'
  139 +import FormDialog from './form-dialog.vue'
  140 +
  141 +export default {
  142 + components: { FormDialog },
  143 + data() {
  144 + return {
  145 + list: [],
  146 + loading: false,
  147 + total: 0,
  148 + storeOptions: [],
  149 + query: {
  150 + currentPage: 1,
  151 + pageSize: 20,
  152 + storeId: undefined,
  153 + productType: undefined,
  154 + isEffective: undefined
  155 + },
  156 + formVisible: false
  157 + }
  158 + },
  159 + created() {
  160 + this.initData()
  161 + this.loadStoreOptions()
  162 + },
  163 + methods: {
  164 + // 初始化数据
  165 + initData() {
  166 + this.loading = true
  167 + let query = { ...this.query }
  168 + // 移除空值
  169 + Object.keys(query).forEach(key => {
  170 + if (query[key] === undefined || query[key] === null || query[key] === '') {
  171 + delete query[key]
  172 + }
  173 + })
  174 + request({
  175 + url: '/api/Extend/LqStoreConsumableInventory/GetList',
  176 + method: 'GET',
  177 + data: query
  178 + }).then(res => {
  179 + if (res.code == 200 && res.data) {
  180 + this.list = res.data.list || []
  181 + this.total = res.data.pagination ? res.data.pagination.total : 0
  182 + } else {
  183 + this.list = []
  184 + this.total = 0
  185 + }
  186 + this.loading = false
  187 + }).catch(() => {
  188 + this.loading = false
  189 + this.list = []
  190 + this.total = 0
  191 + })
  192 + },
  193 + // 加载门店选项
  194 + loadStoreOptions() {
  195 + request({
  196 + url: '/api/Extend/LqMdxx',
  197 + method: 'GET',
  198 + data: {
  199 + currentPage: 1,
  200 + pageSize: 1000
  201 + }
  202 + }).then(res => {
  203 + if (res.code == 200 && res.data && res.data.list) {
  204 + this.storeOptions = res.data.list
  205 + } else {
  206 + this.storeOptions = []
  207 + }
  208 + }).catch(() => {
  209 + this.storeOptions = []
  210 + })
  211 + },
  212 + // 查询
  213 + search() {
  214 + this.query.currentPage = 1
  215 + this.initData()
  216 + },
  217 + // 重置
  218 + reset() {
  219 + this.query = {
  220 + currentPage: 1,
  221 + pageSize: 20,
  222 + storeId: undefined,
  223 + productType: undefined,
  224 + isEffective: undefined
  225 + }
  226 + this.initData()
  227 + },
  228 + // 新增
  229 + add() {
  230 + this.formVisible = true
  231 + this.$nextTick(() => {
  232 + this.$refs.FormDialog.init()
  233 + })
  234 + },
  235 + // 编辑
  236 + edit(row) {
  237 + this.formVisible = true
  238 + this.$nextTick(() => {
  239 + this.$refs.FormDialog.init(row.id)
  240 + })
  241 + },
  242 + // 删除
  243 + handleDelete(row) {
  244 + this.$confirm(`确定要删除该库存记录吗?`, '提示', {
  245 + type: 'warning'
  246 + }).then(() => {
  247 + request({
  248 + url: `/api/Extend/LqStoreConsumableInventory/${row.id}`,
  249 + method: 'DELETE'
  250 + }).then(res => {
  251 + this.$message({
  252 + type: 'success',
  253 + message: res.msg || '删除成功',
  254 + onClose: () => {
  255 + this.initData()
  256 + }
  257 + })
  258 + })
  259 + }).catch(() => { })
  260 + },
  261 + // 刷新
  262 + refresh() {
  263 + this.formVisible = false
  264 + this.initData()
  265 + },
  266 + // 格式化日期时间
  267 + formatDateTime(dateTime) {
  268 + if (!dateTime) return ''
  269 + const date = new Date(dateTime)
  270 + const year = date.getFullYear()
  271 + const month = String(date.getMonth() + 1).padStart(2, '0')
  272 + const day = String(date.getDate()).padStart(2, '0')
  273 + const hours = String(date.getHours()).padStart(2, '0')
  274 + const minutes = String(date.getMinutes()).padStart(2, '0')
  275 + const seconds = String(date.getSeconds()).padStart(2, '0')
  276 + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
  277 + }
  278 + }
  279 +}
  280 +</script>
  281 +
  282 +<style lang="scss" scoped>
  283 +// 通用文本不换行样式
  284 +.text-nowrap {
  285 + white-space: nowrap;
  286 + overflow: hidden;
  287 + text-overflow: ellipsis;
  288 + max-width: 100%;
  289 +}
  290 +
  291 +// 信息显示样式
  292 +.store-info,
  293 +.product-type-info,
  294 +.quantity-info,
  295 +.user-info,
  296 +.time-info {
  297 + display: flex;
  298 + align-items: center;
  299 + justify-content: center;
  300 + gap: 6px;
  301 +}
  302 +
  303 +.store-icon {
  304 + color: #409EFF;
  305 + font-size: 16px;
  306 +}
  307 +
  308 +.product-type-icon {
  309 + color: #67C23A;
  310 + font-size: 16px;
  311 +}
  312 +
  313 +.quantity-icon {
  314 + color: #E6A23C;
  315 + font-size: 16px;
  316 +}
  317 +
  318 +.user-icon {
  319 + color: #909399;
  320 + font-size: 16px;
  321 +}
  322 +
  323 +.time-icon {
  324 + color: #909399;
  325 + font-size: 16px;
  326 +}
  327 +
  328 +// 操作按钮样式
  329 +.action-buttons {
  330 + display: flex;
  331 + align-items: center;
  332 + gap: 8px;
  333 +}
  334 +
  335 +.edit-btn {
  336 + color: #409EFF;
  337 +
  338 + &:hover {
  339 + color: #66b1ff;
  340 + }
  341 +}
  342 +
  343 +.delete-btn {
  344 + color: #F56C6C !important;
  345 +
  346 + &:hover {
  347 + color: #f56c6c !important;
  348 + opacity: 0.8;
  349 + }
  350 +}
  351 +
  352 +// 表格行悬停效果
  353 +::v-deep .el-table__row:hover {
  354 + background-color: #f5f7fa;
  355 +}
  356 +
  357 +// 表格头部样式
  358 +::v-deep .el-table__header-wrapper {
  359 + .el-table__header {
  360 + th {
  361 + background-color: #f5f7fa;
  362 + color: #606266;
  363 + font-weight: 600;
  364 + }
  365 + }
  366 +}
  367 +</style>
  368 +
... ...
antis-ncc-admin/src/views/lqInventory/AddUsageRecordForm.vue renamed to antis-ncc-admin/src/views/lqInventory copy/AddUsageRecordForm.vue
antis-ncc-admin/src/views/lqInventory/InventoryForm.vue renamed to antis-ncc-admin/src/views/lqInventory copy/InventoryForm.vue
antis-ncc-admin/src/views/lqInventory/InventoryInfoDialog.vue renamed to antis-ncc-admin/src/views/lqInventory copy/InventoryInfoDialog.vue
antis-ncc-admin/src/views/lqInventory/UsageRecordDialog.vue renamed to antis-ncc-admin/src/views/lqInventory copy/UsageRecordDialog.vue
antis-ncc-admin/src/views/lqInventory copy/index.vue 0 → 100644
  1 +<template>
  2 + <div class="NCC-common-layout">
  3 + <div class="NCC-common-layout-center">
  4 + <!-- 搜索区域 -->
  5 + <el-row class="NCC-common-search-box" :gutter="16">
  6 + <el-form @submit.native.prevent>
  7 + <el-col :span="6">
  8 + <el-form-item label="产品名称">
  9 + <el-input v-model="query.productName" placeholder="请输入产品名称" clearable />
  10 + </el-form-item>
  11 + </el-col>
  12 + <el-col :span="6">
  13 + <el-form-item label="产品分类">
  14 + <el-input v-model="query.productCategory" placeholder="请输入产品分类" clearable />
  15 + </el-form-item>
  16 + </el-col>
  17 + <el-col :span="6">
  18 + <el-form-item label="负责部门">
  19 + <el-select v-model="query.departmentId" placeholder="请选择负责部门" clearable style="width: 100%">
  20 + <el-option v-for="dept in departmentList" :key="dept.id" :label="dept.name"
  21 + :value="dept.id">
  22 + </el-option>
  23 + </el-select>
  24 + </el-form-item>
  25 + </el-col>
  26 + <el-col :span="6">
  27 + <el-form-item>
  28 + <el-button type="primary" icon="el-icon-search" @click="search()">查询</el-button>
  29 + <el-button icon="el-icon-refresh-right" @click="reset()">重置</el-button>
  30 + </el-form-item>
  31 + </el-col>
  32 + </el-form>
  33 + </el-row>
  34 +
  35 + <!-- 主要内容区域 -->
  36 + <div class="NCC-common-layout-main NCC-flex-main">
  37 + <!-- 操作按钮区域 -->
  38 + <div class="NCC-common-head">
  39 + <div>
  40 + <el-button type="primary" icon="el-icon-plus" @click="addInventoryHandle()">添加库存</el-button>
  41 + <!-- <el-button type="success" icon="el-icon-edit" @click="editInventoryHandle()"
  42 + :disabled="selectedIds.length !== 1">编辑库存</el-button> -->
  43 + <el-button type="warning" icon="el-icon-s-operation"
  44 + @click="usageRecordHandle()">使用记录</el-button>
  45 + <el-button type="text" icon="el-icon-download" @click="exportData()">导出</el-button>
  46 + </div>
  47 + <div class="NCC-common-head-right">
  48 + <el-tooltip effect="dark" content="刷新" placement="top">
  49 + <el-link icon="icon-ym icon-ym-Refresh NCC-common-head-icon" :underline="false"
  50 + @click="reset()" />
  51 + </el-tooltip>
  52 + <screenfull isContainer />
  53 + </div>
  54 + </div>
  55 +
  56 + <!-- 库存列表表格 -->
  57 + <NCC-table v-loading="listLoading" :data="list" has-c @selection-change="handleSelectionChange">
  58 + <el-table-column prop="productName" label="产品名称" align="left" show-overflow-tooltip />
  59 + <el-table-column prop="productCategory" label="产品分类" align="left" />
  60 + <el-table-column prop="price" label="单价" align="left">
  61 + <template slot-scope="scope">
  62 + {{ formatMoney(scope.row.price) }}
  63 + </template>
  64 + </el-table-column>
  65 + <el-table-column prop="quantity" label="库存数量" align="left" >
  66 + <template slot-scope="scope">
  67 + <span :class="scope.row.quantity <= 10 ? 'text-danger' : ''">{{ scope.row.quantity }}</span>
  68 + </template>
  69 + </el-table-column>
  70 + <el-table-column prop="standardUnit" label="单位" align="left" />
  71 + <el-table-column prop="totalValue" label="总价值" align="left">
  72 + <template slot-scope="scope">
  73 + {{ formatMoney(scope.row.totalValue) }}
  74 + </template>
  75 + </el-table-column>
  76 + <!-- <el-table-column prop="departmentName" label="负责部门" align="left" /> -->
  77 + <el-table-column prop="createTime" label="创建时间" align="left" :formatter="ncc.tableDateFormat">
  78 + <!-- <template slot-scope="scope">
  79 + {{ scope.row.createTime | dateTimeFormat }}
  80 + </template> -->
  81 + </el-table-column>
  82 + <el-table-column label="操作" align="center" width="200" fixed="right">
  83 + <template slot-scope="scope">
  84 + <el-button type="text" @click="viewInventoryInfo(scope.row)">查看详情</el-button>
  85 + <el-button type="text" @click="editInventory(scope.row)">编辑</el-button>
  86 + <el-button type="text" @click="viewUsageRecords(scope.row)">使用记录</el-button>
  87 + </template>
  88 + </el-table-column>
  89 + </NCC-table>
  90 +
  91 + <!-- 分页组件 -->
  92 + <pagination v-show="total > 0" :total="total" :page.sync="query.currentPage"
  93 + :limit.sync="query.pageSize" @pagination="getList" />
  94 + </div>
  95 + </div>
  96 +
  97 + <!-- 添加/编辑库存弹窗 -->
  98 + <InventoryForm v-if="inventoryFormVisible" ref="InventoryForm" @refreshDataList="getList" />
  99 +
  100 + <!-- 库存详情弹窗 -->
  101 + <InventoryInfoDialog v-if="inventoryInfoVisible" ref="InventoryInfoDialog" />
  102 +
  103 + <!-- 使用记录弹窗 -->
  104 + <UsageRecordDialog v-if="usageRecordVisible" ref="UsageRecordDialog" />
  105 + </div>
  106 +</template>
  107 +
  108 +<script>
  109 +import { getInventoryList } from '@/api/extend/lqInventory'
  110 +import { getUserList } from '@/api/permission/user'
  111 +import InventoryForm from './InventoryForm'
  112 +import InventoryInfoDialog from './InventoryInfoDialog'
  113 +import UsageRecordDialog from './UsageRecordDialog'
  114 +import Pagination from '@/components/Pagination'
  115 +import request from '@/utils/request'
  116 +export default {
  117 + name: 'LqInventory',
  118 + components: {
  119 + InventoryForm,
  120 + InventoryInfoDialog,
  121 + UsageRecordDialog,
  122 + Pagination
  123 + },
  124 + data() {
  125 + return {
  126 + query: {
  127 + productName: '',
  128 + productCategory: '',
  129 + departmentId: '',
  130 + currentPage: 1,
  131 + pageSize: 20
  132 + },
  133 + list: [],
  134 + total: 0,
  135 + listLoading: true,
  136 + inventoryFormVisible: false,
  137 + inventoryInfoVisible: false,
  138 + usageRecordVisible: false,
  139 + selectedIds: [],
  140 + departmentList: [] // 部门列表
  141 + }
  142 + },
  143 + created() {
  144 + this.getDepartmentList()
  145 + this.getList()
  146 + },
  147 + methods: {
  148 + // 获取部门列表
  149 + getDepartmentList() {
  150 + request({
  151 + url: `/api/permission/Organize/96240625-934F-490B-8AA6-0BC775B18468/Department`,
  152 + method: 'GET',
  153 + }).then((res) => {
  154 + if (res.code == 200 && res.data.list.length > 0) {
  155 + this.departmentList = res.data.list.map(item => ({
  156 + id: item.id,
  157 + name: item.fullName,
  158 + }))
  159 + } else {
  160 + this.departmentList = []
  161 + }
  162 + })
  163 + },
  164 +
  165 + // 获取库存列表
  166 + getList() {
  167 + this.listLoading = true
  168 + getInventoryList(this.query).then(response => {
  169 + if (response.code === 200) {
  170 + this.list = response.data.list || []
  171 + this.total = (response.data.pagination && response.data.pagination.total) || 0
  172 + } else {
  173 + this.$message.error(response.msg || '获取库存列表失败')
  174 + }
  175 + this.listLoading = false
  176 + }).catch(() => {
  177 + this.listLoading = false
  178 + })
  179 + },
  180 +
  181 + // 搜索
  182 + search() {
  183 + this.query.currentPage = 1
  184 + this.getList()
  185 + },
  186 +
  187 + // 重置
  188 + reset() {
  189 + this.query = {
  190 + productName: '',
  191 + productCategory: '',
  192 + departmentId: '',
  193 + currentPage: 1,
  194 + pageSize: 20
  195 + }
  196 + this.getList()
  197 + },
  198 +
  199 + // 添加库存
  200 + addInventoryHandle() {
  201 + this.inventoryFormVisible = true
  202 + this.$nextTick(() => {
  203 + this.$refs.InventoryForm.init()
  204 + })
  205 + },
  206 +
  207 + // 编辑库存
  208 + editInventoryHandle() {
  209 + if (this.selectedIds.length === 0) {
  210 + this.$message.warning('请选择一个库存记录')
  211 + return
  212 + }
  213 + if (this.selectedIds.length > 1) {
  214 + this.$message.warning('请只选择一个库存记录')
  215 + return
  216 + }
  217 +
  218 + const selectedInventory = this.list.find(item => item.id === this.selectedIds[0])
  219 + if (selectedInventory) {
  220 + this.inventoryFormVisible = true
  221 + this.$nextTick(() => {
  222 + this.$refs.InventoryForm.init(selectedInventory.id)
  223 + })
  224 + }
  225 + },
  226 +
  227 + // 编辑库存(从操作列)
  228 + editInventory(row) {
  229 + this.inventoryFormVisible = true
  230 + this.$nextTick(() => {
  231 + this.$refs.InventoryForm.init(row.id)
  232 + })
  233 + },
  234 +
  235 + // 查看库存详情
  236 + viewInventoryInfo(row) {
  237 + this.inventoryInfoVisible = true
  238 + this.$nextTick(() => {
  239 + this.$refs.InventoryInfoDialog.init(row.id)
  240 + })
  241 + },
  242 +
  243 + // 使用记录管理
  244 + usageRecordHandle() {
  245 + this.usageRecordVisible = true
  246 + this.$nextTick(() => {
  247 + this.$refs.UsageRecordDialog.init()
  248 + })
  249 + },
  250 +
  251 + // 查看使用记录
  252 + viewUsageRecords(row) {
  253 + this.usageRecordVisible = true
  254 + this.$nextTick(() => {
  255 + this.$refs.UsageRecordDialog.init(row.id, row.productName)
  256 + })
  257 + },
  258 +
  259 + // 多选
  260 + handleSelectionChange(selection) {
  261 + this.selectedIds = selection.map(item => item.id)
  262 + },
  263 +
  264 + // 格式化金额
  265 + formatMoney(value) {
  266 + if (value === null || value === undefined || value === '') {
  267 + return '0.00'
  268 + }
  269 + return Number(value).toFixed(2)
  270 + },
  271 +
  272 + // 导出
  273 + exportData() {
  274 + this.$message.info('导出功能开发中...')
  275 + }
  276 + }
  277 +}
  278 +</script>
  279 +
  280 +<style scoped>
  281 +.NCC-common-layout {
  282 + height: 100%;
  283 +}
  284 +
  285 +.text-danger {
  286 + color: #f56c6c;
  287 + font-weight: bold;
  288 +}
  289 +</style>
... ...
antis-ncc-admin/src/views/lqInventory/index.vue
1 1 <template>
2   - <div class="NCC-common-layout">
3   - <div class="NCC-common-layout-center">
4   - <!-- 搜索区域 -->
5   - <el-row class="NCC-common-search-box" :gutter="16">
6   - <el-form @submit.native.prevent>
7   - <el-col :span="6">
8   - <el-form-item label="产品名称">
9   - <el-input v-model="query.productName" placeholder="请输入产品名称" clearable />
10   - </el-form-item>
11   - </el-col>
12   - <el-col :span="6">
13   - <el-form-item label="产品分类">
14   - <el-input v-model="query.productCategory" placeholder="请输入产品分类" clearable />
15   - </el-form-item>
16   - </el-col>
17   - <el-col :span="6">
18   - <el-form-item label="负责部门">
19   - <el-select v-model="query.departmentId" placeholder="请选择负责部门" clearable style="width: 100%">
20   - <el-option v-for="dept in departmentList" :key="dept.id" :label="dept.name"
21   - :value="dept.id">
22   - </el-option>
23   - </el-select>
24   - </el-form-item>
25   - </el-col>
26   - <el-col :span="6">
27   - <el-form-item>
28   - <el-button type="primary" icon="el-icon-search" @click="search()">查询</el-button>
29   - <el-button icon="el-icon-refresh-right" @click="reset()">重置</el-button>
30   - </el-form-item>
31   - </el-col>
32   - </el-form>
33   - </el-row>
  2 + <div class="NCC-common-layout">
  3 + <div class="NCC-common-layout-center">
  4 + <!-- 产品查询条件 -->
  5 + <el-row class="NCC-common-search-box" :gutter="16">
  6 + <el-form @submit.native.prevent>
  7 + <el-col :span="6">
  8 + <el-form-item label="产品名称">
  9 + <el-input v-model="productQuery.productName" placeholder="产品名称" clearable />
  10 + </el-form-item>
  11 + </el-col>
  12 + <el-col :span="6">
  13 + <el-form-item label="产品类别">
  14 + <el-input v-model="productQuery.productCategory" placeholder="产品类别" clearable />
  15 + </el-form-item>
  16 + </el-col>
  17 + <el-col :span="6">
  18 + <el-form-item label="上架状态">
  19 + <el-select v-model="productQuery.onShelfStatus" placeholder="上架状态" clearable>
  20 + <el-option label="上架" :value="1" />
  21 + <el-option label="下架" :value="0" />
  22 + </el-select>
  23 + </el-form-item>
  24 + </el-col>
  25 + <el-col :span="6">
  26 + <el-form-item>
  27 + <el-button type="primary" icon="el-icon-search" @click="searchProduct()">查询</el-button>
  28 + <el-button icon="el-icon-refresh-right" @click="resetProduct()">重置</el-button>
  29 + </el-form-item>
  30 + </el-col>
  31 + </el-form>
  32 + </el-row>
  33 + <!-- 产品列表 -->
  34 + <div class="NCC-common-layout-main NCC-flex-main">
  35 + <div class="NCC-common-head">
  36 + <div>
  37 + <el-button type="primary" icon="el-icon-plus" @click="addProduct()">新增产品</el-button>
  38 + <el-button type="primary" icon="el-icon-plus" @click="addInventory()">添加库存</el-button>
  39 + <el-button type="primary" icon="el-icon-plus" @click="addUsage()">添加使用记录</el-button>
  40 + </div>
  41 + <div class="NCC-common-head-right">
  42 + <el-tooltip effect="dark" content="刷新" placement="top">
  43 + <el-link icon="icon-ym icon-ym-Refresh NCC-common-head-icon" :underline="false"
  44 + @click="initProductData()" />
  45 + </el-tooltip>
  46 + <screenfull isContainer />
  47 + </div>
  48 + </div>
  49 + <NCC-table v-loading="productLoading" :data="productList"
  50 + :header-cell-style="{ background: '#f5f7fa', color: '#606266' }">
  51 + <!-- <el-table-column label="产品ID" align="center">
  52 + <template slot-scope="scope">
  53 + <div class="product-id-info">
  54 + <i class="el-icon-postcard product-id-icon"></i>
  55 + <span class="text-nowrap">{{ scope.row.id || '无' }}</span>
  56 + </div>
  57 + </template>
  58 + </el-table-column> -->
  59 + <el-table-column label="产品名称" width="180" align="center" prop="productName"/>
  60 + <el-table-column label="价格" align="center">
  61 + <template slot-scope="scope">
  62 + <div class="price-info">
  63 + <i class="el-icon-coin price-icon"></i>
  64 + <span class="text-nowrap">¥{{ formatMoney(scope.row.price) }}</span>
  65 + </div>
  66 + </template>
  67 + </el-table-column>
  68 + <el-table-column label="库存" align="center" prop="currentInventory"/>
  69 + <el-table-column label="产品类别" align="center" prop="productCategory"/>
  70 + <el-table-column label="归属部门" align="center" prop="departmentName">
  71 + <template slot-scope="scope">
  72 + <div class="department-info">
  73 + <span class="text-nowrap">{{ scope.row.departmentName || '无' }}</span>
  74 + </div>
  75 + </template>
  76 + </el-table-column>
34 77  
35   - <!-- 主要内容区域 -->
36   - <div class="NCC-common-layout-main NCC-flex-main">
37   - <!-- 操作按钮区域 -->
38   - <div class="NCC-common-head">
39   - <div>
40   - <el-button type="primary" icon="el-icon-plus" @click="addInventoryHandle()">添加库存</el-button>
41   - <!-- <el-button type="success" icon="el-icon-edit" @click="editInventoryHandle()"
42   - :disabled="selectedIds.length !== 1">编辑库存</el-button> -->
43   - <el-button type="warning" icon="el-icon-s-operation"
44   - @click="usageRecordHandle()">使用记录</el-button>
45   - <el-button type="text" icon="el-icon-download" @click="exportData()">导出</el-button>
46   - </div>
47   - <div class="NCC-common-head-right">
48   - <el-tooltip effect="dark" content="刷新" placement="top">
49   - <el-link icon="icon-ym icon-ym-Refresh NCC-common-head-icon" :underline="false"
50   - @click="reset()" />
51   - </el-tooltip>
52   - <screenfull isContainer />
53   - </div>
54   - </div>
55   -
56   - <!-- 库存列表表格 -->
57   - <NCC-table v-loading="listLoading" :data="list" has-c @selection-change="handleSelectionChange">
58   - <el-table-column prop="productName" label="产品名称" align="left" show-overflow-tooltip />
59   - <el-table-column prop="productCategory" label="产品分类" align="left" />
60   - <el-table-column prop="price" label="单价" align="left">
61   - <template slot-scope="scope">
62   - {{ formatMoney(scope.row.price) }}
63   - </template>
64   - </el-table-column>
65   - <el-table-column prop="quantity" label="库存数量" align="left" >
66   - <template slot-scope="scope">
67   - <span :class="scope.row.quantity <= 10 ? 'text-danger' : ''">{{ scope.row.quantity }}</span>
68   - </template>
69   - </el-table-column>
70   - <el-table-column prop="standardUnit" label="单位" align="left" />
71   - <el-table-column prop="totalValue" label="总价值" align="left">
72   - <template slot-scope="scope">
73   - {{ formatMoney(scope.row.totalValue) }}
74   - </template>
75   - </el-table-column>
76   - <!-- <el-table-column prop="departmentName" label="负责部门" align="left" /> -->
77   - <el-table-column prop="createTime" label="创建时间" align="left" :formatter="ncc.tableDateFormat">
78   - <!-- <template slot-scope="scope">
79   - {{ scope.row.createTime | dateTimeFormat }}
80   - </template> -->
81   - </el-table-column>
82   - <el-table-column label="操作" align="center" width="200" fixed="right">
83   - <template slot-scope="scope">
84   - <el-button type="text" @click="viewInventoryInfo(scope.row)">查看详情</el-button>
85   - <el-button type="text" @click="editInventory(scope.row)">编辑</el-button>
86   - <el-button type="text" @click="viewUsageRecords(scope.row)">使用记录</el-button>
87   - </template>
88   - </el-table-column>
89   - </NCC-table>
90   -
91   - <!-- 分页组件 -->
92   - <pagination v-show="total > 0" :total="total" :page.sync="query.currentPage"
93   - :limit.sync="query.pageSize" @pagination="getList" />
94   - </div>
95   - </div>
96   -
97   - <!-- 添加/编辑库存弹窗 -->
98   - <InventoryForm v-if="inventoryFormVisible" ref="InventoryForm" @refreshDataList="getList" />
99   -
100   - <!-- 库存详情弹窗 -->
101   - <InventoryInfoDialog v-if="inventoryInfoVisible" ref="InventoryInfoDialog" />
102   -
103   - <!-- 使用记录弹窗 -->
104   - <UsageRecordDialog v-if="usageRecordVisible" ref="UsageRecordDialog" />
105   - </div>
  78 + <el-table-column label="标准单位" align="center" prop="standardUnit"/>
  79 + <el-table-column label="上架状态" align="center" prop="onShelfStatus">
  80 + <template slot-scope="scope">
  81 + <div class="shelf-status-info">
  82 + <el-tag :type="scope.row.onShelfStatus === 1 ? 'success' : 'info'" size="small">
  83 + {{ scope.row.onShelfStatus === 1 ? '上架' : '下架' }}
  84 + </el-tag>
  85 + </div>
  86 + </template>
  87 + </el-table-column>
  88 + <el-table-column label="统计分类" align="center" prop="statisticsCategory"/>
  89 + <el-table-column label="归属仓库" align="center" prop="warehouse">
  90 + <template slot-scope="scope">
  91 + <div class="warehouse-info">
  92 + <i class="el-icon-office-building warehouse-icon"></i>
  93 + <span class="text-nowrap">{{ scope.row.warehouse || '无' }}</span>
  94 + </div>
  95 + </template>
  96 + </el-table-column>
  97 + <el-table-column label="供应商名称" align="center" prop="supplierName"/>
  98 + <!-- <el-table-column label="合同签订日期" align="center" prop="contractSignDate"/>
  99 + <el-table-column label="合同结束日期" align="center" prop="contractEndDate"/>
  100 + <el-table-column label="备注" align="center" prop="remark"/> -->
  101 + <el-table-column label="操作" width="280" align="left" fixed="right">
  102 + <template slot-scope="scope">
  103 + <div class="action-buttons">
  104 + <el-button v-if="scope.row.onShelfStatus === 1" type="text" icon="el-icon-view" @click="viewDetail(scope.row)"
  105 + class="view-btn">
  106 + 查看详情
  107 + </el-button>
  108 + <el-button type="text" @click="toggleShelf(scope.row)">
  109 + {{ scope.row.onShelfStatus === 1 ? '下架' : '上架' }}
  110 + </el-button>
  111 + <el-button v-if="scope.row.onShelfStatus === 1" type="text" icon="el-icon-edit" @click="editProduct(scope.row)"
  112 + class="edit-btn">
  113 + 编辑
  114 + </el-button>
  115 + </div>
  116 + </template>
  117 + </el-table-column>
  118 + </NCC-table>
  119 + <pagination :total="productTotal" :page.sync="productQuery.currentPage"
  120 + :limit.sync="productQuery.pageSize" @pagination="initProductData" />
  121 + </div>
  122 + </div>
  123 + <!-- 产品表单弹窗 -->
  124 + <ProductForm v-if="productFormVisible" ref="ProductForm" @refresh="refreshProduct" />
  125 + <!-- 产品详情弹窗(库存和使用记录) -->
  126 + <ProductDetailDialog v-if="detailDialogVisible" ref="ProductDetailDialog" @refresh="refreshProduct" />
  127 + <!-- 添加使用记录弹窗(多产品) -->
  128 + <UsageMultiForm v-if="usageMultiFormVisible" ref="UsageMultiForm" @refresh="refreshProduct" />
  129 + <!-- 添加库存弹窗 -->
  130 + <InventoryForm v-if="inventoryFormVisible" ref="InventoryForm" @refresh="refreshProduct" />
  131 + </div>
106 132 </template>
107 133  
108 134 <script>
109   -import { getInventoryList } from '@/api/extend/lqInventory'
110   -import { getUserList } from '@/api/permission/user'
111   -import InventoryForm from './InventoryForm'
112   -import InventoryInfoDialog from './InventoryInfoDialog'
113   -import UsageRecordDialog from './UsageRecordDialog'
114   -import Pagination from '@/components/Pagination'
115 135 import request from '@/utils/request'
  136 +import ProductForm from './product-form.vue'
  137 +import ProductDetailDialog from './product-detail-dialog.vue'
  138 +import UsageMultiForm from './usage-multi-form.vue'
  139 +import InventoryForm from './inventory-form.vue'
  140 +
116 141 export default {
117   - name: 'LqInventory',
118   - components: {
119   - InventoryForm,
120   - InventoryInfoDialog,
121   - UsageRecordDialog,
122   - Pagination
123   - },
124   - data() {
125   - return {
126   - query: {
127   - productName: '',
128   - productCategory: '',
129   - departmentId: '',
130   - currentPage: 1,
131   - pageSize: 20
132   - },
133   - list: [],
134   - total: 0,
135   - listLoading: true,
136   - inventoryFormVisible: false,
137   - inventoryInfoVisible: false,
138   - usageRecordVisible: false,
139   - selectedIds: [],
140   - departmentList: [] // 部门列表
141   - }
142   - },
143   - created() {
144   - this.getDepartmentList()
145   - this.getList()
146   - },
147   - methods: {
148   - // 获取部门列表
149   - getDepartmentList() {
150   - request({
151   - url: `/api/permission/Organize/96240625-934F-490B-8AA6-0BC775B18468/Department`,
  142 + components: { ProductForm, ProductDetailDialog, UsageMultiForm, InventoryForm },
  143 + data() {
  144 + return {
  145 + // 产品相关
  146 + productList: [],
  147 + productLoading: false,
  148 + productTotal: 0,
  149 + productQuery: {
  150 + currentPage: 1,
  151 + pageSize: 20,
  152 + productName: undefined,
  153 + productCategory: undefined,
  154 + onShelfStatus: undefined
  155 + },
  156 + productFormVisible: false,
  157 + detailDialogVisible: false,
  158 + usageMultiFormVisible: false,
  159 + inventoryFormVisible: false
  160 + }
  161 + },
  162 + created() {
  163 + this.initProductData()
  164 + },
  165 + methods: {
  166 + // 产品相关方法
  167 + initProductData() {
  168 + this.productLoading = true
  169 + let query = { ...this.productQuery }
  170 + // 移除空值
  171 + Object.keys(query).forEach(key => {
  172 + if (query[key] === undefined || query[key] === null || query[key] === '') {
  173 + delete query[key]
  174 + }
  175 + })
  176 + request({
  177 + url: '/api/Extend/LqProduct/GetList',
152 178 method: 'GET',
153   - }).then((res) => {
154   - if (res.code == 200 && res.data.list.length > 0) {
155   - this.departmentList = res.data.list.map(item => ({
156   - id: item.id,
157   - name: item.fullName,
158   - }))
159   - } else {
160   - this.departmentList = []
161   - }
162   - })
163   - },
  179 + data: query
  180 + }).then(res => {
  181 + if (res.code == 200 && res.data) {
  182 + this.productList = res.data.list || []
  183 + this.productTotal = res.data.pagination ? res.data.pagination.total : 0
  184 + } else {
  185 + this.productList = []
  186 + this.productTotal = 0
  187 + }
  188 + this.productLoading = false
  189 + }).catch(() => {
  190 + this.productLoading = false
  191 + this.productList = []
  192 + this.productTotal = 0
  193 + })
  194 + },
  195 + searchProduct() {
  196 + this.productQuery.currentPage = 1
  197 + this.initProductData()
  198 + },
  199 + resetProduct() {
  200 + this.productQuery = {
  201 + currentPage: 1,
  202 + pageSize: 20,
  203 + productName: undefined,
  204 + productCategory: undefined,
  205 + onShelfStatus: undefined
  206 + }
  207 + this.initProductData()
  208 + },
  209 + addProduct() {
  210 + this.productFormVisible = true
  211 + this.$nextTick(() => {
  212 + this.$refs.ProductForm.init()
  213 + })
  214 + },
  215 + editProduct(row) {
  216 + this.productFormVisible = true
  217 + this.$nextTick(() => {
  218 + this.$refs.ProductForm.init(row.id)
  219 + })
  220 + },
  221 + viewDetail(row) {
  222 + this.detailDialogVisible = true
  223 + this.$nextTick(() => {
  224 + this.$refs.ProductDetailDialog.init(row.id, row)
  225 + })
  226 + },
  227 + toggleShelf(row) {
  228 + const status = row.onShelfStatus === 1 ? 0 : 1
  229 + const statusText = status === 1 ? '上架' : '下架'
  230 + this.$confirm(`确定要${statusText}该产品吗?`, '提示', {
  231 + type: 'warning'
  232 + }).then(() => {
  233 + request({
  234 + url: '/api/Extend/LqProduct/ToggleShelf',
  235 + method: 'PUT',
  236 + data: {
  237 + productId: row.id,
  238 + onShelfStatus: status
  239 + }
  240 + }).then(res => {
  241 + this.$message({
  242 + type: 'success',
  243 + message: res.msg || `${statusText}成功`,
  244 + onClose: () => {
  245 + this.initProductData()
  246 + }
  247 + })
  248 + })
  249 + }).catch(() => { })
  250 + },
  251 + refreshProduct() {
  252 + this.productFormVisible = false
  253 + this.usageMultiFormVisible = false
  254 + this.inventoryFormVisible = false
  255 + this.initProductData()
  256 + },
  257 + addInventory() {
  258 + this.inventoryFormVisible = true
  259 + this.$nextTick(() => {
  260 + // 从列表页调用,不传产品ID,只显示上架产品
  261 + this.$refs.InventoryForm.init()
  262 + })
  263 + },
  264 + addUsage() {
  265 + this.usageMultiFormVisible = true
  266 + this.$nextTick(() => {
  267 + this.$refs.UsageMultiForm.init()
  268 + })
  269 + },
  270 + // 工具方法
  271 + formatMoney(amount) {
  272 + if (!amount && amount !== 0) return '0.00'
  273 + return Number(amount).toFixed(2)
  274 + }
  275 + }
  276 +}
  277 +</script>
164 278  
165   - // 获取库存列表
166   - getList() {
167   - this.listLoading = true
168   - getInventoryList(this.query).then(response => {
169   - if (response.code === 200) {
170   - this.list = response.data.list || []
171   - this.total = (response.data.pagination && response.data.pagination.total) || 0
172   - } else {
173   - this.$message.error(response.msg || '获取库存列表失败')
174   - }
175   - this.listLoading = false
176   - }).catch(() => {
177   - this.listLoading = false
178   - })
179   - },
  279 +<style lang="scss" scoped>
  280 +// 通用文本不换行样式
  281 +.text-nowrap {
  282 + white-space: nowrap;
  283 + overflow: hidden;
  284 + text-overflow: ellipsis;
  285 + max-width: 100%;
  286 +}
180 287  
181   - // 搜索
182   - search() {
183   - this.query.currentPage = 1
184   - this.getList()
185   - },
  288 +// 产品相关样式
  289 +.product-id-info,
  290 +.product-name-info,
  291 +.price-info,
  292 +.category-info,
  293 +.unit-info,
  294 +.shelf-status-info,
  295 +.statistics-info,
  296 +.warehouse-info,
  297 +.supplier-info {
  298 + display: flex;
  299 + align-items: center;
  300 + justify-content: center;
  301 + gap: 6px;
  302 +}
  303 +
  304 +.product-id-icon,
  305 +.product-name-icon {
  306 + color: #409EFF;
  307 + font-size: 16px;
  308 +}
186 309  
187   - // 重置
188   - reset() {
189   - this.query = {
190   - productName: '',
191   - productCategory: '',
192   - departmentId: '',
193   - currentPage: 1,
194   - pageSize: 20
195   - }
196   - this.getList()
197   - },
  310 +.price-icon {
  311 + color: #67C23A;
  312 + font-size: 16px;
  313 +}
198 314  
199   - // 添加库存
200   - addInventoryHandle() {
201   - this.inventoryFormVisible = true
202   - this.$nextTick(() => {
203   - this.$refs.InventoryForm.init()
204   - })
205   - },
  315 +.category-icon,
  316 +.unit-icon,
  317 +.statistics-icon {
  318 + color: #909399;
  319 + font-size: 16px;
  320 +}
206 321  
207   - // 编辑库存
208   - editInventoryHandle() {
209   - if (this.selectedIds.length === 0) {
210   - this.$message.warning('请选择一个库存记录')
211   - return
212   - }
213   - if (this.selectedIds.length > 1) {
214   - this.$message.warning('请只选择一个库存记录')
215   - return
216   - }
  322 +.shelf-status-icon {
  323 + font-size: 16px;
217 324  
218   - const selectedInventory = this.list.find(item => item.id === this.selectedIds[0])
219   - if (selectedInventory) {
220   - this.inventoryFormVisible = true
221   - this.$nextTick(() => {
222   - this.$refs.InventoryForm.init(selectedInventory.id)
223   - })
224   - }
225   - },
  325 + &.status-on {
  326 + color: #67C23A;
  327 + }
226 328  
227   - // 编辑库存(从操作列)
228   - editInventory(row) {
229   - this.inventoryFormVisible = true
230   - this.$nextTick(() => {
231   - this.$refs.InventoryForm.init(row.id)
232   - })
233   - },
  329 + &.status-off {
  330 + color: #909399;
  331 + }
  332 +}
234 333  
235   - // 查看库存详情
236   - viewInventoryInfo(row) {
237   - this.inventoryInfoVisible = true
238   - this.$nextTick(() => {
239   - this.$refs.InventoryInfoDialog.init(row.id)
240   - })
241   - },
  334 +.warehouse-icon,
  335 +.supplier-icon {
  336 + color: #409EFF;
  337 + font-size: 16px;
  338 +}
242 339  
243   - // 使用记录管理
244   - usageRecordHandle() {
245   - this.usageRecordVisible = true
246   - this.$nextTick(() => {
247   - this.$refs.UsageRecordDialog.init()
248   - })
249   - },
  340 +// 操作按钮样式
  341 +.action-buttons {
  342 + display: flex;
  343 + align-items: center;
  344 + gap: 8px;
250 345  
251   - // 查看使用记录
252   - viewUsageRecords(row) {
253   - this.usageRecordVisible = true
254   - this.$nextTick(() => {
255   - this.$refs.UsageRecordDialog.init(row.id, row.productName)
256   - })
257   - },
  346 + .view-btn {
  347 + color: #409EFF;
258 348  
259   - // 多选
260   - handleSelectionChange(selection) {
261   - this.selectedIds = selection.map(item => item.id)
262   - },
  349 + &:hover {
  350 + color: #66b1ff;
  351 + }
  352 + }
263 353  
264   - // 格式化金额
265   - formatMoney(value) {
266   - if (value === null || value === undefined || value === '') {
267   - return '0.00'
268   - }
269   - return Number(value).toFixed(2)
270   - },
  354 + .edit-btn {
  355 + color: #409EFF;
271 356  
272   - // 导出
273   - exportData() {
274   - this.$message.info('导出功能开发中...')
275   - }
276   - }
  357 + &:hover {
  358 + color: #66b1ff;
  359 + }
  360 + }
277 361 }
278   -</script>
279 362  
280   -<style scoped>
281   -.NCC-common-layout {
282   - height: 100%;
  363 +// 表格行悬停效果
  364 +::v-deep .el-table__row:hover {
  365 + background-color: #f5f7fa;
283 366 }
284 367  
285   -.text-danger {
286   - color: #f56c6c;
287   - font-weight: bold;
  368 +// 表格头部样式
  369 +::v-deep .el-table__header-wrapper {
  370 + .el-table__header {
  371 + th {
  372 + background-color: #f5f7fa;
  373 + color: #606266;
  374 + font-weight: 600;
  375 + }
  376 + }
288 377 }
289 378 </style>
... ...
antis-ncc-admin/src/views/lqInventory/inventory-form.vue 0 → 100644
  1 +<template>
  2 + <el-dialog title="添加库存" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center"
  3 + lock-scroll width="800px">
  4 + <el-form ref="elForm" :model="dataForm" size="small" label-width="120px" label-position="right" :rules="rules">
  5 + <el-row :gutter="15">
  6 + <el-col :span="12">
  7 + <el-form-item label="产品" prop="productId">
  8 + <el-select v-model="dataForm.productId" placeholder="请选择产品" clearable filterable
  9 + :style='{"width":"100%"}' :disabled="!!defaultProductId" @change="onProductChange">
  10 + <el-option v-for="item in productOptions" :key="item.id" :label="item.productName"
  11 + :value="item.id" />
  12 + </el-select>
  13 + </el-form-item>
  14 + </el-col>
  15 + <el-col :span="12">
  16 + <el-form-item label="数量" prop="quantity">
  17 + <el-input-number v-model="dataForm.quantity" placeholder="请输入数量" :min="1" :precision="0"
  18 + :style='{"width":"100%"}' />
  19 + </el-form-item>
  20 + </el-col>
  21 + <el-col :span="12">
  22 + <el-form-item label="入库时间" prop="stockInTime">
  23 + <el-date-picker v-model="dataForm.stockInTime" type="date" placeholder="请选择入库时间"
  24 + value-format="yyyy-MM-dd" format="yyyy-MM-dd" :style='{"width":"100%"}' />
  25 + </el-form-item>
  26 + </el-col>
  27 + <el-col :span="12">
  28 + <el-form-item label="生产日期" prop="productionDate">
  29 + <el-date-picker v-model="dataForm.productionDate" type="date" placeholder="请选择生产日期"
  30 + value-format="yyyy-MM-dd" format="yyyy-MM-dd" :style='{"width":"100%"}' />
  31 + </el-form-item>
  32 + </el-col>
  33 + <el-col :span="12">
  34 + <el-form-item label="保质期(天)" prop="shelfLife">
  35 + <el-input-number v-model="dataForm.shelfLife" placeholder="请输入保质期" :min="1" :precision="0"
  36 + :style='{"width":"100%"}' />
  37 + </el-form-item>
  38 + </el-col>
  39 + <el-col :span="12">
  40 + <el-form-item label="批次号" prop="batchNumber">
  41 + <el-input v-model="dataForm.batchNumber" placeholder="请输入批次号" clearable
  42 + :style='{"width":"100%"}' />
  43 + </el-form-item>
  44 + </el-col>
  45 + </el-row>
  46 + </el-form>
  47 + <span slot="footer" class="dialog-footer">
  48 + <el-button @click="visible = false">取 消</el-button>
  49 + <el-button type="primary" @click="dataFormSubmit()" :loading="btnLoading">确 定</el-button>
  50 + </span>
  51 + </el-dialog>
  52 +</template>
  53 +
  54 +<script>
  55 +import request from '@/utils/request'
  56 +
  57 +export default {
  58 + data() {
  59 + return {
  60 + visible: false,
  61 + btnLoading: false,
  62 + defaultProductId: '',
  63 + dataForm: {
  64 + productId: '',
  65 + quantity: 1,
  66 + stockInTime: '',
  67 + productionDate: '',
  68 + shelfLife: 365,
  69 + batchNumber: ''
  70 + },
  71 + productOptions: [],
  72 + rules: {
  73 + productId: [
  74 + { required: true, message: '请选择产品', trigger: 'change' }
  75 + ],
  76 + quantity: [
  77 + { required: true, message: '请输入数量', trigger: 'blur' }
  78 + ],
  79 + stockInTime: [
  80 + { required: true, message: '请选择入库时间', trigger: 'change' }
  81 + ]
  82 + }
  83 + }
  84 + },
  85 + methods: {
  86 + init(productId) {
  87 + this.visible = true
  88 + this.defaultProductId = productId || ''
  89 + this.dataForm = {
  90 + productId: productId || '',
  91 + quantity: 1,
  92 + stockInTime: '',
  93 + productionDate: '',
  94 + shelfLife: 365,
  95 + batchNumber: ''
  96 + }
  97 + this.initProductOptions()
  98 + },
  99 + initProductOptions() {
  100 + // 如果传入了产品ID,只显示该产品;否则只显示上架的产品
  101 + const query = {
  102 + currentPage: 1,
  103 + pageSize: 1000
  104 + }
  105 + // 如果传入了产品ID,则查询该产品;否则只查询上架的产品
  106 + if (this.defaultProductId) {
  107 + query.id = this.defaultProductId
  108 + } else {
  109 + query.onShelfStatus = 1 // 只获取上架的产品
  110 + }
  111 + request({
  112 + url: '/api/Extend/LqProduct/GetList',
  113 + method: 'GET',
  114 + data: query
  115 + }).then(res => {
  116 + if (res.code == 200 && res.data && res.data.list) {
  117 + if (this.defaultProductId) {
  118 + // 如果传入了产品ID,只显示该产品
  119 + this.productOptions = res.data.list
  120 + } else {
  121 + // 只显示上架的产品
  122 + this.productOptions = res.data.list.filter(product => product.onShelfStatus === 1)
  123 + }
  124 + }
  125 + }).catch(() => {
  126 + this.productOptions = []
  127 + })
  128 + },
  129 + onProductChange() {
  130 + // 产品选择变化时的处理
  131 + },
  132 + dataFormSubmit() {
  133 + this.$refs.elForm.validate((valid) => {
  134 + if (!valid) {
  135 + return false
  136 + }
  137 + this.btnLoading = true
  138 + request({
  139 + url: '/api/Extend/LqInventory/Create',
  140 + method: 'POST',
  141 + data: {
  142 + productId: this.dataForm.productId,
  143 + quantity: this.dataForm.quantity,
  144 + stockInTime: this.dataForm.stockInTime,
  145 + productionDate: this.dataForm.productionDate,
  146 + shelfLife: this.dataForm.shelfLife,
  147 + batchNumber: this.dataForm.batchNumber
  148 + }
  149 + }).then(res => {
  150 + this.btnLoading = false
  151 + this.$message({
  152 + type: 'success',
  153 + message: res.msg || '添加成功',
  154 + onClose: () => {
  155 + this.visible = false
  156 + this.$emit('refresh')
  157 + }
  158 + })
  159 + }).catch(() => {
  160 + this.btnLoading = false
  161 + })
  162 + })
  163 + }
  164 + }
  165 +}
  166 +</script>
  167 +
  168 +<style lang="scss" scoped>
  169 +</style>
  170 +
... ...
antis-ncc-admin/src/views/lqInventory/product-detail-dialog.vue 0 → 100644
  1 +<template>
  2 + <div>
  3 + <el-dialog title="产品详情" :close-on-click-modal="false" :visible.sync="visible" class="NCC-dialog NCC-dialog_center"
  4 + lock-scroll width="1200px">
  5 + <div class="detail-content">
  6 + <!-- 产品基本信息 -->
  7 + <el-card class="info-card" shadow="hover" v-if="productInfo">
  8 + <div slot="header" class="card-header">
  9 + <i class="el-icon-goods"></i>
  10 + <span class="card-title">产品信息</span>
  11 + </div>
  12 + <el-row :gutter="20">
  13 + <el-col :span="8">
  14 + <div class="info-item">
  15 + <label class="info-label">产品名称:</label>
  16 + <span class="info-value">{{ productInfo.productName || '无' }}</span>
  17 + </div>
  18 + </el-col>
  19 + <el-col :span="8">
  20 + <div class="info-item">
  21 + <label class="info-label">价格:</label>
  22 + <span class="info-value">¥{{ formatMoney(productInfo.price) }}</span>
  23 + </div>
  24 + </el-col>
  25 + <el-col :span="8">
  26 + <div class="info-item">
  27 + <label class="info-label">库存:</label>
  28 + <span class="info-value">{{ productInfo.currentInventory !== undefined ? productInfo.currentInventory : '无' }}</span>
  29 + </div>
  30 + </el-col>
  31 + </el-row>
  32 + <el-row :gutter="20">
  33 + <el-col :span="8">
  34 + <div class="info-item">
  35 + <label class="info-label">产品类别:</label>
  36 + <span class="info-value">{{ productInfo.productCategory || '无' }}</span>
  37 + </div>
  38 + </el-col>
  39 + <el-col :span="8">
  40 + <div class="info-item">
  41 + <label class="info-label">归属部门:</label>
  42 + <span class="info-value">{{ productInfo.departmentName || '无' }}</span>
  43 + </div>
  44 + </el-col>
  45 + <el-col :span="8">
  46 + <div class="info-item">
  47 + <label class="info-label">标准单位:</label>
  48 + <span class="info-value">{{ productInfo.standardUnit || '无' }}</span>
  49 + </div>
  50 + </el-col>
  51 + </el-row>
  52 + <el-row :gutter="20">
  53 + <el-col :span="8">
  54 + <div class="info-item">
  55 + <label class="info-label">上架状态:</label>
  56 + <el-tag :type="productInfo.onShelfStatus === 1 ? 'success' : 'info'" size="small">
  57 + {{ productInfo.onShelfStatus === 1 ? '上架' : '下架' }}
  58 + </el-tag>
  59 + </div>
  60 + </el-col>
  61 + <el-col :span="8">
  62 + <div class="info-item">
  63 + <label class="info-label">统计分类:</label>
  64 + <span class="info-value">{{ productInfo.statisticsCategory || '无' }}</span>
  65 + </div>
  66 + </el-col>
  67 + <el-col :span="8">
  68 + <div class="info-item">
  69 + <label class="info-label">归属仓库:</label>
  70 + <span class="info-value">{{ productInfo.warehouse || '无' }}</span>
  71 + </div>
  72 + </el-col>
  73 + </el-row>
  74 + <el-row :gutter="20">
  75 + <el-col :span="8">
  76 + <div class="info-item">
  77 + <label class="info-label">供应商名称:</label>
  78 + <span class="info-value">{{ productInfo.supplierName || '无' }}</span>
  79 + </div>
  80 + </el-col>
  81 + <el-col :span="8">
  82 + <div class="info-item">
  83 + <label class="info-label">合同签订日期:</label>
  84 + <span class="info-value">{{ formatDate(productInfo.contractSignDate) || '无' }}</span>
  85 + </div>
  86 + </el-col>
  87 + <el-col :span="8">
  88 + <div class="info-item">
  89 + <label class="info-label">合同结束日期:</label>
  90 + <span class="info-value">{{ formatDate(productInfo.contractEndDate) || '无' }}</span>
  91 + </div>
  92 + </el-col>
  93 + </el-row>
  94 + <el-row :gutter="20">
  95 + <el-col :span="24">
  96 + <div class="info-item">
  97 + <label class="info-label">备注:</label>
  98 + <span class="info-value">{{ productInfo.remark || '无' }}</span>
  99 + </div>
  100 + </el-col>
  101 + </el-row>
  102 + </el-card>
  103 +
  104 + <!-- Tab标签页:库存和使用记录 -->
  105 + <el-tabs v-model="activeTab" class="detail-tabs">
  106 + <!-- 库存管理Tab -->
  107 + <el-tab-pane label="库存管理" name="inventory">
  108 + <div class="tab-content">
  109 + <div class="tab-header">
  110 + <el-button type="primary" icon="el-icon-plus" size="small" @click="addInventory()">添加库存</el-button>
  111 + </div>
  112 + <NCC-table v-loading="inventoryLoading" :data="inventoryList" has-c
  113 + :header-cell-style="{ background: '#f5f7fa', color: '#606266' }">
  114 + <el-table-column label="库存ID" align="center">
  115 + <template slot-scope="scope">
  116 + <div class="inventory-id-info">
  117 + <i class="el-icon-postcard inventory-id-icon"></i>
  118 + <span class="text-nowrap">{{ scope.row.id || '无' }}</span>
  119 + </div>
  120 + </template>
  121 + </el-table-column>
  122 + <el-table-column label="数量" align="center">
  123 + <template slot-scope="scope">
  124 + <div class="quantity-info">
  125 + <i class="el-icon-s-data quantity-icon"></i>
  126 + <span class="text-nowrap">{{ scope.row.quantity || 0 }}</span>
  127 + </div>
  128 + </template>
  129 + </el-table-column>
  130 + <el-table-column label="入库时间" align="center">
  131 + <template slot-scope="scope">
  132 + <div class="stock-time-info">
  133 + <i class="el-icon-time stock-time-icon"></i>
  134 + <span class="text-nowrap">{{ formatDate(scope.row.stockInTime) || '无' }}</span>
  135 + </div>
  136 + </template>
  137 + </el-table-column>
  138 + <el-table-column label="生产日期" align="center">
  139 + <template slot-scope="scope">
  140 + <div class="production-date-info">
  141 + <i class="el-icon-date production-date-icon"></i>
  142 + <span class="text-nowrap">{{ formatDate(scope.row.productionDate) || '无' }}</span>
  143 + </div>
  144 + </template>
  145 + </el-table-column>
  146 + <el-table-column label="保质期(天)" align="center">
  147 + <template slot-scope="scope">
  148 + <div class="shelf-life-info">
  149 + <i class="el-icon-timer shelf-life-icon"></i>
  150 + <span class="text-nowrap">{{ scope.row.shelfLife || '无' }}</span>
  151 + </div>
  152 + </template>
  153 + </el-table-column>
  154 + <el-table-column label="批次号" align="center">
  155 + <template slot-scope="scope">
  156 + <div class="batch-number-info">
  157 + <i class="el-icon-document batch-number-icon"></i>
  158 + <span class="text-nowrap">{{ scope.row.batchNumber || '无' }}</span>
  159 + </div>
  160 + </template>
  161 + </el-table-column>
  162 + <!-- isEffective 是否有效 -->
  163 + <el-table-column label="是否有效" align="center">
  164 + <template slot-scope="scope">
  165 + <el-tag :type="scope.row.isEffective === 1 ? 'success' : 'info'" size="small">
  166 + {{ scope.row.isEffective === 1 ? '有效' : '无效' }}
  167 + </el-tag>
  168 + </template>
  169 + </el-table-column>
  170 + <el-table-column label="操作" align="left" width="100">
  171 + <template slot-scope="scope">
  172 + <el-button v-if="scope.row.isEffective === 1" type="text" icon="el-icon-delete"
  173 + @click="deleteInventory(scope.row)" class="cancel-btn">作废</el-button>
  174 + </template>
  175 + </el-table-column>
  176 + </NCC-table>
  177 + <pagination :total="inventoryTotal" :page.sync="inventoryQuery.currentPage"
  178 + :limit.sync="inventoryQuery.pageSize" @pagination="initInventoryData" />
  179 + </div>
  180 + </el-tab-pane>
  181 +
  182 + <!-- 使用记录Tab -->
  183 + <el-tab-pane label="使用记录" name="usage">
  184 + <div class="tab-content">
  185 + <div class="tab-header">
  186 + <el-button type="primary" icon="el-icon-plus" size="small" @click="addUsage()">批量添加使用记录</el-button>
  187 + </div>
  188 + <NCC-table v-loading="usageLoading" :data="usageList" has-c
  189 + :header-cell-style="{ background: '#f5f7fa', color: '#606266' }">
  190 + <el-table-column label="批次号" align="center">
  191 + <template slot-scope="scope">
  192 + <div class="usage-id-info">
  193 + <i class="el-icon-postcard usage-id-icon"></i>
  194 + <span class="text-nowrap">{{ scope.row.usageBatchId || '无' }}</span>
  195 + </div>
  196 + </template>
  197 + </el-table-column>
  198 + <el-table-column label="门店名称" align="center">
  199 + <template slot-scope="scope">
  200 + <div class="store-name-info">
  201 + <i class="el-icon-office-building store-name-icon"></i>
  202 + <span class="text-nowrap">{{ scope.row.storeName || '无' }}</span>
  203 + </div>
  204 + </template>
  205 + </el-table-column>
  206 + <el-table-column label="使用数量" align="center">
  207 + <template slot-scope="scope">
  208 + <div class="usage-quantity-info">
  209 + <i class="el-icon-s-data usage-quantity-icon"></i>
  210 + <span class="text-nowrap">{{ scope.row.usageQuantity || 0 }}</span>
  211 + </div>
  212 + </template>
  213 + </el-table-column>
  214 + <el-table-column label="使用时间" align="center">
  215 + <template slot-scope="scope">
  216 + <div class="usage-time-info">
  217 + <i class="el-icon-time usage-time-icon"></i>
  218 + <span class="text-nowrap">{{ formatDateTime(scope.row.usageTime) || '无' }}</span>
  219 + </div>
  220 + </template>
  221 + </el-table-column>
  222 + <!-- isEffective 是否有效 -->
  223 + <el-table-column label="是否有效" align="center">
  224 + <template slot-scope="scope">
  225 + <el-tag :type="scope.row.isEffective === 1 ? 'success' : 'info'" size="small">
  226 + {{ scope.row.isEffective === 1 ? '有效' : '无效' }}
  227 + </el-tag>
  228 + </template>
  229 + </el-table-column>
  230 + <el-table-column label="操作" align="left" width="140">
  231 + <template slot-scope="scope">
  232 + <el-button v-if="scope.row.isEffective === 1" type="text" icon="el-icon-printer"
  233 + @click="handlePrint(scope.row)" class="edit-btn">打印</el-button>
  234 + <el-button v-if="scope.row.isEffective === 1" type="text" icon="el-icon-delete"
  235 + @click="deleteUsage(scope.row)" class="cancel-btn">作废</el-button>
  236 + </template>
  237 + </el-table-column>
  238 + </NCC-table>
  239 + <pagination :total="usageTotal" :page.sync="usageQuery.currentPage"
  240 + :limit.sync="usageQuery.pageSize" @pagination="initUsageData" />
  241 + </div>
  242 + </el-tab-pane>
  243 + </el-tabs>
  244 + </div>
  245 + <span slot="footer" class="dialog-footer">
  246 + <el-button @click="visible = false">关 闭</el-button>
  247 + </span>
  248 +
  249 + </el-dialog>
  250 + <!-- 库存表单弹窗 -->
  251 + <InventoryForm v-if="inventoryFormVisible" ref="InventoryForm" @refresh="refreshInventory" />
  252 + <!-- 使用记录表单弹窗 -->
  253 + <UsageForm v-if="usageFormVisible" ref="UsageForm" @refresh="refreshUsage" />
  254 + </div>
  255 +</template>
  256 +
  257 +<script>
  258 +import request from '@/utils/request'
  259 +import InventoryForm from './inventory-form.vue'
  260 +import UsageForm from './usage-form.vue'
  261 +
  262 +export default {
  263 + components: { InventoryForm, UsageForm },
  264 + data() {
  265 + return {
  266 + visible: false,
  267 + activeTab: 'inventory',
  268 + productId: '',
  269 + productInfo: null,
  270 + // 库存相关
  271 + inventoryList: [],
  272 + inventoryLoading: false,
  273 + inventoryTotal: 0,
  274 + inventoryQuery: {
  275 + currentPage: 1,
  276 + pageSize: 20,
  277 + ProductId: ''
  278 + },
  279 + inventoryFormVisible: false,
  280 + // 使用记录相关
  281 + usageList: [],
  282 + usageLoading: false,
  283 + usageTotal: 0,
  284 + usageQuery: {
  285 + currentPage: 1,
  286 + pageSize: 20,
  287 + ProductId: '',
  288 + StoreId: undefined
  289 + },
  290 + usageFormVisible: false,
  291 + // 选项数据
  292 + storeOptions: []
  293 + }
  294 + },
  295 + methods: {
  296 + init(productId, productInfo) {
  297 + this.visible = true
  298 + this.productId = productId
  299 + this.productInfo = productInfo
  300 + this.activeTab = 'inventory'
  301 + this.inventoryQuery.ProductId = productId
  302 + this.usageQuery.ProductId = productId
  303 + this.initInventoryData()
  304 + this.initStoreOptions()
  305 + },
  306 + // 初始化门店选项
  307 + initStoreOptions() {
  308 + request({
  309 + url: '/api/Extend/LqMdxx',
  310 + method: 'GET',
  311 + data: {
  312 + currentPage: 1,
  313 + pageSize: 1000
  314 + }
  315 + }).then(res => {
  316 + if (res.data && res.data.list) {
  317 + this.storeOptions = res.data.list
  318 + }
  319 + }).catch(() => {
  320 + this.storeOptions = []
  321 + })
  322 + },
  323 + // 库存相关方法
  324 + initInventoryData() {
  325 + this.inventoryLoading = true
  326 + let query = { ...this.inventoryQuery }
  327 + // 移除空值
  328 + Object.keys(query).forEach(key => {
  329 + if (query[key] === undefined || query[key] === null || query[key] === '') {
  330 + delete query[key]
  331 + }
  332 + })
  333 + request({
  334 + url: '/api/Extend/LqInventory/GetList',
  335 + method: 'GET',
  336 + data: query
  337 + }).then(res => {
  338 + if (res.code == 200 && res.data) {
  339 + this.inventoryList = res.data.list || []
  340 + this.inventoryTotal = res.data.pagination ? res.data.pagination.total : 0
  341 + } else {
  342 + this.inventoryList = []
  343 + this.inventoryTotal = 0
  344 + }
  345 + this.inventoryLoading = false
  346 + }).catch(() => {
  347 + this.inventoryLoading = false
  348 + this.inventoryList = []
  349 + this.inventoryTotal = 0
  350 + })
  351 + },
  352 + addInventory() {
  353 + this.inventoryFormVisible = true
  354 + this.$nextTick(() => {
  355 + // 从详情页调用,传入产品ID,默认选中当前产品
  356 + this.$refs.InventoryForm.init(this.productId)
  357 + })
  358 + },
  359 + refreshInventory() {
  360 + this.inventoryFormVisible = false
  361 + this.initInventoryData()
  362 + },
  363 + // 作废库存
  364 + deleteInventory(row) {
  365 + this.$confirm('确定要作废这条库存记录吗?', '提示', {
  366 + confirmButtonText: '确定',
  367 + cancelButtonText: '取消',
  368 + type: 'warning'
  369 + }).then(() => {
  370 + const remarks = encodeURIComponent('作废')
  371 + request({
  372 + url: `/api/Extend/LqInventory/Cancel/${row.id}?remarks=${remarks}`,
  373 + method: 'PUT'
  374 + }).then(res => {
  375 + this.$message({
  376 + type: 'success',
  377 + message: res.msg || '作废成功',
  378 + onClose: () => {
  379 + this.initInventoryData()
  380 + }
  381 + })
  382 + }).catch(() => {
  383 + // 请求失败
  384 + })
  385 + }).catch(() => {
  386 + // 用户取消
  387 + })
  388 + },
  389 + // 使用记录相关方法
  390 + initUsageData() {
  391 + this.usageLoading = true
  392 + let query = { ...this.usageQuery }
  393 + // 移除空值
  394 + Object.keys(query).forEach(key => {
  395 + if (query[key] === undefined || query[key] === null || query[key] === '') {
  396 + delete query[key]
  397 + }
  398 + })
  399 + request({
  400 + url: '/api/Extend/LqInventoryUsage/GetList',
  401 + method: 'GET',
  402 + data: query
  403 + }).then(res => {
  404 + if (res.code == 200 && res.data) {
  405 + this.usageList = res.data.list || []
  406 + this.usageTotal = res.data.pagination ? res.data.pagination.total : 0
  407 + } else {
  408 + this.usageList = []
  409 + this.usageTotal = 0
  410 + }
  411 + this.usageLoading = false
  412 + }).catch(() => {
  413 + this.usageLoading = false
  414 + this.usageList = []
  415 + this.usageTotal = 0
  416 + })
  417 + },
  418 + addUsage() {
  419 + this.usageFormVisible = true
  420 + this.$nextTick(() => {
  421 + this.$refs.UsageForm.init(this.productId)
  422 + })
  423 + },
  424 + refreshUsage() {
  425 + this.usageFormVisible = false
  426 + this.initUsageData()
  427 + },
  428 + // 作废使用记录
  429 + deleteUsage(row) {
  430 + this.$confirm('确定要作废这条使用记录吗?', '提示', {
  431 + confirmButtonText: '确定',
  432 + cancelButtonText: '取消',
  433 + type: 'warning'
  434 + }).then(() => {
  435 + request({
  436 + url: `/api/Extend/LqInventoryUsage/Cancel?id=${row.id}`,
  437 + method: 'PUT'
  438 + }).then(res => {
  439 + this.$message({
  440 + type: 'success',
  441 + message: res.msg || '作废成功',
  442 + onClose: () => {
  443 + this.initUsageData()
  444 + }
  445 + })
  446 + }).catch(() => {
  447 + // 请求失败
  448 + })
  449 + }).catch(() => {
  450 + // 用户取消
  451 + })
  452 + },
  453 + // 打印功能
  454 + handlePrint(row) {
  455 + if (!row.usageBatchId) {
  456 + this.$message.warning('无法打印,批次ID不存在')
  457 + return
  458 + }
  459 +
  460 + // 调用接口获取批次信息
  461 + request({
  462 + url: `/api/Extend/LqInventoryUsage/GetBatchInfo?batchId=${row.usageBatchId}`,
  463 + method: 'GET'
  464 + }).then(res => {
  465 + if (res.code === 200 && res.data) {
  466 + // 构建打印内容
  467 + let printContent = this.buildPrintContent(res.data)
  468 +
  469 + // 打开新窗口并打印
  470 + let newWindow = window.open('_blank')
  471 + if (!newWindow) {
  472 + this.$message.error('无法打开打印窗口,请检查浏览器弹窗设置')
  473 + return
  474 + }
  475 +
  476 + newWindow.document.write(printContent)
  477 + newWindow.document.close()
  478 +
  479 + // 等待内容加载完成后打印
  480 + setTimeout(() => {
  481 + newWindow.print()
  482 + }, 300)
  483 + } else {
  484 + this.$message.error(res.msg || '获取批次信息失败')
  485 + }
  486 + }).catch(err => {
  487 + this.$message.error('获取批次信息失败,请稍后重试')
  488 + })
  489 + },
  490 + // 构建打印内容
  491 + buildPrintContent(batchData) {
  492 + // 格式化时间
  493 + const formatDateTime = (timestamp) => {
  494 + if (!timestamp) return '无'
  495 + try {
  496 + const date = new Date(timestamp)
  497 + if (isNaN(date.getTime())) return '无'
  498 + const year = date.getFullYear()
  499 + const month = String(date.getMonth() + 1).padStart(2, '0')
  500 + const day = String(date.getDate()).padStart(2, '0')
  501 + const hours = String(date.getHours()).padStart(2, '0')
  502 + const minutes = String(date.getMinutes()).padStart(2, '0')
  503 + const seconds = String(date.getSeconds()).padStart(2, '0')
  504 + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
  505 + } catch (e) {
  506 + return '无'
  507 + }
  508 + }
  509 +
  510 + const formatDate = (timestamp) => {
  511 + if (!timestamp) return '无'
  512 + try {
  513 + const date = new Date(timestamp)
  514 + if (isNaN(date.getTime())) return '无'
  515 + const year = date.getFullYear()
  516 + const month = String(date.getMonth() + 1).padStart(2, '0')
  517 + const day = String(date.getDate()).padStart(2, '0')
  518 + return `${year}-${month}-${day}`
  519 + } catch (e) {
  520 + return '无'
  521 + }
  522 + }
  523 +
  524 + const formatMoney = (amount) => {
  525 + if (!amount && amount !== 0) return '0.00'
  526 + return Number(amount).toFixed(2)
  527 + }
  528 +
  529 + // 构建使用记录表格行
  530 + let usageTableRows = ''
  531 + if (batchData.UsageRecords && batchData.UsageRecords.length > 0) {
  532 + batchData.UsageRecords.forEach((item, index) => {
  533 + usageTableRows += `
  534 + <tr>
  535 + <td style="text-align: center;">${index + 1}</td>
  536 + <td>${item.productName || '无'}</td>
  537 + <td>${item.productCategory || '无'}</td>
  538 + <td style="text-align: right;">¥${formatMoney(item.productPrice)}</td>
  539 + <td>${item.storeName || '无'}</td>
  540 + <td style="text-align: center;">${item.usageQuantity || 0}</td>
  541 + <td style="text-align: right;">¥${formatMoney(item.usageTotalValue)}</td>
  542 + <td>${formatDateTime(item.usageTime)}</td>
  543 + <td>${item.createUserName || '无'}</td>
  544 + </tr>
  545 + `
  546 + })
  547 + } else {
  548 + usageTableRows = '<tr><td colspan="9" style="text-align: center;">暂无使用记录</td></tr>'
  549 + }
  550 +
  551 + return `
  552 + <!DOCTYPE html>
  553 + <html>
  554 + <head>
  555 + <meta charset="UTF-8">
  556 + <title>产品使用批次详情</title>
  557 + <style>
  558 + * {
  559 + margin: 0;
  560 + padding: 0;
  561 + box-sizing: border-box;
  562 + }
  563 + body {
  564 + font-family: "Microsoft YaHei", Arial, sans-serif;
  565 + font-size: 13px;
  566 + padding: 15px;
  567 + color: #333;
  568 + }
  569 + .print-header {
  570 + text-align: center;
  571 + margin-bottom: 15px;
  572 + border-bottom: 2px solid #333;
  573 + padding-bottom: 10px;
  574 + }
  575 + .print-header h1 {
  576 + font-size: 22px;
  577 + font-weight: bold;
  578 + margin-bottom: 5px;
  579 + }
  580 + .print-info {
  581 + margin-bottom: 12px;
  582 + }
  583 + .print-info table {
  584 + width: 100%;
  585 + border-collapse: collapse;
  586 + margin-bottom: 12px;
  587 + }
  588 + .print-info table td {
  589 + padding: 6px 10px;
  590 + border: 1px solid #ddd;
  591 + }
  592 + .print-info table td:first-child {
  593 + background-color: #f5f5f5;
  594 + font-weight: bold;
  595 + width: 140px;
  596 + text-align: right;
  597 + }
  598 + .print-table {
  599 + width: 100%;
  600 + border-collapse: collapse;
  601 + margin-bottom: 12px;
  602 + }
  603 + .print-table thead {
  604 + display: table-header-group;
  605 + }
  606 + .print-table tbody {
  607 + display: table-row-group;
  608 + }
  609 + .print-table th,
  610 + .print-table td {
  611 + border: 1px solid #ddd;
  612 + padding: 5px 6px;
  613 + text-align: left;
  614 + font-size: 12px;
  615 + }
  616 + .print-table th {
  617 + background-color: #f5f5f5;
  618 + font-weight: bold;
  619 + text-align: center;
  620 + }
  621 + .print-table tbody tr {
  622 + page-break-inside: avoid;
  623 + }
  624 + .print-footer {
  625 + margin-top: 15px;
  626 + padding-top: 10px;
  627 + border-top: 1px solid #ddd;
  628 + text-align: right;
  629 + font-size: 11px;
  630 + color: #666;
  631 + }
  632 + @media print {
  633 + body {
  634 + padding: 8px;
  635 + }
  636 + .print-header {
  637 + page-break-after: avoid;
  638 + margin-bottom: 12px;
  639 + padding-bottom: 8px;
  640 + }
  641 + .print-info {
  642 + page-break-after: avoid;
  643 + margin-bottom: 10px;
  644 + }
  645 + .print-table {
  646 + page-break-inside: auto;
  647 + }
  648 + .print-table thead {
  649 + display: table-header-group;
  650 + }
  651 + .print-table tbody tr {
  652 + page-break-inside: avoid;
  653 + }
  654 + }
  655 + </style>
  656 + </head>
  657 + <body>
  658 + <div class="print-header">
  659 + <h1>产品使用批次详情</h1>
  660 + </div>
  661 +
  662 + <div class="print-info">
  663 + <table>
  664 + <tr>
  665 + <td>批次ID:</td>
  666 + <td>${batchData.BatchId || '无'}</td>
  667 + </tr>
  668 + <tr>
  669 + <td>创建时间:</td>
  670 + <td>${formatDateTime(batchData.CreateTime)}</td>
  671 + </tr>
  672 + <tr>
  673 + <td>创建人:</td>
  674 + <td>${batchData.CreateUserName || batchData.CreateUser || '无'}</td>
  675 + </tr>
  676 + <tr>
  677 + <td>总记录数:</td>
  678 + <td>${batchData.TotalCount || 0}</td>
  679 + </tr>
  680 + <tr>
  681 + <td>有效记录数:</td>
  682 + <td>${batchData.EffectiveCount || 0}</td>
  683 + </tr>
  684 + <tr>
  685 + <td>无效记录数:</td>
  686 + <td>${batchData.IneffectiveCount || 0}</td>
  687 + </tr>
  688 + <tr>
  689 + <td>总使用数量:</td>
  690 + <td>${batchData.TotalUsageQuantity || 0}</td>
  691 + </tr>
  692 + <tr>
  693 + <td>总使用金额:</td>
  694 + <td>¥${formatMoney(batchData.TotalUsageAmount)}</td>
  695 + </tr>
  696 + </table>
  697 + </div>
  698 +
  699 + <div class="print-info">
  700 + <h3 style="margin-bottom: 8px; font-size: 14px;">使用记录明细</h3>
  701 + <table class="print-table">
  702 + <thead>
  703 + <tr>
  704 + <th style="width: 50px;">序号</th>
  705 + <th>产品名称</th>
  706 + <th>产品类别</th>
  707 + <th style="width: 100px;">单价</th>
  708 + <th>门店名称</th>
  709 + <th style="width: 80px;">使用数量</th>
  710 + <th style="width: 100px;">使用金额</th>
  711 + <th style="width: 150px;">使用时间</th>
  712 + <th>创建人</th>
  713 + </tr>
  714 + </thead>
  715 + <tbody>
  716 + ${usageTableRows}
  717 + </tbody>
  718 + </table>
  719 + </div>
  720 +
  721 + <div class="print-footer">
  722 + <p>打印时间:${formatDateTime(new Date().getTime())}</p>
  723 + </div>
  724 + </body>
  725 + </html>
  726 + `
  727 + },
  728 + // 工具方法
  729 + formatMoney(amount) {
  730 + if (!amount && amount !== 0) return '0.00'
  731 + return Number(amount).toFixed(2)
  732 + },
  733 + formatDate(date) {
  734 + if (!date) return '无'
  735 + try {
  736 + const d = new Date(date)
  737 + if (isNaN(d.getTime())) return '无'
  738 + const year = d.getFullYear()
  739 + const month = String(d.getMonth() + 1).padStart(2, '0')
  740 + const day = String(d.getDate()).padStart(2, '0')
  741 + return `${year}-${month}-${day}`
  742 + } catch (e) {
  743 + return date
  744 + }
  745 + },
  746 + formatDateTime(dateTime) {
  747 + if (!dateTime) return '无'
  748 + try {
  749 + const date = new Date(dateTime)
  750 + if (isNaN(date.getTime())) return '无'
  751 + const year = date.getFullYear()
  752 + const month = String(date.getMonth() + 1).padStart(2, '0')
  753 + const day = String(date.getDate()).padStart(2, '0')
  754 + const hours = String(date.getHours()).padStart(2, '0')
  755 + const minutes = String(date.getMinutes()).padStart(2, '0')
  756 + const seconds = String(date.getSeconds()).padStart(2, '0')
  757 + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
  758 + } catch (e) {
  759 + return dateTime
  760 + }
  761 + }
  762 + },
  763 + watch: {
  764 + activeTab(newVal) {
  765 + if (newVal === 'usage' && this.usageList.length === 0) {
  766 + this.initUsageData()
  767 + }
  768 + }
  769 + }
  770 +}
  771 +</script>
  772 +
  773 +<style lang="scss" scoped>
  774 +.detail-content {
  775 + .info-card {
  776 + margin-bottom: 20px;
  777 +
  778 + .card-header {
  779 + display: flex;
  780 + align-items: center;
  781 + gap: 8px;
  782 +
  783 + i {
  784 + color: #409EFF;
  785 + font-size: 18px;
  786 + }
  787 +
  788 + .card-title {
  789 + font-weight: 600;
  790 + font-size: 16px;
  791 + }
  792 + }
  793 +
  794 + .info-item {
  795 + margin-bottom: 15px;
  796 + display: flex;
  797 + align-items: center;
  798 +
  799 + .info-label {
  800 + color: #909399;
  801 + font-weight: 500;
  802 + min-width: 100px;
  803 + text-align: right;
  804 + margin-right: 8px;
  805 + flex-shrink: 0;
  806 + }
  807 +
  808 + .info-value {
  809 + color: #303133;
  810 + flex: 1;
  811 + }
  812 + }
  813 + }
  814 +
  815 + .detail-tabs {
  816 + margin-top: 20px;
  817 +
  818 + .tab-content {
  819 + .tab-header {
  820 + margin-bottom: 15px;
  821 + display: flex;
  822 + justify-content: flex-start;
  823 + }
  824 + }
  825 + }
  826 +}
  827 +
  828 +// 通用文本不换行样式
  829 +.text-nowrap {
  830 + white-space: nowrap;
  831 + overflow: hidden;
  832 + text-overflow: ellipsis;
  833 + max-width: 100%;
  834 +}
  835 +
  836 +// 库存相关样式
  837 +.inventory-id-info,
  838 +.quantity-info,
  839 +.stock-time-info,
  840 +.production-date-info,
  841 +.shelf-life-info,
  842 +.batch-number-info {
  843 + display: flex;
  844 + align-items: center;
  845 + justify-content: center;
  846 + gap: 6px;
  847 +}
  848 +
  849 +.inventory-id-icon {
  850 + color: #409EFF;
  851 + font-size: 16px;
  852 +}
  853 +
  854 +.quantity-icon {
  855 + color: #67C23A;
  856 + font-size: 16px;
  857 +}
  858 +
  859 +.stock-time-icon,
  860 +.production-date-icon {
  861 + color: #909399;
  862 + font-size: 16px;
  863 +}
  864 +
  865 +.shelf-life-icon,
  866 +.batch-number-icon {
  867 + color: #E6A23C;
  868 + font-size: 16px;
  869 +}
  870 +
  871 +// 使用记录相关样式
  872 +.usage-id-info,
  873 +.store-name-info,
  874 +.usage-quantity-info,
  875 +.usage-time-info,
  876 +.related-consume-info {
  877 + display: flex;
  878 + align-items: center;
  879 + justify-content: center;
  880 + gap: 6px;
  881 +}
  882 +
  883 +.usage-id-icon {
  884 + color: #409EFF;
  885 + font-size: 16px;
  886 +}
  887 +
  888 +.store-name-icon {
  889 + color: #67C23A;
  890 + font-size: 16px;
  891 +}
  892 +
  893 +.usage-quantity-icon {
  894 + color: #409EFF;
  895 + font-size: 16px;
  896 +}
  897 +
  898 +.usage-time-icon {
  899 + color: #909399;
  900 + font-size: 16px;
  901 +}
  902 +
  903 +.related-consume-icon {
  904 + color: #E6A23C;
  905 + font-size: 16px;
  906 +}
  907 +
  908 +// 作废按钮样式
  909 +.cancel-btn {
  910 + color: #F56C6C !important;
  911 + &:hover {
  912 + color: #f78989 !important;
  913 + }
  914 +}
  915 +</style>
  916 +
... ...
antis-ncc-admin/src/views/lqInventory/product-form.vue 0 → 100644
  1 +<template>
  2 + <el-dialog :title="!dataForm.id ? '新建产品' : '编辑产品'" :close-on-click-modal="false" :visible.sync="visible"
  3 + class="NCC-dialog NCC-dialog_center" lock-scroll width="800px">
  4 + <el-form ref="elForm" :model="dataForm" size="small" label-width="120px" label-position="right" :rules="rules">
  5 + <el-row :gutter="15">
  6 + <el-col :span="12">
  7 + <el-form-item label="产品名称" prop="productName">
  8 + <el-input v-model="dataForm.productName" placeholder="请输入产品名称" clearable
  9 + :style='{"width":"100%"}' />
  10 + </el-form-item>
  11 + </el-col>
  12 + <el-col :span="12">
  13 + <el-form-item label="价格" prop="price">
  14 + <el-input-number v-model="dataForm.price" placeholder="请输入价格" :precision="2" :min="0"
  15 + :style='{"width":"100%"}' />
  16 + </el-form-item>
  17 + </el-col>
  18 + <el-col :span="12">
  19 + <el-form-item label="产品类别" prop="productCategory">
  20 + <el-input v-model="dataForm.productCategory" placeholder="请输入产品类别" clearable
  21 + :style='{"width":"100%"}' />
  22 + </el-form-item>
  23 + </el-col>
  24 + <el-col :span="12">
  25 + <el-form-item label="负责部门" prop="departmentId">
  26 + <el-select v-model="dataForm.departmentId" placeholder="请选择负责部门" clearable filterable
  27 + :style='{"width":"100%"}'>
  28 + <el-option v-for="dept in departmentList" :key="dept.id" :label="dept.name"
  29 + :value="dept.id" />
  30 + </el-select>
  31 + </el-form-item>
  32 + </el-col>
  33 + <el-col :span="12">
  34 + <el-form-item label="标准单位" prop="standardUnit">
  35 + <el-input v-model="dataForm.standardUnit" placeholder="请输入标准单位" clearable
  36 + :style='{"width":"100%"}' />
  37 + </el-form-item>
  38 + </el-col>
  39 + <!-- <el-col :span="12">
  40 + <el-form-item label="上架状态" prop="onShelfStatus">
  41 + <el-select v-model="dataForm.onShelfStatus" placeholder="请选择上架状态" clearable
  42 + :style='{"width":"100%"}'>
  43 + <el-option label="上架" :value="1" />
  44 + <el-option label="下架" :value="0" />
  45 + </el-select>
  46 + </el-form-item>
  47 + </el-col> -->
  48 + <el-col :span="12">
  49 + <el-form-item label="统计分类" prop="statisticsCategory">
  50 + <el-input v-model="dataForm.statisticsCategory" placeholder="请输入统计分类" clearable
  51 + :style='{"width":"100%"}' />
  52 + </el-form-item>
  53 + </el-col>
  54 + <el-col :span="12">
  55 + <el-form-item label="归属仓库" prop="warehouse">
  56 + <el-input v-model="dataForm.warehouse" placeholder="请输入归属仓库" clearable
  57 + :style='{"width":"100%"}' />
  58 + </el-form-item>
  59 + </el-col>
  60 + <el-col :span="12">
  61 + <el-form-item label="供应商名称" prop="supplierName">
  62 + <el-input v-model="dataForm.supplierName" placeholder="请输入供应商名称" clearable
  63 + :style='{"width":"100%"}' />
  64 + </el-form-item>
  65 + </el-col>
  66 + <el-col :span="12">
  67 + <el-form-item label="合同签订日期" prop="contractSignDate">
  68 + <el-date-picker v-model="dataForm.contractSignDate" type="date" placeholder="请选择合同签订日期"
  69 + value-format="yyyy-MM-dd" format="yyyy-MM-dd" :style='{"width":"100%"}' />
  70 + </el-form-item>
  71 + </el-col>
  72 + <el-col :span="12">
  73 + <el-form-item label="合同结束日期" prop="contractEndDate">
  74 + <el-date-picker v-model="dataForm.contractEndDate" type="date" placeholder="请选择合同结束日期"
  75 + value-format="yyyy-MM-dd" format="yyyy-MM-dd" :style='{"width":"100%"}' />
  76 + </el-form-item>
  77 + </el-col>
  78 + <el-col :span="24">
  79 + <el-form-item label="备注" prop="remark">
  80 + <el-input v-model="dataForm.remark" type="textarea" :rows="3" placeholder="请输入备注信息"
  81 + clearable :style='{"width":"100%"}' />
  82 + </el-form-item>
  83 + </el-col>
  84 + </el-row>
  85 + </el-form>
  86 + <span slot="footer" class="dialog-footer">
  87 + <el-button @click="visible = false">取 消</el-button>
  88 + <el-button type="primary" @click="dataFormSubmit()" :loading="btnLoading">确 定</el-button>
  89 + </span>
  90 + </el-dialog>
  91 +</template>
  92 +
  93 +<script>
  94 +import request from '@/utils/request'
  95 +
  96 +export default {
  97 + data() {
  98 + return {
  99 + visible: false,
  100 + btnLoading: false,
  101 + departmentList: [], // 部门列表
  102 + dataForm: {
  103 + id: undefined,
  104 + productName: '',
  105 + price: 0,
  106 + productCategory: '',
  107 + departmentId: '',
  108 + standardUnit: '',
  109 + onShelfStatus: 1,
  110 + statisticsCategory: '',
  111 + warehouse: '',
  112 + supplierName: '',
  113 + contractSignDate: '',
  114 + contractEndDate: '',
  115 + remark: ''
  116 + },
  117 + rules: {
  118 + productName: [
  119 + { required: true, message: '请输入产品名称', trigger: 'blur' }
  120 + ],
  121 + price: [
  122 + { required: true, message: '请输入价格', trigger: 'blur' }
  123 + ],
  124 + onShelfStatus: [
  125 + { required: true, message: '请选择上架状态', trigger: 'change' }
  126 + ]
  127 + }
  128 + }
  129 + },
  130 + methods: {
  131 + init(id) {
  132 + this.visible = true
  133 + this.dataForm = {
  134 + id: undefined,
  135 + productName: '',
  136 + price: 0,
  137 + productCategory: '',
  138 + departmentId: '',
  139 + standardUnit: '',
  140 + onShelfStatus: 1,
  141 + statisticsCategory: '',
  142 + warehouse: '',
  143 + supplierName: '',
  144 + contractSignDate: '',
  145 + contractEndDate: '',
  146 + remark: ''
  147 + }
  148 + this.getDepartmentList()
  149 + if (id) {
  150 + // 编辑模式,获取产品详情
  151 + this.getProductDetail(id)
  152 + }
  153 + },
  154 + // 获取部门列表
  155 + getDepartmentList() {
  156 + request({
  157 + url: `/api/permission/Organize/96240625-934F-490B-8AA6-0BC775B18468/Department`,
  158 + method: 'GET',
  159 + }).then((res) => {
  160 + if (res.code == 200 && res.data.list && res.data.list.length > 0) {
  161 + this.departmentList = res.data.list.map(item => ({
  162 + id: item.id,
  163 + name: item.fullName,
  164 + }))
  165 + } else {
  166 + this.departmentList = []
  167 + }
  168 + }).catch(() => {
  169 + this.departmentList = []
  170 + })
  171 + },
  172 + getProductDetail(id) {
  173 + request({
  174 + url: `/api/Extend/LqProduct/GetList`,
  175 + method: 'GET',
  176 + data: {
  177 + currentPage: 1,
  178 + pageSize: 1,
  179 + id: id
  180 + }
  181 + }).then(res => {
  182 + if (res.code == 200 && res.data && res.data.list && res.data.list.length > 0) {
  183 + const product = res.data.list[0]
  184 + // 处理日期格式:从ISO格式转换为 yyyy-MM-dd 格式
  185 + let contractSignDate = product.contractSignDate || ''
  186 + let contractEndDate = product.contractEndDate || ''
  187 + if (contractSignDate && contractSignDate.includes('T')) {
  188 + const date = new Date(contractSignDate)
  189 + const year = date.getFullYear()
  190 + const month = String(date.getMonth() + 1).padStart(2, '0')
  191 + const day = String(date.getDate()).padStart(2, '0')
  192 + contractSignDate = `${year}-${month}-${day}`
  193 + }
  194 + if (contractEndDate && contractEndDate.includes('T')) {
  195 + const date = new Date(contractEndDate)
  196 + const year = date.getFullYear()
  197 + const month = String(date.getMonth() + 1).padStart(2, '0')
  198 + const day = String(date.getDate()).padStart(2, '0')
  199 + contractEndDate = `${year}-${month}-${day}`
  200 + }
  201 + this.dataForm = {
  202 + id: product.id,
  203 + productName: product.productName || '',
  204 + price: product.price || 0,
  205 + productCategory: product.productCategory || '',
  206 + departmentId: product.departmentId || '',
  207 + standardUnit: product.standardUnit || '',
  208 + onShelfStatus: product.onShelfStatus !== undefined ? product.onShelfStatus : 1,
  209 + statisticsCategory: product.statisticsCategory || '',
  210 + warehouse: product.warehouse || '',
  211 + supplierName: product.supplierName || '',
  212 + contractSignDate: contractSignDate,
  213 + contractEndDate: contractEndDate,
  214 + remark: product.remark || ''
  215 + }
  216 + }
  217 + }).catch(() => {
  218 + this.$message({
  219 + type: 'error',
  220 + message: '获取产品详情失败'
  221 + })
  222 + })
  223 + },
  224 + dataFormSubmit() {
  225 + this.$refs.elForm.validate((valid) => {
  226 + if (!valid) {
  227 + return false
  228 + }
  229 + this.btnLoading = true
  230 + const url = this.dataForm.id ? '/api/Extend/LqProduct/Update' : '/api/Extend/LqProduct/Create'
  231 + const method = this.dataForm.id ? 'PUT' : 'POST'
  232 + // 处理日期格式,确保提交ISO格式
  233 + let contractSignDate = this.dataForm.contractSignDate
  234 + let contractEndDate = this.dataForm.contractEndDate
  235 + if (contractSignDate && !contractSignDate.includes('T')) {
  236 + const date = new Date(contractSignDate)
  237 + contractSignDate = date.toISOString()
  238 + }
  239 + if (contractEndDate && !contractEndDate.includes('T')) {
  240 + const date = new Date(contractEndDate)
  241 + contractEndDate = date.toISOString()
  242 + }
  243 + request({
  244 + url: url,
  245 + method: method,
  246 + data: {
  247 + id: this.dataForm.id,
  248 + productName: this.dataForm.productName,
  249 + price: this.dataForm.price,
  250 + productCategory: this.dataForm.productCategory,
  251 + departmentId: this.dataForm.departmentId,
  252 + standardUnit: this.dataForm.standardUnit,
  253 + onShelfStatus: this.dataForm.onShelfStatus,
  254 + statisticsCategory: this.dataForm.statisticsCategory,
  255 + warehouse: this.dataForm.warehouse,
  256 + supplierName: this.dataForm.supplierName,
  257 + contractSignDate: contractSignDate,
  258 + contractEndDate: contractEndDate,
  259 + remark: this.dataForm.remark
  260 + }
  261 + }).then(res => {
  262 + this.btnLoading = false
  263 + this.$message({
  264 + type: 'success',
  265 + message: res.msg || (this.dataForm.id ? '编辑成功' : '创建成功'),
  266 + onClose: () => {
  267 + this.visible = false
  268 + this.$emit('refresh')
  269 + }
  270 + })
  271 + }).catch(() => {
  272 + this.btnLoading = false
  273 + })
  274 + })
  275 + }
  276 + }
  277 +}
  278 +</script>
  279 +
  280 +<style lang="scss" scoped>
  281 +</style>
  282 +
... ...
antis-ncc-admin/src/views/lqInventory/usage-form.vue 0 → 100644
  1 +<template>
  2 + <el-dialog title="批量添加使用记录" :close-on-click-modal="false" :visible.sync="visible"
  3 + class="NCC-dialog NCC-dialog_center" lock-scroll width="900px">
  4 + <el-form ref="elForm" :model="dataForm" size="small" label-width="80px" label-position="right" :rules="rules">
  5 + <div class="usage-items-container">
  6 + <div v-for="(item, index) in dataForm.usageItems" :key="index" class="usage-item-row">
  7 + <el-row :gutter="15">
  8 + <el-col :span="12">
  9 + <el-form-item label="产品" :prop="`usageItems.${index}.productId`"
  10 + :rules="rules.productId">
  11 + <el-select v-model="item.productId" placeholder="请选择产品" clearable filterable
  12 + :style='{"width":"100%"}' :disabled="!!defaultProductId">
  13 + <el-option v-for="product in productOptions" :key="product.id"
  14 + :label="product.productName" :value="product.id" />
  15 + </el-select>
  16 + </el-form-item>
  17 + </el-col>
  18 + <el-col :span="12">
  19 + <el-form-item label="门店" :prop="`usageItems.${index}.storeId`"
  20 + :rules="rules.storeId">
  21 + <el-select v-model="item.storeId" placeholder="请选择门店" clearable filterable
  22 + :style='{"width":"100%"}'>
  23 + <el-option v-for="store in storeOptions" :key="store.id" :label="store.dm"
  24 + :value="store.id" />
  25 + </el-select>
  26 + </el-form-item>
  27 + </el-col>
  28 + <el-col :span="12">
  29 + <el-form-item label="使用数量"
  30 + :prop="`usageItems.${index}.usageQuantity`" :rules="rules.usageQuantity">
  31 + <el-input-number v-model="item.usageQuantity" placeholder="数量" :min="1" :precision="0"
  32 + :style='{"width":"100%"}' />
  33 + </el-form-item>
  34 + </el-col>
  35 + <el-col :span="12">
  36 + <el-form-item label="使用时间"
  37 + :prop="`usageItems.${index}.usageTime`" :rules="rules.usageTime">
  38 + <el-date-picker v-model="item.usageTime" type="datetime" placeholder="使用时间"
  39 + value-format="yyyy-MM-dd HH:mm:ss" format="yyyy-MM-dd HH:mm:ss"
  40 + :style='{"width":"100%"}' />
  41 + </el-form-item>
  42 + </el-col>
  43 + <!-- <el-col :span="24">
  44 + <el-form-item :label="index === 0 ? '关联消费ID' : ''"
  45 + :prop="`usageItems.${index}.relatedConsumeId`">
  46 + <el-input v-model="item.relatedConsumeId" placeholder="请输入关联消费ID(可选)" clearable
  47 + :style='{"width":"100%"}' />
  48 + </el-form-item>
  49 + </el-col> -->
  50 + <el-col :span="24" v-if="dataForm.usageItems.length > 1">
  51 + <div class="item-actions">
  52 + <el-button type="text" icon="el-icon-delete" @click="removeItem(index)"
  53 + class="delete-btn">删除</el-button>
  54 + </div>
  55 + </el-col>
  56 + </el-row>
  57 + <el-divider v-if="index < dataForm.usageItems.length - 1"></el-divider>
  58 + </div>
  59 + </div>
  60 + <div class="add-item-btn">
  61 + <el-button type="text" icon="el-icon-plus" @click="addItem()">添加一条记录</el-button>
  62 + </div>
  63 + </el-form>
  64 + <span slot="footer" class="dialog-footer">
  65 + <el-button @click="visible = false">取 消</el-button>
  66 + <el-button type="primary" @click="dataFormSubmit()" :loading="btnLoading">确 定</el-button>
  67 + </span>
  68 + </el-dialog>
  69 +</template>
  70 +
  71 +<script>
  72 +import request from '@/utils/request'
  73 +
  74 +export default {
  75 + data() {
  76 + return {
  77 + visible: false,
  78 + btnLoading: false,
  79 + defaultProductId: '',
  80 + dataForm: {
  81 + usageItems: [
  82 + {
  83 + productId: '',
  84 + storeId: '',
  85 + usageTime: '',
  86 + usageQuantity: 1,
  87 + relatedConsumeId: ''
  88 + }
  89 + ]
  90 + },
  91 + productOptions: [],
  92 + storeOptions: [],
  93 + rules: {
  94 + productId: [
  95 + { required: true, message: '请选择产品', trigger: 'change' }
  96 + ],
  97 + storeId: [
  98 + { required: true, message: '请选择门店', trigger: 'change' }
  99 + ],
  100 + usageQuantity: [
  101 + { required: true, message: '请输入使用数量', trigger: 'blur' }
  102 + ],
  103 + usageTime: [
  104 + { required: true, message: '请选择使用时间', trigger: 'change' }
  105 + ]
  106 + }
  107 + }
  108 + },
  109 + methods: {
  110 + init(productId) {
  111 + this.visible = true
  112 + this.defaultProductId = productId || ''
  113 + this.dataForm = {
  114 + usageItems: [
  115 + {
  116 + productId: productId || '',
  117 + storeId: '',
  118 + usageTime: '',
  119 + usageQuantity: 1,
  120 + relatedConsumeId: ''
  121 + }
  122 + ]
  123 + }
  124 + this.initProductOptions()
  125 + this.initStoreOptions()
  126 + },
  127 + initProductOptions() {
  128 + request({
  129 + url: '/api/Extend/LqProduct/GetList',
  130 + method: 'GET',
  131 + data: {
  132 + currentPage: 1,
  133 + pageSize: 1000
  134 + }
  135 + }).then(res => {
  136 + if (res.code == 200 && res.data && res.data.list) {
  137 + this.productOptions = res.data.list
  138 + }
  139 + }).catch(() => {
  140 + this.productOptions = []
  141 + })
  142 + },
  143 + initStoreOptions() {
  144 + request({
  145 + url: '/api/Extend/LqMdxx',
  146 + method: 'GET',
  147 + data: {
  148 + currentPage: 1,
  149 + pageSize: 1000
  150 + }
  151 + }).then(res => {
  152 + if (res.data && res.data.list) {
  153 + this.storeOptions = res.data.list
  154 + }
  155 + }).catch(() => {
  156 + this.storeOptions = []
  157 + })
  158 + },
  159 + addItem() {
  160 + this.dataForm.usageItems.push({
  161 + productId: this.defaultProductId || '',
  162 + storeId: '',
  163 + usageTime: '',
  164 + usageQuantity: 1,
  165 + relatedConsumeId: ''
  166 + })
  167 + },
  168 + removeItem(index) {
  169 + if (this.dataForm.usageItems.length > 1) {
  170 + this.dataForm.usageItems.splice(index, 1)
  171 + } else {
  172 + this.$message({
  173 + type: 'warning',
  174 + message: '至少保留一条记录'
  175 + })
  176 + }
  177 + },
  178 + dataFormSubmit() {
  179 + // 验证所有表单项
  180 + let isValid = true
  181 + this.dataForm.usageItems.forEach((item, index) => {
  182 + if (!item.productId) {
  183 + this.$message({
  184 + type: 'error',
  185 + message: `第${index + 1}条记录:请选择产品`
  186 + })
  187 + isValid = false
  188 + }
  189 + if (!item.storeId) {
  190 + this.$message({
  191 + type: 'error',
  192 + message: `第${index + 1}条记录:请选择门店`
  193 + })
  194 + isValid = false
  195 + }
  196 + if (!item.usageQuantity || item.usageQuantity <= 0) {
  197 + this.$message({
  198 + type: 'error',
  199 + message: `第${index + 1}条记录:请输入使用数量`
  200 + })
  201 + isValid = false
  202 + }
  203 + if (!item.usageTime) {
  204 + this.$message({
  205 + type: 'error',
  206 + message: `第${index + 1}条记录:请选择使用时间`
  207 + })
  208 + isValid = false
  209 + }
  210 + })
  211 +
  212 + if (!isValid) {
  213 + return false
  214 + }
  215 +
  216 + this.btnLoading = true
  217 + // 转换日期时间为ISO格式
  218 + const usageItems = this.dataForm.usageItems.map(item => {
  219 + let usageTime = item.usageTime
  220 + // 如果日期时间不是ISO格式,转换为ISO格式
  221 + if (usageTime && !usageTime.includes('T')) {
  222 + const date = new Date(usageTime)
  223 + usageTime = date.toISOString()
  224 + }
  225 + return {
  226 + productId: item.productId,
  227 + storeId: item.storeId,
  228 + usageTime: usageTime,
  229 + usageQuantity: item.usageQuantity,
  230 + relatedConsumeId: item.relatedConsumeId || ''
  231 + }
  232 + })
  233 + request({
  234 + url: '/api/Extend/LqInventoryUsage/BatchCreate',
  235 + method: 'POST',
  236 + data: {
  237 + usageItems: usageItems
  238 + }
  239 + }).then(res => {
  240 + this.btnLoading = false
  241 + this.$message({
  242 + type: 'success',
  243 + message: res.msg || '添加成功',
  244 + onClose: () => {
  245 + this.visible = false
  246 + this.$emit('refresh')
  247 + }
  248 + })
  249 + }).catch(() => {
  250 + this.btnLoading = false
  251 + })
  252 + }
  253 + }
  254 +}
  255 +</script>
  256 +
  257 +<style lang="scss" scoped>
  258 +.usage-items-container {
  259 + // max-height: 500px;
  260 + // overflow-y: scroll;
  261 + padding: 10px 0;
  262 +}
  263 +
  264 +.usage-item-row {
  265 + margin-bottom: 10px;
  266 +}
  267 +
  268 +.item-actions {
  269 + display: flex;
  270 + justify-content: flex-end;
  271 + margin-top: 10px;
  272 +
  273 + .delete-btn {
  274 + color: #F56C6C;
  275 +
  276 + &:hover {
  277 + color: #f78989;
  278 + }
  279 + }
  280 +}
  281 +
  282 +.add-item-btn {
  283 + margin-top: 10px;
  284 + text-align: center;
  285 + padding: 10px 0;
  286 + border-top: 1px dashed #dcdfe6;
  287 +}
  288 +</style>
  289 +
... ...
antis-ncc-admin/src/views/lqInventory/usage-multi-form.vue 0 → 100644
  1 +<template>
  2 + <el-dialog title="添加使用记录" :close-on-click-modal="false" :visible.sync="visible"
  3 + class="NCC-dialog NCC-dialog_center" lock-scroll width="900px">
  4 + <el-form ref="elForm" :model="dataForm" size="small" label-width="80px" label-position="right" :rules="rules">
  5 + <div class="usage-items-container">
  6 + <div v-for="(item, index) in dataForm.usageItems" :key="index" class="usage-item-row">
  7 + <el-row :gutter="15">
  8 + <el-col :span="12">
  9 + <el-form-item label="产品" :prop="`usageItems.${index}.productId`"
  10 + :rules="rules.productId">
  11 + <el-select v-model="item.productId" placeholder="请选择产品" clearable filterable
  12 + :style='{"width":"100%"}' @change="onProductChange(item, index)">
  13 + <el-option v-for="product in productOptions" :key="product.id"
  14 + :label="product.productName" :value="product.id" />
  15 + </el-select>
  16 + </el-form-item>
  17 + </el-col>
  18 + <el-col :span="12">
  19 + <el-form-item label="门店" :prop="`usageItems.${index}.storeId`"
  20 + :rules="rules.storeId">
  21 + <el-select v-model="item.storeId" placeholder="请选择门店" clearable filterable
  22 + :style='{"width":"100%"}'>
  23 + <el-option v-for="store in storeOptions" :key="store.id" :label="store.dm"
  24 + :value="store.id" />
  25 + </el-select>
  26 + </el-form-item>
  27 + </el-col>
  28 + <el-col :span="12">
  29 + <el-form-item label="使用数量"
  30 + :prop="`usageItems.${index}.usageQuantity`" :rules="rules.usageQuantity">
  31 + <el-input-number v-model="item.usageQuantity" placeholder="数量" :min="1" :precision="0"
  32 + :style='{"width":"100%"}' />
  33 + </el-form-item>
  34 + </el-col>
  35 + <el-col :span="12">
  36 + <el-form-item label="使用时间"
  37 + :prop="`usageItems.${index}.usageTime`" :rules="rules.usageTime">
  38 + <el-date-picker v-model="item.usageTime" type="datetime" placeholder="使用时间"
  39 + value-format="yyyy-MM-dd HH:mm:ss" format="yyyy-MM-dd HH:mm:ss"
  40 + :style='{"width":"100%"}' />
  41 + </el-form-item>
  42 + </el-col>
  43 + <el-col :span="24" v-if="dataForm.usageItems.length > 1">
  44 + <div class="item-actions">
  45 + <el-button type="text" icon="el-icon-delete" @click="removeItem(index)"
  46 + class="delete-btn">删除</el-button>
  47 + </div>
  48 + </el-col>
  49 + </el-row>
  50 + <el-divider v-if="index < dataForm.usageItems.length - 1"></el-divider>
  51 + </div>
  52 + </div>
  53 + <div class="add-item-btn">
  54 + <el-button type="text" icon="el-icon-plus" @click="addItem()">添加一条记录</el-button>
  55 + </div>
  56 + </el-form>
  57 + <span slot="footer" class="dialog-footer">
  58 + <el-button @click="visible = false">取 消</el-button>
  59 + <el-button type="primary" @click="dataFormSubmit()" :loading="btnLoading">确 定</el-button>
  60 + </span>
  61 + </el-dialog>
  62 +</template>
  63 +
  64 +<script>
  65 +import request from '@/utils/request'
  66 +
  67 +export default {
  68 + data() {
  69 + return {
  70 + visible: false,
  71 + btnLoading: false,
  72 + dataForm: {
  73 + usageItems: [
  74 + {
  75 + productId: '',
  76 + storeId: '',
  77 + usageTime: '',
  78 + usageQuantity: 1,
  79 + relatedConsumeId: ''
  80 + }
  81 + ]
  82 + },
  83 + productOptions: [],
  84 + storeOptions: [],
  85 + rules: {
  86 + productId: [
  87 + { required: true, message: '请选择产品', trigger: 'change' }
  88 + ],
  89 + storeId: [
  90 + { required: true, message: '请选择门店', trigger: 'change' }
  91 + ],
  92 + usageQuantity: [
  93 + { required: true, message: '请输入使用数量', trigger: 'blur' }
  94 + ],
  95 + usageTime: [
  96 + { required: true, message: '请选择使用时间', trigger: 'change' }
  97 + ]
  98 + }
  99 + }
  100 + },
  101 + methods: {
  102 + init() {
  103 + this.visible = true
  104 + this.dataForm = {
  105 + usageItems: [
  106 + {
  107 + productId: '',
  108 + storeId: '',
  109 + usageTime: '',
  110 + usageQuantity: 1,
  111 + relatedConsumeId: ''
  112 + }
  113 + ]
  114 + }
  115 + this.initProductOptions()
  116 + this.initStoreOptions()
  117 + },
  118 + initProductOptions() {
  119 + request({
  120 + url: '/api/Extend/LqProduct/GetList',
  121 + method: 'GET',
  122 + data: {
  123 + currentPage: 1,
  124 + pageSize: 1000,
  125 + onShelfStatus: 1 // 只获取上架的产品
  126 + }
  127 + }).then(res => {
  128 + if (res.code == 200 && res.data && res.data.list) {
  129 + // 过滤出上架的产品
  130 + this.productOptions = res.data.list.filter(product => product.onShelfStatus === 1)
  131 + }
  132 + }).catch(() => {
  133 + this.productOptions = []
  134 + })
  135 + },
  136 + initStoreOptions() {
  137 + request({
  138 + url: '/api/Extend/LqMdxx',
  139 + method: 'GET',
  140 + data: {
  141 + currentPage: 1,
  142 + pageSize: 1000
  143 + }
  144 + }).then(res => {
  145 + if (res.data && res.data.list) {
  146 + this.storeOptions = res.data.list
  147 + }
  148 + }).catch(() => {
  149 + this.storeOptions = []
  150 + })
  151 + },
  152 + onProductChange(item, index) {
  153 + // 产品选择变化时的处理
  154 + },
  155 + addItem() {
  156 + this.dataForm.usageItems.push({
  157 + productId: '',
  158 + storeId: '',
  159 + usageTime: '',
  160 + usageQuantity: 1,
  161 + relatedConsumeId: ''
  162 + })
  163 + },
  164 + removeItem(index) {
  165 + if (this.dataForm.usageItems.length > 1) {
  166 + this.dataForm.usageItems.splice(index, 1)
  167 + } else {
  168 + this.$message({
  169 + type: 'warning',
  170 + message: '至少保留一条记录'
  171 + })
  172 + }
  173 + },
  174 + dataFormSubmit() {
  175 + // 验证所有表单项
  176 + let isValid = true
  177 + this.dataForm.usageItems.forEach((item, index) => {
  178 + if (!item.productId) {
  179 + this.$message({
  180 + type: 'error',
  181 + message: `第${index + 1}条记录:请选择产品`
  182 + })
  183 + isValid = false
  184 + }
  185 + if (!item.storeId) {
  186 + this.$message({
  187 + type: 'error',
  188 + message: `第${index + 1}条记录:请选择门店`
  189 + })
  190 + isValid = false
  191 + }
  192 + if (!item.usageQuantity || item.usageQuantity <= 0) {
  193 + this.$message({
  194 + type: 'error',
  195 + message: `第${index + 1}条记录:请输入使用数量`
  196 + })
  197 + isValid = false
  198 + }
  199 + if (!item.usageTime) {
  200 + this.$message({
  201 + type: 'error',
  202 + message: `第${index + 1}条记录:请选择使用时间`
  203 + })
  204 + isValid = false
  205 + }
  206 + })
  207 +
  208 + if (!isValid) {
  209 + return false
  210 + }
  211 +
  212 + this.btnLoading = true
  213 + // 转换日期时间为ISO格式
  214 + const usageItems = this.dataForm.usageItems.map(item => {
  215 + let usageTime = item.usageTime
  216 + // 如果日期时间不是ISO格式,转换为ISO格式
  217 + if (usageTime && !usageTime.includes('T')) {
  218 + const date = new Date(usageTime)
  219 + usageTime = date.toISOString()
  220 + }
  221 + return {
  222 + productId: item.productId,
  223 + storeId: item.storeId,
  224 + usageTime: usageTime,
  225 + usageQuantity: item.usageQuantity,
  226 + relatedConsumeId: item.relatedConsumeId || ''
  227 + }
  228 + })
  229 + request({
  230 + url: '/api/Extend/LqInventoryUsage/BatchCreate',
  231 + method: 'POST',
  232 + data: {
  233 + usageItems: usageItems
  234 + }
  235 + }).then(res => {
  236 + this.btnLoading = false
  237 + this.$message({
  238 + type: 'success',
  239 + message: res.msg || '添加成功',
  240 + onClose: () => {
  241 + this.visible = false
  242 + this.$emit('refresh')
  243 + }
  244 + })
  245 + }).catch(() => {
  246 + this.btnLoading = false
  247 + })
  248 + }
  249 + }
  250 +}
  251 +</script>
  252 +
  253 +<style lang="scss" scoped>
  254 +.usage-items-container {
  255 + padding: 10px 0;
  256 +}
  257 +
  258 +.usage-item-row {
  259 + margin-bottom: 10px;
  260 +}
  261 +
  262 +.item-actions {
  263 + display: flex;
  264 + justify-content: flex-end;
  265 + margin-top: 10px;
  266 +
  267 + .delete-btn {
  268 + color: #F56C6C;
  269 +
  270 + &:hover {
  271 + color: #f78989;
  272 + }
  273 + }
  274 +}
  275 +
  276 +.add-item-btn {
  277 + margin-top: 10px;
  278 + text-align: center;
  279 + padding: 10px 0;
  280 + border-top: 1px dashed #dcdfe6;
  281 +}
  282 +</style>
  283 +
... ...
antis-ncc-admin/src/views/statisticsList/form1.vue
... ... @@ -19,9 +19,7 @@
19 19 value-format="timestamp"
20 20 format="yyyy-MM-dd HH:mm:ss"
21 21 placeholder="开始时间"
22   - >
23   - </el-date-picker>
24   - <!-- :picker-options="startTimePickerOptions" -->
  22 + />
25 23 </el-form-item>
26 24 </el-col>
27 25 <el-col :span="6">
... ... @@ -32,15 +30,14 @@
32 30 value-format="timestamp"
33 31 format="yyyy-MM-dd HH:mm:ss"
34 32 placeholder="结束时间"
35   - >
36   - <!-- :picker-options="endTimePickerOptions" -->
37   - </el-date-picker>
  33 + />
38 34 </el-form-item>
39 35 </el-col>
40 36 <el-col :span="6">
41 37 <el-form-item>
42 38 <el-button type="primary" icon="el-icon-search" @click="search()">查询</el-button>
43 39 <el-button icon="el-icon-refresh-right" @click="reset()">重置</el-button>
  40 + <el-button icon="el-icon-setting" @click="showColumnDialog = true">字段设置</el-button>
44 41 </el-form-item>
45 42 </el-col>
46 43 </el-form>
... ... @@ -143,109 +140,128 @@
143 140 </div>
144 141  
145 142 <!-- 数据表格 -->
146   - <div class="NCC-common-layout-main NCC-flex-main">
147   - <!-- <div class="NCC-common-head">
148   - <div>
149   - <el-button type="text" icon="el-icon-download" @click="exportData()">导出</el-button>
150   - </div>
151   - <div class="NCC-common-head-right">
152   - <el-tooltip effect="dark" content="刷新" placement="top">
153   - <el-link icon="icon-ym icon-ym-Refresh NCC-common-head-icon" :underline="false" @click="reset()" />
154   - </el-tooltip>
155   - <screenfull isContainer />
156   - </div>
157   - </div> -->
158   -
159   - <NCC-table v-loading="listLoading" :data="records" has-c>
160   - <el-table-column prop="date" label="开单日期" align="left" width="120">
  143 + <div class="NCC-common-layout-main NCC-flex-main table-wrapper">
  144 + <el-table
  145 + v-loading="listLoading"
  146 + :data="records"
  147 + border
  148 + class="NCC-common-table"
  149 + :height="tableHeight"
  150 + @sort-change="handleSortChange">
  151 + <el-table-column
  152 + v-for="col in visibleColumns"
  153 + :key="col.prop"
  154 + :prop="col.prop"
  155 + :label="col.label"
  156 + :width="col.width"
  157 + :min-width="col.minWidth"
  158 + :sortable="col.sortable ? 'custom' : false"
  159 + :align="col.align || 'left'">
161 160 <template slot-scope="scope">
162   - {{ formatDate(scope.row.date) }}
163   - </template>
164   - </el-table-column>
165   - <el-table-column prop="customerName" label="客户姓名" align="left" width="100" />
166   - <el-table-column prop="customerPhone" label="客户电话" align="left" width="120" />
167   - <el-table-column prop="goldTriangle" label="金三角" align="left" width="100" />
168   - <el-table-column prop="customerType" label="客户类型" align="left" width="100">
169   - <template slot-scope="scope">
170   - <el-tag :type="scope.row.customerType === '会员' ? 'success' : 'info'" size="small">
  161 + <!-- 开单日期 -->
  162 + <span v-if="col.prop === 'date'">
  163 + {{ formatDate(scope.row.date) }}
  164 + </span>
  165 + <!-- 客户类型 -->
  166 + <el-tag v-else-if="col.prop === 'customerType'" :type="scope.row.customerType === '会员' ? 'success' : 'info'" size="small">
171 167 {{ scope.row.customerType }}
172 168 </el-tag>
173   - </template>
174   - </el-table-column>
175   - <el-table-column label="购买项目" align="left" min-width="200">
176   - <template slot-scope="scope">
177   - <div v-if="scope.row.purchasedItems && scope.row.purchasedItems.length > 0">
178   - <div v-for="item in scope.row.purchasedItems" :key="item.id" class="item-row">
179   - <span class="item-name">{{ item.itemName }}</span>
180   - <span class="item-info">({{ item.projectNumber }}次 × ¥{{ item.price }})</span>
  169 + <!-- 购买项目 -->
  170 + <div v-else-if="col.prop === 'purchasedItems'">
  171 + <div v-if="scope.row.purchasedItems && scope.row.purchasedItems.length > 0">
  172 + <div v-for="(item, itemIndex) in scope.row.purchasedItems" :key="`purchased-${scope.$index}-${itemIndex}-${item.id || itemIndex}`" class="item-row">
  173 + <span class="item-name">{{ item.itemName }}</span>
  174 + <span class="item-info">({{ item.projectNumber }}次 × ¥{{ item.price }})</span>
  175 + </div>
181 176 </div>
  177 + <span v-else class="no-data">无</span>
182 178 </div>
183   - <span v-else class="no-data">无</span>
184   - </template>
185   - </el-table-column>
186   - <el-table-column label="赠送项目" align="left" min-width="200">
187   - <template slot-scope="scope">
188   - <div v-if="scope.row.giftedItems && scope.row.giftedItems.length > 0">
189   - <div v-for="item in scope.row.giftedItems" :key="item.id" class="item-row">
190   - <span class="item-name">{{ item.itemName }}</span>
191   - <span class="item-info">({{ item.projectNumber }}次)</span>
192   - <span v-if="item.remark" class="item-remark">{{ item.remark }}</span>
  179 + <!-- 赠送项目 -->
  180 + <div v-else-if="col.prop === 'giftedItems'">
  181 + <div v-if="scope.row.giftedItems && scope.row.giftedItems.length > 0">
  182 + <div v-for="(item, itemIndex) in scope.row.giftedItems" :key="`gifted-${scope.$index}-${itemIndex}-${item.id || itemIndex}`" class="item-row">
  183 + <span class="item-name">{{ item.itemName }}</span>
  184 + <span class="item-info">({{ item.projectNumber }}次)</span>
  185 + <span v-if="item.remark" class="item-remark">{{ item.remark }}</span>
  186 + </div>
193 187 </div>
  188 + <span v-else class="no-data">无</span>
194 189 </div>
195   - <span v-else class="no-data">无</span>
196   - </template>
197   - </el-table-column>
198   - <el-table-column label="体验项目" align="left" min-width="200">
199   - <template slot-scope="scope">
200   - <div v-if="scope.row.experienceItems && scope.row.experienceItems.length > 0">
201   - <div v-for="item in scope.row.experienceItems" :key="item.id" class="item-row">
202   - <span class="item-name">{{ item.itemName }}</span>
203   - <span class="item-info">({{ item.projectNumber }}次)</span>
  190 + <!-- 体验项目 -->
  191 + <div v-else-if="col.prop === 'experienceItems'">
  192 + <div v-if="scope.row.experienceItems && scope.row.experienceItems.length > 0">
  193 + <div v-for="(item, itemIndex) in scope.row.experienceItems" :key="`experience-${scope.$index}-${itemIndex}-${item.id || itemIndex}`" class="item-row">
  194 + <span class="item-name">{{ item.itemName }}</span>
  195 + <span class="item-info">({{ item.projectNumber }}次)</span>
  196 + </div>
204 197 </div>
  198 + <span v-else class="no-data">无</span>
205 199 </div>
206   - <span v-else class="no-data">无</span>
207   - </template>
208   - </el-table-column>
209   - <el-table-column label="健康师" align="left" min-width="150">
210   - <template slot-scope="scope">
211   - <div v-if="scope.row.healthTeachers && scope.row.healthTeachers.length > 0">
212   - <div v-for="teacher in scope.row.healthTeachers" :key="teacher.teacherId" class="teacher-row">
213   - <span class="teacher-name">{{ teacher.teacherName }}</span>
214   - <span class="teacher-performance">业绩: ¥{{ teacher.performance }}</span>
  200 + <!-- 健康师 -->
  201 + <div v-else-if="col.prop === 'healthTeachers'">
  202 + <div v-if="scope.row.healthTeachers && scope.row.healthTeachers.length > 0">
  203 + <div v-for="(teacher, teacherIndex) in scope.row.healthTeachers" :key="`teacher-${scope.$index}-${teacherIndex}-${teacher.teacherId || teacher.teacherName || teacherIndex}`" class="teacher-row">
  204 + <span class="teacher-name">{{ teacher.teacherName }}</span>
  205 + <span class="teacher-performance">业绩: ¥{{ teacher.performance }}</span>
  206 + </div>
215 207 </div>
  208 + <span v-else class="no-data">无</span>
216 209 </div>
217   - <span v-else class="no-data">无</span>
218   - </template>
219   - </el-table-column>
220   - <el-table-column prop="paidAmount" label="已付金额" align="left" width="100">
221   - <template slot-scope="scope">
222   - <span class="amount-paid">¥{{ formatMoney(scope.row.paidAmount) }}</span>
223   - </template>
224   - </el-table-column>
225   - <el-table-column prop="debtAmount" label="欠款金额" align="left" width="100">
226   - <template slot-scope="scope">
227   - <span class="amount-debt">¥{{ formatMoney(scope.row.debtAmount) }}</span>
228   - </template>
229   - </el-table-column>
230   - <el-table-column prop="totalAmount" label="总金额" align="left" width="100">
231   - <template slot-scope="scope">
232   - <span class="amount-total">¥{{ formatMoney(scope.row.totalAmount) }}</span>
233   - </template>
234   - </el-table-column>
235   - <el-table-column prop="paymentMethod" label="支付方式" align="left" width="100" />
236   - <el-table-column prop="remark" label="备注" align="left" min-width="150">
237   - <template slot-scope="scope">
238   - {{ scope.row.remark || '无' }}
239   - </template>
240   - </el-table-column>
241   - <el-table-column label="开单时间" prop="createTime" align="left" width="150">
242   - <template slot-scope="scope">
243   - {{ formatTime(scope.row.createTime) }}
  210 + <!-- 已付金额 -->
  211 + <span v-else-if="col.prop === 'paidAmount'" class="amount-paid">
  212 + ¥{{ formatMoney(scope.row.paidAmount) }}
  213 + </span>
  214 + <!-- 欠款金额 -->
  215 + <span v-else-if="col.prop === 'debtAmount'" class="amount-debt">
  216 + ¥{{ formatMoney(scope.row.debtAmount) }}
  217 + </span>
  218 + <!-- 总金额 -->
  219 + <span v-else-if="col.prop === 'totalAmount'" class="amount-total">
  220 + ¥{{ formatMoney(scope.row.totalAmount) }}
  221 + </span>
  222 + <!-- 备注 -->
  223 + <span v-else-if="col.prop === 'remark'">
  224 + {{ scope.row.remark || '无' }}
  225 + </span>
  226 + <!-- 开单时间 -->
  227 + <span v-else-if="col.prop === 'createTime'">
  228 + {{ formatTime(scope.row.createTime) }}
  229 + </span>
  230 + <!-- 默认显示 -->
  231 + <span v-else>
  232 + {{ scope.row[col.prop] !== null && scope.row[col.prop] !== undefined ? scope.row[col.prop] : '无' }}
  233 + </span>
244 234 </template>
245 235 </el-table-column>
246   - </NCC-table>
  236 + <template slot="empty">
  237 + <el-empty description="暂无数据" :image-size="120"></el-empty>
  238 + </template>
  239 + </el-table>
247 240 </div>
248 241 </div>
  242 +
  243 + <!-- 字段设置弹窗 -->
  244 + <el-dialog title="字段设置" :visible.sync="showColumnDialog" width="500px" append-to-body>
  245 + <div class="column-settings">
  246 + <div class="column-actions" style="margin-bottom: 15px;">
  247 + <el-button size="small" @click="selectAllColumns">全选</el-button>
  248 + <el-button size="small" @click="clearAllColumns">清空</el-button>
  249 + </div>
  250 + <el-checkbox-group v-model="selectedColumns">
  251 + <el-checkbox
  252 + v-for="col in columnOptions"
  253 + :key="col.prop"
  254 + :label="col.prop"
  255 + style="display: block; margin-bottom: 10px;">
  256 + {{ col.label }}
  257 + </el-checkbox>
  258 + </el-checkbox-group>
  259 + </div>
  260 + <div slot="footer" class="dialog-footer">
  261 + <el-button @click="showColumnDialog = false">取消</el-button>
  262 + <el-button type="primary" @click="saveColumnSettings">确定</el-button>
  263 + </div>
  264 + </el-dialog>
249 265 </div>
250 266 </template>
251 267  
... ... @@ -265,41 +281,47 @@ export default {
265 281 summaryData: null,
266 282 records: [],
267 283 listLoading: false,
268   - startTimePickerOptions: {
269   - disabledDate: (time) => {
270   - const timeMs = time.getTime()
271   - const nowMs = Date.now()
272   - const ninetyDaysMs = 90 * 24 * 60 * 60 * 1000
273   - // 不能选择未来日期
274   - if (timeMs > nowMs) return true
275   - // 若已选择结束时间:开始时间需在 [end-90天, end] 区间内
276   - if (this.query.endTime) {
277   - const minStart = this.query.endTime - ninetyDaysMs
278   - return timeMs < minStart || timeMs > this.query.endTime
279   - }
280   - return false
281   - }
  284 + showColumnDialog: false,
  285 + selectedColumns: [],
  286 + sortInfo: {
  287 + prop: '',
  288 + order: ''
282 289 },
283   - endTimePickerOptions: {
284   - disabledDate: (time) => {
285   - const timeMs = time.getTime()
286   - const nowMs = Date.now()
287   - const ninetyDaysMs = 90 * 24 * 60 * 60 * 1000
288   - // 不能选择未来日期
289   - if (timeMs > nowMs) return true
290   - // 若已选择开始时间:结束时间需在 [start, start+90天] 区间内
291   - if (this.query.startTime) {
292   - const maxEnd = this.query.startTime + ninetyDaysMs
293   - return timeMs < this.query.startTime || timeMs > maxEnd
294   - }
295   - return false
296   - }
297   - }
  290 + // 所有列配置
  291 + columnOptions: [
  292 + { prop: 'date', label: '开单日期', width: 120, sortable: false, align: 'left' },
  293 + { prop: 'customerName', label: '客户姓名', width: 100, sortable: false, align: 'left' },
  294 + { prop: 'customerPhone', label: '客户电话', width: 120, sortable: false, align: 'left' },
  295 + { prop: 'goldTriangle', label: '金三角', width: 100, sortable: false, align: 'left' },
  296 + { prop: 'customerType', label: '客户类型', width: 100, sortable: false, align: 'left' },
  297 + { prop: 'purchasedItems', label: '购买项目', minWidth: 200, sortable: false, align: 'left' },
  298 + { prop: 'giftedItems', label: '赠送项目', minWidth: 200, sortable: false, align: 'left' },
  299 + { prop: 'experienceItems', label: '体验项目', minWidth: 200, sortable: false, align: 'left' },
  300 + { prop: 'healthTeachers', label: '健康师', minWidth: 150, sortable: false, align: 'left' },
  301 + { prop: 'paidAmount', label: '已付金额', width: 100, sortable: true, align: 'left' },
  302 + { prop: 'debtAmount', label: '欠款金额', width: 100, sortable: true, align: 'left' },
  303 + { prop: 'totalAmount', label: '总金额', width: 100, sortable: true, align: 'left' },
  304 + { prop: 'paymentMethod', label: '支付方式', width: 100, sortable: false, align: 'left' },
  305 + { prop: 'remark', label: '备注', minWidth: 150, sortable: false, align: 'left' },
  306 + { prop: 'createTime', label: '开单时间', width: 150, sortable: true, align: 'left' }
  307 + ]
  308 + }
  309 + },
  310 + computed: {
  311 + // 可见的列
  312 + visibleColumns() {
  313 + return this.columnOptions.filter(col => this.selectedColumns.includes(col.prop))
  314 + },
  315 + // 计算表格高度
  316 + tableHeight() {
  317 + // 根据视口高度动态计算,减去筛选条件、统计卡片等的高度
  318 + return 'calc(100vh - 400px)'
298 319 }
299 320 },
300 321 created() {
301 322 this.initStoreOptions()
302 323 this.setDefaultTimeRange()
  324 + this.initColumnSettings()
303 325 },
304 326 methods: {
305 327 // 初始化门店选项
... ... @@ -382,31 +404,6 @@ export default {
382 404 this.setDefaultTimeRange()
383 405 },
384 406  
385   - // 导出数据
386   - exportData() {
387   - if (!this.query.storeId) {
388   - this.$message({
389   - type: 'warning',
390   - message: '请先选择门店并查询数据',
391   - duration: 1500
392   - })
393   - return
394   - }
395   -
396   - const params = {
397   - storeId: this.query.storeId,
398   - startTime: this.query.startTime,
399   - endTime: this.query.endTime
400   - }
401   -
402   - // 这里可以调用导出接口
403   - this.$message({
404   - type: 'info',
405   - message: '导出功能开发中...',
406   - duration: 1500
407   - })
408   - },
409   -
410 407 // 格式化金额
411 408 formatMoney(amount) {
412 409 if (!amount && amount !== 0) return '0.00'
... ... @@ -444,6 +441,96 @@ export default {
444 441 const minutes = String(date.getMinutes()).padStart(2, '0')
445 442 const seconds = String(date.getSeconds()).padStart(2, '0')
446 443 return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
  444 + },
  445 +
  446 + // 初始化列设置
  447 + initColumnSettings() {
  448 + // 默认显示所有列
  449 + this.selectedColumns = this.columnOptions.map(col => col.prop)
  450 + // 尝试从本地存储读取
  451 + const savedColumns = localStorage.getItem('form1_column_settings')
  452 + if (savedColumns) {
  453 + try {
  454 + const columns = JSON.parse(savedColumns)
  455 + if (Array.isArray(columns) && columns.length > 0) {
  456 + this.selectedColumns = columns
  457 + }
  458 + } catch (e) {
  459 + console.error('读取列设置失败:', e)
  460 + }
  461 + }
  462 + },
  463 +
  464 + // 全选列
  465 + selectAllColumns() {
  466 + this.selectedColumns = this.columnOptions.map(col => col.prop)
  467 + },
  468 +
  469 + // 清空列
  470 + clearAllColumns() {
  471 + this.selectedColumns = []
  472 + },
  473 +
  474 + // 保存列设置
  475 + saveColumnSettings() {
  476 + if (this.selectedColumns.length === 0) {
  477 + this.$message({
  478 + type: 'warning',
  479 + message: '至少需要显示一列',
  480 + duration: 1500
  481 + })
  482 + return
  483 + }
  484 + // 保存到本地存储
  485 + localStorage.setItem('form1_column_settings', JSON.stringify(this.selectedColumns))
  486 + this.showColumnDialog = false
  487 + this.$message({
  488 + type: 'success',
  489 + message: '字段设置已保存',
  490 + duration: 1500
  491 + })
  492 + },
  493 +
  494 + // 排序变化
  495 + handleSortChange({ column, prop, order }) {
  496 + this.sortInfo = {
  497 + prop: prop || '',
  498 + order: order || ''
  499 + }
  500 + this.sortList()
  501 + },
  502 +
  503 + // 排序列表
  504 + sortList() {
  505 + if (!this.sortInfo.prop || !this.sortInfo.order) {
  506 + return
  507 + }
  508 +
  509 + const prop = this.sortInfo.prop
  510 + const order = this.sortInfo.order
  511 + const isAsc = order === 'ascending'
  512 +
  513 + this.records.sort((a, b) => {
  514 + let aVal = a[prop]
  515 + let bVal = b[prop]
  516 +
  517 + // 处理空值
  518 + if (aVal === null || aVal === undefined) aVal = 0
  519 + if (bVal === null || bVal === undefined) bVal = 0
  520 +
  521 + // 转换为数字进行比较
  522 + aVal = Number(aVal)
  523 + bVal = Number(bVal)
  524 +
  525 + if (isNaN(aVal)) aVal = 0
  526 + if (isNaN(bVal)) bVal = 0
  527 +
  528 + if (isAsc) {
  529 + return aVal - bVal
  530 + } else {
  531 + return bVal - aVal
  532 + }
  533 + })
447 534 }
448 535 }
449 536 }
... ... @@ -541,6 +628,34 @@ export default {
541 628 font-weight: 500;
542 629 }
543 630  
  631 +.table-wrapper {
  632 + height: 100%;
  633 + overflow: hidden;
  634 +
  635 + ::v-deep .el-table {
  636 + width: 100%;
  637 + }
  638 +
  639 + ::v-deep .el-table__body-wrapper {
  640 + overflow-y: auto;
  641 + overflow-x: auto;
  642 + }
  643 +
  644 + ::v-deep .el-table__header-wrapper {
  645 + overflow-x: auto;
  646 + overflow-y: hidden;
  647 + // 隐藏滚动条但保持滚动功能
  648 + scrollbar-width: none; // Firefox
  649 + -ms-overflow-style: none; // IE 和 Edge
  650 +
  651 + &::-webkit-scrollbar {
  652 + display: none; // Chrome, Safari, Opera
  653 + width: 0;
  654 + height: 0;
  655 + }
  656 + }
  657 +}
  658 +
544 659 // 响应式设计
545 660 @media (max-width: 768px) {
546 661 .statistics-cards {
... ...
antis-ncc-admin/src/views/statisticsList/form12.vue
... ... @@ -115,6 +115,23 @@
115 115 </el-form-item>
116 116 </el-col>
117 117 <el-col :span="6">
  118 + <el-form-item label="客户类型">
  119 + <el-select
  120 + v-model="query.memberType"
  121 + placeholder="请选择客户类型"
  122 + filterable
  123 + clearable
  124 + :style='{"width":"100%"}'>
  125 + <el-option
  126 + v-for="item in memberTypeOptions"
  127 + :key="item.id"
  128 + :label="item.fullName"
  129 + :value="item.id">
  130 + </el-option>
  131 + </el-select>
  132 + </el-form-item>
  133 + </el-col>
  134 + <el-col :span="6">
118 135 <el-form-item>
119 136 <el-button type="primary" icon="el-icon-search" @click="search()">查询</el-button>
120 137 <el-button icon="el-icon-refresh-right" @click="reset()">重置</el-button>
... ... @@ -126,6 +143,7 @@
126 143 <!-- 数据表格 -->
127 144 <div class="NCC-common-layout-main NCC-flex-main">
128 145 <el-table
  146 + style="overflow-y: scroll;"
129 147 v-loading="listLoading"
130 148 :data="treeList"
131 149 border
... ... @@ -228,12 +246,14 @@ export default {
228 246 BillingUserId: undefined,
229 247 PurchaseItemId: undefined,
230 248 GiftItemId: undefined,
231   - ExperienceItemId: undefined
  249 + ExperienceItemId: undefined,
  250 + memberType: undefined
232 251 },
233 252 memberOptions: [],
234 253 memberLoading: false,
235 254 storeOptions: [],
236 255 itemOptions: [],
  256 + memberTypeOptions: [],
237 257 list: [],
238 258 treeList: [],
239 259 listLoading: false,
... ... @@ -249,6 +269,7 @@ export default {
249 269 this.initStoreOptions()
250 270 this.initItemOptions()
251 271 this.initMemberOptions()
  272 + this.getMemberTypeOptions()
252 273 this.search()
253 274 },
254 275 methods: {
... ... @@ -334,6 +355,28 @@ export default {
334 355 }
335 356 },
336 357  
  358 + // 初始化客户类型选项
  359 + getMemberTypeOptions() {
  360 + request({
  361 + url: '/api/Extend/lqkhxx/deduct-types',
  362 + method: 'GET',
  363 + }).then((res) => {
  364 + if (res.code == 200 && res.data) {
  365 + this.memberTypeOptions = res.data.map(item => ({
  366 + fullName: item.Name,
  367 + id: item.Value + '',
  368 + value: item.Value + '',
  369 + label: item.Name,
  370 + }))
  371 + } else {
  372 + this.memberTypeOptions = []
  373 + }
  374 + }).catch((err) => {
  375 + console.error('获取客户类型失败:', err)
  376 + this.memberTypeOptions = []
  377 + })
  378 + },
  379 +
337 380 // 查询数据
338 381 search() {
339 382 this.listLoading = true
... ... @@ -523,7 +566,8 @@ export default {
523 566 BillingUserId: undefined,
524 567 PurchaseItemId: undefined,
525 568 GiftItemId: undefined,
526   - ExperienceItemId: undefined
  569 + ExperienceItemId: undefined,
  570 + memberType: undefined
527 571 }
528 572 this.listQuery.currentPage = 1
529 573 this.listQuery.pageSize = 20
... ...
antis-ncc-admin/src/views/statisticsList/form13.vue
... ... @@ -477,6 +477,7 @@ export default {
477 477 this.listQuery.pageSize = 20
478 478 this.list = []
479 479 this.total = 0
  480 + this.search()
480 481 },
481 482  
482 483 // 获取品项类型的标签类型
... ...
antis-ncc-admin/src/views/statisticsList/form14.vue 0 → 100644
  1 +<template>
  2 + <div class="NCC-common-layout">
  3 + <div class="NCC-common-layout-center">
  4 + <!-- 筛选条件 -->
  5 + <el-row class="NCC-common-search-box" :gutter="16">
  6 + <el-form @submit.native.prevent>
  7 + <el-col :span="6">
  8 + <el-form-item label="开始时间">
  9 + <el-date-picker
  10 + v-model="query.StartConsumeTime"
  11 + type="date"
  12 + value-format="yyyy-MM-dd"
  13 + format="yyyy-MM-dd"
  14 + placeholder="开始时间"
  15 + />
  16 + </el-form-item>
  17 + </el-col>
  18 + <el-col :span="6">
  19 + <el-form-item label="结束时间">
  20 + <el-date-picker
  21 + v-model="query.EndConsumeTime"
  22 + type="date"
  23 + value-format="yyyy-MM-dd"
  24 + format="yyyy-MM-dd"
  25 + placeholder="结束时间"
  26 + />
  27 + </el-form-item>
  28 + </el-col>
  29 + <el-col :span="6">
  30 + <el-form-item label="品项">
  31 + <el-select
  32 + v-model="query.ItemId"
  33 + placeholder="请选择品项"
  34 + filterable
  35 + clearable
  36 + :style='{"width":"100%"}'>
  37 + <el-option
  38 + v-for="item in itemOptions"
  39 + :key="item.id"
  40 + :label="item.xmmc"
  41 + :value="item.id">
  42 + </el-option>
  43 + </el-select>
  44 + </el-form-item>
  45 + </el-col>
  46 + <el-col :span="6">
  47 + <el-form-item label="会员">
  48 + <el-select
  49 + v-model="query.MemberId"
  50 + placeholder="请选择会员"
  51 + filterable
  52 + remote
  53 + :remote-method="remoteMemberMethod"
  54 + :loading="memberLoading"
  55 + clearable
  56 + :style='{"width":"100%"}'>
  57 + <el-option
  58 + v-for="item in memberOptions"
  59 + :key="item.id"
  60 + :label="`${item.khmc}(${item.sjh || ''} ${item.gsmdName || ''})`"
  61 + :value="item.id">
  62 + </el-option>
  63 + </el-select>
  64 + </el-form-item>
  65 + </el-col>
  66 + <el-col :span="6">
  67 + <el-form-item label="品项类型">
  68 + <el-select
  69 + v-model="query.ItemType"
  70 + placeholder="请选择品项类型"
  71 + filterable
  72 + clearable
  73 + :style='{"width":"100%"}'>
  74 + <el-option
  75 + v-for="item in itemTypeOptions"
  76 + :key="item.value"
  77 + :label="item.label"
  78 + :value="item.value">
  79 + </el-option>
  80 + </el-select>
  81 + </el-form-item>
  82 + </el-col>
  83 + <el-col :span="6">
  84 + <el-form-item label="来源类型">
  85 + <el-select
  86 + v-model="query.SourceType"
  87 + placeholder="请选择来源类型"
  88 + clearable
  89 + :style='{"width":"100%"}'>
  90 + <el-option label="购买" value="购买" />
  91 + <el-option label="赠送" value="赠送" />
  92 + <el-option label="体验" value="体验" />
  93 + </el-select>
  94 + </el-form-item>
  95 + </el-col>
  96 + <el-col :span="6">
  97 + <el-form-item label="门店">
  98 + <el-select
  99 + v-model="query.StoreId"
  100 + placeholder="请选择门店"
  101 + filterable
  102 + clearable
  103 + :style='{"width":"100%"}'>
  104 + <el-option
  105 + v-for="store in storeOptions"
  106 + :key="store.id"
  107 + :label="store.dm"
  108 + :value="store.id">
  109 + </el-option>
  110 + </el-select>
  111 + </el-form-item>
  112 + </el-col>
  113 + <el-col :span="6">
  114 + <el-form-item>
  115 + <el-button type="primary" icon="el-icon-search" @click="search()">查询</el-button>
  116 + <el-button icon="el-icon-refresh-right" @click="reset()">重置</el-button>
  117 + </el-form-item>
  118 + </el-col>
  119 + </el-form>
  120 + </el-row>
  121 +
  122 + <!-- 数据表格 -->
  123 + <div class="NCC-common-layout-main NCC-flex-main">
  124 + <NCC-table v-loading="listLoading" :data="list" has-c>
  125 + <el-table-column prop="consumeTime" label="耗卡时间" min-width="120">
  126 + <template slot-scope="scope">
  127 + <i class="el-icon-time" style="margin-right: 4px; color: #409EFF;"></i>
  128 + <span>{{ formatDateTime(scope.row.consumeTime) || '无' }}</span>
  129 + </template>
  130 + </el-table-column>
  131 + <el-table-column prop="memberName" label="会员名称" min-width="120">
  132 + <template slot-scope="scope">
  133 + <i class="el-icon-user" style="margin-right: 4px; color: #409EFF;"></i>
  134 + <span>{{ scope.row.memberName || '无' }}</span>
  135 + </template>
  136 + </el-table-column>
  137 + <el-table-column prop="memberPhone" label="会员手机号" min-width="120">
  138 + <template slot-scope="scope">
  139 + <i class="el-icon-phone" style="margin-right: 4px; color: #67C23A;"></i>
  140 + <span>{{ scope.row.memberPhone || '无' }}</span>
  141 + </template>
  142 + </el-table-column>
  143 + <el-table-column prop="storeName" label="门店名称" min-width="120">
  144 + <template slot-scope="scope">
  145 + <i class="el-icon-shop" style="margin-right: 4px; color: #409EFF;"></i>
  146 + <span>{{ scope.row.storeName || '无' }}</span>
  147 + </template>
  148 + </el-table-column>
  149 + <el-table-column prop="itemName" label="品项名称" min-width="150">
  150 + <template slot-scope="scope">
  151 + <i class="el-icon-goods" style="margin-right: 4px; color: #409EFF;"></i>
  152 + <span>{{ scope.row.itemName || '无' }}</span>
  153 + </template>
  154 + </el-table-column>
  155 + <el-table-column prop="itemType" label="品项类型" min-width="100">
  156 + <template slot-scope="scope">
  157 + <el-tag
  158 + :type="getItemTypeTagType(scope.row.itemType)"
  159 + size="small">
  160 + {{ scope.row.itemType || '无' }}
  161 + </el-tag>
  162 + </template>
  163 + </el-table-column>
  164 + <el-table-column prop="itemPrice" label="品项金额" min-width="120" align="right">
  165 + <template slot-scope="scope">
  166 + <i class="el-icon-money" style="margin-right: 4px; color: #67C23A;"></i>
  167 + <span class="amount-paid">¥{{ formatMoney(scope.row.itemPrice) }}</span>
  168 + </template>
  169 + </el-table-column>
  170 + <el-table-column prop="projectNumber" label="项目数" min-width="100" align="right">
  171 + <template slot-scope="scope">
  172 + <i class="el-icon-menu" style="margin-right: 4px; color: #67C23A;"></i>
  173 + <span>{{ scope.row.projectNumber || 0 }}</span>
  174 + </template>
  175 + </el-table-column>
  176 + <el-table-column prop="originalProjectNumber" label="原始项目数" min-width="120" align="right">
  177 + <template slot-scope="scope">
  178 + <i class="el-icon-document" style="margin-right: 4px; color: #909399;"></i>
  179 + <span>{{ scope.row.originalProjectNumber || 0 }}</span>
  180 + </template>
  181 + </el-table-column>
  182 + <el-table-column prop="totalPrice" label="总金额" min-width="120" align="right">
  183 + <template slot-scope="scope">
  184 + <i class="el-icon-coin" style="margin-right: 4px; color: #67C23A;"></i>
  185 + <span class="amount-paid">¥{{ formatMoney(scope.row.totalPrice) }}</span>
  186 + </template>
  187 + </el-table-column>
  188 + <el-table-column prop="sourceType" label="来源类型" min-width="100">
  189 + <template slot-scope="scope">
  190 + <i class="el-icon-link" style="margin-right: 4px; color: #909399;"></i>
  191 + <span>{{ scope.row.sourceType || '无' }}</span>
  192 + </template>
  193 + </el-table-column>
  194 + </NCC-table>
  195 + <pagination
  196 + v-show="total > 0"
  197 + :total="total"
  198 + :page.sync="listQuery.currentPage"
  199 + :limit.sync="listQuery.pageSize"
  200 + @pagination="search"
  201 + />
  202 + </div>
  203 + </div>
  204 + </div>
  205 +</template>
  206 +
  207 +<script>
  208 +import request from '@/utils/request'
  209 +import Pagination from '@/components/Pagination'
  210 +
  211 +export default {
  212 + name: 'ConsumeItemDetailList',
  213 + components: {
  214 + Pagination
  215 + },
  216 + data() {
  217 + return {
  218 + query: {
  219 + StartConsumeTime: undefined,
  220 + EndConsumeTime: undefined,
  221 + ItemId: undefined,
  222 + MemberId: undefined,
  223 + ItemType: undefined,
  224 + SourceType: undefined,
  225 + StoreId: undefined
  226 + },
  227 + itemOptions: [],
  228 + itemTypeOptions: [],
  229 + memberOptions: [],
  230 + memberLoading: false,
  231 + storeOptions: [],
  232 + list: [],
  233 + listLoading: false,
  234 + total: 0,
  235 + listQuery: {
  236 + currentPage: 1,
  237 + pageSize: 10
  238 + }
  239 + }
  240 + },
  241 + created() {
  242 + this.setDefaultTimeRange()
  243 + this.initItemOptions()
  244 + this.initItemTypeOptions()
  245 + this.initMemberOptions()
  246 + this.initStoreOptions()
  247 + this.search()
  248 + },
  249 + methods: {
  250 + // 设置默认时间范围(本月1号到现在)
  251 + setDefaultTimeRange() {
  252 + const now = new Date()
  253 + const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
  254 +
  255 + this.query.StartConsumeTime = this.formatDate(firstDayOfMonth)
  256 + this.query.EndConsumeTime = this.formatDate(now)
  257 + },
  258 +
  259 + // 初始化品项选项
  260 + initItemOptions() {
  261 + request({
  262 + url: '/api/Extend/LqXmzl',
  263 + method: 'GET',
  264 + data: {
  265 + currentPage: 1,
  266 + pageSize: 1000
  267 + }
  268 + }).then(res => {
  269 + if (res.data && res.data.list) {
  270 + this.itemOptions = res.data.list
  271 + }
  272 + }).catch(() => {
  273 + this.itemOptions = []
  274 + })
  275 + },
  276 +
  277 + // 初始化品项类型选项(使用qt2字段数据)
  278 + initItemTypeOptions() {
  279 + request({
  280 + url: '/api/Extend/lqxmzl/GetDistinctFieldData?fieldName=qt2',
  281 + method: 'GET'
  282 + }).then(res => {
  283 + if (res.code == 200 && res.data && res.data.values) {
  284 + this.itemTypeOptions = res.data.values.map(item => ({
  285 + label: item,
  286 + value: item
  287 + }))
  288 + } else {
  289 + this.itemTypeOptions = []
  290 + }
  291 + }).catch(() => {
  292 + this.itemTypeOptions = []
  293 + })
  294 + },
  295 +
  296 + // 初始化会员选项
  297 + initMemberOptions() {
  298 + request({
  299 + url: '/api/Extend/LqKhxx',
  300 + method: 'GET',
  301 + data: {
  302 + currentPage: 1,
  303 + pageSize: 10
  304 + }
  305 + }).then(res => {
  306 + if (res.code == 200 && res.data.list && res.data.list.length > 0) {
  307 + this.memberOptions = res.data.list
  308 + }
  309 + }).catch(() => {
  310 + this.memberOptions = []
  311 + })
  312 + },
  313 +
  314 + // 初始化门店选项
  315 + initStoreOptions() {
  316 + request({
  317 + url: '/api/Extend/LqMdxx',
  318 + method: 'GET',
  319 + data: {
  320 + currentPage: 1,
  321 + pageSize: 1000
  322 + }
  323 + }).then(res => {
  324 + if (res.data && res.data.list) {
  325 + this.storeOptions = res.data.list
  326 + }
  327 + }).catch(() => {
  328 + this.storeOptions = []
  329 + })
  330 + },
  331 +
  332 + // 会员远程搜索
  333 + remoteMemberMethod(query) {
  334 + if (query !== '') {
  335 + this.memberLoading = true
  336 + request({
  337 + url: '/api/Extend/LqKhxx',
  338 + method: 'GET',
  339 + data: {
  340 + currentPage: 1,
  341 + pageSize: 20,
  342 + keyword: query
  343 + }
  344 + }).then(res => {
  345 + this.memberLoading = false
  346 + if (res.code == 200 && res.data.list && res.data.list.length > 0) {
  347 + this.memberOptions = res.data.list
  348 + } else {
  349 + this.memberOptions = []
  350 + }
  351 + }).catch(() => {
  352 + this.memberLoading = false
  353 + this.memberOptions = []
  354 + })
  355 + } else {
  356 + this.memberOptions = []
  357 + }
  358 + },
  359 +
  360 + // 查询数据
  361 + search() {
  362 + this.listLoading = true
  363 +
  364 + const params = {
  365 + currentPage: this.listQuery.currentPage,
  366 + pageSize: this.listQuery.pageSize
  367 + }
  368 +
  369 + if (this.query.StartConsumeTime) {
  370 + params.StartConsumeTime = this.query.StartConsumeTime
  371 + }
  372 +
  373 + if (this.query.EndConsumeTime) {
  374 + params.EndConsumeTime = this.query.EndConsumeTime
  375 + }
  376 +
  377 + if (this.query.ItemId) {
  378 + params.ItemId = this.query.ItemId
  379 + }
  380 +
  381 + if (this.query.MemberId) {
  382 + params.MemberId = this.query.MemberId
  383 + }
  384 +
  385 + if (this.query.ItemType) {
  386 + params.ItemType = this.query.ItemType
  387 + }
  388 +
  389 + if (this.query.SourceType) {
  390 + params.SourceType = this.query.SourceType
  391 + }
  392 +
  393 + if (this.query.StoreId) {
  394 + params.StoreId = this.query.StoreId
  395 + }
  396 +
  397 + request({
  398 + url: '/api/Extend/lqxhhyhk/consume-item-detail-list',
  399 + method: 'GET',
  400 + data: params
  401 + }).then(res => {
  402 + if (res.data && res.data.list) {
  403 + this.list = res.data.list
  404 + this.total = (res.data.pagination && res.data.pagination.total) || res.data.list.length || 0
  405 + } else {
  406 + this.list = []
  407 + this.total = 0
  408 + }
  409 + this.listLoading = false
  410 + }).catch(err => {
  411 + console.error('查询失败:', err)
  412 + this.$message({
  413 + type: 'error',
  414 + message: '查询失败,请重试',
  415 + duration: 1500
  416 + })
  417 + this.listLoading = false
  418 + this.list = []
  419 + this.total = 0
  420 + })
  421 + },
  422 +
  423 + // 重置查询条件
  424 + reset() {
  425 + this.query = {
  426 + StartConsumeTime: undefined,
  427 + EndConsumeTime: undefined,
  428 + ItemId: undefined,
  429 + MemberId: undefined,
  430 + ItemType: undefined,
  431 + SourceType: undefined,
  432 + StoreId: undefined
  433 + }
  434 + this.setDefaultTimeRange()
  435 + this.listQuery.currentPage = 1
  436 + this.listQuery.pageSize = 10
  437 + this.list = []
  438 + this.total = 0
  439 + this.search()
  440 + },
  441 +
  442 + // 获取品项类型的标签类型
  443 + getItemTypeTagType(itemType) {
  444 + const typeMap = {
  445 + '生美': 'success',
  446 + '医美': 'warning',
  447 + '其他': 'info'
  448 + }
  449 + return typeMap[itemType] || ''
  450 + },
  451 +
  452 + // 格式化金额
  453 + formatMoney(amount) {
  454 + if (!amount && amount !== 0) return '0.00'
  455 + return Number(amount).toFixed(2)
  456 + },
  457 +
  458 + // 格式化日期(yyyy-MM-dd)
  459 + formatDate(date) {
  460 + if (!date) return ''
  461 + try {
  462 + const d = new Date(date)
  463 + const year = d.getFullYear()
  464 + const month = String(d.getMonth() + 1).padStart(2, '0')
  465 + const day = String(d.getDate()).padStart(2, '0')
  466 + return `${year}-${month}-${day}`
  467 + } catch (e) {
  468 + return ''
  469 + }
  470 + },
  471 +
  472 + // 格式化日期时间
  473 + formatDateTime(dateTime) {
  474 + if (!dateTime) return '无'
  475 + try {
  476 + const date = new Date(dateTime)
  477 + const year = date.getFullYear()
  478 + const month = String(date.getMonth() + 1).padStart(2, '0')
  479 + const day = String(date.getDate()).padStart(2, '0')
  480 + const hours = String(date.getHours()).padStart(2, '0')
  481 + const minutes = String(date.getMinutes()).padStart(2, '0')
  482 + const seconds = String(date.getSeconds()).padStart(2, '0')
  483 + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
  484 + } catch (e) {
  485 + return dateTime
  486 + }
  487 + }
  488 + }
  489 +}
  490 +</script>
  491 +
  492 +<style lang="scss" scoped>
  493 +.amount-paid {
  494 + color: #67C23A;
  495 + font-weight: 500;
  496 +}
  497 +</style>
  498 +
... ...
antis-ncc-admin/src/views/statisticsList/form4.vue
... ... @@ -54,6 +54,7 @@
54 54 <el-form-item>
55 55 <el-button type="primary" icon="el-icon-search" @click="search()">查询</el-button>
56 56 <el-button icon="el-icon-refresh-right" @click="reset()">重置</el-button>
  57 + <el-button icon="el-icon-setting" @click="showColumnDialog = true">字段设置</el-button>
57 58 </el-form-item>
58 59 </el-col>
59 60 </el-form>
... ... @@ -110,63 +111,61 @@
110 111 </div>
111 112  
112 113 <!-- 数据表格 -->
113   - <div class="NCC-common-layout-main NCC-flex-main">
114   - <NCC-table v-loading="listLoading" :data="list" has-c>
115   - <el-table-column prop="ItemNumber" label="品项编号" align="left" width="120" />
116   - <el-table-column prop="ItemName" label="品项名称" align="left" min-width="150">
  114 + <div class="NCC-common-layout-main NCC-flex-main table-wrapper">
  115 + <el-table
  116 + v-loading="listLoading"
  117 + :data="list"
  118 + border
  119 + class="NCC-common-table"
  120 + :height="tableHeight"
  121 + @sort-change="handleSortChange">
  122 + <el-table-column
  123 + v-for="col in visibleColumns"
  124 + :key="col.prop"
  125 + :prop="col.prop"
  126 + :label="col.label"
  127 + :width="col.width"
  128 + :min-width="col.minWidth"
  129 + :sortable="col.sortable ? 'custom' : false"
  130 + :align="col.align || 'left'">
117 131 <template slot-scope="scope">
118   - <span class="item-name-link" @click="handleItemNameClick(scope.row)">
  132 + <span v-if="col.prop === 'ItemName'" class="item-name-link" @click="handleItemNameClick(scope.row)">
119 133 <i class="el-icon-goods" style="margin-right: 4px; color: #409EFF;"></i>
120 134 {{ scope.row.ItemName || '无' }}
121 135 </span>
  136 + <span v-else-if="col.prop === 'BillingAmount'" class="amount-value">
  137 + ¥{{ formatMoney(scope.row.BillingAmount) }}
  138 + </span>
  139 + <span v-else-if="col.prop === 'BillingAmountRatio'">
  140 + {{ formatPercent(scope.row.BillingAmountRatio) }}%
  141 + </span>
  142 + <span v-else-if="col.prop === 'ItemRatio'">
  143 + {{ formatPercent(scope.row.ItemRatio) }}%
  144 + </span>
  145 + <span v-else-if="col.prop === 'RepeatBuyRate'">
  146 + {{ formatPercent(scope.row.RepeatBuyRate) }}%
  147 + </span>
  148 + <span v-else-if="col.prop === 'ConsumeAmount'" class="amount-paid">
  149 + ¥{{ formatMoney(scope.row.ConsumeAmount) }}
  150 + </span>
  151 + <span v-else-if="col.prop === 'ConsumeAmountRatio'">
  152 + {{ formatPercent(scope.row.ConsumeAmountRatio) }}%
  153 + </span>
  154 + <span v-else-if="col.prop === 'RefundAmount'" class="amount-debt">
  155 + ¥{{ formatMoney(scope.row.RefundAmount) }}
  156 + </span>
  157 + <span v-else-if="col.prop === 'RefundCount'">
  158 + {{ scope.row.RefundCount || 0 }}
  159 + </span>
  160 + <span v-else>
  161 + {{ scope.row[col.prop] !== null && scope.row[col.prop] !== undefined ? scope.row[col.prop] : '无' }}
  162 + </span>
122 163 </template>
123 164 </el-table-column>
124   - <el-table-column prop="BillingAmount" label="开单金额" align="right" width="120">
125   - <template slot-scope="scope">
126   - <span class="amount-value">¥{{ formatMoney(scope.row.BillingAmount) }}</span>
127   - </template>
128   - </el-table-column>
129   - <el-table-column prop="BillingAmountRatio" label="开单占比" align="right" width="110">
130   - <template slot-scope="scope">
131   - {{ formatPercent(scope.row.BillingAmountRatio) }}%
132   - </template>
133   - </el-table-column>
134   - <el-table-column prop="TotalBuyers" label="购买人数" align="right" width="100" />
135   - <el-table-column prop="ItemRatio" label="人次占比" align="right" width="110">
136   - <template slot-scope="scope">
137   - {{ formatPercent(scope.row.ItemRatio) }}%
138   - </template>
139   - </el-table-column>
140   - <el-table-column prop="RepeatBuyers" label="复购人数" align="right" width="100" />
141   - <el-table-column prop="RepeatBuyRate" label="复购率" align="right" width="100">
142   - <template slot-scope="scope">
143   - {{ formatPercent(scope.row.RepeatBuyRate) }}%
144   - </template>
145   - </el-table-column>
146   - <el-table-column prop="ConsumeAmount" label="耗卡金额" align="right" width="120">
147   - <template slot-scope="scope">
148   - <span class="amount-paid">¥{{ formatMoney(scope.row.ConsumeAmount) }}</span>
149   - </template>
150   - </el-table-column>
151   - <el-table-column prop="ConsumeAmountRatio" label="耗卡占比" align="right" width="110">
152   - <template slot-scope="scope">
153   - {{ formatPercent(scope.row.ConsumeAmountRatio) }}%
154   - </template>
155   - </el-table-column>
156   - <el-table-column prop="ConsumePurchaseCount" label="购买耗卡次数" align="right" width="140" />
157   - <el-table-column prop="ConsumeGiftCount" label="赠送耗卡次数" align="right" width="140" />
158   - <el-table-column prop="ConsumeExperienceCount" label="体验耗卡次数" align="right" width="140" />
159   - <el-table-column prop="RefundAmount" label="退款金额" align="right" width="120">
160   - <template slot-scope="scope">
161   - <span class="amount-debt">¥{{ formatMoney(scope.row.RefundAmount) }}</span>
162   - </template>
163   - </el-table-column>
164   - <el-table-column prop="RefundCount" label="退款次数" align="right" width="100">
165   - <template slot-scope="scope">
166   - {{ scope.row.RefundCount || 0 }}
167   - </template>
168   - </el-table-column>
169   - </NCC-table>
  165 + <template slot="empty">
  166 + <el-empty description="暂无数据" :image-size="120"></el-empty>
  167 + </template>
  168 + </el-table>
170 169 </div>
171 170 </div>
172 171  
... ... @@ -178,6 +177,29 @@
178 177 :end-time="query.endTime"
179 178 @close="dialogVisible = false"
180 179 />
  180 +
  181 + <!-- 字段设置弹窗 -->
  182 + <el-dialog title="字段设置" :visible.sync="showColumnDialog" width="500px" append-to-body>
  183 + <div class="column-settings">
  184 + <div class="column-actions" style="margin-bottom: 15px;">
  185 + <el-button size="small" @click="selectAllColumns">全选</el-button>
  186 + <el-button size="small" @click="clearAllColumns">清空</el-button>
  187 + </div>
  188 + <el-checkbox-group v-model="selectedColumns">
  189 + <el-checkbox
  190 + v-for="col in columnOptions"
  191 + :key="col.prop"
  192 + :label="col.prop"
  193 + style="display: block; margin-bottom: 10px;">
  194 + {{ col.label }}
  195 + </el-checkbox>
  196 + </el-checkbox-group>
  197 + </div>
  198 + <div slot="footer" class="dialog-footer">
  199 + <el-button @click="showColumnDialog = false">取消</el-button>
  200 + <el-button type="primary" @click="saveColumnSettings">确定</el-button>
  201 + </div>
  202 + </el-dialog>
181 203 </div>
182 204 </template>
183 205  
... ... @@ -204,13 +226,49 @@ export default {
204 226 list: [],
205 227 listLoading: false,
206 228 dialogVisible: false,
207   - currentItemId: null
  229 + currentItemId: null,
  230 + showColumnDialog: false,
  231 + selectedColumns: [],
  232 + sortInfo: {
  233 + prop: '',
  234 + order: ''
  235 + },
  236 + // 所有列配置
  237 + columnOptions: [
  238 + { prop: 'ItemNumber', label: '品项编号', width: 120, sortable: true, align: 'left' },
  239 + { prop: 'ItemName', label: '品项名称', minWidth: 150, sortable: true, align: 'left', slot: true },
  240 + { prop: 'BillingAmount', label: '开单金额', width: 120, sortable: true, align: 'right', slot: true },
  241 + { prop: 'BillingAmountRatio', label: '开单占比', width: 110, sortable: true, align: 'right', slot: true },
  242 + { prop: 'TotalBuyers', label: '购买人数', width: 100, sortable: true, align: 'right' },
  243 + { prop: 'ItemRatio', label: '人次占比', width: 110, sortable: true, align: 'right', slot: true },
  244 + { prop: 'RepeatBuyers', label: '复购人数', width: 100, sortable: true, align: 'right' },
  245 + { prop: 'RepeatBuyRate', label: '复购率', width: 100, sortable: true, align: 'right', slot: true },
  246 + { prop: 'ConsumeAmount', label: '耗卡金额', width: 120, sortable: true, align: 'right', slot: true },
  247 + { prop: 'ConsumeAmountRatio', label: '耗卡占比', width: 110, sortable: true, align: 'right', slot: true },
  248 + { prop: 'ConsumePurchaseCount', label: '购买耗卡次数', width: 140, sortable: true, align: 'right' },
  249 + { prop: 'ConsumeGiftCount', label: '赠送耗卡次数', width: 140, sortable: true, align: 'right' },
  250 + { prop: 'ConsumeExperienceCount', label: '体验耗卡次数', width: 140, sortable: true, align: 'right' },
  251 + { prop: 'RefundAmount', label: '退款金额', width: 120, sortable: true, align: 'right', slot: true },
  252 + { prop: 'RefundCount', label: '退款次数', width: 100, sortable: true, align: 'right', slot: true }
  253 + ]
  254 + }
  255 + },
  256 + computed: {
  257 + // 可见的列
  258 + visibleColumns() {
  259 + return this.columnOptions.filter(col => this.selectedColumns.includes(col.prop))
  260 + },
  261 + // 计算表格高度
  262 + tableHeight() {
  263 + // 根据视口高度动态计算,减去筛选条件、统计卡片等的高度
  264 + return 'calc(100vh - 400px)'
208 265 }
209 266 },
210 267 created() {
211 268 this.setDefaultTimeRange()
212 269 this.initStoreOptions()
213 270 this.initItemOptions()
  271 + this.initColumnSettings()
214 272 this.search()
215 273 },
216 274 methods: {
... ... @@ -379,6 +437,96 @@ export default {
379 437  
380 438 this.currentItemId = row.ItemId
381 439 this.dialogVisible = true
  440 + },
  441 +
  442 + // 初始化列设置
  443 + initColumnSettings() {
  444 + // 默认显示所有列
  445 + this.selectedColumns = this.columnOptions.map(col => col.prop)
  446 + // 尝试从本地存储读取
  447 + const savedColumns = localStorage.getItem('form4_column_settings')
  448 + if (savedColumns) {
  449 + try {
  450 + const columns = JSON.parse(savedColumns)
  451 + if (Array.isArray(columns) && columns.length > 0) {
  452 + this.selectedColumns = columns
  453 + }
  454 + } catch (e) {
  455 + console.error('读取列设置失败:', e)
  456 + }
  457 + }
  458 + },
  459 +
  460 + // 全选列
  461 + selectAllColumns() {
  462 + this.selectedColumns = this.columnOptions.map(col => col.prop)
  463 + },
  464 +
  465 + // 清空列
  466 + clearAllColumns() {
  467 + this.selectedColumns = []
  468 + },
  469 +
  470 + // 保存列设置
  471 + saveColumnSettings() {
  472 + if (this.selectedColumns.length === 0) {
  473 + this.$message({
  474 + type: 'warning',
  475 + message: '至少需要显示一列',
  476 + duration: 1500
  477 + })
  478 + return
  479 + }
  480 + // 保存到本地存储
  481 + localStorage.setItem('form4_column_settings', JSON.stringify(this.selectedColumns))
  482 + this.showColumnDialog = false
  483 + this.$message({
  484 + type: 'success',
  485 + message: '字段设置已保存',
  486 + duration: 1500
  487 + })
  488 + },
  489 +
  490 + // 排序变化
  491 + handleSortChange({ column, prop, order }) {
  492 + this.sortInfo = {
  493 + prop: prop || '',
  494 + order: order || ''
  495 + }
  496 + this.sortList()
  497 + },
  498 +
  499 + // 排序列表
  500 + sortList() {
  501 + if (!this.sortInfo.prop || !this.sortInfo.order) {
  502 + return
  503 + }
  504 +
  505 + const prop = this.sortInfo.prop
  506 + const order = this.sortInfo.order
  507 + const isAsc = order === 'ascending'
  508 +
  509 + this.list.sort((a, b) => {
  510 + let aVal = a[prop]
  511 + let bVal = b[prop]
  512 +
  513 + // 处理空值
  514 + if (aVal === null || aVal === undefined) aVal = 0
  515 + if (bVal === null || bVal === undefined) bVal = 0
  516 +
  517 + // 转换为数字进行比较
  518 + aVal = Number(aVal)
  519 + bVal = Number(bVal)
  520 +
  521 + if (isNaN(aVal)) aVal = 0
  522 + if (isNaN(bVal)) bVal = 0
  523 +
  524 + if (isAsc) {
  525 + return aVal - bVal
  526 + } else {
  527 + return bVal - aVal
  528 + }
  529 + })
382 530 }
383 531 }
384 532 }
... ... @@ -456,5 +604,33 @@ export default {
456 604 text-decoration: underline;
457 605 }
458 606 }
  607 +
  608 +.table-wrapper {
  609 + height: 100%;
  610 + overflow: hidden;
  611 +
  612 + ::v-deep .el-table {
  613 + width: 100%;
  614 + }
  615 +
  616 + ::v-deep .el-table__body-wrapper {
  617 + overflow-y: auto;
  618 + overflow-x: auto;
  619 + }
  620 +
  621 + ::v-deep .el-table__header-wrapper {
  622 + overflow-x: auto;
  623 + overflow-y: hidden;
  624 + // 隐藏滚动条但保持滚动功能
  625 + scrollbar-width: none; // Firefox
  626 + -ms-overflow-style: none; // IE 和 Edge
  627 +
  628 + &::-webkit-scrollbar {
  629 + display: none; // Chrome, Safari, Opera
  630 + width: 0;
  631 + height: 0;
  632 + }
  633 + }
  634 +}
459 635 </style>
460 636  
... ...
绿纤uni-app/apis/modules/laundry-flow.js 0 → 100644
  1 +import request from '@/service/request.js'
  2 +import config from '@/common/config.js'
  3 +
  4 +export default {
  5 + // 获取毛巾流水列表
  6 + getLaundryFlowList(params) {
  7 + return request.get(`${config.getApiBaseUrl()}/api/Extend/LqLaundryFlow/GetList`, params);
  8 + },
  9 +
  10 + // 创建送出记录
  11 + createSendRecord(data) {
  12 + return request.post(`${config.getApiBaseUrl()}/api/Extend/LqLaundryFlow/Send`, data);
  13 + },
  14 +
  15 + // 创建送回记录
  16 + createReturnRecord(data) {
  17 + return request.post(`${config.getApiBaseUrl()}/api/Extend/LqLaundryFlow/Return`, data);
  18 + },
  19 +
  20 + // 获取清洗商列表
  21 + getSupplierList(params) {
  22 + return request.get(`${config.getApiBaseUrl()}/api/Extend/LqLaundrySupplier/GetList`, params);
  23 + },
  24 +
  25 + // 获取毛巾流水详情
  26 + getLaundryFlowDetail(id) {
  27 + return request.get(`${config.getApiBaseUrl()}/api/Extend/LqLaundryFlow/${id}`);
  28 + },
  29 +
  30 + // 获取产品类型列表
  31 + getProductTypeList() {
  32 + return request.get(`${config.getApiBaseUrl()}/api/Extend/LqStoreConsumableInventory/consumable-product-type`);
  33 + }
  34 +}
  35 +
... ...
绿纤uni-app/apis/modules/usage.js 0 → 100644
  1 +import request from '@/service/request.js'
  2 +import config from '@/common/config.js'
  3 +
  4 +export default {
  5 + // 获取使用记录列表
  6 + getUsageList(params) {
  7 + return request.get(`${config.getApiBaseUrl()}/api/Extend/LqInventoryUsage/GetList`, params);
  8 + },
  9 +
  10 + // 批量创建使用记录
  11 + batchCreateUsage(data) {
  12 + return request.post(`${config.getApiBaseUrl()}/api/Extend/LqInventoryUsage/BatchCreate`, data);
  13 + },
  14 +
  15 + // 作废使用记录
  16 + cancelUsage(id, remarks) {
  17 + return request.put(`${config.getApiBaseUrl()}/api/Extend/LqInventoryUsage/Cancel`, {
  18 + id: id,
  19 + remarks: remarks || ''
  20 + });
  21 + }
  22 +}
  23 +
... ...
绿纤uni-app/common/config.js
... ... @@ -10,8 +10,8 @@ const ENV_CONFIG = {
10 10 // 正式环境
11 11 production: {
12 12 name: '正式环境',
13   - // apiBaseUrl: 'http://erp_test.lvqianmeiye.com',
14   - apiBaseUrl: 'https://erp.lvqianmeiye.com',
  13 + apiBaseUrl: 'http://erp_test.lvqianmeiye.com',
  14 + // apiBaseUrl: 'https://erp.lvqianmeiye.com',
15 15 description: '生产环境服务器'
16 16 }
17 17 };
... ...
绿纤uni-app/pages.json
... ... @@ -244,6 +244,48 @@
244 244 {
245 245 "navigationBarTitleText" : "报销申请详情"
246 246 }
  247 + },
  248 + {
  249 + "path" : "pages/usage-list/usage-list",
  250 + "style" :
  251 + {
  252 + "navigationBarTitleText" : "使用记录"
  253 + }
  254 + },
  255 + {
  256 + "path" : "pages/usage-form/usage-form",
  257 + "style" :
  258 + {
  259 + "navigationBarTitleText" : "添加使用记录"
  260 + }
  261 + },
  262 + {
  263 + "path" : "pages/laundry-flow-list/laundry-flow-list",
  264 + "style" :
  265 + {
  266 + "navigationBarTitleText" : "毛巾记录"
  267 + }
  268 + },
  269 + {
  270 + "path" : "pages/laundry-flow-send/laundry-flow-send",
  271 + "style" :
  272 + {
  273 + "navigationBarTitleText" : "创建送出记录"
  274 + }
  275 + },
  276 + {
  277 + "path" : "pages/laundry-flow-return/laundry-flow-return",
  278 + "style" :
  279 + {
  280 + "navigationBarTitleText" : "创建送回记录"
  281 + }
  282 + },
  283 + {
  284 + "path" : "pages/laundry-flow-detail/laundry-flow-detail",
  285 + "style" :
  286 + {
  287 + "navigationBarTitleText" : "毛巾记录详情"
  288 + }
247 289 }
248 290 ],
249 291 "globalStyle": {
... ...
绿纤uni-app/pages/clue-list/clue-list.vue
... ... @@ -135,6 +135,16 @@
135 135 >
136 136 剩余权益
137 137 </button>
  138 + <!-- <button
  139 + type="warning"
  140 + size="small"
  141 + class="action-btn remaining-rights-btn"
  142 + @click.stop="goToServiceLog(clue)"
  143 + >
  144 + 日志
  145 + </button> -->
  146 + <view></view>
  147 + <view></view>
138 148 </view>
139 149 </view>
140 150 </view>
... ... @@ -315,7 +325,13 @@
315 325 this.loadCustomerData(true);
316 326 }, 500);
317 327 },
318   -
  328 + // 跳转到服务日志页面
  329 + goToServiceLog(item) {
  330 + console.log('跳转到服务日志:', item)
  331 + uni.navigateTo({
  332 + url: `/pages/serviceDiary/serviceDiary?consumeId=${item.id}&memberName=${encodeURIComponent(item.name || '未知会员')}`
  333 + })
  334 + },
319 335 // 跨店开关变化
320 336 onCrossStoreChange() {
321 337 // 重置分页状态
... ...
绿纤uni-app/pages/index/index.vue
... ... @@ -128,6 +128,18 @@
128 128 </view>
129 129 <view class="icon-label">报销申请</view>
130 130 </view>
  131 + <view class="icon-btn" @click="goToPage('/pages/usage-list/usage-list')">
  132 + <view class="icon">
  133 + <u-icon name="list-dot" size="32" color="#43a047"></u-icon>
  134 + </view>
  135 + <view class="icon-label">使用记录</view>
  136 + </view>
  137 + <view class="icon-btn" @click="goToPage('/pages/laundry-flow-list/laundry-flow-list')">
  138 + <view class="icon">
  139 + <u-icon name="list-dot" size="32" color="#43a047"></u-icon>
  140 + </view>
  141 + <view class="icon-label">毛巾记录</view>
  142 + </view>
131 143 <view class="icon-btn" @click="goToPage('/pages/web/web')">
132 144 <view class="icon">
133 145 <u-icon name="kefu-ermai" size="32" color="#43a047"></u-icon>
... ...
绿纤uni-app/pages/laundry-flow-detail/laundry-flow-detail.vue 0 → 100644
  1 +<template>
  2 + <view class="container">
  3 + <!-- 详情卡片 -->
  4 + <view class="detail-card">
  5 + <view class="card-header">毛巾记录详情</view>
  6 + <view class="detail-content">
  7 + <!-- 加载状态 -->
  8 + <view v-if="loading" class="loading">正在加载详情...</view>
  9 +
  10 + <!-- 错误状态 -->
  11 + <view v-else-if="error" class="error-state">
  12 + <view>{{ error }}</view>
  13 + <u-button class="retry-btn" @click="retryLoad" type="primary" size="small">重试</u-button>
  14 + </view>
  15 +
  16 + <!-- 详情内容 -->
  17 + <view v-else-if="detailData" class="info-sections">
  18 + <!-- 基本信息 -->
  19 + <view class="info-section">
  20 + <view class="section-title">
  21 + <text class="section-icon">📋</text>
  22 + <text>基本信息</text>
  23 + </view>
  24 + <view class="info-grid">
  25 + <view class="info-item">
  26 + <text class="info-label">流水类型</text>
  27 + <view class="flow-type-badge" :class="detailData.flowType === 0 ? 'send-type' : 'return-type'">
  28 + <text class="flow-type-icon">{{ detailData.flowType === 0 ? '📤' : '📥' }}</text>
  29 + <text class="flow-type-text">{{ detailData.flowTypeName || '无' }}</text>
  30 + </view>
  31 + </view>
  32 + <view class="info-item">
  33 + <text class="info-label">批次号</text>
  34 + <text class="info-value">{{ detailData.batchNumber || '无' }}</text>
  35 + </view>
  36 + <view class="info-item">
  37 + <text class="info-label">门店名称</text>
  38 + <text class="info-value">{{ detailData.storeName || '无' }}</text>
  39 + </view>
  40 + <view class="info-item">
  41 + <text class="info-label">产品类型</text>
  42 + <text class="info-value">{{ detailData.productType || '无' }}</text>
  43 + </view>
  44 + <view class="info-item">
  45 + <text class="info-label">清洗商名称</text>
  46 + <text class="info-value">{{ detailData.laundrySupplierName || '无' }}</text>
  47 + </view>
  48 + <view class="info-item">
  49 + <text class="info-label">数量</text>
  50 + <text class="info-value">{{ detailData.quantity || 0 }}</text>
  51 + </view>
  52 + </view>
  53 + </view>
  54 +
  55 + <!-- 费用信息 -->
  56 + <view class="info-section">
  57 + <view class="section-title">
  58 + <text class="section-icon">💰</text>
  59 + <text>费用信息</text>
  60 + </view>
  61 + <view class="info-grid">
  62 + <view class="info-item">
  63 + <text class="info-label">清洗单价</text>
  64 + <text class="info-value amount">¥{{ detailData.laundryPrice || 0 }}</text>
  65 + </view>
  66 + <view class="info-item">
  67 + <text class="info-label">总费用</text>
  68 + <text class="info-value amount total">¥{{ detailData.totalPrice || 0 }}</text>
  69 + </view>
  70 + </view>
  71 + </view>
  72 +
  73 + <!-- 其他信息 -->
  74 + <view class="info-section">
  75 + <view class="section-title">
  76 + <text class="section-icon">📝</text>
  77 + <text>其他信息</text>
  78 + </view>
  79 + <view class="info-grid">
  80 + <view class="info-item full-width">
  81 + <text class="info-label">备注</text>
  82 + <text class="info-value">{{ detailData.remark || '无' }}</text>
  83 + </view>
  84 + <view class="info-item">
  85 + <text class="info-label">是否有效</text>
  86 + <view class="status-badge" :class="detailData.isEffective === 1 ? 'effective' : 'ineffective'">
  87 + {{ detailData.isEffective === 1 ? '有效' : '无效' }}
  88 + </view>
  89 + </view>
  90 + <view class="info-item">
  91 + <text class="info-label">创建人</text>
  92 + <text class="info-value">{{ detailData.createUserName || '无' }}</text>
  93 + </view>
  94 + <view class="info-item">
  95 + <text class="info-label">创建时间</text>
  96 + <text class="info-value">{{ formatTime(detailData.createTime) }}</text>
  97 + </view>
  98 + </view>
  99 + </view>
  100 + </view>
  101 + </view>
  102 + </view>
  103 + </view>
  104 +</template>
  105 +
  106 +<script>
  107 + import laundryFlowApi from '@/apis/modules/laundry-flow.js'
  108 +
  109 + export default {
  110 + data() {
  111 + return {
  112 + loading: false,
  113 + error: null,
  114 + detailData: null,
  115 + flowId: null
  116 + }
  117 + },
  118 +
  119 + onLoad(options) {
  120 + if (options.id) {
  121 + this.flowId = options.id
  122 + this.loadDetail()
  123 + } else {
  124 + this.error = '缺少记录ID'
  125 + }
  126 + },
  127 +
  128 + methods: {
  129 + // 加载详情数据
  130 + async loadDetail() {
  131 + if (!this.flowId) return
  132 +
  133 + try {
  134 + this.loading = true
  135 + this.error = null
  136 +
  137 + const res = await laundryFlowApi.getLaundryFlowDetail(this.flowId)
  138 +
  139 + if (res.code === 200 && res.data) {
  140 + this.detailData = res.data
  141 + } else {
  142 + this.error = res.msg || res.message || '加载详情失败'
  143 + }
  144 + } catch (error) {
  145 + console.error('加载详情失败:', error)
  146 + this.error = '加载详情失败,请重试'
  147 + } finally {
  148 + this.loading = false
  149 + }
  150 + },
  151 +
  152 + // 重试加载
  153 + retryLoad() {
  154 + this.loadDetail()
  155 + },
  156 +
  157 + // 格式化时间
  158 + formatTime(timestamp) {
  159 + if (!timestamp) return '无'
  160 + const date = new Date(timestamp)
  161 + return date.toLocaleString('zh-CN', {
  162 + year: 'numeric',
  163 + month: '2-digit',
  164 + day: '2-digit',
  165 + hour: '2-digit',
  166 + minute: '2-digit',
  167 + second: '2-digit'
  168 + })
  169 + }
  170 + }
  171 + }
  172 +</script>
  173 +
  174 +<style lang="scss" scoped>
  175 + .container {
  176 + min-height: 100vh;
  177 + background: linear-gradient(135deg, #e8f5e9 0%, #b2dfdb 100%);
  178 + padding: 40rpx;
  179 + box-sizing: border-box;
  180 + }
  181 +
  182 + .detail-card {
  183 + background: #fff;
  184 + border-radius: 32rpx;
  185 + box-shadow: 0 8rpx 32rpx rgba(76, 175, 80, 0.1);
  186 + overflow: hidden;
  187 + }
  188 +
  189 + .card-header {
  190 + background: linear-gradient(120deg, #43e97b 0%, #38f9d7 100%);
  191 + padding: 32rpx 40rpx;
  192 + color: #fff;
  193 + font-size: 36rpx;
  194 + font-weight: 600;
  195 + letter-spacing: 2rpx;
  196 + text-align: center;
  197 + }
  198 +
  199 + .detail-content {
  200 + padding: 40rpx;
  201 + }
  202 +
  203 + .loading {
  204 + text-align: center;
  205 + padding: 80rpx 40rpx;
  206 + color: #6a9c6a;
  207 + font-size: 28rpx;
  208 + }
  209 +
  210 + .error-state {
  211 + text-align: center;
  212 + padding: 80rpx 40rpx;
  213 + color: #f56c6c;
  214 + font-size: 28rpx;
  215 + }
  216 +
  217 + .retry-btn {
  218 + margin-top: 32rpx;
  219 + }
  220 +
  221 + .info-sections {
  222 + display: flex;
  223 + flex-direction: column;
  224 + gap: 32rpx;
  225 + }
  226 +
  227 + .info-section {
  228 + background: #f9fff9;
  229 + border-radius: 24rpx;
  230 + padding: 32rpx;
  231 + border: 2rpx solid #e8f5e9;
  232 + }
  233 +
  234 + .section-title {
  235 + display: flex;
  236 + align-items: center;
  237 + gap: 12rpx;
  238 + font-size: 32rpx;
  239 + font-weight: 600;
  240 + color: #2e7d32;
  241 + margin-bottom: 24rpx;
  242 + padding-bottom: 16rpx;
  243 + border-bottom: 2rpx solid #c8e6c9;
  244 + }
  245 +
  246 + .section-icon {
  247 + font-size: 32rpx;
  248 + }
  249 +
  250 + .info-grid {
  251 + display: grid;
  252 + grid-template-columns: 1fr 1fr;
  253 + gap: 24rpx;
  254 + }
  255 +
  256 + .info-item {
  257 + display: flex;
  258 + flex-direction: column;
  259 + gap: 8rpx;
  260 + }
  261 +
  262 + .info-item.full-width {
  263 + grid-column: 1 / -1;
  264 + }
  265 +
  266 + .info-label {
  267 + font-size: 24rpx;
  268 + color: #6a9c6a;
  269 + font-weight: 500;
  270 + }
  271 +
  272 + .info-value {
  273 + font-size: 28rpx;
  274 + color: #2e7d32;
  275 + font-weight: 500;
  276 + word-break: break-all;
  277 + }
  278 +
  279 + .info-value.amount {
  280 + color: #f57c00;
  281 + font-weight: 600;
  282 + font-size: 32rpx;
  283 + }
  284 +
  285 + .info-value.amount.total {
  286 + font-size: 36rpx;
  287 + color: #e65100;
  288 + }
  289 +
  290 + .flow-type-badge {
  291 + display: flex;
  292 + align-items: center;
  293 + padding: 8rpx 24rpx;
  294 + border-radius: 40rpx;
  295 + font-size: 24rpx;
  296 + font-weight: 500;
  297 + width: fit-content;
  298 + }
  299 +
  300 + .flow-type-badge.send-type {
  301 + background: #e3f2fd;
  302 + color: #1976d2;
  303 + border: 2rpx solid #90caf9;
  304 + }
  305 +
  306 + .flow-type-badge.return-type {
  307 + background: #e8f5e9;
  308 + color: #388e3c;
  309 + border: 2rpx solid #a5d6a7;
  310 + }
  311 +
  312 + .flow-type-icon {
  313 + margin-right: 8rpx;
  314 + font-size: 28rpx;
  315 + }
  316 +
  317 + .flow-type-text {
  318 + font-size: 24rpx;
  319 + }
  320 +
  321 + .status-badge {
  322 + display: inline-block;
  323 + padding: 8rpx 24rpx;
  324 + border-radius: 40rpx;
  325 + font-size: 24rpx;
  326 + font-weight: 500;
  327 + text-align: center;
  328 + width: fit-content;
  329 + }
  330 +
  331 + .status-badge.effective {
  332 + background: #e8f5e9;
  333 + color: #2e7d32;
  334 + border: 2rpx solid #c8e6c9;
  335 + }
  336 +
  337 + .status-badge.ineffective {
  338 + background: #ffebee;
  339 + color: #c62828;
  340 + border: 2rpx solid #ffcdd2;
  341 + }
  342 +</style>
  343 +
... ...
绿纤uni-app/pages/laundry-flow-list/laundry-flow-list.vue 0 → 100644
  1 +<template>
  2 + <view class="container">
  3 + <!-- 查询条件 -->
  4 + <view class="filter-card">
  5 + <view class="filter-row">
  6 + <view class="filter-item">
  7 + <text class="filter-label">流水类型</text>
  8 + <picker mode="selector" :range="flowTypeOptions" range-key="label" :value="flowTypeIndex" @change="onFlowTypeChange">
  9 + <view class="picker-view">
  10 + {{ flowTypeOptions[flowTypeIndex].label }}
  11 + <text class="picker-arrow">▼</text>
  12 + </view>
  13 + </picker>
  14 + </view>
  15 + </view>
  16 + <view class="filter-buttons">
  17 + <view class="filter-btn" @click="search">查询</view>
  18 + <view class="filter-btn reset-btn" @click="reset">重置</view>
  19 + </view>
  20 + </view>
  21 +
  22 + <!-- 操作按钮 -->
  23 + <view class="action-buttons">
  24 + <view class="action-btn send-btn" @click="goToSend">
  25 + <text class="btn-text">创建送出记录</text>
  26 + </view>
  27 + <view class="action-btn return-btn" @click="goToReturn">
  28 + <text class="btn-text">创建送回记录</text>
  29 + </view>
  30 + </view>
  31 +
  32 + <!-- 数据列表 -->
  33 + <view class="list-card">
  34 + <view class="list-header">
  35 + <text class="header-text">毛巾记录</text>
  36 + <text class="total-count">共 {{ totalCount }} 条</text>
  37 + </view>
  38 +
  39 + <scroll-view scroll-y class="list-content" @scrolltolower="loadMore">
  40 + <view v-for="(item, index) in dataList" :key="index" class="list-item" @click="viewDetail(item)">
  41 + <view class="item-header">
  42 + <view class="flow-type-badge" :class="item.flowType === 0 ? 'send-type' : 'return-type'">
  43 + <text class="flow-type-icon">{{ item.flowType === 0 ? '📤' : '📥' }}</text>
  44 + <text class="flow-type-text">{{ item.flowTypeName || '无' }}</text>
  45 + </view>
  46 + <view class="create-time">{{ formatTime(item.createTime) }}</view>
  47 + </view>
  48 + <view class="item-details">
  49 + <view class="detail-item">
  50 + <text class="detail-label">批次号:</text>
  51 + <text class="detail-value">{{ item.batchNumber || '无' }}</text>
  52 + </view>
  53 + <view class="detail-item">
  54 + <text class="detail-label">门店:</text>
  55 + <text class="detail-value">{{ item.storeName || '无' }}</text>
  56 + </view>
  57 + <view class="detail-item">
  58 + <text class="detail-label">产品类型:</text>
  59 + <text class="detail-value">{{ item.productType || '无' }}</text>
  60 + </view>
  61 + <view class="detail-item">
  62 + <text class="detail-label">清洗商:</text>
  63 + <text class="detail-value">{{ item.laundrySupplierName || '无' }}</text>
  64 + </view>
  65 + <view class="detail-item">
  66 + <text class="detail-label">数量:</text>
  67 + <text class="detail-value">{{ item.quantity || 0 }}</text>
  68 + </view>
  69 + <view class="detail-item">
  70 + <text class="detail-label">清洗单价:</text>
  71 + <text class="detail-value">¥{{ item.laundryPrice || 0 }}</text>
  72 + </view>
  73 + <view class="detail-item">
  74 + <text class="detail-label">总费用:</text>
  75 + <text class="detail-value highlight">¥{{ item.totalPrice || 0 }}</text>
  76 + </view>
  77 + <view class="detail-item full-width">
  78 + <text class="detail-label">备注:</text>
  79 + <text class="detail-value">{{ item.remark || '无' }}</text>
  80 + </view>
  81 + </view>
  82 + <view class="item-footer">
  83 + <view class="status-badge" :class="item.isEffective === 1 ? 'effective' : 'ineffective'">
  84 + {{ item.isEffective === 1 ? '有效' : '无效' }}
  85 + </view>
  86 + <view class="create-user">创建人: {{ item.createUserName || '无' }}</view>
  87 + </view>
  88 + </view>
  89 +
  90 + <!-- 空状态 -->
  91 + <view v-if="!loading && dataList.length === 0" class="empty-state">
  92 + <view class="empty-icon">📋</view>
  93 + <view>暂无记录</view>
  94 + </view>
  95 +
  96 + <!-- 加载状态 -->
  97 + <view v-if="loading && dataList.length === 0" class="loading">正在加载数据...</view>
  98 +
  99 + <!-- 加载更多 -->
  100 + <u-loadmore v-if="dataList.length > 0" :status="loadmoreStatus" :load-text="loadText"></u-loadmore>
  101 + </scroll-view>
  102 + </view>
  103 + </view>
  104 +</template>
  105 +
  106 +<script>
  107 + import laundryFlowApi from '@/apis/modules/laundry-flow.js'
  108 +
  109 + export default {
  110 + data() {
  111 + return {
  112 + loading: false,
  113 + dataList: [],
  114 + currentPage: 1,
  115 + pageSize: 10,
  116 + totalCount: 0,
  117 + hasMore: true,
  118 + loadmoreStatus: 'loadmore',
  119 + loadText: {
  120 + loadmore: '点击或上拉加载更多',
  121 + loading: '正在加载...',
  122 + nomore: '没有更多了'
  123 + },
  124 + // 查询条件
  125 + query: {
  126 + flowType: undefined
  127 + },
  128 + flowTypeOptions: [
  129 + { label: '全部', value: undefined },
  130 + { label: '送出', value: 0 },
  131 + { label: '送回', value: 1 }
  132 + ],
  133 + flowTypeIndex: 0,
  134 + userInfo: uni.getStorageSync('userInfo'),
  135 + newuserInfo: uni.getStorageSync('newuserInfo'),
  136 + }
  137 + },
  138 +
  139 + onLoad() {
  140 + this.initializePage()
  141 + },
  142 +
  143 + onShow() {
  144 + // 页面显示时刷新数据(如果从添加页面返回)
  145 + if (this.dataList.length > 0) {
  146 + this.refreshData()
  147 + }
  148 + },
  149 +
  150 + methods: {
  151 + // 初始化页面
  152 + async initializePage() {
  153 + try {
  154 + await this.checkLoginStatus()
  155 + await this.loadList()
  156 + } catch (error) {
  157 + console.error('页面初始化失败:', error)
  158 + this.showErrorState('页面初始化失败,请刷新重试')
  159 + }
  160 + },
  161 +
  162 + // 检查登录状态
  163 + async checkLoginStatus() {
  164 + const token = uni.getStorageSync('token')
  165 + if (!token) {
  166 + uni.reLaunch({
  167 + url: '/pages/login/login'
  168 + })
  169 + return
  170 + }
  171 + },
  172 +
  173 + // 加载列表数据
  174 + async loadList() {
  175 + if (this.loading) return
  176 +
  177 + try {
  178 + this.loading = true
  179 +
  180 + const params = {
  181 + currentPage: this.currentPage,
  182 + pageSize: this.pageSize
  183 + }
  184 +
  185 + // 添加查询条件
  186 + if (this.query.flowType !== undefined) {
  187 + params.flowType = this.query.flowType
  188 + }
  189 + // if (this.userInfo && this.userInfo.userId) {
  190 + // params.createUser = this.userInfo.userId
  191 + // } else {
  192 + // params.createUser = '暂无'
  193 + // }
  194 +
  195 + params.StoreId = this.newuserInfo.mdid || '暂无'
  196 + const res = await laundryFlowApi.getLaundryFlowList(params)
  197 +
  198 + if (res.code === 200) {
  199 + const data = res.data
  200 + this.totalCount = data.pagination?.total || 0
  201 + const list = data.list || []
  202 +
  203 + if (this.currentPage === 1) {
  204 + this.dataList = list
  205 + } else {
  206 + this.dataList = [...this.dataList, ...list]
  207 + }
  208 +
  209 + this.hasMore = list.length === this.pageSize
  210 +
  211 + if (this.hasMore) {
  212 + this.loadmoreStatus = 'loadmore'
  213 + } else {
  214 + this.loadmoreStatus = 'nomore'
  215 + }
  216 + } else {
  217 + throw new Error(res.message || '获取数据失败')
  218 + }
  219 +
  220 + } catch (error) {
  221 + console.error('加载列表失败:', error)
  222 + if (this.currentPage === 1) {
  223 + this.showErrorState('加载数据失败,请重试')
  224 + } else {
  225 + this.showMessage('加载更多数据失败', 'error')
  226 + }
  227 + } finally {
  228 + this.loading = false
  229 + }
  230 + },
  231 +
  232 + // 刷新数据
  233 + refreshData() {
  234 + this.currentPage = 1
  235 + this.hasMore = true
  236 + this.loadmoreStatus = 'loadmore'
  237 + this.dataList = []
  238 + this.loadList()
  239 + },
  240 +
  241 + // 加载更多
  242 + async loadMore() {
  243 + if (this.loading || !this.hasMore || this.loadmoreStatus === 'nomore') return
  244 +
  245 + this.loadmoreStatus = 'loading'
  246 + this.currentPage++
  247 + await this.loadList()
  248 + },
  249 +
  250 + // 查询
  251 + search() {
  252 + this.refreshData()
  253 + },
  254 +
  255 + // 重置
  256 + reset() {
  257 + this.query = {
  258 + flowType: undefined
  259 + }
  260 + this.flowTypeIndex = 0
  261 + this.refreshData()
  262 + },
  263 +
  264 + // 流水类型变化
  265 + onFlowTypeChange(e) {
  266 + this.flowTypeIndex = e.detail.value
  267 + this.query.flowType = this.flowTypeOptions[this.flowTypeIndex].value
  268 + },
  269 +
  270 + // 跳转到送出记录页面
  271 + goToSend() {
  272 + uni.navigateTo({
  273 + url: '/pages/laundry-flow-send/laundry-flow-send'
  274 + })
  275 + },
  276 +
  277 + // 跳转到送回记录页面
  278 + goToReturn() {
  279 + uni.navigateTo({
  280 + url: '/pages/laundry-flow-return/laundry-flow-return'
  281 + })
  282 + },
  283 +
  284 + // 查看详情
  285 + viewDetail(item) {
  286 + uni.navigateTo({
  287 + url: `/pages/laundry-flow-detail/laundry-flow-detail?id=${item.id}`
  288 + })
  289 + },
  290 +
  291 + // 格式化时间
  292 + formatTime(timestamp) {
  293 + if (!timestamp) return '无'
  294 + const date = new Date(timestamp)
  295 + return date.toLocaleString('zh-CN', {
  296 + year: 'numeric',
  297 + month: '2-digit',
  298 + day: '2-digit',
  299 + hour: '2-digit',
  300 + minute: '2-digit'
  301 + })
  302 + },
  303 +
  304 + // 显示错误状态
  305 + showErrorState(message) {
  306 + uni.showToast({
  307 + title: message,
  308 + icon: 'none'
  309 + })
  310 + },
  311 +
  312 + // 显示消息提示
  313 + showMessage(message, type = 'info') {
  314 + uni.showToast({
  315 + title: message,
  316 + icon: type === 'error' ? 'error' : 'none'
  317 + })
  318 + }
  319 + }
  320 + }
  321 +</script>
  322 +
  323 +<style lang="scss" scoped>
  324 + .container {
  325 + min-height: 100vh;
  326 + background: linear-gradient(135deg, #e8f5e9 0%, #b2dfdb 100%);
  327 + padding: 40rpx 40rpx;
  328 + box-sizing: border-box;
  329 + }
  330 +
  331 + .filter-card {
  332 + background: #fff;
  333 + border-radius: 32rpx;
  334 + box-shadow: 0 8rpx 32rpx rgba(76, 175, 80, 0.1);
  335 + padding: 40rpx;
  336 + margin-bottom: 40rpx;
  337 + }
  338 +
  339 + .filter-row {
  340 + display: flex;
  341 + flex-wrap: wrap;
  342 + gap: 24rpx;
  343 + margin-bottom: 32rpx;
  344 + }
  345 +
  346 + .filter-item {
  347 + flex: 1;
  348 + min-width: 200rpx;
  349 + }
  350 +
  351 + .filter-label {
  352 + font-size: 28rpx;
  353 + color: #2e7d32;
  354 + margin-bottom: 16rpx;
  355 + display: block;
  356 + }
  357 +
  358 + .picker-view {
  359 + display: flex;
  360 + align-items: center;
  361 + justify-content: space-between;
  362 + background: #f9fff9;
  363 + border: 3rpx solid #c8e6c9;
  364 + border-radius: 24rpx;
  365 + padding: 24rpx 32rpx;
  366 + font-size: 28rpx;
  367 + color: #2e7d32;
  368 + }
  369 +
  370 + .picker-arrow {
  371 + font-size: 24rpx;
  372 + color: #6a9c6a;
  373 + margin-left: 16rpx;
  374 + }
  375 +
  376 + .filter-buttons {
  377 + display: flex;
  378 + gap: 24rpx;
  379 + }
  380 +
  381 + .filter-btn {
  382 + flex: 1;
  383 + text-align: center;
  384 + background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
  385 + color: #fff;
  386 + padding: 24rpx;
  387 + border-radius: 24rpx;
  388 + font-size: 28rpx;
  389 + font-weight: 600;
  390 + box-shadow: 0 4rpx 16rpx rgba(67, 233, 123, 0.3);
  391 + }
  392 +
  393 + .filter-btn.reset-btn {
  394 + background: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%);
  395 + color: #666;
  396 + box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
  397 + }
  398 +
  399 + .action-buttons {
  400 + display: flex;
  401 + gap: 24rpx;
  402 + margin-bottom: 40rpx;
  403 + }
  404 +
  405 + .action-btn {
  406 + flex: 1;
  407 + text-align: center;
  408 + padding: 28rpx;
  409 + border-radius: 32rpx;
  410 + font-size: 28rpx;
  411 + font-weight: 600;
  412 + box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15);
  413 + }
  414 +
  415 + .send-btn {
  416 + background: linear-gradient(135deg, #409EFF 0%, #66b1ff 100%);
  417 + color: #fff;
  418 + }
  419 +
  420 + .return-btn {
  421 + background: linear-gradient(135deg, #67C23A 0%, #85ce61 100%);
  422 + color: #fff;
  423 + }
  424 +
  425 + .btn-text {
  426 + letter-spacing: 2rpx;
  427 + }
  428 +
  429 + .list-card {
  430 + background: #fff;
  431 + border-radius: 32rpx;
  432 + box-shadow: 0 8rpx 32rpx rgba(76, 175, 80, 0.1);
  433 + overflow: hidden;
  434 + max-height: 60vh;
  435 + }
  436 +
  437 + .list-header {
  438 + background: linear-gradient(120deg, #43e97b 0%, #38f9d7 100%);
  439 + padding: 32rpx 40rpx;
  440 + color: #fff;
  441 + font-weight: 600;
  442 + letter-spacing: 2rpx;
  443 + display: flex;
  444 + justify-content: space-between;
  445 + align-items: center;
  446 + }
  447 +
  448 + .header-text {
  449 + font-size: 32rpx;
  450 + }
  451 +
  452 + .total-count {
  453 + font-size: 28rpx;
  454 + opacity: 0.9;
  455 + }
  456 +
  457 + .list-content {
  458 + max-height: calc(60vh - 120rpx);
  459 + overflow-y: auto;
  460 + }
  461 +
  462 + .list-item {
  463 + padding: 32rpx 40rpx;
  464 + border-bottom: 2rpx solid #f0f0f0;
  465 + cursor: pointer;
  466 + transition: all 0.2s ease;
  467 + }
  468 +
  469 + .list-item:active {
  470 + background: #f8fff8;
  471 + transform: translateX(8rpx);
  472 + }
  473 +
  474 + .list-item:last-child {
  475 + border-bottom: none;
  476 + }
  477 +
  478 + .item-header {
  479 + display: flex;
  480 + justify-content: space-between;
  481 + align-items: center;
  482 + margin-bottom: 16rpx;
  483 + }
  484 +
  485 + .flow-type-badge {
  486 + display: flex;
  487 + align-items: center;
  488 + padding: 8rpx 24rpx;
  489 + border-radius: 40rpx;
  490 + font-size: 24rpx;
  491 + font-weight: 500;
  492 + }
  493 +
  494 + .flow-type-badge.send-type {
  495 + background: #e3f2fd;
  496 + color: #1976d2;
  497 + border: 2rpx solid #90caf9;
  498 + }
  499 +
  500 + .flow-type-badge.return-type {
  501 + background: #e8f5e9;
  502 + color: #388e3c;
  503 + border: 2rpx solid #a5d6a7;
  504 + }
  505 +
  506 + .flow-type-icon {
  507 + margin-right: 8rpx;
  508 + font-size: 28rpx;
  509 + }
  510 +
  511 + .flow-type-text {
  512 + font-size: 24rpx;
  513 + }
  514 +
  515 + .create-time {
  516 + font-size: 24rpx;
  517 + color: #6a9c6a;
  518 + }
  519 +
  520 + .item-details {
  521 + display: grid;
  522 + grid-template-columns: 1fr 1fr;
  523 + gap: 16rpx;
  524 + margin-bottom: 16rpx;
  525 + }
  526 +
  527 + .detail-item {
  528 + display: flex;
  529 + align-items: center;
  530 + font-size: 28rpx;
  531 + color: #555;
  532 + }
  533 +
  534 + .detail-item.full-width {
  535 + grid-column: 1 / -1;
  536 + }
  537 +
  538 + .detail-label {
  539 + color: #6a9c6a;
  540 + margin-right: 12rpx;
  541 + font-size: 24rpx;
  542 + }
  543 +
  544 + .detail-value {
  545 + color: #2e7d32;
  546 + font-weight: 500;
  547 + }
  548 +
  549 + .detail-value.highlight {
  550 + color: #f57c00;
  551 + font-weight: 600;
  552 + }
  553 +
  554 + .item-footer {
  555 + display: flex;
  556 + justify-content: space-between;
  557 + align-items: center;
  558 + margin-top: 16rpx;
  559 + }
  560 +
  561 + .status-badge {
  562 + display: inline-block;
  563 + padding: 8rpx 24rpx;
  564 + border-radius: 40rpx;
  565 + font-size: 24rpx;
  566 + font-weight: 500;
  567 + }
  568 +
  569 + .status-badge.effective {
  570 + background: #e8f5e9;
  571 + color: #2e7d32;
  572 + border: 2rpx solid #c8e6c9;
  573 + }
  574 +
  575 + .status-badge.ineffective {
  576 + background: #ffebee;
  577 + color: #c62828;
  578 + border: 2rpx solid #ffcdd2;
  579 + }
  580 +
  581 + .create-user {
  582 + font-size: 24rpx;
  583 + color: #6a9c6a;
  584 + }
  585 +
  586 + .loading {
  587 + text-align: center;
  588 + padding: 80rpx 40rpx;
  589 + color: #6a9c6a;
  590 + font-size: 28rpx;
  591 + }
  592 +
  593 + .empty-state {
  594 + text-align: center;
  595 + padding: 120rpx 40rpx;
  596 + color: #6a9c6a;
  597 + }
  598 +
  599 + .empty-icon {
  600 + font-size: 120rpx;
  601 + margin-bottom: 32rpx;
  602 + opacity: 0.5;
  603 + }
  604 +</style>
  605 +
... ...
绿纤uni-app/pages/laundry-flow-return/laundry-flow-return.vue 0 → 100644
  1 +<template>
  2 + <view class="form-container">
  3 + <view class="form-card">
  4 + <view class="form-content">
  5 + <!-- 批次号 -->
  6 + <view class="form-group">
  7 + <text class="form-label">批次号</text>
  8 + <view class="input-wrapper">
  9 + <view class="custom-select" @tap="openSelectModal('batch')">
  10 + <text class="select-text">{{ formData.batchNumber || '请选择批次号' }}</text>
  11 + <text class="select-arrow">▼</text>
  12 + </view>
  13 + </view>
  14 + </view>
  15 +
  16 + <!-- 门店(只读) -->
  17 + <view class="form-group">
  18 + <text class="form-label">门店</text>
  19 + <view class="input-wrapper">
  20 + <u-input v-model="sendRecordInfo.storeName" placeholder="无"
  21 + disabled class="select-input" />
  22 + </view>
  23 + </view>
  24 +
  25 + <!-- 产品类型(只读) -->
  26 + <view class="form-group">
  27 + <text class="form-label">产品类型</text>
  28 + <view class="input-wrapper">
  29 + <u-input v-model="sendRecordInfo.productType" placeholder="无"
  30 + disabled class="select-input" />
  31 + </view>
  32 + </view>
  33 +
  34 + <!-- 送出数量(只读) -->
  35 + <view class="form-group">
  36 + <text class="form-label">送出数量</text>
  37 + <view class="input-wrapper">
  38 + <u-input v-model="sendRecordInfo.quantity" placeholder="无"
  39 + disabled class="select-input" />
  40 + </view>
  41 + </view>
  42 +
  43 + <!-- 清洗商 -->
  44 + <view class="form-group">
  45 + <text class="form-label">清洗商</text>
  46 + <view class="input-wrapper">
  47 + <view class="custom-select" @tap="openSelectModal('supplier')">
  48 + <text class="select-text">{{ formData.supplierName || '请选择清洗商' }}</text>
  49 + <text class="select-arrow">▼</text>
  50 + </view>
  51 + </view>
  52 + </view>
  53 +
  54 + <!-- 清洗单价 -->
  55 + <view class="form-group">
  56 + <text class="form-label">清洗单价</text>
  57 + <view class="input-wrapper">
  58 + <u-input v-model="formData.laundryPrice" placeholder="自动填充"
  59 + disabled class="select-input" />
  60 + </view>
  61 + </view>
  62 +
  63 + <!-- 送回数量 -->
  64 + <view class="form-group">
  65 + <text class="form-label">送回数量</text>
  66 + <view class="input-wrapper">
  67 + <u-input v-model="formData.quantity" placeholder="请输入送回数量"
  68 + type="number" class="select-input" />
  69 + </view>
  70 + </view>
  71 +
  72 + <!-- 预计总费用 -->
  73 + <view class="form-group">
  74 + <text class="form-label">预计总费用</text>
  75 + <view class="input-wrapper">
  76 + <u-input v-model="totalPrice" placeholder="自动计算"
  77 + disabled class="select-input" />
  78 + </view>
  79 + </view>
  80 +
  81 + <!-- 备注 -->
  82 + <view class="form-group">
  83 + <text class="form-label">备注</text>
  84 + <view class="input-wrapper">
  85 + <u-input v-model="formData.remark" placeholder="请输入备注(可说明差异原因,如损坏、丢失等)"
  86 + type="textarea" :maxlength="500" class="select-input" />
  87 + </view>
  88 + </view>
  89 +
  90 + <!-- 提交按钮 -->
  91 + <view class="btn-group">
  92 + <button type="submit" class="btn btn-primary"
  93 + :style="{opacity: isSubmitting ? 0.5 : 1}"
  94 + @tap="isSubmitting ? null : handleFormSubmit()">
  95 + {{ isSubmitting ? '提交中...' : '提交' }}
  96 + </button>
  97 + </view>
  98 + </view>
  99 + </view>
  100 +
  101 + <!-- 选择弹窗 -->
  102 + <SearchSelectModal
  103 + :show="showModal"
  104 + :title="modalTitle"
  105 + :options="currentOptions"
  106 + :loading="modalLoading"
  107 + :has-more="hasMoreData"
  108 + :search-param="searchParam"
  109 + @confirm="handleModalConfirm"
  110 + @close="closeModal"
  111 + @load-more="handleLoadMore"
  112 + @refresh="handleRefresh"
  113 + @search="handleSearch" />
  114 + </view>
  115 +</template>
  116 +
  117 +<script>
  118 + import SearchSelectModal from '@/components/SearchSelectModal.vue'
  119 + import laundryFlowApi from '@/apis/modules/laundry-flow.js'
  120 +
  121 + export default {
  122 + components: {
  123 + SearchSelectModal
  124 + },
  125 + data() {
  126 + return {
  127 + isSubmitting: false,
  128 + formData: {
  129 + batchNumber: '',
  130 + laundrySupplierId: '',
  131 + supplierName: '',
  132 + laundryPrice: '',
  133 + quantity: 0,
  134 + remark: ''
  135 + },
  136 + sendRecordInfo: {
  137 + storeName: '',
  138 + productType: '',
  139 + quantity: 0
  140 + },
  141 + selectedSupplier: null,
  142 + batchList: [],
  143 + allSupplierList: [],
  144 + // 选择弹窗相关
  145 + showModal: false,
  146 + modalTitle: '',
  147 + currentSelectField: '',
  148 + currentOptions: [],
  149 + modalLoading: false,
  150 + hasMoreData: true,
  151 + currentPage: 1,
  152 + pageSize: 20,
  153 + searchKeyword: '',
  154 + searchParam: ''
  155 + }
  156 + },
  157 +
  158 + computed: {
  159 + // 计算总费用
  160 + totalPrice() {
  161 + const quantity = parseFloat(this.formData.quantity) || 0
  162 + const price = parseFloat(this.formData.laundryPrice) || 0
  163 + return (quantity * price).toFixed(2)
  164 + },
  165 + // 过滤后的清洗商列表(根据产品类型)
  166 + filteredSupplierList() {
  167 + if (!this.sendRecordInfo.productType) {
  168 + return this.allSupplierList
  169 + }
  170 + return this.allSupplierList.filter(supplier => supplier.productType === this.sendRecordInfo.productType)
  171 + }
  172 + },
  173 +
  174 + onLoad() {
  175 + this.checkLoginStatus()
  176 + this.initBatchList()
  177 + this.initSupplierList()
  178 + },
  179 +
  180 + methods: {
  181 + // 检查登录状态
  182 + checkLoginStatus() {
  183 + const token = uni.getStorageSync('token')
  184 + if (!token) {
  185 + uni.reLaunch({
  186 + url: '/pages/login/login'
  187 + })
  188 + return
  189 + }
  190 + },
  191 +
  192 + // 初始化批次列表
  193 + async initBatchList() {
  194 + try {
  195 + // 获取所有送出记录
  196 + const sendRes = await laundryFlowApi.getLaundryFlowList({
  197 + currentPage: 1,
  198 + pageSize: 1000,
  199 + flowType: 0,
  200 + isEffective: 1
  201 + })
  202 +
  203 + if (sendRes.code === 200 && sendRes.data && sendRes.data.list) {
  204 + const sendRecords = sendRes.data.list
  205 +
  206 + // 获取所有送回记录
  207 + const returnRes = await laundryFlowApi.getLaundryFlowList({
  208 + currentPage: 1,
  209 + pageSize: 1000,
  210 + flowType: 1,
  211 + isEffective: 1
  212 + })
  213 +
  214 + let returnRecords = []
  215 + if (returnRes.code === 200 && returnRes.data && returnRes.data.list) {
  216 + returnRecords = returnRes.data.list
  217 + }
  218 +
  219 + // 按批次号分组统计送回数量
  220 + const returnMap = {}
  221 + returnRecords.forEach(item => {
  222 + if (!returnMap[item.batchNumber]) {
  223 + returnMap[item.batchNumber] = 0
  224 + }
  225 + returnMap[item.batchNumber] += item.quantity
  226 + })
  227 +
  228 + // 过滤出可以送回记录的批次(送出数量 > 已送回数量)
  229 + this.batchList = sendRecords.filter(send => {
  230 + const returnedQty = returnMap[send.batchNumber] || 0
  231 + return send.quantity > returnedQty
  232 + }).map(send => ({
  233 + batchNumber: send.batchNumber,
  234 + storeName: send.storeName,
  235 + productType: send.productType,
  236 + quantity: send.quantity,
  237 + returnedQty: returnMap[send.batchNumber] || 0
  238 + }))
  239 + } else {
  240 + this.batchList = []
  241 + }
  242 + } catch (error) {
  243 + console.error('初始化批次列表失败:', error)
  244 + this.batchList = []
  245 + }
  246 + },
  247 +
  248 + // 初始化清洗商列表
  249 + async initSupplierList() {
  250 + try {
  251 + const res = await laundryFlowApi.getSupplierList({
  252 + currentPage: 1,
  253 + pageSize: 1000,
  254 + isEffective: 1
  255 + })
  256 +
  257 + if (res.code === 200 && res.data && res.data.list) {
  258 + this.allSupplierList = res.data.list
  259 + } else {
  260 + this.allSupplierList = []
  261 + }
  262 + } catch (error) {
  263 + console.error('初始化清洗商列表失败:', error)
  264 + this.allSupplierList = []
  265 + }
  266 + },
  267 +
  268 + // 打开选择弹窗
  269 + openSelectModal(field) {
  270 + this.currentSelectField = field
  271 + this.currentPage = 1
  272 + this.hasMoreData = true
  273 + this.searchKeyword = ''
  274 + this.searchParam = ''
  275 +
  276 + if (field === 'batch') {
  277 + this.modalTitle = '选择批次号'
  278 + this.searchParam = '请输入批次号'
  279 + } else if (field === 'supplier') {
  280 + this.modalTitle = '选择清洗商'
  281 + this.searchParam = '请输入清洗商名称'
  282 + }
  283 +
  284 + this.showModal = true
  285 + this.loadOptionsData(field, 1)
  286 + },
  287 +
  288 + // 加载选项数据
  289 + async loadOptionsData(fieldId, page = 1, searchKeyword = '') {
  290 + this.modalLoading = true
  291 + let options = []
  292 +
  293 + try {
  294 + if (fieldId === 'batch') {
  295 + options = this.getBatchOptions(searchKeyword)
  296 + } else if (fieldId === 'supplier') {
  297 + options = this.getSupplierOptions(searchKeyword)
  298 + }
  299 +
  300 + if (page === 1) {
  301 + this.currentOptions = options
  302 + } else {
  303 + this.currentOptions = [...this.currentOptions, ...options]
  304 + }
  305 +
  306 + this.hasMoreData = false // 批次和清洗商列表不需要分页
  307 + } catch (error) {
  308 + console.error('加载选项数据失败:', error)
  309 + this.currentOptions = []
  310 + } finally {
  311 + this.modalLoading = false
  312 + }
  313 + },
  314 +
  315 + // 获取批次选项
  316 + getBatchOptions(searchKeyword = '') {
  317 + let list = this.batchList
  318 +
  319 + if (searchKeyword) {
  320 + list = list.filter(item =>
  321 + item.batchNumber.includes(searchKeyword) ||
  322 + item.storeName.includes(searchKeyword) ||
  323 + item.productType.includes(searchKeyword)
  324 + )
  325 + }
  326 +
  327 + return list.map(item => ({
  328 + value: item.batchNumber,
  329 + label: `${item.batchNumber} (${item.storeName} - ${item.productType} - 送出${item.quantity}件)`,
  330 + batchNumber: item.batchNumber,
  331 + storeName: item.storeName,
  332 + productType: item.productType,
  333 + quantity: item.quantity
  334 + }))
  335 + },
  336 +
  337 + // 获取清洗商选项
  338 + getSupplierOptions(searchKeyword = '') {
  339 + let list = this.filteredSupplierList
  340 +
  341 + if (searchKeyword) {
  342 + list = list.filter(item =>
  343 + item.supplierName && item.supplierName.includes(searchKeyword)
  344 + )
  345 + }
  346 +
  347 + return list.map(item => ({
  348 + value: item.id,
  349 + label: item.supplierName || '未知清洗商',
  350 + supplierName: item.supplierName,
  351 + productType: item.productType,
  352 + laundryPrice: item.laundryPrice,
  353 + id: item.id
  354 + }))
  355 + },
  356 +
  357 + // 处理弹窗确认
  358 + handleModalConfirm(item) {
  359 + if (this.currentSelectField === 'batch') {
  360 + this.formData.batchNumber = item.batchNumber
  361 + this.sendRecordInfo = {
  362 + storeName: item.storeName || '',
  363 + productType: item.productType || '',
  364 + quantity: item.quantity || 0
  365 + }
  366 + // 清空已选择的清洗商
  367 + this.formData.laundrySupplierId = ''
  368 + this.formData.supplierName = ''
  369 + this.formData.laundryPrice = ''
  370 + this.selectedSupplier = null
  371 + } else if (this.currentSelectField === 'supplier') {
  372 + this.formData.laundrySupplierId = item.value
  373 + this.formData.supplierName = item.label
  374 + this.selectedSupplier = item
  375 + // 自动填充清洗单价
  376 + this.formData.laundryPrice = item.laundryPrice || ''
  377 + // 验证产品类型是否匹配
  378 + if (this.sendRecordInfo.productType && item.productType && this.sendRecordInfo.productType !== item.productType) {
  379 + uni.showToast({
  380 + title: `清洗商【${item.supplierName}】不支持清洗产品类型【${this.sendRecordInfo.productType}】`,
  381 + icon: 'none',
  382 + duration: 3000
  383 + })
  384 + }
  385 + }
  386 + this.closeModal()
  387 + },
  388 +
  389 + // 关闭弹窗
  390 + closeModal() {
  391 + this.showModal = false
  392 + this.currentSelectField = ''
  393 + this.currentOptions = []
  394 + this.modalLoading = false
  395 + this.hasMoreData = true
  396 + this.currentPage = 1
  397 + this.searchKeyword = ''
  398 + },
  399 +
  400 + // 处理加载更多
  401 + async handleLoadMore() {
  402 + // 批次和清洗商列表不需要分页
  403 + },
  404 +
  405 + // 处理刷新
  406 + async handleRefresh() {
  407 + this.currentPage = 1
  408 + this.hasMoreData = true
  409 + await this.loadOptionsData(this.currentSelectField, 1, this.searchKeyword)
  410 + },
  411 +
  412 + // 处理搜索
  413 + async handleSearch(keyword) {
  414 + this.searchKeyword = keyword
  415 + this.currentPage = 1
  416 + this.hasMoreData = true
  417 + await this.loadOptionsData(this.currentSelectField, 1, keyword)
  418 + },
  419 +
  420 + // 表单提交
  421 + async handleFormSubmit() {
  422 + // 验证必填字段
  423 + if (!this.formData.batchNumber) {
  424 + uni.showToast({
  425 + title: '请选择批次号',
  426 + icon: 'none'
  427 + })
  428 + return
  429 + }
  430 +
  431 + if (!this.formData.laundrySupplierId) {
  432 + uni.showToast({
  433 + title: '请选择清洗商',
  434 + icon: 'none'
  435 + })
  436 + return
  437 + }
  438 +
  439 + // 验证产品类型是否匹配
  440 + if (this.selectedSupplier && this.sendRecordInfo.productType && this.sendRecordInfo.productType !== this.selectedSupplier.productType) {
  441 + uni.showToast({
  442 + title: `清洗商【${this.selectedSupplier.supplierName}】不支持清洗产品类型【${this.sendRecordInfo.productType}】`,
  443 + icon: 'none',
  444 + duration: 3000
  445 + })
  446 + return
  447 + }
  448 +
  449 + if (!this.formData.quantity || this.formData.quantity <= 0) {
  450 + uni.showToast({
  451 + title: '请输入有效的送回数量',
  452 + icon: 'none'
  453 + })
  454 + return
  455 + }
  456 +
  457 + this.isSubmitting = true
  458 +
  459 + try {
  460 + uni.showLoading({
  461 + title: '提交中...'
  462 + })
  463 +
  464 + const submitData = {
  465 + batchNumber: this.formData.batchNumber,
  466 + laundrySupplierId: this.formData.laundrySupplierId,
  467 + quantity: parseInt(this.formData.quantity),
  468 + remark: this.formData.remark || ''
  469 + }
  470 +
  471 + const res = await laundryFlowApi.createReturnRecord(submitData)
  472 +
  473 + if (res.code === 200) {
  474 + uni.showToast({
  475 + title: res.msg || '创建成功',
  476 + icon: 'success'
  477 + })
  478 + setTimeout(() => {
  479 + uni.navigateBack()
  480 + }, 1500)
  481 + } else {
  482 + throw new Error(res.msg || res.message || '创建失败')
  483 + }
  484 + } catch (error) {
  485 + console.error('提交失败:', error)
  486 + uni.showToast({
  487 + title: error.message || error.msg || '创建失败,请重试',
  488 + icon: 'none',
  489 + duration: 3000
  490 + })
  491 + } finally {
  492 + this.isSubmitting = false
  493 + uni.hideLoading()
  494 + }
  495 + }
  496 + }
  497 + }
  498 +</script>
  499 +
  500 +<style lang="scss" scoped>
  501 + .form-container {
  502 + min-height: 100vh;
  503 + background: linear-gradient(135deg, #e8f5e9 0%, #b2dfdb 100%);
  504 + padding: 40rpx;
  505 + box-sizing: border-box;
  506 + }
  507 +
  508 + .form-card {
  509 + background: #fff;
  510 + border-radius: 32rpx;
  511 + box-shadow: 0 8rpx 32rpx rgba(76, 175, 80, 0.1);
  512 + overflow: hidden;
  513 + }
  514 +
  515 + .form-content {
  516 + padding: 40rpx;
  517 + }
  518 +
  519 + .form-group {
  520 + margin-bottom: 40rpx;
  521 + }
  522 +
  523 + .form-label {
  524 + display: block;
  525 + font-size: 28rpx;
  526 + color: #2e7d32;
  527 + margin-bottom: 16rpx;
  528 + font-weight: 500;
  529 + }
  530 +
  531 + .input-wrapper {
  532 + width: 100%;
  533 + }
  534 +
  535 + .custom-select {
  536 + display: flex;
  537 + align-items: center;
  538 + justify-content: space-between;
  539 + background: #f9fff9;
  540 + border: 3rpx solid #c8e6c9;
  541 + border-radius: 24rpx;
  542 + padding: 24rpx 32rpx;
  543 + min-height: 88rpx;
  544 + box-sizing: border-box;
  545 + }
  546 +
  547 + .select-text {
  548 + flex: 1;
  549 + font-size: 28rpx;
  550 + color: #2e7d32;
  551 + }
  552 +
  553 + .select-text:empty::before {
  554 + content: '请选择';
  555 + color: #999;
  556 + }
  557 +
  558 + .select-arrow {
  559 + font-size: 24rpx;
  560 + color: #6a9c6a;
  561 + margin-left: 16rpx;
  562 + }
  563 +
  564 + .select-input {
  565 + background: #f9fff9;
  566 + border: 3rpx solid #c8e6c9;
  567 + border-radius: 24rpx;
  568 + padding: 24rpx 32rpx;
  569 + font-size: 28rpx;
  570 + color: #2e7d32;
  571 + min-height: 88rpx;
  572 + box-sizing: border-box;
  573 + }
  574 +
  575 + .btn-group {
  576 + margin-top: 60rpx;
  577 + }
  578 +
  579 + .btn {
  580 + width: 100%;
  581 + height: 96rpx;
  582 + border-radius: 32rpx;
  583 + font-size: 32rpx;
  584 + font-weight: 600;
  585 + border: none;
  586 + display: flex;
  587 + align-items: center;
  588 + justify-content: center;
  589 + letter-spacing: 2rpx;
  590 + }
  591 +
  592 + .btn-primary {
  593 + background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
  594 + color: #fff;
  595 + box-shadow: 0 8rpx 24rpx rgba(67, 233, 123, 0.3);
  596 + }
  597 +
  598 + .btn-primary:active {
  599 + transform: scale(0.98);
  600 + box-shadow: 0 4rpx 12rpx rgba(67, 233, 123, 0.4);
  601 + }
  602 +</style>
  603 +
... ...
绿纤uni-app/pages/laundry-flow-send/laundry-flow-send.vue 0 → 100644
  1 +<template>
  2 + <view class="form-container">
  3 + <view class="form-card">
  4 + <view class="form-content">
  5 + <!-- 门店 -->
  6 + <view class="form-group">
  7 + <text class="form-label">门店</text>
  8 + <view class="input-wrapper">
  9 + <view class="custom-select" @tap="openSelectModal('store')">
  10 + <text class="select-text">{{ formData.storeName || '请选择门店' }}</text>
  11 + <text class="select-arrow">▼</text>
  12 + </view>
  13 + </view>
  14 + </view>
  15 +
  16 + <!-- 产品类型 -->
  17 + <view class="form-group">
  18 + <text class="form-label">产品类型</text>
  19 + <view class="input-wrapper">
  20 + <view class="custom-select" @tap="openSelectModal('productType')">
  21 + <text class="select-text">{{ formData.productType || '请选择产品类型' }}</text>
  22 + <text class="select-arrow">▼</text>
  23 + </view>
  24 + </view>
  25 + </view>
  26 +
  27 + <!-- 清洗商 -->
  28 + <view class="form-group">
  29 + <text class="form-label">清洗商</text>
  30 + <view class="input-wrapper">
  31 + <view class="custom-select" @tap="openSelectModal('supplier')">
  32 + <text class="select-text">{{ formData.supplierName || '请选择清洗商' }}</text>
  33 + <text class="select-arrow">▼</text>
  34 + </view>
  35 + </view>
  36 + </view>
  37 +
  38 + <!-- 清洗单价 -->
  39 + <view class="form-group">
  40 + <text class="form-label">清洗单价</text>
  41 + <view class="input-wrapper">
  42 + <u-input v-model="formData.laundryPrice" placeholder="自动填充"
  43 + disabled class="select-input" />
  44 + </view>
  45 + </view>
  46 +
  47 + <!-- 送出数量 -->
  48 + <view class="form-group">
  49 + <text class="form-label">送出数量</text>
  50 + <view class="input-wrapper">
  51 + <u-input v-model="formData.quantity" placeholder="请输入送出数量"
  52 + type="number" class="select-input" />
  53 + </view>
  54 + </view>
  55 +
  56 + <!-- 备注 -->
  57 + <view class="form-group">
  58 + <text class="form-label">备注</text>
  59 + <view class="input-wrapper">
  60 + <u-input v-model="formData.remark" placeholder="请输入备注"
  61 + type="textarea" :maxlength="500" class="select-input" />
  62 + </view>
  63 + </view>
  64 +
  65 + <!-- 提交按钮 -->
  66 + <view class="btn-group">
  67 + <button type="submit" class="btn btn-primary"
  68 + :style="{opacity: isSubmitting ? 0.5 : 1}"
  69 + @tap="isSubmitting ? null : handleFormSubmit()">
  70 + {{ isSubmitting ? '提交中...' : '提交' }}
  71 + </button>
  72 + </view>
  73 + </view>
  74 + </view>
  75 +
  76 + <!-- 选择弹窗 -->
  77 + <SearchSelectModal
  78 + :show="showModal"
  79 + :title="modalTitle"
  80 + :options="currentOptions"
  81 + :loading="modalLoading"
  82 + :has-more="hasMoreData"
  83 + :search-param="searchParam"
  84 + @confirm="handleModalConfirm"
  85 + @close="closeModal"
  86 + @load-more="handleLoadMore"
  87 + @refresh="handleRefresh"
  88 + @search="handleSearch" />
  89 + </view>
  90 +</template>
  91 +
  92 +<script>
  93 + import SearchSelectModal from '@/components/SearchSelectModal.vue'
  94 + import laundryFlowApi from '@/apis/modules/laundry-flow.js'
  95 + import memberApi from '@/apis/modules/member.js'
  96 +
  97 + export default {
  98 + components: {
  99 + SearchSelectModal
  100 + },
  101 + data() {
  102 + return {
  103 + isSubmitting: false,
  104 + formData: {
  105 + storeId: '',
  106 + storeName: '',
  107 + productType: '',
  108 + laundrySupplierId: '',
  109 + supplierName: '',
  110 + laundryPrice: '',
  111 + quantity: 1,
  112 + remark: ''
  113 + },
  114 + selectedSupplier: null,
  115 + productTypeOptions: [],
  116 + // 选择弹窗相关
  117 + showModal: false,
  118 + modalTitle: '',
  119 + currentSelectField: '',
  120 + currentOptions: [],
  121 + modalLoading: false,
  122 + hasMoreData: true,
  123 + currentPage: 1,
  124 + pageSize: 20,
  125 + searchKeyword: '',
  126 + searchParam: ''
  127 + }
  128 + },
  129 +
  130 + onLoad() {
  131 + this.checkLoginStatus()
  132 + this.loadProductTypeOptions()
  133 + },
  134 +
  135 + methods: {
  136 + // 检查登录状态
  137 + checkLoginStatus() {
  138 + const token = uni.getStorageSync('token')
  139 + if (!token) {
  140 + uni.reLaunch({
  141 + url: '/pages/login/login'
  142 + })
  143 + return
  144 + }
  145 + },
  146 +
  147 + // 打开选择弹窗
  148 + openSelectModal(field) {
  149 + this.currentSelectField = field
  150 + this.currentPage = 1
  151 + this.hasMoreData = true
  152 + this.searchKeyword = ''
  153 + this.searchParam = ''
  154 +
  155 + if (field === 'store') {
  156 + this.modalTitle = '选择门店'
  157 + this.searchParam = '请输入门店名称'
  158 + } else if (field === 'supplier') {
  159 + this.modalTitle = '选择清洗商'
  160 + this.searchParam = '请输入清洗商名称'
  161 + } else if (field === 'productType') {
  162 + this.modalTitle = '选择产品类型'
  163 + this.searchParam = '请输入产品类型名称'
  164 + }
  165 +
  166 + this.showModal = true
  167 + this.loadOptionsData(field, 1)
  168 + },
  169 +
  170 + // 加载选项数据
  171 + async loadOptionsData(fieldId, page = 1, searchKeyword = '') {
  172 + this.modalLoading = true
  173 + let options = []
  174 +
  175 + try {
  176 + if (fieldId === 'store') {
  177 + options = await this.getStoreOptions(page, searchKeyword)
  178 + } else if (fieldId === 'supplier') {
  179 + options = await this.getSupplierOptions(page, searchKeyword)
  180 + } else if (fieldId === 'productType') {
  181 + options = this.getProductTypeOptions(searchKeyword)
  182 + }
  183 +
  184 + if (page === 1) {
  185 + this.currentOptions = options
  186 + } else {
  187 + this.currentOptions = [...this.currentOptions, ...options]
  188 + }
  189 +
  190 + this.hasMoreData = options.length === this.pageSize
  191 + } catch (error) {
  192 + console.error('加载选项数据失败:', error)
  193 + this.currentOptions = []
  194 + } finally {
  195 + this.modalLoading = false
  196 + }
  197 + },
  198 +
  199 + // 获取门店选项
  200 + async getStoreOptions(page = 1, searchKeyword = '') {
  201 + try {
  202 + const params = {
  203 + currentPage: page,
  204 + pageSize: this.pageSize
  205 + }
  206 + if (searchKeyword) {
  207 + params.dm = searchKeyword
  208 + }
  209 +
  210 + const result = await memberApi.getStoreList(params)
  211 + if (result.code === 200 && result.data) {
  212 + let storeList = []
  213 + if (Array.isArray(result.data)) {
  214 + storeList = result.data
  215 + } else if (result.data.list) {
  216 + storeList = result.data.list
  217 + }
  218 +
  219 + const options = storeList.map(item => ({
  220 + value: item.id,
  221 + label: item.dm || '未知门店',
  222 + fullName: item.dm,
  223 + id: item.id
  224 + }))
  225 +
  226 + return options
  227 + }
  228 + return []
  229 + } catch (error) {
  230 + console.error('获取门店列表失败:', error)
  231 + return []
  232 + }
  233 + },
  234 +
  235 + // 获取清洗商选项
  236 + async getSupplierOptions(page = 1, searchKeyword = '') {
  237 + try {
  238 + const params = {
  239 + currentPage: page,
  240 + pageSize: this.pageSize,
  241 + isEffective: 1
  242 + }
  243 + if (searchKeyword) {
  244 + params.supplierName = searchKeyword
  245 + }
  246 +
  247 + const result = await laundryFlowApi.getSupplierList(params)
  248 + if (result.code === 200 && result.data) {
  249 + const list = result.data.list || []
  250 + const options = list.map(item => ({
  251 + value: item.id,
  252 + label: item.supplierName || '未知清洗商',
  253 + supplierName: item.supplierName,
  254 + productType: item.productType,
  255 + laundryPrice: item.laundryPrice,
  256 + id: item.id
  257 + }))
  258 +
  259 + return options
  260 + }
  261 + return []
  262 + } catch (error) {
  263 + console.error('获取清洗商列表失败:', error)
  264 + return []
  265 + }
  266 + },
  267 +
  268 + // 加载产品类型选项
  269 + async loadProductTypeOptions() {
  270 + try {
  271 + const result = await laundryFlowApi.getProductTypeList()
  272 + if (result.code === 200 && result.data && Array.isArray(result.data)) {
  273 + this.productTypeOptions = result.data
  274 + } else {
  275 + this.productTypeOptions = []
  276 + }
  277 + } catch (error) {
  278 + console.error('获取产品类型列表失败:', error)
  279 + this.productTypeOptions = []
  280 + }
  281 + },
  282 +
  283 + // 获取产品类型选项
  284 + getProductTypeOptions(searchKeyword = '') {
  285 + let list = this.productTypeOptions
  286 +
  287 + if (searchKeyword) {
  288 + list = list.filter(item =>
  289 + item.Name && item.Name.includes(searchKeyword)
  290 + )
  291 + }
  292 +
  293 + return list.map(item => ({
  294 + value: item.Name,
  295 + label: item.Name || '未知产品类型',
  296 + Name: item.Name
  297 + }))
  298 + },
  299 +
  300 + // 处理弹窗确认
  301 + handleModalConfirm(item) {
  302 + if (this.currentSelectField === 'store') {
  303 + this.formData.storeId = item.value
  304 + this.formData.storeName = item.label
  305 + } else if (this.currentSelectField === 'productType') {
  306 + this.formData.productType = item.value
  307 + // 验证产品类型是否匹配已选择的清洗商
  308 + if (this.selectedSupplier && this.selectedSupplier.productType && this.formData.productType !== this.selectedSupplier.productType) {
  309 + uni.showToast({
  310 + title: `清洗商【${this.selectedSupplier.supplierName}】不支持清洗产品类型【${this.formData.productType}】`,
  311 + icon: 'none',
  312 + duration: 3000
  313 + })
  314 + }
  315 + } else if (this.currentSelectField === 'supplier') {
  316 + this.formData.laundrySupplierId = item.value
  317 + this.formData.supplierName = item.label
  318 + this.selectedSupplier = item
  319 + // 自动填充清洗单价
  320 + this.formData.laundryPrice = item.laundryPrice || ''
  321 + // 验证产品类型是否匹配
  322 + if (this.formData.productType && item.productType && this.formData.productType !== item.productType) {
  323 + uni.showToast({
  324 + title: `清洗商【${item.supplierName}】不支持清洗产品类型【${this.formData.productType}】`,
  325 + icon: 'none',
  326 + duration: 3000
  327 + })
  328 + }
  329 + }
  330 + this.closeModal()
  331 + },
  332 +
  333 + // 关闭弹窗
  334 + closeModal() {
  335 + this.showModal = false
  336 + this.currentSelectField = ''
  337 + this.currentOptions = []
  338 + this.modalLoading = false
  339 + this.hasMoreData = true
  340 + this.currentPage = 1
  341 + this.searchKeyword = ''
  342 + },
  343 +
  344 + // 处理加载更多
  345 + async handleLoadMore() {
  346 + if (!this.hasMoreData || this.modalLoading) return
  347 + this.currentPage++
  348 + await this.loadOptionsData(this.currentSelectField, this.currentPage, this.searchKeyword)
  349 + },
  350 +
  351 + // 处理刷新
  352 + async handleRefresh() {
  353 + this.currentPage = 1
  354 + this.hasMoreData = true
  355 + await this.loadOptionsData(this.currentSelectField, 1, this.searchKeyword)
  356 + },
  357 +
  358 + // 处理搜索
  359 + async handleSearch(keyword) {
  360 + this.searchKeyword = keyword
  361 + this.currentPage = 1
  362 + this.hasMoreData = true
  363 + await this.loadOptionsData(this.currentSelectField, 1, keyword)
  364 + },
  365 +
  366 + // 表单提交
  367 + async handleFormSubmit() {
  368 + // 验证必填字段
  369 + if (!this.formData.storeId) {
  370 + uni.showToast({
  371 + title: '请选择门店',
  372 + icon: 'none'
  373 + })
  374 + return
  375 + }
  376 +
  377 + if (!this.formData.productType) {
  378 + uni.showToast({
  379 + title: '请选择产品类型',
  380 + icon: 'none'
  381 + })
  382 + return
  383 + }
  384 +
  385 + if (!this.formData.laundrySupplierId) {
  386 + uni.showToast({
  387 + title: '请选择清洗商',
  388 + icon: 'none'
  389 + })
  390 + return
  391 + }
  392 +
  393 + // 验证产品类型是否匹配
  394 + if (this.selectedSupplier && this.formData.productType !== this.selectedSupplier.productType) {
  395 + uni.showToast({
  396 + title: `清洗商【${this.selectedSupplier.supplierName}】不支持清洗产品类型【${this.formData.productType}】`,
  397 + icon: 'none',
  398 + duration: 3000
  399 + })
  400 + return
  401 + }
  402 +
  403 + if (!this.formData.quantity || this.formData.quantity <= 0) {
  404 + uni.showToast({
  405 + title: '请输入有效的送出数量',
  406 + icon: 'none'
  407 + })
  408 + return
  409 + }
  410 +
  411 + this.isSubmitting = true
  412 +
  413 + try {
  414 + uni.showLoading({
  415 + title: '提交中...'
  416 + })
  417 +
  418 + const submitData = {
  419 + storeId: this.formData.storeId,
  420 + productType: this.formData.productType,
  421 + laundrySupplierId: this.formData.laundrySupplierId,
  422 + quantity: parseInt(this.formData.quantity),
  423 + remark: this.formData.remark || ''
  424 + }
  425 +
  426 + const res = await laundryFlowApi.createSendRecord(submitData)
  427 +
  428 + if (res.code === 200) {
  429 + uni.showToast({
  430 + title: res.msg || '创建成功',
  431 + icon: 'success'
  432 + })
  433 + setTimeout(() => {
  434 + uni.navigateBack()
  435 + }, 1500)
  436 + } else {
  437 + throw new Error(res.msg || res.message || '创建失败')
  438 + }
  439 + } catch (error) {
  440 + console.error('提交失败:', error)
  441 + uni.showToast({
  442 + title: error.message || error.msg || '创建失败,请重试',
  443 + icon: 'none',
  444 + duration: 3000
  445 + })
  446 + } finally {
  447 + this.isSubmitting = false
  448 + uni.hideLoading()
  449 + }
  450 + }
  451 + }
  452 + }
  453 +</script>
  454 +
  455 +<style lang="scss" scoped>
  456 + .form-container {
  457 + min-height: 100vh;
  458 + background: linear-gradient(135deg, #e8f5e9 0%, #b2dfdb 100%);
  459 + padding: 40rpx;
  460 + box-sizing: border-box;
  461 + }
  462 +
  463 + .form-card {
  464 + background: #fff;
  465 + border-radius: 32rpx;
  466 + box-shadow: 0 8rpx 32rpx rgba(76, 175, 80, 0.1);
  467 + overflow: hidden;
  468 + }
  469 +
  470 + .form-content {
  471 + padding: 40rpx;
  472 + }
  473 +
  474 + .form-group {
  475 + margin-bottom: 40rpx;
  476 + }
  477 +
  478 + .form-label {
  479 + display: block;
  480 + font-size: 28rpx;
  481 + color: #2e7d32;
  482 + margin-bottom: 16rpx;
  483 + font-weight: 500;
  484 + }
  485 +
  486 + .input-wrapper {
  487 + width: 100%;
  488 + }
  489 +
  490 + .custom-select {
  491 + display: flex;
  492 + align-items: center;
  493 + justify-content: space-between;
  494 + background: #f9fff9;
  495 + border: 3rpx solid #c8e6c9;
  496 + border-radius: 24rpx;
  497 + padding: 24rpx 32rpx;
  498 + min-height: 88rpx;
  499 + box-sizing: border-box;
  500 + }
  501 +
  502 + .select-text {
  503 + flex: 1;
  504 + font-size: 28rpx;
  505 + color: #2e7d32;
  506 + }
  507 +
  508 + .select-text:empty::before {
  509 + content: '请选择';
  510 + color: #999;
  511 + }
  512 +
  513 + .select-arrow {
  514 + font-size: 24rpx;
  515 + color: #6a9c6a;
  516 + margin-left: 16rpx;
  517 + }
  518 +
  519 + .select-input {
  520 + background: #f9fff9;
  521 + border: 3rpx solid #c8e6c9;
  522 + border-radius: 24rpx;
  523 + padding: 24rpx 32rpx;
  524 + font-size: 28rpx;
  525 + color: #2e7d32;
  526 + min-height: 88rpx;
  527 + box-sizing: border-box;
  528 + }
  529 +
  530 + .btn-group {
  531 + margin-top: 60rpx;
  532 + }
  533 +
  534 + .btn {
  535 + width: 100%;
  536 + height: 96rpx;
  537 + border-radius: 32rpx;
  538 + font-size: 32rpx;
  539 + font-weight: 600;
  540 + border: none;
  541 + display: flex;
  542 + align-items: center;
  543 + justify-content: center;
  544 + letter-spacing: 2rpx;
  545 + }
  546 +
  547 + .btn-primary {
  548 + background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
  549 + color: #fff;
  550 + box-shadow: 0 8rpx 24rpx rgba(67, 233, 123, 0.3);
  551 + }
  552 +
  553 + .btn-primary:active {
  554 + transform: scale(0.98);
  555 + box-shadow: 0 4rpx 12rpx rgba(67, 233, 123, 0.4);
  556 + }
  557 +</style>
  558 +
... ...
绿纤uni-app/pages/usage-form/usage-form.vue 0 → 100644
  1 +<template>
  2 + <view class="form-container">
  3 + <view class="form-card">
  4 + <view class="form-content">
  5 + <!-- 使用记录列表 -->
  6 + <view v-for="(item, index) in usageItems" :key="index" class="usage-item-card">
  7 + <view class="item-header">
  8 + <text class="item-title">使用记录 {{ index + 1 }}</text>
  9 + <view v-if="usageItems.length > 1" class="delete-btn" @click="removeItem(index)">
  10 + <text class="delete-icon">🗑️</text>
  11 + </view>
  12 + </view>
  13 +
  14 + <!-- 产品 -->
  15 + <view class="form-group">
  16 + <text class="form-label">产品</text>
  17 + <view class="input-wrapper">
  18 + <view class="custom-select" @tap="openSelectModal('product', index)">
  19 + <text class="select-text">{{ item.productName || '请选择产品' }}</text>
  20 + <text class="select-arrow">▼</text>
  21 + </view>
  22 + </view>
  23 + </view>
  24 +
  25 + <!-- 门店 -->
  26 + <view class="form-group">
  27 + <text class="form-label">门店</text>
  28 + <view class="input-wrapper">
  29 + <view class="custom-select" @tap="openSelectModal('store', index)">
  30 + <text class="select-text">{{ item.storeName || '请选择门店' }}</text>
  31 + <text class="select-arrow">▼</text>
  32 + </view>
  33 + </view>
  34 + </view>
  35 +
  36 + <!-- 使用数量 -->
  37 + <view class="form-group">
  38 + <text class="form-label">使用数量</text>
  39 + <view class="input-wrapper">
  40 + <u-input v-model="item.usageQuantity" placeholder="请输入使用数量" type="number"
  41 + class="select-input" />
  42 + </view>
  43 + </view>
  44 +
  45 + <!-- 使用时间 -->
  46 + <view class="form-group">
  47 + <text class="form-label">使用时间</text>
  48 + <view class="input-wrapper">
  49 + <view class="custom-select" @tap="openDateTimePicker(index)">
  50 + <text class="select-text">{{ item.usageTimeStr || '请选择使用时间' }}</text>
  51 + <text class="select-arrow">▼</text>
  52 + </view>
  53 + </view>
  54 + </view>
  55 +
  56 + <!-- 分隔线 -->
  57 + <view v-if="index < usageItems.length - 1" class="divider"></view>
  58 + </view>
  59 +
  60 + <!-- 添加记录按钮 -->
  61 + <view class="add-item-btn" @click="addItem">
  62 + <text class="add-text">添加一条记录</text>
  63 + </view>
  64 +
  65 + <!-- 提交按钮 -->
  66 + <view class="btn-group">
  67 + <button type="submit" class="btn btn-primary"
  68 + :style="{opacity: isSubmitting ? 0.5 : 1}"
  69 + @tap="isSubmitting ? null : handleFormSubmit()">
  70 + {{ isSubmitting ? '提交中...' : '提交' }}
  71 + </button>
  72 + </view>
  73 + </view>
  74 + </view>
  75 +
  76 + <!-- 选择弹窗 -->
  77 + <SearchSelectModal
  78 + :show="showModal"
  79 + :title="modalTitle"
  80 + :options="currentOptions"
  81 + :loading="modalLoading"
  82 + :has-more="hasMoreData"
  83 + :search-param="searchParam"
  84 + @confirm="handleModalConfirm"
  85 + @close="closeModal"
  86 + @load-more="handleLoadMore"
  87 + @refresh="handleRefresh"
  88 + @search="handleSearch" />
  89 +
  90 + <!-- 日期时间选择器 -->
  91 + <u-datetime-picker
  92 + v-if="showDateTimePicker"
  93 + :show="showDateTimePicker"
  94 + :value="currentDateTimeValue"
  95 + mode="datetime"
  96 + @confirm="onDateTimeConfirm"
  97 + @close="closeDateTimePicker"
  98 + @cancel="closeDateTimePicker"
  99 + :show-toolbar="true"
  100 + :close-on-click-overlay="true"
  101 + confirm-color="#43e97b"
  102 + title="选择使用时间"
  103 + class="datetime-picker" />
  104 + </view>
  105 +</template>
  106 +
  107 +<script>
  108 + import SearchSelectModal from '@/components/SearchSelectModal.vue'
  109 + import usageApi from '@/apis/modules/usage.js'
  110 + import memberApi from '@/apis/modules/member.js'
  111 + import request from '@/service/request.js'
  112 + import config from '@/common/config.js'
  113 +
  114 + export default {
  115 + components: {
  116 + SearchSelectModal
  117 + },
  118 + data() {
  119 + return {
  120 + isSubmitting: false,
  121 + usageItems: [
  122 + {
  123 + productId: '',
  124 + productName: '',
  125 + storeId: '',
  126 + storeName: '',
  127 + usageTime: '',
  128 + usageTimeStr: '',
  129 + usageQuantity: 1,
  130 + relatedConsumeId: ''
  131 + }
  132 + ],
  133 + // 选择弹窗相关
  134 + showModal: false,
  135 + modalTitle: '',
  136 + currentSelectField: '',
  137 + currentSelectIndex: 0,
  138 + currentOptions: [],
  139 + modalLoading: false,
  140 + hasMoreData: true,
  141 + currentPage: 1,
  142 + pageSize: 20,
  143 + searchKeyword: '',
  144 + searchParam: '',
  145 + // 用户信息
  146 + userInfo: null,
  147 + newuserInfo: null,
  148 + // 日期时间选择器相关
  149 + showDateTimePicker: false,
  150 + currentDateTimeIndex: -1,
  151 + currentDateTimeValue: 0
  152 + }
  153 + },
  154 +
  155 + onLoad() {
  156 + this.initializePage()
  157 + },
  158 +
  159 + methods: {
  160 + // 初始化页面
  161 + async initializePage() {
  162 + try {
  163 + // 获取用户信息
  164 + this.userInfo = uni.getStorageSync('userInfo')
  165 + this.newuserInfo = uni.getStorageSync('newuserInfo')
  166 +
  167 + if (!this.userInfo || Object.keys(this.userInfo).length === 0) {
  168 + uni.showToast({
  169 + title: '请先登录',
  170 + icon: 'none'
  171 + })
  172 + setTimeout(() => {
  173 + uni.reLaunch({
  174 + url: '/pages/login/login'
  175 + })
  176 + }, 1500)
  177 + return
  178 + }
  179 +
  180 + // 设置默认值
  181 + this.setDefaultValues()
  182 + } catch (error) {
  183 + console.error('页面初始化失败:', error)
  184 + uni.showToast({
  185 + title: '页面初始化失败',
  186 + icon: 'none'
  187 + })
  188 + }
  189 + },
  190 +
  191 + // 设置默认值
  192 + setDefaultValues() {
  193 + // 使用时间:默认当前时间
  194 + const now = new Date()
  195 + this.usageItems.forEach(item => {
  196 + if (!item.usageTimeStr) {
  197 + item.usageTime = now.getTime()
  198 + item.usageTimeStr = this.formatDateTime(now)
  199 + }
  200 + })
  201 + },
  202 +
  203 + // 格式化日期时间
  204 + formatDateTime(date) {
  205 + const year = date.getFullYear()
  206 + const month = String(date.getMonth() + 1).padStart(2, '0')
  207 + const day = String(date.getDate()).padStart(2, '0')
  208 + const hours = String(date.getHours()).padStart(2, '0')
  209 + const minutes = String(date.getMinutes()).padStart(2, '0')
  210 + return `${year}-${month}-${day} ${hours}:${minutes}`
  211 + },
  212 +
  213 + // 打开选择弹窗
  214 + async openSelectModal(fieldId, index) {
  215 + this.currentSelectField = fieldId
  216 + this.currentSelectIndex = index
  217 + this.showModal = true
  218 + this.modalTitle = '加载中...'
  219 + this.modalLoading = true
  220 + this.currentPage = 1
  221 + this.hasMoreData = true
  222 + this.searchKeyword = ''
  223 + this.currentOptions = []
  224 +
  225 + try {
  226 + if (fieldId === 'product') {
  227 + this.searchParam = 'productName'
  228 + this.modalTitle = '选择产品'
  229 + } else if (fieldId === 'store') {
  230 + this.searchParam = 'fullName'
  231 + this.modalTitle = '选择门店'
  232 + }
  233 +
  234 + await this.loadOptionsData(fieldId, 1)
  235 + } catch (error) {
  236 + console.error('获取选项数据失败:', error)
  237 + this.modalTitle = '加载失败'
  238 + this.currentOptions = []
  239 + uni.showToast({
  240 + title: '数据加载失败,请检查网络连接',
  241 + icon: 'none'
  242 + })
  243 + } finally {
  244 + this.modalLoading = false
  245 + }
  246 + },
  247 +
  248 + // 关闭弹窗
  249 + closeModal() {
  250 + this.showModal = false
  251 + this.currentSelectField = ''
  252 + this.currentSelectIndex = 0
  253 + this.currentOptions = []
  254 + this.modalLoading = false
  255 + this.hasMoreData = true
  256 + this.currentPage = 1
  257 + this.searchKeyword = ''
  258 + },
  259 +
  260 + // 加载选项数据
  261 + async loadOptionsData(fieldId, page = 1, searchKeyword = '') {
  262 + let options = []
  263 +
  264 + try {
  265 + if (fieldId === 'product') {
  266 + // 加载产品列表(只获取上架的产品)
  267 + const params = {
  268 + currentPage: page,
  269 + pageSize: this.pageSize,
  270 + onShelfStatus: 1
  271 + }
  272 + if (searchKeyword) {
  273 + params.productName = searchKeyword
  274 + }
  275 +
  276 + const res = await request.get(`${config.getApiBaseUrl()}/api/Extend/LqProduct/GetList`, params)
  277 +
  278 + if (res.code === 200 && res.data) {
  279 + const list = res.data.list || []
  280 + options = list.map(item => ({
  281 + value: item.id,
  282 + label: item.productName || '未知产品',
  283 + productName: item.productName,
  284 + id: item.id
  285 + }))
  286 + }
  287 + } else if (fieldId === 'store') {
  288 + // 加载门店列表
  289 + options = await this.getStoreOptions(page, searchKeyword)
  290 + }
  291 +
  292 + if (page === 1) {
  293 + this.currentOptions = options
  294 + } else {
  295 + this.currentOptions = [...this.currentOptions, ...options]
  296 + }
  297 +
  298 + this.hasMoreData = options.length === this.pageSize
  299 + } catch (error) {
  300 + console.error('加载选项数据失败:', error)
  301 + this.currentOptions = []
  302 + }
  303 + },
  304 +
  305 + // 获取门店选项
  306 + async getStoreOptions(page = 1, searchKeyword = '') {
  307 + try {
  308 + const params = {
  309 + currentPage: page,
  310 + pageSize: this.pageSize
  311 + }
  312 + if (searchKeyword) {
  313 + params.dm = searchKeyword
  314 + }
  315 +
  316 + const result = await memberApi.getStoreList(params)
  317 + if (result.code === 200 && result.data) {
  318 + // 处理分页数据
  319 + let storeList = []
  320 + if (Array.isArray(result.data)) {
  321 + storeList = result.data
  322 + } else if (result.data.list) {
  323 + storeList = result.data.list
  324 + }
  325 +
  326 + const options = storeList.map(item => ({
  327 + value: item.id,
  328 + label: item.dm || '未知门店',
  329 + fullName: item.dm,
  330 + id: item.id
  331 + }))
  332 +
  333 + return options
  334 + }
  335 + return []
  336 + } catch (error) {
  337 + console.error('获取门店列表失败:', error)
  338 + return []
  339 + }
  340 + },
  341 +
  342 + // 处理弹窗确认
  343 + handleModalConfirm(item) {
  344 + const index = this.currentSelectIndex
  345 + if (this.currentSelectField === 'product') {
  346 + this.usageItems[index].productId = item.value
  347 + this.usageItems[index].productName = item.label
  348 + } else if (this.currentSelectField === 'store') {
  349 + this.usageItems[index].storeId = item.value
  350 + this.usageItems[index].storeName = item.label
  351 + }
  352 + this.closeModal()
  353 + },
  354 +
  355 + // 处理加载更多
  356 + async handleLoadMore() {
  357 + if (!this.hasMoreData || this.modalLoading) return
  358 + this.currentPage++
  359 + await this.loadOptionsData(this.currentSelectField, this.currentPage, this.searchKeyword)
  360 + },
  361 +
  362 + // 处理刷新
  363 + async handleRefresh() {
  364 + this.currentPage = 1
  365 + this.hasMoreData = true
  366 + await this.loadOptionsData(this.currentSelectField, 1, this.searchKeyword)
  367 + },
  368 +
  369 + // 处理搜索
  370 + async handleSearch(keyword) {
  371 + this.searchKeyword = keyword
  372 + this.currentPage = 1
  373 + this.hasMoreData = true
  374 + await this.loadOptionsData(this.currentSelectField, 1, keyword)
  375 + },
  376 +
  377 + // 打开日期时间选择器
  378 + openDateTimePicker(index) {
  379 + this.currentDateTimeIndex = index
  380 + const item = this.usageItems[index]
  381 + this.currentDateTimeValue = item.usageTime || new Date().getTime()
  382 + this.showDateTimePicker = true
  383 + },
  384 +
  385 + // 日期时间确认
  386 + onDateTimeConfirm(e) {
  387 + const timestamp = e.value
  388 + const index = this.currentDateTimeIndex
  389 + if (index >= 0 && index < this.usageItems.length) {
  390 + const date = new Date(timestamp)
  391 + this.usageItems[index].usageTime = timestamp
  392 + this.usageItems[index].usageTimeStr = this.formatDateTime(date)
  393 + }
  394 + this.closeDateTimePicker()
  395 + },
  396 +
  397 + // 关闭日期时间选择器
  398 + closeDateTimePicker() {
  399 + this.showDateTimePicker = false
  400 + this.currentDateTimeIndex = -1
  401 + this.currentDateTimeValue = 0
  402 + },
  403 +
  404 + // 添加记录
  405 + addItem() {
  406 + const now = new Date()
  407 + this.usageItems.push({
  408 + productId: '',
  409 + productName: '',
  410 + storeId: '',
  411 + storeName: '',
  412 + usageTime: now.getTime(),
  413 + usageTimeStr: this.formatDateTime(now),
  414 + usageQuantity: 1,
  415 + relatedConsumeId: ''
  416 + })
  417 + },
  418 +
  419 + // 删除记录
  420 + removeItem(index) {
  421 + if (this.usageItems.length <= 1) {
  422 + uni.showToast({
  423 + title: '至少保留一条记录',
  424 + icon: 'none'
  425 + })
  426 + return
  427 + }
  428 + this.usageItems.splice(index, 1)
  429 + },
  430 +
  431 + // 表单提交
  432 + async handleFormSubmit() {
  433 + // 验证所有表单项
  434 + let isValid = true
  435 + let errorMessage = ''
  436 +
  437 + this.usageItems.forEach((item, index) => {
  438 + if (!item.productId) {
  439 + errorMessage = `第${index + 1}条记录:请选择产品`
  440 + isValid = false
  441 + }
  442 + if (!item.storeId) {
  443 + errorMessage = `第${index + 1}条记录:请选择门店`
  444 + isValid = false
  445 + }
  446 + if (!item.usageQuantity || item.usageQuantity <= 0) {
  447 + errorMessage = `第${index + 1}条记录:请输入使用数量`
  448 + isValid = false
  449 + }
  450 + if (!item.usageTime) {
  451 + errorMessage = `第${index + 1}条记录:请选择使用时间`
  452 + isValid = false
  453 + }
  454 + })
  455 +
  456 + if (!isValid) {
  457 + uni.showToast({
  458 + title: errorMessage,
  459 + icon: 'none',
  460 + duration: 3000
  461 + })
  462 + return
  463 + }
  464 +
  465 + if (this.isSubmitting) return
  466 +
  467 + try {
  468 + this.isSubmitting = true
  469 + uni.showLoading({
  470 + title: '提交中...'
  471 + })
  472 +
  473 + // 转换日期时间为ISO格式
  474 + const usageItems = this.usageItems.map(item => {
  475 + let usageTime = item.usageTime
  476 + // 如果日期时间不是ISO格式,转换为ISO格式
  477 + if (usageTime && typeof usageTime === 'number') {
  478 + const date = new Date(usageTime)
  479 + usageTime = date.toISOString()
  480 + }
  481 + return {
  482 + productId: item.productId,
  483 + storeId: item.storeId,
  484 + usageTime: usageTime,
  485 + usageQuantity: item.usageQuantity,
  486 + relatedConsumeId: item.relatedConsumeId || ''
  487 + }
  488 + })
  489 +
  490 + const res = await usageApi.batchCreateUsage({
  491 + usageItems: usageItems
  492 + })
  493 +
  494 + if (res.code === 200) {
  495 + uni.showToast({
  496 + title: res.msg || res.message || '添加成功',
  497 + icon: 'success'
  498 + })
  499 + setTimeout(() => {
  500 + uni.navigateBack()
  501 + }, 1500)
  502 + } else {
  503 + throw new Error(res.message || res.msg || '操作失败')
  504 + }
  505 + } catch (error) {
  506 + console.error('提交失败:', error)
  507 + uni.showToast({
  508 + title: error.message || error.msg || '提交失败,请重试',
  509 + icon: 'none'
  510 + })
  511 + } finally {
  512 + this.isSubmitting = false
  513 + uni.hideLoading()
  514 + }
  515 + }
  516 + }
  517 + }
  518 +</script>
  519 +
  520 +<style lang="scss" scoped>
  521 + .form-container {
  522 + min-height: 100vh;
  523 + background: linear-gradient(135deg, #e8f5e9 0%, #b2dfdb 100%);
  524 + padding: 40rpx;
  525 + box-sizing: border-box;
  526 + }
  527 +
  528 + .form-card {
  529 + background: #fff;
  530 + border-radius: 32rpx;
  531 + box-shadow: 0 8rpx 32rpx rgba(76, 175, 80, 0.1);
  532 + overflow: hidden;
  533 + }
  534 +
  535 + .form-content {
  536 + padding: 40rpx;
  537 + }
  538 +
  539 + .usage-item-card {
  540 + margin-bottom: 40rpx;
  541 + }
  542 +
  543 + .item-header {
  544 + display: flex;
  545 + justify-content: space-between;
  546 + align-items: center;
  547 + margin-bottom: 32rpx;
  548 + padding-bottom: 16rpx;
  549 + border-bottom: 2rpx solid #f0f0f0;
  550 + }
  551 +
  552 + .item-title {
  553 + font-size: 32rpx;
  554 + font-weight: 600;
  555 + color: #2e7d32;
  556 + }
  557 +
  558 + .delete-btn {
  559 + padding: 8rpx 16rpx;
  560 + border-radius: 16rpx;
  561 + background: #ffebee;
  562 + display: flex;
  563 + align-items: center;
  564 + justify-content: center;
  565 + }
  566 +
  567 + .delete-icon {
  568 + font-size: 28rpx;
  569 + }
  570 +
  571 + .form-group {
  572 + margin-bottom: 32rpx;
  573 + }
  574 +
  575 + .form-label {
  576 + display: block;
  577 + font-size: 28rpx;
  578 + color: #2e7d32;
  579 + margin-bottom: 16rpx;
  580 + font-weight: 500;
  581 + }
  582 +
  583 + .input-wrapper {
  584 + width: 100%;
  585 + }
  586 +
  587 + .custom-select {
  588 + display: flex;
  589 + align-items: center;
  590 + justify-content: space-between;
  591 + background: #f9fff9;
  592 + border: 3rpx solid #c8e6c9;
  593 + border-radius: 24rpx;
  594 + padding: 24rpx 32rpx;
  595 + min-height: 80rpx;
  596 + box-sizing: border-box;
  597 + }
  598 +
  599 + .select-text {
  600 + font-size: 28rpx;
  601 + color: #2e7d32;
  602 + flex: 1;
  603 + }
  604 +
  605 + .select-text:empty::before {
  606 + content: attr(placeholder);
  607 + color: #999;
  608 + }
  609 +
  610 + .select-arrow {
  611 + font-size: 24rpx;
  612 + color: #6a9c6a;
  613 + margin-left: 16rpx;
  614 + }
  615 +
  616 + .select-input {
  617 + background: #f9fff9;
  618 + border: 3rpx solid #c8e6c9;
  619 + border-radius: 24rpx;
  620 + padding: 24rpx 32rpx;
  621 + font-size: 28rpx;
  622 + color: #2e7d32;
  623 + }
  624 +
  625 + .divider {
  626 + height: 2rpx;
  627 + background: #f0f0f0;
  628 + margin: 40rpx 0;
  629 + }
  630 +
  631 + .add-item-btn {
  632 + display: flex;
  633 + align-items: center;
  634 + justify-content: center;
  635 + padding: 24rpx;
  636 + background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
  637 + border: 3rpx dashed #43a047;
  638 + border-radius: 24rpx;
  639 + margin-bottom: 40rpx;
  640 + }
  641 +
  642 + .add-icon {
  643 + font-size: 32rpx;
  644 + margin-right: 12rpx;
  645 + }
  646 +
  647 + .add-text {
  648 + font-size: 28rpx;
  649 + color: #2e7d32;
  650 + font-weight: 500;
  651 + }
  652 +
  653 + .btn-group {
  654 + margin-top: 40rpx;
  655 + }
  656 +
  657 + .btn {
  658 + width: 100%;
  659 + padding:6rpx 28rpx;
  660 + border-radius: 32rpx;
  661 + font-size: 32rpx;
  662 + font-weight: 600;
  663 + letter-spacing: 2rpx;
  664 + border: none;
  665 + }
  666 +
  667 + .btn-primary {
  668 + background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
  669 + color: #fff;
  670 + box-shadow: 0 8rpx 24rpx rgba(67, 233, 123, 0.3);
  671 + }
  672 +
  673 + .btn-primary:active {
  674 + transform: scale(0.98);
  675 + box-shadow: 0 4rpx 12rpx rgba(67, 233, 123, 0.4);
  676 + }
  677 +</style>
  678 +
... ...
绿纤uni-app/pages/usage-list/usage-list.vue 0 → 100644
  1 +<template>
  2 + <view class="container">
  3 + <!-- 日期范围筛选 -->
  4 + <view class="date-filter-card">
  5 + <view class="date-filter-header">
  6 + <view class="date-range-display" @click="showCalendarFun">
  7 + <text class="date-text">{{ formatDateRange() }}</text>
  8 + <text class="calendar-icon">📅</text>
  9 + </view>
  10 + </view>
  11 + </view>
  12 +
  13 + <!-- 添加按钮 -->
  14 + <view class="add-btn-wrapper">
  15 + <view class="add-btn" @click="goToAdd">
  16 + <text class="add-text">添加使用记录</text>
  17 + </view>
  18 + </view>
  19 +
  20 + <!-- 日历组件 -->
  21 + <uni-calendar
  22 + :range="true"
  23 + ref="calendar"
  24 + @confirm="handleDateConfirm"
  25 + :insert="false"
  26 + ></uni-calendar>
  27 +
  28 + <!-- 数据列表 -->
  29 + <view class="list-card">
  30 + <view class="list-header">
  31 + <text class="header-text">使用记录</text>
  32 + <text class="total-count">共 {{ totalCount }} 条</text>
  33 + </view>
  34 +
  35 + <scroll-view scroll-y class="list-content" @scrolltolower="loadMore">
  36 + <view v-for="(item, index) in dataList" :key="index" class="list-item">
  37 + <view class="item-header">
  38 + <view class="product-name">{{ item.productName || '无' }}</view>
  39 + <view class="usage-time">{{ formatTime(item.usageTime) }}</view>
  40 + </view>
  41 + <view class="item-details">
  42 + <view class="detail-item">
  43 + <text class="detail-label">门店:</text>
  44 + <text class="detail-value">{{ item.storeName || '无' }}</text>
  45 + </view>
  46 + <view class="detail-item">
  47 + <text class="detail-label">使用数量:</text>
  48 + <text class="detail-value">{{ item.usageQuantity || 0 }}</text>
  49 + </view>
  50 + <view class="detail-item">
  51 + <text class="detail-label">产品类别:</text>
  52 + <text class="detail-value">{{ item.productCategory || '无' }}</text>
  53 + </view>
  54 + <view class="detail-item">
  55 + <text class="detail-label">创建人:</text>
  56 + <text class="detail-value">{{ item.createUserName || '无' }}</text>
  57 + </view>
  58 + </view>
  59 + <view class="item-footer">
  60 + <view class="status-badge" :class="getStatusClass(item.isCanceled)">
  61 + {{ item.isCanceled ? '已作废' : '正常' }}
  62 + </view>
  63 + <view class="action-buttons">
  64 + <view v-if="!item.isCanceled" class="action-btn cancel-btn" @click.stop="handleCancel(item)">
  65 + <text class="btn-text">作废</text>
  66 + </view>
  67 + </view>
  68 + </view>
  69 + </view>
  70 +
  71 + <!-- 空状态 -->
  72 + <view v-if="!loading && dataList.length === 0" class="empty-state">
  73 + <view class="empty-icon">📋</view>
  74 + <view>暂无使用记录</view>
  75 + </view>
  76 +
  77 + <!-- 加载状态 -->
  78 + <view v-if="loading && dataList.length === 0" class="loading">正在加载数据...</view>
  79 +
  80 + <!-- 加载更多 -->
  81 + <u-loadmore v-if="dataList.length > 0" :status="loadmoreStatus" :load-text="loadText"></u-loadmore>
  82 + </scroll-view>
  83 + </view>
  84 + </view>
  85 +</template>
  86 +
  87 +<script>
  88 + import usageApi from '@/apis/modules/usage.js'
  89 + import uniCalendar from '@/uni_modules/uni-calendar/components/uni-calendar/uni-calendar.vue'
  90 +
  91 + export default {
  92 + components: {
  93 + uniCalendar
  94 + },
  95 + data() {
  96 + // 初始化日期范围(本月1号到今天)
  97 + const now = new Date()
  98 + const year = now.getFullYear()
  99 + const month = now.getMonth()
  100 + const firstDay = new Date(year, month, 1)
  101 + const today = new Date(year, now.getMonth(), now.getDate(), 23, 59, 59, 999)
  102 +
  103 + // 格式化日期为 YYYY-MM-DD(使用本地时间,避免时区问题)
  104 + const formatDateLocal = (date) => {
  105 + const y = date.getFullYear()
  106 + const m = String(date.getMonth() + 1).padStart(2, '0')
  107 + const d = String(date.getDate()).padStart(2, '0')
  108 + return `${y}-${m}-${d}`
  109 + }
  110 +
  111 + return {
  112 + loading: false,
  113 + dataList: [],
  114 + currentPage: 1,
  115 + pageSize: 10,
  116 + totalCount: 0,
  117 + hasMore: true,
  118 + loadmoreStatus: 'loadmore',
  119 + loadText: {
  120 + loadmore: '点击或上拉加载更多',
  121 + loading: '正在加载...',
  122 + nomore: '没有更多了'
  123 + },
  124 + userInfo: uni.getStorageSync('userInfo'),
  125 + newuserInfo: uni.getStorageSync('newuserInfo'),
  126 + // 日期范围筛选相关
  127 + showCalendar: false,
  128 + startDate: formatDateLocal(firstDay),
  129 + endDate: formatDateLocal(now),
  130 + dateRange: [firstDay.getTime(), today.getTime()]
  131 + }
  132 + },
  133 +
  134 + onLoad() {
  135 + this.initializePage()
  136 + },
  137 +
  138 + onShow() {
  139 + // 页面显示时刷新数据(如果从添加页面返回)
  140 + if (this.dataList.length > 0) {
  141 + this.refreshData()
  142 + }
  143 + },
  144 +
  145 + methods: {
  146 + showCalendarFun() {
  147 + this.$refs.calendar.open();
  148 + },
  149 +
  150 + // 初始化页面
  151 + async initializePage() {
  152 + try {
  153 + // 检查登录状态
  154 + await this.checkLoginStatus()
  155 +
  156 + // 加载第一页数据
  157 + await this.loadUsageList()
  158 + } catch (error) {
  159 + console.error('页面初始化失败:', error)
  160 + this.showErrorState('页面初始化失败,请刷新重试')
  161 + }
  162 + },
  163 +
  164 + // 检查登录状态
  165 + async checkLoginStatus() {
  166 + const token = uni.getStorageSync('token')
  167 + if (!token) {
  168 + uni.reLaunch({
  169 + url: '/pages/login/login'
  170 + })
  171 + return
  172 + }
  173 + },
  174 +
  175 + // 加载使用记录列表
  176 + async loadUsageList() {
  177 + if (this.loading) return
  178 +
  179 + try {
  180 + this.loading = true
  181 + this.showLoadingState()
  182 +
  183 + const params = {
  184 + currentPage: this.currentPage,
  185 + pageSize: this.pageSize
  186 + }
  187 +
  188 + // 添加使用时间范围参数
  189 + if (this.dateRange && this.dateRange.length === 2) {
  190 + params.usageTime = `${this.dateRange[0]},${this.dateRange[1]}`
  191 + }
  192 +
  193 + // if (this.userInfo && this.userInfo.userId) {
  194 + // params.createUser = this.userInfo.userId
  195 + // } else {
  196 + // params.createUser = '暂无'
  197 + // }
  198 +
  199 + params.StoreId = this.newuserInfo.mdid || '暂无'
  200 + const res = await usageApi.getUsageList(params)
  201 +
  202 + if (res.code === 200) {
  203 + const data = res.data
  204 + this.totalCount = data.pagination?.total || 0
  205 + const list = data.list || []
  206 +
  207 + if (this.currentPage === 1) {
  208 + // 第一页,清空列表
  209 + this.dataList = list
  210 + } else {
  211 + // 加载更多,追加到列表
  212 + this.dataList = [...this.dataList, ...list]
  213 + }
  214 +
  215 + // 判断是否还有更多数据
  216 + this.hasMore = list.length === this.pageSize
  217 +
  218 + // 更新加载状态
  219 + if (this.hasMore) {
  220 + this.loadmoreStatus = 'loadmore'
  221 + } else {
  222 + this.loadmoreStatus = 'nomore'
  223 + }
  224 + } else {
  225 + throw new Error(res.message || '获取数据失败')
  226 + }
  227 +
  228 + } catch (error) {
  229 + console.error('加载使用记录列表失败:', error)
  230 + if (this.currentPage === 1) {
  231 + this.showErrorState('加载数据失败,请重试')
  232 + } else {
  233 + this.showMessage('加载更多数据失败', 'error')
  234 + }
  235 + } finally {
  236 + this.loading = false
  237 + }
  238 + },
  239 +
  240 + // 刷新数据
  241 + refreshData() {
  242 + this.currentPage = 1
  243 + this.hasMore = true
  244 + this.loadmoreStatus = 'loadmore'
  245 + this.dataList = []
  246 + this.loadUsageList()
  247 + },
  248 +
  249 + // 加载更多
  250 + async loadMore() {
  251 + if (this.loading || !this.hasMore || this.loadmoreStatus === 'nomore') return
  252 +
  253 + this.loadmoreStatus = 'loading'
  254 + this.currentPage++
  255 + await this.loadUsageList()
  256 + },
  257 +
  258 + // 跳转到添加页面
  259 + goToAdd() {
  260 + uni.navigateTo({
  261 + url: '/pages/usage-form/usage-form'
  262 + })
  263 + },
  264 +
  265 + // 作废使用记录
  266 + async handleCancel(item) {
  267 + if (item.isCanceled) {
  268 + uni.showToast({
  269 + title: '该记录已作废',
  270 + icon: 'none'
  271 + })
  272 + return
  273 + }
  274 +
  275 + // 显示确认对话框
  276 + const confirm = await this.showConfirmDialog('确认作废', '确定要作废这条使用记录吗?作废后无法恢复。')
  277 +
  278 + if (!confirm) {
  279 + return
  280 + }
  281 +
  282 + try {
  283 + uni.showLoading({
  284 + title: '正在作废...'
  285 + })
  286 +
  287 + const res = await usageApi.cancelUsage(item.id, '用户手动作废')
  288 +
  289 + if (res.code === 200) {
  290 + uni.showToast({
  291 + title: '作废成功',
  292 + icon: 'success'
  293 + })
  294 + // 刷新列表
  295 + this.refreshData()
  296 + } else {
  297 + throw new Error(res.message || res.msg || '作废失败')
  298 + }
  299 + } catch (error) {
  300 + console.error('作废使用记录失败:', error)
  301 + uni.showToast({
  302 + title: error.message || error.msg || '作废失败,请重试',
  303 + icon: 'none'
  304 + })
  305 + } finally {
  306 + uni.hideLoading()
  307 + }
  308 + },
  309 +
  310 + // 显示确认对话框
  311 + showConfirmDialog(title, content) {
  312 + return new Promise((resolve) => {
  313 + uni.showModal({
  314 + title: title,
  315 + content: content,
  316 + success: (res) => {
  317 + resolve(res.confirm)
  318 + },
  319 + fail: () => {
  320 + resolve(false)
  321 + }
  322 + })
  323 + })
  324 + },
  325 +
  326 + // 格式化时间
  327 + formatTime(timestamp) {
  328 + if (!timestamp) return '无'
  329 + const date = new Date(timestamp)
  330 + return date.toLocaleString('zh-CN', {
  331 + year: 'numeric',
  332 + month: '2-digit',
  333 + day: '2-digit',
  334 + hour: '2-digit',
  335 + minute: '2-digit'
  336 + })
  337 + },
  338 +
  339 + // 获取状态样式类
  340 + getStatusClass(isCanceled) {
  341 + return isCanceled ? 'canceled' : 'normal'
  342 + },
  343 +
  344 + // 显示加载状态
  345 + showLoadingState() {
  346 + // 加载状态由模板控制
  347 + },
  348 +
  349 + // 显示错误状态
  350 + showErrorState(message) {
  351 + uni.showToast({
  352 + title: message,
  353 + icon: 'none'
  354 + })
  355 + },
  356 +
  357 + // 显示消息提示
  358 + showMessage(message, type = 'info') {
  359 + uni.showToast({
  360 + title: message,
  361 + icon: type === 'error' ? 'error' : 'none'
  362 + })
  363 + },
  364 +
  365 + // 格式化日期范围显示
  366 + formatDateRange() {
  367 + if (!this.startDate || !this.endDate) {
  368 + return '请选择日期'
  369 + }
  370 +
  371 + // 直接格式化日期字符串,避免时区问题
  372 + const formatDisplay = (dateStr) => {
  373 + // dateStr 格式为 YYYY-MM-DD
  374 + const parts = dateStr.split('-')
  375 + if (parts.length === 3) {
  376 + return `${parts[0]}/${parts[1]}/${parts[2]}`
  377 + }
  378 + return dateStr
  379 + }
  380 +
  381 + return `${formatDisplay(this.startDate)} - ${formatDisplay(this.endDate)}`
  382 + },
  383 +
  384 + // 处理日期确认
  385 + handleDateConfirm(e) {
  386 + console.log('日期选择结果:', e)
  387 +
  388 + // uni-calendar组件返回的是包含start和end的对象
  389 + if (e && e.range && e.range.data) {
  390 + // 更新dateRange用于API调用(时间戳格式)
  391 + const startTime = new Date(e.range.data[0]).getTime()
  392 + const endTime = new Date(e.range.data[e.range.data.length-1]).getTime()
  393 + this.dateRange = [startTime, endTime]
  394 +
  395 + // 更新startDate和endDate用于显示
  396 + this.startDate = e.range.data[0]
  397 + this.endDate = e.range.data[e.range.data.length-1]
  398 +
  399 + // 日期变化时重新加载数据
  400 + this.refreshData()
  401 + }
  402 + }
  403 + }
  404 + }
  405 +</script>
  406 +
  407 +<style lang="scss" scoped>
  408 + .container {
  409 + min-height: 100vh;
  410 + background: linear-gradient(135deg, #e8f5e9 0%, #b2dfdb 100%);
  411 + padding: 40rpx 40rpx;
  412 + box-sizing: border-box;
  413 + }
  414 +
  415 + .date-filter-card {
  416 + background: #fff;
  417 + border-radius: 32rpx;
  418 + box-shadow: 0 8rpx 32rpx rgba(76, 175, 80, 0.1);
  419 + padding: 40rpx;
  420 + margin-bottom: 40rpx;
  421 + }
  422 +
  423 + .date-filter-header {
  424 + display: flex;
  425 + justify-content: space-between;
  426 + align-items: center;
  427 + }
  428 +
  429 + .date-range-display {
  430 + display: flex;
  431 + align-items: center;
  432 + background: #f9fff9;
  433 + border: 3rpx solid #c8e6c9;
  434 + border-radius: 24rpx;
  435 + padding: 24rpx 32rpx;
  436 + cursor: pointer;
  437 + transition: all 0.2s ease;
  438 + justify-content: space-between;
  439 + width: 100%;
  440 + box-sizing: border-box;
  441 + }
  442 +
  443 + .date-range-display:active {
  444 + border-color: #43a047;
  445 + background: #fff;
  446 + transform: scale(0.98);
  447 + }
  448 +
  449 + .date-text {
  450 + font-size: 28rpx;
  451 + color: #2e7d32;
  452 + flex: 1;
  453 + }
  454 +
  455 + .calendar-icon {
  456 + font-size: 32rpx;
  457 + margin-left: 16rpx;
  458 + }
  459 +
  460 + .add-btn-wrapper {
  461 + margin-bottom: 40rpx;
  462 + display: flex;
  463 + justify-content: flex-end;
  464 + }
  465 +
  466 + .add-btn {
  467 + display: flex;
  468 + align-items: center;
  469 + background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
  470 + color: #fff;
  471 + padding: 24rpx 48rpx;
  472 + border-radius: 32rpx;
  473 + font-size: 28rpx;
  474 + font-weight: 600;
  475 + box-shadow: 0 8rpx 24rpx rgba(67, 233, 123, 0.3);
  476 + transition: all 0.2s ease;
  477 + }
  478 +
  479 + .add-btn:active {
  480 + transform: scale(0.95);
  481 + box-shadow: 0 4rpx 12rpx rgba(67, 233, 123, 0.4);
  482 + }
  483 +
  484 + .add-text {
  485 + letter-spacing: 2rpx;
  486 + }
  487 +
  488 + .list-card {
  489 + background: #fff;
  490 + border-radius: 32rpx;
  491 + box-shadow: 0 8rpx 32rpx rgba(76, 175, 80, 0.1);
  492 + overflow: hidden;
  493 + max-height: 60vh;
  494 + }
  495 +
  496 + .list-header {
  497 + background: linear-gradient(120deg, #43e97b 0%, #38f9d7 100%);
  498 + padding: 32rpx 40rpx;
  499 + color: #fff;
  500 + font-weight: 600;
  501 + letter-spacing: 2rpx;
  502 + display: flex;
  503 + justify-content: space-between;
  504 + align-items: center;
  505 + }
  506 +
  507 + .header-text {
  508 + font-size: 32rpx;
  509 + }
  510 +
  511 + .total-count {
  512 + font-size: 28rpx;
  513 + opacity: 0.9;
  514 + }
  515 +
  516 + .list-content {
  517 + max-height: calc(60vh - 120rpx);
  518 + overflow-y: auto;
  519 + }
  520 +
  521 + .list-item {
  522 + padding: 32rpx 40rpx;
  523 + border-bottom: 2rpx solid #f0f0f0;
  524 + cursor: pointer;
  525 + transition: all 0.2s ease;
  526 + position: relative;
  527 + }
  528 +
  529 + .list-item:active {
  530 + background: #f8fff8;
  531 + transform: translateX(8rpx);
  532 + }
  533 +
  534 + .list-item:last-child {
  535 + border-bottom: none;
  536 + }
  537 +
  538 + .item-header {
  539 + display: flex;
  540 + justify-content: space-between;
  541 + align-items: center;
  542 + margin-bottom: 16rpx;
  543 + }
  544 +
  545 + .product-name {
  546 + font-weight: 600;
  547 + color: #2e7d32;
  548 + font-size: 32rpx;
  549 + }
  550 +
  551 + .usage-time {
  552 + font-size: 24rpx;
  553 + color: #6a9c6a;
  554 + }
  555 +
  556 + .item-details {
  557 + display: grid;
  558 + grid-template-columns: 1fr 1fr;
  559 + gap: 16rpx;
  560 + margin-bottom: 16rpx;
  561 + }
  562 +
  563 + .detail-item {
  564 + display: flex;
  565 + align-items: center;
  566 + font-size: 28rpx;
  567 + color: #555;
  568 + }
  569 +
  570 + .detail-label {
  571 + color: #6a9c6a;
  572 + margin-right: 12rpx;
  573 + font-size: 24rpx;
  574 + }
  575 +
  576 + .detail-value {
  577 + color: #2e7d32;
  578 + font-weight: 500;
  579 + }
  580 +
  581 + .item-footer {
  582 + display: flex;
  583 + justify-content: space-between;
  584 + align-items: center;
  585 + margin-top: 16rpx;
  586 + }
  587 +
  588 + .status-badge {
  589 + display: inline-block;
  590 + padding: 8rpx 24rpx;
  591 + border-radius: 40rpx;
  592 + font-size: 24rpx;
  593 + font-weight: 500;
  594 + text-align: center;
  595 + }
  596 +
  597 + .status-badge.normal {
  598 + background: #e8f5e9;
  599 + color: #2e7d32;
  600 + border: 2rpx solid #c8e6c9;
  601 + }
  602 +
  603 + .status-badge.canceled {
  604 + background: #ffebee;
  605 + color: #c62828;
  606 + border: 2rpx solid #ffcdd2;
  607 + }
  608 +
  609 + .action-buttons {
  610 + display: flex;
  611 + align-items: center;
  612 + gap: 16rpx;
  613 + }
  614 +
  615 + .action-btn {
  616 + display: flex;
  617 + align-items: center;
  618 + padding: 12rpx 24rpx;
  619 + border-radius: 24rpx;
  620 + font-size: 24rpx;
  621 + font-weight: 500;
  622 + transition: all 0.2s ease;
  623 + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
  624 + }
  625 +
  626 + .action-btn:active {
  627 + transform: scale(0.95);
  628 + box-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.15);
  629 + }
  630 +
  631 + .cancel-btn {
  632 + background: linear-gradient(135deg, #ef5350 0%, #e53935 100%);
  633 + color: #fff;
  634 + }
  635 +
  636 + .cancel-btn:active {
  637 + background: linear-gradient(135deg, #e53935 0%, #c62828 100%);
  638 + }
  639 +
  640 + .btn-text {
  641 + letter-spacing: 1rpx;
  642 + }
  643 +
  644 + .loading {
  645 + text-align: center;
  646 + padding: 80rpx 40rpx;
  647 + color: #6a9c6a;
  648 + font-size: 28rpx;
  649 + }
  650 +
  651 + .empty-state {
  652 + text-align: center;
  653 + padding: 120rpx 40rpx;
  654 + color: #6a9c6a;
  655 + }
  656 +
  657 + .empty-icon {
  658 + font-size: 120rpx;
  659 + margin-bottom: 32rpx;
  660 + opacity: 0.5;
  661 + }
  662 +</style>
  663 +
... ...