chore: 初始化平芝126KV总装线 PDA

This commit is contained in:
yexingqiang
2026-06-08 17:19:19 +08:00
commit 7b8b2af47f
48 changed files with 12380 additions and 0 deletions

View File

@@ -0,0 +1,797 @@
<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>