fix ProductStockList.vue style
This commit is contained in:
@@ -2,42 +2,7 @@
|
||||
<div v-can="permissionCodes.product.read" class="product-detail">
|
||||
<t-loading v-if="isLoading" class="loading" size="32px" />
|
||||
<template v-if="product">
|
||||
<card-container class="info" v-entrance-transition>
|
||||
<div class="content">
|
||||
<div class="left">
|
||||
<t-image
|
||||
v-if="getProductPhotoURL(product)"
|
||||
:src="getProductPhotoURL(product)"
|
||||
:alt="product.name || '产品图片'"
|
||||
fit="cover"
|
||||
class="avatar"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="avatar placeholder"
|
||||
v-text="(product.name || product.code || '产').slice(0, 1)"
|
||||
/>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="header">
|
||||
<h3 class="name" v-text="product.name || '未命名产品'" />
|
||||
<div class="code gray" v-text="product.code || '-'" />
|
||||
</div>
|
||||
<div class="tags">
|
||||
<t-tag
|
||||
variant="light-outline"
|
||||
theme="primary"
|
||||
:content="getProductTypeText(product.productType)"
|
||||
/>
|
||||
<t-tag
|
||||
variant="light-outline"
|
||||
:theme="getProductStatusTheme(product.status)"
|
||||
:content="getProductStatusText(product.status)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</card-container>
|
||||
<product-profile :product="product" />
|
||||
<t-cell-group title="基本信息" theme="card" v-entrance-transition="70">
|
||||
<t-cell title="产品分类" :note="product.category?.name || '未分类'" />
|
||||
<t-cell title="库存管理" :note="product.stockEnabled ? '启用' : '关闭'" />
|
||||
@@ -114,6 +79,7 @@ import { useUserStore } from "timi-web-api";
|
||||
import ProductAPI from "@/api/ProductAPI";
|
||||
import NavBarActionMenu from "@/components/NavBarActionMenu.vue";
|
||||
import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||
import ProductProfile from "@/pages/product/component/ProductProfile.vue";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { NavBarAction } from "@/types/NavBarAction";
|
||||
import type { PhotoItem } from "@/types/Photo";
|
||||
@@ -123,10 +89,7 @@ import { getErrorMessage } from "@/utils/error";
|
||||
import {
|
||||
formatProductAmount,
|
||||
getProductBaseUnitName,
|
||||
getProductPhotoURL,
|
||||
getProductStatusText,
|
||||
getProductStatusTheme,
|
||||
getProductTypeText
|
||||
getProductStatusText
|
||||
} from "@/utils/product";
|
||||
import { Message } from "@/utils/message";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
@@ -233,45 +196,6 @@ onUnmounted(() => navBarStore.clearRight());
|
||||
padding: calc(var(--app-nav-height) + 1rem) 0 1rem 0;
|
||||
flex-direction: column;
|
||||
|
||||
.info {
|
||||
margin: 0 1rem;
|
||||
|
||||
.content {
|
||||
gap: .5rem;
|
||||
padding: .5rem;
|
||||
display: flex;
|
||||
|
||||
.avatar {
|
||||
width: 7rem;
|
||||
height: 7rem;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
.header {
|
||||
|
||||
.name {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.code {
|
||||
font-size: .875rem;
|
||||
margin-top: .25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.tags {
|
||||
gap: .5rem;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.units {
|
||||
|
||||
.title {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<div
|
||||
v-else
|
||||
class="avatar placeholder"
|
||||
v-text="(item.name || item.code || '产').slice(0, 1)"
|
||||
v-text="(item.name || '产').slice(0, 1)"
|
||||
/>
|
||||
</template>
|
||||
<template #title>
|
||||
|
||||
@@ -2,17 +2,11 @@
|
||||
<div v-can="permissionCodes.productStock.read" class="product-stock-detail">
|
||||
<t-loading v-if="isLoading" class="loading" size="32px" />
|
||||
<template v-else-if="product && stock">
|
||||
<section class="hero" v-entrance-transition>
|
||||
<span class="eyebrow" v-text="'库存中心'" />
|
||||
<div class="hero-title">
|
||||
<h1 v-text="product.name || '未命名产品'" />
|
||||
<product-profile :product="product">
|
||||
<template #tags>
|
||||
<t-tag :content="getStockStatusText(stock)" variant="light-outline" :theme="getStockTheme(stock)" />
|
||||
</div>
|
||||
<div class="hero-meta">
|
||||
<span v-text="product.code || '无产品编码'" />
|
||||
<strong v-text="`${getProductQuantityText(stock.availableQuantity)} ${getProductBaseUnitName(product)}`" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</product-profile>
|
||||
<card-container title="库存余额">
|
||||
<t-cell-group theme="card" class="summary">
|
||||
<t-cell title="产品" :note="product.name || '未命名产品'" />
|
||||
@@ -68,6 +62,7 @@
|
||||
import { Time } from "timi-web";
|
||||
import { useUserStore } from "timi-web-api";
|
||||
import ProductAPI from "@/api/ProductAPI";
|
||||
import ProductProfile from "@/pages/product/component/ProductProfile.vue";
|
||||
import ProductStockOperationDialog from "@/pages/product/ProductStockOperationDialog.vue";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { Product, ProductStock, StockOperationType, StockRecord } from "@/types/Product";
|
||||
@@ -169,51 +164,6 @@ onUnmounted(() => navBarStore.clearRight());
|
||||
display: grid;
|
||||
padding: calc(var(--app-nav-height) + 1rem) 0 1rem;
|
||||
|
||||
.hero {
|
||||
margin: 0 1rem;
|
||||
padding: 1rem;
|
||||
background: linear-gradient(135deg, #eff6ff, #f0fdf4);
|
||||
box-shadow: var(--td-shadow-1);
|
||||
border-radius: 1.25rem;
|
||||
|
||||
.eyebrow {
|
||||
color: #2563eb;
|
||||
display: block;
|
||||
font-size: .6875rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: .08em;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
gap: .5rem;
|
||||
display: flex;
|
||||
margin-top: .35rem;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.hero-meta {
|
||||
gap: .75rem;
|
||||
display: flex;
|
||||
margin-top: .6rem;
|
||||
color: var(--td-text-color-secondary);
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: #0f766e;
|
||||
font-size: 1.2rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.loading {
|
||||
margin: 3rem auto;
|
||||
}
|
||||
@@ -254,15 +204,4 @@ onUnmounted(() => navBarStore.clearRight());
|
||||
}
|
||||
}
|
||||
|
||||
:global(html.theme-dark) .product-stock-detail .hero {
|
||||
background: linear-gradient(135deg, rgba(37, 99, 235, .2), rgba(15, 118, 110, .18));
|
||||
|
||||
.eyebrow {
|
||||
color: #93c5fd;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: #99f6e4;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,24 +1,62 @@
|
||||
<template>
|
||||
<div v-can="permissionCodes.productStock.read" class="product-stock-list">
|
||||
<div class="search bg-white">
|
||||
<t-input v-model.trim="keyword" class="keyword" type="search" borderless clearable placeholder="搜索产品名称或编码" />
|
||||
<t-input
|
||||
v-model="keyword"
|
||||
class="keyword"
|
||||
type="search"
|
||||
borderless
|
||||
clearable
|
||||
placeholder="搜索产品名称或编码"
|
||||
/>
|
||||
</div>
|
||||
<t-pull-down-refresh v-model="isRefreshing" :refresh-timeout="30000" @refresh="refresh" @scrolltolower="loadMore">
|
||||
<t-pull-down-refresh
|
||||
class="list-wrap empty-cell-title-margin"
|
||||
v-model="isRefreshing"
|
||||
:refresh-timeout="30000"
|
||||
@refresh="refresh"
|
||||
@scrolltolower="loadMore"
|
||||
>
|
||||
<t-cell-group v-if="stockList.length" theme="card" class="list fix-last-cell-line">
|
||||
<t-cell v-for="(item, index) in stockList" :key="item.productId || item.id" arrow hover class="item" v-entrance-transition="index * 30" @click="toDetail(item)">
|
||||
<template #leftIcon>
|
||||
<t-image
|
||||
v-if="getProductPhotoURL(item.product!)"
|
||||
:src="getProductPhotoURL(item.product!)"
|
||||
:alt="item.product?.name || '产品图片'"
|
||||
fit="cover"
|
||||
class="avatar"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="avatar placeholder"
|
||||
v-text="(item.product?.name || '产').slice(0, 1)"
|
||||
/>
|
||||
</template>
|
||||
<template #title>
|
||||
<div class="title">
|
||||
<strong v-text="item.product?.name || '未命名产品'" />
|
||||
<t-tag size="small" variant="outline" :theme="getStockTheme(item)" :content="getStockStatusText(item)" />
|
||||
<t-tag
|
||||
size="small"
|
||||
variant="outline"
|
||||
:content="item.product?.code || '无产品编码'"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #note>
|
||||
<span v-text="`${getProductQuantityText(item.availableQuantity)} ${getProductBaseUnitName(item.product)}`" />
|
||||
</template>
|
||||
<template #description>
|
||||
<div class="description">
|
||||
<span class="gray" v-text="item.product?.code || '无产品编码'" />
|
||||
<span v-text="`实际 ${getProductQuantityText(item.onHandQuantity)} · 预占 ${getProductQuantityText(item.reservedQuantity)} · 可用 ${getProductQuantityText(item.availableQuantity)}`" />
|
||||
<div class="stock">
|
||||
<span class="on-hand">
|
||||
<span>库存</span>
|
||||
<strong v-text="getProductQuantityText(item.onHandQuantity)"/>
|
||||
</span>
|
||||
<span class="available">
|
||||
<span>可用</span>
|
||||
<strong v-text="getProductQuantityText(item.availableQuantity)"/>
|
||||
</span>
|
||||
<span class="reserved">
|
||||
<span>预占</span>
|
||||
<strong v-text="getProductQuantityText(item.reservedQuantity)" />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</t-cell>
|
||||
@@ -35,7 +73,7 @@ import ProductAPI from "@/api/ProductAPI";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { ProductStock } from "@/types/Product";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { getProductBaseUnitName, getProductQuantityText } from "@/utils/product";
|
||||
import { getProductPhotoURL, getProductQuantityText } from "@/utils/product";
|
||||
import { Message } from "@/utils/message";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
|
||||
@@ -56,24 +94,6 @@ let requestToken = 0;
|
||||
|
||||
const emptyText = computed(() => isLoading.value ? "正在加载库存" : keyword.value ? "未找到匹配库存" : "暂无库存记录");
|
||||
|
||||
function getStockStatusText(item: ProductStock): string {
|
||||
if (!item.product?.stockEnabled) {
|
||||
return "不管理库存";
|
||||
}
|
||||
const available = Number(item.availableQuantity || 0);
|
||||
const min = Number(item.product.minStockQuantity || 0);
|
||||
return 0 < min && available < min ? "库存不足" : "库存正常";
|
||||
}
|
||||
|
||||
function getStockTheme(item: ProductStock): "danger" | "success" | "default" {
|
||||
if (!item.product?.stockEnabled) {
|
||||
return "default";
|
||||
}
|
||||
const available = Number(item.availableQuantity || 0);
|
||||
const min = Number(item.product.minStockQuantity || 0);
|
||||
return 0 < min && available < min ? "danger" : "success";
|
||||
}
|
||||
|
||||
async function fetch(reset = false): Promise<void> {
|
||||
if (!reset && (isLoading.value || isNoMore.value)) {
|
||||
return;
|
||||
@@ -178,19 +198,47 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
padding-top: .75rem;
|
||||
.list-wrap {
|
||||
min-height: 70vh;
|
||||
|
||||
.item {
|
||||
.title {
|
||||
gap: .5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.list {
|
||||
padding-top: .75rem;
|
||||
|
||||
.description {
|
||||
display: grid;
|
||||
gap: .125rem;
|
||||
.item {
|
||||
padding: 10px;
|
||||
|
||||
.avatar {
|
||||
width: 3.5rem;
|
||||
height: 3.5rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
gap: .5rem;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.stock {
|
||||
gap: .5rem;
|
||||
display: flex;
|
||||
margin-top: .5rem;
|
||||
flex-wrap: wrap;
|
||||
|
||||
strong {
|
||||
display: inline-block;
|
||||
margin-left: .25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.reserved {
|
||||
color: var(--td-warning-color);
|
||||
}
|
||||
|
||||
.available {
|
||||
color: var(--td-success-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<card-container class="info" v-entrance-transition>
|
||||
<div class="content">
|
||||
<div class="left">
|
||||
<t-image
|
||||
v-if="getProductPhotoURL(props.product)"
|
||||
:src="getProductPhotoURL(props.product)"
|
||||
:alt="props.product.name || '产品图片'"
|
||||
fit="cover"
|
||||
class="avatar"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="avatar placeholder"
|
||||
v-text="(props.product.name || props.product.code || '产').slice(0, 1)"
|
||||
/>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="header">
|
||||
<h3 class="name" v-text="props.product.name || '未命名产品'" />
|
||||
<div class="code gray" v-text="props.product.code || '-'" />
|
||||
</div>
|
||||
<div class="tags">
|
||||
<t-tag
|
||||
variant="light-outline"
|
||||
theme="primary"
|
||||
:content="getProductTypeText(props.product.productType)"
|
||||
/>
|
||||
<t-tag
|
||||
variant="light-outline"
|
||||
:theme="getProductStatusTheme(props.product.status)"
|
||||
:content="getProductStatusText(props.product.status)"
|
||||
/>
|
||||
<slot name="tags" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</card-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Product } from "@/types/Product";
|
||||
import { getProductPhotoURL, getProductStatusText, getProductStatusTheme, getProductTypeText } from "@/utils/product";
|
||||
|
||||
defineOptions({ name: "ProductProfile" });
|
||||
|
||||
interface Props {
|
||||
product: Product;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.info {
|
||||
margin: 0 1rem;
|
||||
|
||||
.content {
|
||||
gap: .5rem;
|
||||
padding: .5rem;
|
||||
display: flex;
|
||||
|
||||
.avatar {
|
||||
width: 7rem;
|
||||
height: 7rem;
|
||||
overflow: hidden;
|
||||
border-radius: .75rem;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: var(--td-text-color-placeholder);
|
||||
display: flex;
|
||||
background: var(--td-bg-color-secondarycontainer);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
.header {
|
||||
|
||||
.name {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.code {
|
||||
font-size: .875rem;
|
||||
margin-top: .25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.tags {
|
||||
gap: .5rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user