Files
46-Pingzhi126-ZZ-PZ-PDA/src/views/CallMaterials/index.vue
2026-06-08 17:19:19 +08:00

797 lines
26 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>
<div class="card modern-card">
<div style="display: flex; justify-content: flex-end; margin-bottom: 12px;position: relative;">
<div v-if="currentPartitionName"
style="position: absolute; left: 10px; top: 10px; font-size: .8rem; color: #409EFF; font-weight: bold; letter-spacing: 1px;">
{{ currentPartitionName }}
</div>
<el-button type="primary" @click="onReturnPalletClick">退空托盘</el-button>
</div>
<div class="storList">
<div class="storList-item modern-item" v-for="item in opStoreData" :key="item.StockCode"
@click="handleClick(item)">
<div class="storList-header">
<span class="storList-title">{{ item.StockName }}</span>
<div class="storList-tags">
<van-tag type="danger" v-if="item.TaskId != null">任务中</van-tag>
<van-tag type="primary" v-if="item.StockStatus == false && !item.TrayNo">空库位</van-tag>
<van-tag type="warning" v-if="item.StockStatus == false && item.TrayNo">空托盘</van-tag>
<van-tag type="success" v-if="item.StockStatus == true">满料</van-tag>
</div>
</div>
<div class="storList-info">
<span><i class="iconfont icon-project"></i> 项目名称<b>{{ item.ProjectNo }}</b></span>
<span><i class="iconfont icon-product"></i> 间隔编号<b>{{ item.IntervalNo }}</b></span>
<span><i class="iconfont icon-model"></i> 部件位置<b>{{ item.ComponentLocation }}</b></span>
<span><i class="iconfont icon-tuopan"></i> 托盘编号<b>{{ item.TrayNo }}</b></span>
</div>
</div>
</div>
<el-dialog v-model="showStockDialog" :title="stockDialogTitle" width="400px" :close-on-click-modal="false"
@close="onStockDialogClose">
<div v-if="stockList.length > 0">
<div v-for="item in stockList" :key="item.code" @click="onStockSelect(item)"
style="cursor:pointer; border-bottom:1px solid #eee; padding:12px 0;">
<div style="display:flex;flex-direction:column;">
<span><b>库位名称</b>{{ item.name }}</span>
<span><b>项目名称</b>{{ item.ProjectNo }}</span>
<span><b>间隔编号</b>{{ item.IntervalNo }}</span>
<span><b>部件位置</b>{{ item.ComponentLocation }}</span>
</div>
</div>
</div>
<div v-else style="text-align:center;color:#888;">暂无可用库位</div>
</el-dialog>
<van-action-sheet v-model:show="showActionSheet" :actions="actionList" @select="onActionSelect" title="请选择操作" />
<van-action-sheet v-model:show="showCallMaterialSheet" :actions="callMaterialActions" @select="onCallMaterialSelect"
title="请选择操作" />
<van-action-sheet v-model:show="showReturnMaterialSheet" :actions="returnMaterialActions"
@select="onReturnMaterialSelect" title="请选择操作" />
<el-dialog v-model="showInputDialog" title="物料验证" width="320px" :close-on-click-modal="false"
@close="onInputCancel">
<el-form @submit.prevent>
<el-form-item label="产品图号">
<el-input v-model="MaterialData.SortNo" disabled clearable autofocus @keyup.enter="onInputConfirm" />
</el-form-item>
<el-form-item label="产品编号">
<el-input v-model="inputValue" placeholder="请扫描物料编号" clearable autofocus @input="onInputChange"
@keyup.enter="onInputConfirm" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="onInputCancel">取消</el-button>
<el-button type="primary" @click="onInputConfirm">校验</el-button>
</template>
</el-dialog>
<el-dialog v-model="showReturnPalletDialog" title="退空托盘" width="320px" :close-on-click-modal="false"
@close="onReturnPalletCancel">
<el-form @submit.prevent>
<el-form-item label="托盘码">
<el-input v-model="returnPalletCode" placeholder="请扫描托盘码" clearable autofocus
@keyup.enter="onReturnPalletConfirm" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="onReturnPalletCancel">取消</el-button>
<el-button type="primary" @click="onReturnPalletConfirm">校验</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { v4 as uuidv4 } from 'uuid'
import { inject, onMounted, onUnmounted, reactive, ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useAppStore } from '@/store/app.js'
import { Dialog, ActionSheet, Toast } from 'vant'
import { ElMessage, ElMessageBox } from 'element-plus'
const store = useAppStore()
const { user } = storeToRefs(store)
const ExecDatabaseByParam = inject('ExecDatabaseByParam')
const ExecAgvApi = inject('ExecAgvApi')
const opStoreCode = ref(-1)
// 补充定义 stockList
const stockList = ref([])
// 添加 selectedStock 定义
const selectedStock = ref(null)
// StockCode 库位编号
// StockName 库位名称
// StockStatus 满料状态 bool
// TrayNo 托盘号
// IntervalNo 间隔号
// ComponentLocation 组件位置
// TaskId 任务号
const opStoreData = reactive([])
// 库区列表
const partitionList = reactive([])
// 物料验证相关数据
const MaterialData = reactive({})
// 目标库区数据
const targetPartition = reactive({
callMaterial: null, // 叫料库区
returnMaterial: null, // 退料库区
returnPallet: null // 退空托库区
})
// 库位选择数据
const showStockDialog = ref(false)
const stockDialogTitle = ref('')
const showActionSheet = ref(false)
const actionList = [
{ name: '退料' }
]
const currentItem = ref(null)
const showInputDialog = ref(false)
const inputValue = ref('')
const showReturnPalletDialog = ref(false)
const returnPalletCode = ref('')
const returnPalletStock = ref(null)
// 叫料相关数据
const showCallMaterialSheet = ref(false)
const callMaterialActions = [
{ name: '叫料' }
]
// 叫料选择处理
const onCallMaterialSelect = async (action) => {
showCallMaterialSheet.value = false
if (action.name === '叫料') {
// 获取叫料目标库区
const routes = await getRoutePartitions(currentItem.value.StockCode)
if (!routes || !routes.callMaterial) {
ElMessage.warning('未配置叫料路线,请联系管理员')
return
}
// 记录目标库区
targetPartition.callMaterial = routes.callMaterial
// 根据目标库区获取库位列表
const stockRes = await ExecAgvApi('getStockList', { PartitionCode: targetPartition.callMaterial })
if (!stockRes.Result) {
ElMessage.error(stockRes.ErrMsg || '获取库位失败')
return
}
// 叫料流程:只显示满料库位,排除"无库位"
stockList.value = (stockRes.Data || [])
.filter(i => i.StockStatus === true && i.StockName !== '无库位')
.map(i => ({ name: i.StockName, code: i.StockCode, ...i }))
if (stockList.value.length === 0) {
ElMessage.warning('目标库区没有满料库位')
return
}
// 设置叫料流程标识
isCallMaterialFlow.value = true
stockDialogTitle.value = await getPartitionName(targetPartition.callMaterial)
showStockDialog.value = true
}
}
const isCallMaterialFlow = ref(false)
const currentPartitionName = ref('')
// 退料相关数据
const showReturnMaterialSheet = ref(false)
const returnMaterialActions = [
{ name: '物料验证' },
{ name: '退料' }
]
// 退料选择处理
const onReturnMaterialSelect = async (action) => {
showReturnMaterialSheet.value = false
if (action.name === '物料验证') {
inputValue.value = ''
showInputDialog.value = true
// 将当前物料信息赋值到验证数据
Object.assign(MaterialData, currentItem.value)
return
}
if (action.name === '退料') {
// 获取退料目标库区
const routes = await getRoutePartitions(currentItem.value.StockCode)
if (!routes || !routes.returnMaterial) {
ElMessage.warning('未配置退料路线,请联系管理员')
return
}
// 记录目标库区
targetPartition.returnMaterial = routes.returnMaterial
// 根据目标库区获取库位列表
const stockRes = await ExecAgvApi('getStockList', { PartitionCode: targetPartition.returnMaterial })
if (!stockRes.Result) {
ElMessage.error(stockRes.ErrMsg || '获取库位失败')
return
}
// 退料流程:只显示空库位(无托盘且状态为空)
stockList.value = stockRes.Data
.filter(i => !i.PalletCode && i.StockStatus === false)
.map(i => ({ name: i.StockName, code: i.StockCode, ...i }))
if (stockList.value.length === 0) {
ElMessage.warning('目标库区没有可用空库位')
return
}
// 设置退料流程标识
isReturnMaterialFlow.value = true
stockDialogTitle.value = await getPartitionName(targetPartition.returnMaterial)
showStockDialog.value = true
}
}
const isReturnMaterialFlow = ref(false)
// 获取预设路线库区信息
const getRoutePartitions = async (stockCode) => {
try {
const res = await ExecDatabaseByParam('11', 'PTCHV_AGV_送料路线_PDA查询_初始化', [['库位号', stockCode]])
if (res.data && res.data.length > 0) {
return {
callMaterial: res.data[0].叫料库区,
returnMaterial: res.data[0].退料库区,
returnPallet: res.data[0].退空托库区
}
} else {
ElMessage.warning('未找到预设的路线信息')
return null
}
} catch (error) {
console.error('获取路线信息失败:', error)
ElMessage.error('获取路线信息失败')
return null
}
}
onMounted(async () => {
await getPartitionList()
await getOpStoreCode()
})
const getPartitionList = async () => {
let res = await ExecDatabaseByParam('11', 'MES_计划BOM_工位与名称_库位编号_查询', [['工位号', user.value.opName]])
if (res.data.length > 0) {
// 处理库区编号,可能会返回多个库区,如 "7,14"
const partitionCodes = res.data[0].库位编号.toString().split(',').map(code => code.trim())
opStoreCode.value = partitionCodes[0] // 保存第一个库区编号作为主库区
// 查找库区名称
partitionList.length = 0
const partitionRes = await ExecAgvApi('getPartitionList', {})
if (partitionRes.Result) {
partitionList.push(...partitionRes.Data)
currentPartitionName.value = await getPartitionName(opStoreCode.value)
}
} else {
ElMessage.warning('此工位无对应库位!')
}
}
// 获取对应库位编号名称
const getPartitionName = async (partitionCode) => {
const found = partitionList.find(i => i.PartitionCode == partitionCode)
return found ? found.PartitionName : ''
}
const getOpStoreCode = async () => {
// 获取MES_计划BOM_工位与名称_库位编号_查询返回的所有库区编号
const res = await ExecDatabaseByParam('11', 'MES_计划BOM_工位与名称_库位编号_查询', [['工位号', user.value.opName]])
if (res.data.length === 0) {
ElMessage.warning('此工位无对应库位!')
return
}
// 清空现有数据
opStoreData.length = 0
// 处理可能有多个库区编号的情况(如 "7,14"
const partitionCodes = res.data[0].库位编号.toString().split(',').map(code => code.trim())
// 依次查询每个库区的库位信息并合并
for (const partitionCode of partitionCodes) {
const agvPar = {
"PartitionCode": partitionCode
}
const agvRes = await ExecAgvApi('getStockList', agvPar)
if (agvRes.Result) {
// 将查询到的库位信息添加到列表中,过滤"无库位"
const filteredData = (agvRes.Data || []).filter(item => item.StockName !== '无库位')
opStoreData.push(...filteredData)
} else {
ElMessage.error(`获取库区${partitionCode}信息失败: ${agvRes.ErrMsg}`)
}
}
// 如果没有任何库位信息
if (opStoreData.length === 0) {
ElMessage.error('未获取到任何库位信息')
}
}
const handleClick = async (item) => {
if (item.TaskId != null) {
Dialog.alert({
title: '提示',
message: '该库位存在任务,不可操作',
})
return
}
// 满料库位:退料流程
if (item.StockStatus === true) {
currentItem.value = item
showReturnMaterialSheet.value = true
return
}
// 空托盘:退空托盘流程
if (item.StockStatus === false && item.TrayNo) {
currentItem.value = item
showActionSheet.value = true
return
}
// 空库位:叫料流程
if (item.StockStatus === false && !item.TrayNo) {
currentItem.value = item
showCallMaterialSheet.value = true
return
}
}
// 选择库位后的处理
const onStockItemClick = (item) => {
// 二次确认
}
const onStockDialogClose = () => {
showStockDialog.value = false
}
const onStockSelect = async (stock) => {
selectedStock.value = stock
showStockDialog.value = false
if (isCallMaterialFlow.value) {
// 获取目标库区名称
const partitionName = await getPartitionName(targetPartition.callMaterial)
// 叫料流程确认
ElMessageBox.confirm(`是否将物料 从库位【${stock.name}】送至库位【${currentItem.value.StockName}】?`, '确认叫料', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const taskId = uuidv4()
const res = await ExecAgvApi('createTask', {
taskId,
pickPosition: stock.code,
dropPosition: currentItem.value.StockCode,
})
if (res.Result) {
await ExecDatabaseByParam('12', 'PTCHV_AGV_送料记录_增加', [
['任务号', taskId],
['取料库位', stock.code],
['取料库位名称', stock.name],
['取料项目名称', stock.ProjectNo || ''],
['取料间隔编号', stock.IntervalNo || ''],
['取料部件位置', stock.ComponentLocation || ''],
['取料托盘编号', stock.TrayNo || ''],
['送料库位', currentItem.value.StockCode],
['送料库位名称', currentItem.value.StockName],
['操作人', user.value.name],
['type', 1],
])
ElMessage.success('叫料任务创建成功')
getOpStoreCode()
} else {
ElMessage.error(res.ErrMsg || '叫料任务创建失败')
}
isCallMaterialFlow.value = false
})
} else if (isReturnMaterialFlow.value) {
// 获取目标库区名称
const partitionName = await getPartitionName(targetPartition.returnMaterial)
// 退料流程确认
Dialog.confirm({
title: '确认退料',
message: `是否将物料退至库位【${stock.name}】?`,
confirmButtonText: '确认',
cancelButtonText: '取消',
}).then(async () => {
const taskId = uuidv4()
const res = await ExecAgvApi('createTask', {
taskId,
pickPosition: currentItem.value.StockCode,
dropPosition: stock.code,
})
if (res.Result) {
await ExecDatabaseByParam('12', 'PTCHV_AGV_送料记录_增加', [
['任务号', taskId],
['取料库位', currentItem.value.StockCode],
['取料库位名称', currentItem.value.StockName],
['取料项目名称', currentItem.value.ProjectNo || ''],
['取料间隔编号', currentItem.value.IntervalNo || ''],
['取料部件位置', currentItem.value.ComponentLocation || ''],
['取料托盘编号', currentItem.value.TrayNo || ''],
['送料库位', stock.code],
['送料库位名称', stock.name],
['操作人', user.value.name],
['type', 2],
])
ElMessage.success('退料任务创建成功')
getOpStoreCode()
} else {
ElMessage.error(res.ErrMsg || '退料任务创建失败')
}
isReturnMaterialFlow.value = false
}).catch(() => {
isReturnMaterialFlow.value = false
})
} else if (isReturnPalletFlow.value) {
// 获取目标库区名称
const partitionName = await getPartitionName(targetPartition.returnPallet)
// 退空托盘流程确认
Dialog.confirm({
title: '确认退空托盘',
message: `是否将空托盘退至 库位【${stock.name}】?`,
confirmButtonText: '确认',
cancelButtonText: '取消',
}).then(async () => {
// 先更新库位信息
const oldTrayNo = returnPalletStock.value.TrayNo || ''
const oldIntervalNo = returnPalletStock.value.IntervalNo || ''
const oldComponentLocation = returnPalletStock.value.ComponentLocation || ''
const stockName = returnPalletStock.value.StockName || ''
const updateRes = await ExecAgvApi('updateStockInfo', {
StockCode: returnPalletStock.value.StockCode,
StockStatus: false,
TrayNo: oldTrayNo,
ProjectNo: null,
IntervalNo: null,
ComponentLocation: null,
TaskId: returnPalletStock.value.TaskId
})
if (!updateRes.Result) {
ElMessage.error(updateRes.ErrMsg || '库位信息更新失败')
return
}
await ExecDatabaseByParam('12', 'PTCHV_AGV_库位更新记录_增加', [
['库位号', returnPalletStock.value.StockCode],
['库位名称', stockName],
['项目', ''],
['间隔号', ''],
['组件位置', ''],
['托盘号', oldTrayNo],
['项目_old', ''],
['间隔号_old', oldIntervalNo],
['组件位置_old', oldComponentLocation],
['托盘号_old', oldTrayNo],
['操作人', user.value.name],
['type', 2],
])
// 创建退空托盘任务
const taskId = uuidv4()
const res = await ExecAgvApi('createTask', {
taskId,
pickPosition: returnPalletStock.value.StockCode,
dropPosition: stock.code,
})
if (res.Result) {
await ExecDatabaseByParam('12', 'PTCHV_AGV_送料记录_增加', [
['任务号', taskId],
['取料库位', returnPalletStock.value.StockCode],
['取料库位名称', stockName],
['项目', ''],
['间隔号', ''],
['组件位置', ''],
['托盘号', oldTrayNo],
['送料库位', stock.code],
['送料库位名称', stock.name],
['操作人', user.value.name],
['type', 3],
])
ElMessage.success('退空托盘任务创建成功')
getOpStoreCode()
} else {
ElMessage.error(res.ErrMsg || '退空托盘任务创建失败')
}
isReturnPalletFlow.value = false
}).catch(() => {
isReturnPalletFlow.value = false
})
}
}
const onInputChange = (val) => {
if (val.length > 10) {
// 如果 出现 &符号说明是扫描了二维码 解析出来是 <物料编号&物料图号> 只把物料编号赋值给inputValue
if (val.split('&').length === 2) {
const [EngineNo, SortNo] = val.split('&')
inputValue.value = EngineNo
}
}
}
const onActionSelect = async (action) => {
showActionSheet.value = false
if (action.name === '退料') {
// 获取退空托盘目标库区
const routes = await getRoutePartitions(currentItem.value.StockCode)
if (!routes || !routes.returnPallet) {
ElMessage.warning('未配置退空托盘路线,请联系管理员')
return
}
// 记录目标库区
targetPartition.returnPallet = routes.returnPallet
// 根据目标库区获取库位列表
const stockRes = await ExecAgvApi('getStockList', { PartitionCode: targetPartition.returnPallet })
if (!stockRes.Result) {
ElMessage.error(stockRes.ErrMsg || '获取库位失败')
return
}
// 退空托盘流程:只显示无托盘库位
stockList.value = stockRes.Data
.filter(i => !i.PalletCode || i.PalletCode === '')
.map(i => ({ name: i.StockName, code: i.StockCode, ...i }))
if (stockList.value.length === 0) {
ElMessage.warning('目标库区没有可用库位')
return
}
// 设置退空托盘流程标识和数据
isReturnPalletFlow.value = true
returnPalletStock.value = currentItem.value
stockDialogTitle.value = await getPartitionName(targetPartition.returnPallet)
showStockDialog.value = true
}
}
const onInputConfirm = async () => {
if (!inputValue.value) {
ElMessage.warning('请输入物料码')
return
}
// 如果 出现 &符号说明是扫描了二维码 解析出来是 <物料编号&物料图号> 只把物料编号赋值给inputValue
if (inputValue.value.split('&').length === 2) {
const [EngineNo, SortNo] = inputValue.value.split('&')
inputValue.value = EngineNo
}
if (MaterialData.EngineNo != inputValue.value) {
ElMessage.warning(`库位物料编号【${MaterialData.EngineNo}】与扫描物料编号【${inputValue.value}】不匹配`)
return
}
const res = await ExecDatabaseByParam('11', 'PTCHV_物料验证记录_增加', [["工位号", user.value.opName], ["物料编号", inputValue.value], ["物料图号", MaterialData.SortNo]])
if (res.data[0].result === 1) {
ElMessage.success('物料校验成功')
showInputDialog.value = false
if (user.value.opName == 'OP010') {
// 1. 调用 PTCHV_AGV_上线绑定 存储过程
const resBind = await ExecDatabaseByParam('11', 'PTCHV_AGV_上线绑定', [['工位号', user.value.opName]])
if (resBind.data && resBind.data[0].result === 1) {
try {
const item = resBind.data[0]
const agvReq = {
taskId: uuidv4(),
EngineNo: item.工件编号,
SortNo: item.产品型号,
RouteCode: item.工艺路线编号
}
// 2. 调用 AGV API 生产上线绑定
const agvRes = await ExecAgvApi('productionLaunchBind', agvReq)
if (agvRes.Result) {
// 3. 调用 PTCHV_AGV_工位放行次数_记录 存储过程
const resPass = await ExecDatabaseByParam('12', 'PTCHV_AGV_工位放行次数_记录', [
['工件编号', item.工件编号],
['OP10', agvRes.Data.OP010PassCount],
['OP20', agvRes.Data.OP020PassCount],
['OP30', agvRes.Data.OP030PassCount]
])
if (resPass.data && resPass.data[0].result === '1') {
// 4. 调用 PTCHV_AGV_进离站 存储过程
const param = [
['工位号', user.value.opName],
['工件编号', item.工件编号],
['type', 1],
['任务编号', agvReq.taskId]
]
const resInOut = await ExecDatabaseByParam('12', 'PTCHV_AGV_进离站', param)
if (resInOut.data && resInOut.data[0].result === '1') {
ElMessage.success('产品上线绑定AGV成功')
} else {
ElMessage.error('产品上线绑定AGV失败')
}
} else {
ElMessage.error('放行次数记录失败')
}
} else {
ElMessage.error(agvRes.ErrMsg)
}
} catch (e) {
ElMessage.error('调度任务出错' + e.message)
console.log(e)
}
} else {
ElMessage.error('调度任务出错' + (resBind.data && resBind.data[0] && resBind.data[0].msg ? resBind.data[0].msg : ''))
}
}
} else {
ElMessage.error(res.data[0].msg)
}
}
const onInputCancel = () => {
showInputDialog.value = false
}
const onReturnPalletClick = () => {
returnPalletCode.value = ''
returnPalletStock.value = null
showReturnPalletDialog.value = true
}
const onReturnPalletCancel = () => {
showReturnPalletDialog.value = false
}
const onReturnPalletConfirm = async () => {
if (!returnPalletCode.value) {
ElMessage.warning('请输入托盘码')
return
}
// 校验托盘码是否属于本库区
const stock = opStoreData.find(i => i.TrayNo === returnPalletCode.value)
if (!stock) {
ElMessage.error('该托盘码不属于本库区')
return
}
returnPalletStock.value = stock
showReturnPalletDialog.value = false
// 获取退空托盘目标库区
const routes = await getRoutePartitions(stock.StockCode)
if (!routes || !routes.returnPallet) {
ElMessage.warning('未配置退空托盘路线,请联系管理员')
return
}
// 记录目标库区
targetPartition.returnPallet = routes.returnPallet
// 根据目标库区获取库位列表
const stockRes = await ExecAgvApi('getStockList', { PartitionCode: targetPartition.returnPallet })
if (!stockRes.Result) {
ElMessage.error(stockRes.ErrMsg || '获取库位失败')
return
}
// 退空托盘流程:只显示无托盘库位,排除"无库位"
stockList.value = (stockRes.Data || [])
.filter(i => (!i.TrayNo || i.TrayNo === '') && i.StockName !== '无库位')
.map(i => ({ name: i.StockName, code: i.StockCode, ...i }))
if (stockList.value.length === 0) {
ElMessage.warning('目标库区没有可用库位')
return
}
// 设置退空托盘流程标识并显示库位选择
isReturnPalletFlow.value = true
showStockDialog.value = true
}
// 标记当前流程类型
const isReturnPalletFlow = ref(false)
</script>
<style lang="scss" scoped>
.card.modern-card {
background: #fff;
border-radius: 18px;
box-shadow: 0 4px 24px 0 rgba(0, 0, 0, 0.08);
padding: 10px 14px;
overflow-y: auto;
}
.storList {
display: flex;
flex-wrap: wrap;
gap: 24px;
}
.storList-item.modern-item {
background: linear-gradient(135deg, #f8fafc 60%, #e0e7ef 100%);
border-radius: 14px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
padding: 20px 18px 16px 18px;
min-width: 260px;
max-width: 320px;
flex: 1 1 260px;
transition: box-shadow 0.2s, transform 0.2s;
display: flex;
flex-direction: column;
gap: 12px;
cursor: pointer;
&:hover {
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.12);
transform: translateY(-2px) scale(1.02);
}
}
.storList-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.storList-title {
font-size: 1.18rem;
font-weight: 600;
color: #222;
}
.storList-tags {
display: flex;
gap: 8px;
}
.storList-info {
display: flex;
flex-direction: column;
gap: 6px;
font-size: 0.98rem;
color: #555;
span {
display: flex;
align-items: center;
gap: 4px;
i {
font-size: 1.1em;
color: #8bb4f8;
}
b {
color: #222;
font-weight: 500;
margin-left: 2px;
}
}
}
</style>