Files
PartsInquiry/frontend/pages/product/list.vue
2025-09-30 00:03:43 +08:00

253 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page">
<view class="tabs">
<view class="tab" :class="{active: tab==='all'}" @click="switchTab('all')">全部</view>
<view class="tab" :class="{active: tab==='search'}" @click="switchTab('search')">查询</view>
<view class="tab extra" @click="goMySubmissions">我的提交</view>
</view>
<view class="search" :class="{ 'template-mode': query.mode==='template' }" v-if="tab==='search'">
<view class="mode">
<picker mode="selector" :range="['直接查询','名称模糊查询','按模板参数查询']" @change="e => (query.mode = ['direct','nameLike','template'][Number(e.detail.value)] || 'direct')">
<view class="picker">{{ modeLabel }}</view>
</picker>
</view>
<block v-if="query.mode==='direct' || query.mode==='nameLike'">
<input v-model.trim="query.kw" placeholder="输入名称/条码/规格查询" @confirm="reload" />
</block>
<block v-if="query.mode==='template'">
<view class="picker-row">
<picker mode="selector" :range="categoryNames" @change="onPickCategory">
<view class="picker">{{ categoryLabel }}</view>
</picker>
<picker mode="selector" :range="templateNames" @change="onPickTemplate">
<view class="picker">{{ templateLabel }}</view>
</picker>
</view>
<view class="params-wrap">
<view class="param-row" v-for="(p,idx) in selectedTemplateParams" :key="p.fieldKey">
<input v-if="p.type==='string'" v-model.trim="paramValues[p.fieldKey]" :placeholder="'输入' + p.fieldLabel" />
<input v-else-if="p.type==='number'" type="number" v-model.number="paramValues[p.fieldKey]" :placeholder="'输入' + p.fieldLabel" />
<switch v-else-if="p.type==='boolean'" :checked="!!paramValues[p.fieldKey]" @change="onParamBoolChange(p, $event)" />
<picker v-else-if="p.type==='enum'" mode="selector" :range="p.enumOptions||[]" @change="onPickParamEnumWrapper(p, $event)">
<view class="picker">{{ displayParamEnum(p) }}</view>
</picker>
<picker v-else-if="p.type==='date'" mode="date" @change="onParamDateChange(p, $event)">
<view class="picker">{{ paramValues[p.fieldKey] || ('选择' + p.fieldLabel) }}</view>
</picker>
<input v-else v-model.trim="paramValues[p.fieldKey]" :placeholder="'输入' + p.fieldLabel" />
</view>
</view>
</block>
<button size="mini" @click="reload">查询</button>
</view>
<scroll-view scroll-y class="list" @scrolltolower="loadMore">
<block v-if="items.length">
<view class="item" v-for="it in items" :key="it.id" @click="openDetail(it.id)">
<image v-if="it.cover" :src="it.cover" class="thumb" mode="aspectFill" />
<view class="content">
<view class="name">
<text>{{ it.name }}</text>
<text v-if="it.deleted" class="tag-deleted">已删除</text>
<text v-if="it.platformStatus==='platform'" class="tag-platform">平台推荐</text>
<text v-else-if="it.sourceSubmissionId" class="tag-custom">我的提交</text>
</view>
<view class="meta">{{ it.brand || '-' }} {{ it.model || '' }} {{ it.spec || '' }}</view>
<view class="card-params" v-if="it.cardParams && Object.keys(it.cardParams).length">
<view class="param" v-for="(v,k) in it.cardParams" :key="k">{{ k }}{{ v }}</view>
</view>
</view>
</view>
</block>
<view v-else class="empty">
<text>暂无数据点击右上角新增</text>
</view>
</scroll-view>
<!-- 保留我的提交页的此处不显示 -->
</view>
</template>
<script>
import { get } from '../../common/http.js'
export default {
data() {
return {
items: [],
query: { kw: '', page: 1, size: 20, categoryId: '', mode: 'direct', templateId: '', params: {} },
finished: false,
loading: false,
tab: 'all',
categories: [],
templates: [],
paramValues: {}
}
},
onLoad() {
const hasToken = (() => { try { return !!uni.getStorageSync('TOKEN') } catch(e){ return false } })()
if (!hasToken) {
this.items = []
this.categories = []
uni.showToast({ title: '请登录使用该功能', icon: 'none' })
return
}
this.fetchCategories()
this.reload()
},
onShow() {
const hasToken = (() => { try { return !!uni.getStorageSync('TOKEN') } catch(e){ return false } })()
if (!hasToken) return
// 从创建/编辑页返回时,确保刷新最新列表
this.reload()
},
computed: {
categoryNames() { return this.categories.map(c => c.name) },
categoryLabel() {
const c = this.categories.find(x => String(x.id) === String(this.query.categoryId))
return c ? '类别:' + c.name : '选择类别'
},
modeLabel() {
const map = { direct: '直接查询', nameLike: '名称模糊查询', template: '按模板参数查询' }
return map[this.query.mode] || '直接查询'
},
templateNames() { return this.templates.map(t => t.name) },
templateLabel() {
const t = this.templates.find(x => String(x.id) === String(this.query.templateId))
return t ? '模板:' + t.name : '选择模板'
},
selectedTemplate() { return this.templates.find(t => String(t.id) === String(this.query.templateId)) || null },
selectedTemplateParams() { return (this.selectedTemplate && Array.isArray(this.selectedTemplate.params)) ? this.selectedTemplate.params : [] }
},
methods: {
switchTab(t) {
this.tab = t
this.query.categoryId = ''
this.query.templateId = ''
this.paramValues = {}
this.reload()
},
onPickCategory(e) {
const idx = Number(e.detail.value)
const c = this.categories[idx]
this.query.categoryId = c ? c.id : ''
this.fetchTemplates()
},
onPickTemplate(e) {
const idx = Number(e.detail.value)
const t = this.templates[idx]
this.query.templateId = t ? t.id : ''
this.paramValues = {}
},
onPickParamEnumWrapper(p, e) {
const idx = Number(e.detail.value)
const arr = p.enumOptions || []
this.paramValues[p.fieldKey] = arr[idx]
},
onParamBoolChange(p, e) { this.paramValues[p.fieldKey] = e?.detail?.value ? true : false },
onParamDateChange(p, e) { this.paramValues[p.fieldKey] = e?.detail?.value || '' },
async fetchCategories() {
try {
const res = await get('/api/product-categories', {})
this.categories = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])
} catch (_) {}
},
async fetchTemplates() {
try {
const res = await get('/api/product-templates', this.query.categoryId ? { categoryId: this.query.categoryId } : {})
const list = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])
this.templates = list
} catch (_) { this.templates = [] }
},
reload() {
this.items = []
this.query.page = 1
this.finished = false
this.loadMore()
},
async loadMore() {
if (this.loading || this.finished) return
this.loading = true
try {
const params = { kw: this.query.kw, page: this.query.page, size: this.query.size }
if (this.tab === 'search') {
if (this.query.categoryId) params.categoryId = this.query.categoryId
if (this.query.templateId) params.templateId = this.query.templateId
if (this.paramValues && Object.keys(this.paramValues).length) {
for (const k of Object.keys(this.paramValues)) {
const v = this.paramValues[k]
if (v !== undefined && v !== null && v !== '') params['param_' + k] = v
}
}
}
const res = await get('/api/products', params)
const list = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])
this.items = this.items.concat(list)
if (list.length < this.query.size) this.finished = true
this.query.page += 1
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
this.loading = false
}
},
openDetail(id) {
uni.navigateTo({ url: '/pages/product/product-detail?id=' + id })
},
goMySubmissions() {
uni.navigateTo({ url: '/pages/product/submissions' })
},
async remove(it) {
try {
const r = await new Promise(resolve => {
uni.showModal({ content: '确认删除该货品?删除后可在后台恢复', success: resolve })
})
if (!r || !r.confirm) return
const { del } = require('../../common/http.js')
await del('/api/products/' + it.id)
uni.showToast({ title: '已删除', icon: 'success' })
this.reload()
} catch (e) {
uni.showToast({ title: '删除失败', icon: 'none' })
}
}
}
}
</script>
<style lang="scss">
.page { display:flex; flex-direction: column; height: 100vh; }
.tabs { display:flex; background:$uni-bg-color-grey; }
.tab { flex:1; text-align:center; padding: 20rpx 0; color:$uni-text-color-grey; }
.tab.active { color:$uni-color-primary; font-weight: 600; }
.tab.extra { flex: 0 0 180rpx; color:$uni-color-primary; font-weight: 600; }
.search { display:flex; gap: 12rpx; padding: 16rpx; background:$uni-bg-color-grey; align-items: center; }
.search input { flex:1; background:$uni-bg-color-hover; border-radius: 12rpx; padding: 12rpx; color: $uni-text-color; }
.picker { padding: 8rpx 12rpx; background:$uni-bg-color-hover; border-radius: 10rpx; color:$uni-text-color-grey; }
.template-mode { flex-direction: column; align-items: stretch; gap: 8rpx; }
.picker-row { display:flex; gap: 12rpx; }
.params-wrap { margin-top: 6rpx; background:$uni-bg-color-grey; border-radius: 12rpx; padding: 8rpx 8rpx; }
.list { flex:1; }
.item { display:flex; padding: 20rpx; background:$uni-bg-color-grey; border-bottom: 1rpx solid $uni-border-color; }
.thumb { width: 120rpx; height: 120rpx; border-radius: 12rpx; margin-right: 16rpx; background:$uni-bg-color-hover; }
.content { flex:1; }
.name { color:$uni-text-color; margin-bottom: 6rpx; font-weight: 600; display:flex; align-items:center; gap: 12rpx; }
.tag-platform { font-size: 22rpx; color:#fff; background:#2d8cf0; padding: 4rpx 10rpx; border-radius: 8rpx; }
.tag-custom { font-size: 22rpx; color:#fff; background:#67c23a; padding: 4rpx 10rpx; border-radius: 8rpx; }
.tag-deleted { font-size: 22rpx; color:#fff; background:#909399; padding: 4rpx 10rpx; border-radius: 8rpx; }
.meta { color:$uni-text-color-grey; font-size: 24rpx; }
.card-params { display:flex; flex-wrap:wrap; gap: 8rpx 16rpx; margin-top: 8rpx; }
.card-params .param { color:$uni-text-color-grey; font-size: 22rpx; background:$uni-bg-color-grey; padding: 2rpx 6rpx; border-radius: 8rpx; }
.price { margin-left: 20rpx; color:$uni-color-primary; }
.empty { height: 60vh; display:flex; align-items:center; justify-content:center; color:$uni-text-color-grey; }
.fab { position: fixed; right: 30rpx; bottom: 120rpx; width: 100rpx; height: 100rpx; background:$uni-color-primary; color:#fff; border-radius: 50rpx; text-align:center; line-height: 100rpx; font-size: 48rpx; box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.15); }
</style>