Browse Source

feat: 库存调整

master
wangqi 2 months ago
parent
commit
c85187b3f8
  1. 14
      src/api/module/eam/device/storage.ts
  2. 8
      src/pages/deviceInfo/components/DeliveryInfo.vue
  3. 190
      src/pages/deviceStorage/components/data-filter.vue
  4. 124
      src/pages/deviceStorage/components/pie-chart.vue
  5. 188
      src/pages/deviceStorage/index.vue
  6. 14
      src/pages/deviceStorage/utils.ts

14
src/api/module/eam/device/storage.ts

@ -16,16 +16,22 @@ export const storageOutDevice = (params: IStorageOutParam) =>
data: params, data: params,
}) })
export const getStorageOutByDeviceId = (deviceId: number) => export const getStorageByDeviceId = (deviceId: number) =>
eamServer({ eamServer({
url: `/out-storage/get-by-device-id`, url: `/storage/get-by-device-id`,
method: 'get', method: 'get',
params: { deviceId }, params: { deviceId },
}) })
export const getStorageOutPage = (params: PageParam) => export const getStoragePage = (params: PageParam) =>
eamServer({ eamServer({
url: `/out-storage/page`, url: `/storage/page`,
method: 'get', method: 'get',
params, params,
}) })
export const getStorageStatics = () =>
eamServer({
url: `/storage/statics`,
method: 'get',
})

8
src/pages/deviceInfo/components/DeliveryInfo.vue

@ -49,7 +49,7 @@ import { getSimpleCustomerList, type CustomerVO } from '@/api/module/eam/custome
import { isResError } from '@/hooks/useMessage' import { isResError } from '@/hooks/useMessage'
import type { IDevice } from '@/api/module/eam/device' import type { IDevice } from '@/api/module/eam/device'
import { import {
getStorageOutByDeviceId, getStorageByDeviceId,
storageOutDevice, storageOutDevice,
type IStorageOutParam, type IStorageOutParam,
} from '@/api/module/eam/device/storage' } from '@/api/module/eam/device/storage'
@ -63,7 +63,7 @@ const props = defineProps<{
deviceId: number | undefined deviceId: number | undefined
}>() }>()
const isShowSaveButton = computed(() => { const isShowSaveButton = computed(() => {
if(action === 'view') return false if (action === 'view') return false
return props.info?.status === undefined ? true : props.info.status < 3 return props.info?.status === undefined ? true : props.info.status < 3
}) })
@ -107,8 +107,8 @@ watch(
async function loadDeviceStorageInfo() { async function loadDeviceStorageInfo() {
if (!props.deviceId) return if (!props.deviceId) return
const res = await getStorageOutByDeviceId(props.deviceId) const res = await getStorageByDeviceId(props.deviceId)
if (isResError(res)) return if (isResError(res) || !res.data) return
for (const key of Object.keys(params.value)) { for (const key of Object.keys(params.value)) {
params.value[key] = res.data[key] ?? '' params.value[key] = res.data[key] ?? ''
} }

190
src/pages/deviceStorage/components/data-filter.vue

@ -0,0 +1,190 @@
<template>
<div class="storage-data-filter">
<div class="time-item">
<el-config-provider :locale="locale">
<span>开始时间:</span>
<div class="item item-time">
<el-date-picker
class="data-picker"
v-model="filterData.startDay"
type="date"
placeholder="请选择时间"
value-format="YYYY-MM-DD HH:mm:ss"
:clearable="false"
:editable="false"
/>
<el-checkbox v-model="checkbox.startDay" size="large" />
</div>
<span>终止时间:</span>
<div class="item item-time">
<el-date-picker
class="data-picker"
v-model="filterData.endDay"
type="date"
placeholder="请选择时间"
value-format="YYYY-MM-DD HH:mm:ss"
:clearable="false"
:editable="false"
:disabled-date="endDisabledDate"
/>
<el-checkbox v-model="checkbox.endDay" size="large" />
</div>
</el-config-provider>
</div>
<div class="item">
<el-input placeholder="设备SN" v-model="filterData.sn" />
<el-checkbox v-model="checkbox.sn" size="large" />
</div>
<div class="item">
<el-select v-model="filterData.customerId" placeholder="客户">
<el-option
v-for="item in prop.customerList"
:key="item.id"
:label="item.name"
:value="item.id as string"
/>
</el-select>
<el-checkbox v-model="checkbox.customerId" size="large" />
</div>
<div class="item">
<el-select v-model="filterData.status" placeholder="设备状态">
<el-option
v-for="item in statusList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-checkbox v-model="checkbox.status" size="large" disabled/>
</div>
<div class="item item-btn">
<EdfsButton
inner-text="搜索"
type="success"
style="width: 100%"
@click="onSearch"
/>
</div>
</div>
</template>
<script setup lang="ts">
import dayjs from 'dayjs'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import EdfsButton from '@/components/dashboard/Edfs-button/index.vue'
import { getIntDictOptions } from '@/utils/dict'
import { isResError } from '@/hooks/useMessage'
import type { CustomerVO } from '@/api/module/eam/customer'
import { statusList } from '../utils'
const locale = zhCn
const prop = defineProps<{
customerList: Array<Pick<CustomerVO, 'id' | 'name'>>
}>()
const endDisabledDate = (time: Date) => {
return (
time.getTime() <
dayjs(filterData.value.startDay).add(1, 'days').startOf('day').valueOf()
)
}
const emit = defineEmits(['search'])
type FilterKeys = keyof typeof filterData.value
const filterData = ref({
// startDay: dayjs().subtract(15, 'days').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
// endDay: dayjs().add(1, 'days').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
startDay: '',
endDay: '',
status: 3,
sn: '',
customerId: '',
})
const checkbox = ref<Record<FilterKeys, boolean>>({
startDay: false,
endDay: false,
status: true,
sn: false,
customerId: false,
})
function onSearch() {
emit('search', formatParams())
}
function formatParams() {
const params: Partial<Record<FilterKeys, string>> = {}
const keys = Object.keys(checkbox.value) as FilterKeys[]
for (const key of keys) {
if (checkbox.value[key]) {
if (key === 'startDay' || key === 'endDay') {
params['createTime'] = [filterData.value.startDay, filterData.value.endDay]
} else {
params[key] = filterData.value[key]
}
}
}
return params
}
onMounted(() => {
onSearch()
})
</script>
<style lang="scss" scoped>
.storage-data-filter {
padding: 16px;
box-sizing: border-box;
span {
font-size: 14px;
color: var(--label-text);
}
.time-item {
margin-bottom: 32px;
.item {
margin-top: 0;
margin-bottom: 16px;
}
}
.item {
margin-top: 16px;
height: 32px;
display: flex;
align-items: center;
:deep(.el-checkbox:last-of-type) {
margin-left: 8px;
}
}
.item-btn {
margin-top: 36px;
}
:deep(.data-picker) {
width: 100%;
}
:deep(.el-input__icon) {
// margin: 0;
}
:deep(.el-date-editor .el-input__wrapper) {
height: 100%;
line-height: 100%;
flex-direction: row-reverse;
box-sizing: border-box;
padding: 1px 0 1px 11px;
}
:deep(.el-input__icon),
:deep(.el-input__prefix-inner),
:deep(.el-input__prefix),
:deep(.perfix-icon) {
height: 100%;
}
}
</style>

124
src/pages/deviceStorage/components/pie-chart.vue

@ -0,0 +1,124 @@
<template>
<v-chart class="chart" :option="option" :autoresize="autoresize" />
</template>
<script setup lang="ts">
import VChart from 'vue-echarts'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { PieChart } from 'echarts/charts'
import {
TitleComponent,
TooltipComponent,
LegendComponent,
GraphicComponent,
} from 'echarts/components'
import type { EChartsOption } from 'echarts'
import { useTheme } from '@/utils/useTheme'
use([
CanvasRenderer,
PieChart,
TitleComponent,
TooltipComponent,
GraphicComponent,
LegendComponent,
])
interface Props {
data: any[]
}
const { chartGraphicTextColor } = useTheme()
const props = withDefaults(defineProps<Props>(), {
data: () => [],
})
const updateTrigger = ref(0)
const autoresize = {
throttle: 2500,
onResize: () => {
updateTrigger.value++
},
}
//
const total = computed(() => props.data.reduce((acc, cur) => acc + cur.value, 0))
const option = computed(() => {
const trigger = updateTrigger.value
const res: EChartsOption = {
tooltip: {
show: false,
},
graphic: {
elements: [
{
type: 'text',
left: 'center',
top: '30%',
style: {
text: total.value.toString(),
fill: chartGraphicTextColor.value,
fontSize: 20,
fontWeight: 700,
},
},
{
type: 'text',
left: 'center',
top: '42%',
style: {
text: '库存总数',
fill: chartGraphicTextColor.value,
fontSize: 16,
},
},
],
},
legend: {
show: true,
bottom: '3%',
itemWidth: 20,
itemHeight: 15,
itemGap: 10,
textStyle: {
fontSize: 14,
},
},
color: ['#437BF8', '#F84343', '#D1D1D1 ', '#FBB852'],
series: [
{
type: 'pie',
radius: ['40%', '55%'],
center: ['50%', '40%'],
data: props.data,
avoidLabelOverlap: false,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 1,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
label: {
show: true,
position: 'outside',
// formatter: '{b}: {c} ({d}%)',
formatter: ' {c}',
},
labelLine: {
show: true,
},
},
],
}
return res
})
</script>
<style scoped lang="scss">
.chart {
width: 100%;
height: 100%;
}
</style>

188
src/pages/deviceStorage/index.vue

@ -1,48 +1,14 @@
<template> <template>
<div class="storage-info-wrap"> <div class="storage-info-wrap">
<!-- <EdfsWrap class="storage-info-from-wrap"> <div class="left-wrap">
<div class="from"> <EdfsWrap class="chart-box" title="设备库存概览">
<div class="from-input"> <PieChart :data="chartData" />
<div class="from-row"> </EdfsWrap>
<div class="label">设备序列:</div> <div class="filter">
<el-input <LeftFilter @search="onSearch" :customerList="customerList" />
v-model="queryParams.number"
placeholder="请输入设备序列号"
clearable
@keyup.enter="handleQuery"
/>
</div>
<div class="from-row">
<div class="label">设备名称:</div>
<el-input
v-model="queryParams.deviceName"
placeholder="请输入设备名称"
clearable
@keyup.enter="handleQuery"
/>
</div>
<div class="from-row">
<div class="label">注册时段:</div>
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="datetimerange"
start-placeholder="开始时间"
end-placeholder="结束时间"
/>
</div>
<div class="btn-group">
<el-button @click="addDevice"><Icon icon="ep:plus" />添加设备</el-button>
<el-button @click="handleQuery"><Icon icon="ep:search" />搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" />重置</el-button>
</div>
</div>
</div> </div>
</EdfsWrap> --> </div>
<EdfsWrap title="出库设备列表" class="storage-info-table"> <EdfsWrap title="设备库存列表" class="storage-info-table">
<EdfsTable <EdfsTable
class="table" class="table"
v-loading="loading" v-loading="loading"
@ -57,11 +23,23 @@
<template v-for="(col, idx) in tableCol" :key="idx"> <template v-for="(col, idx) in tableCol" :key="idx">
<el-table-column <el-table-column
v-if="col.prop.endsWith('Time')" v-if="col.prop.endsWith('Time')"
:label="storageStatus + col.label"
:min-width="col.minWidth"
>
<template #default="scope">
{{ dayjs(scope.row[col.prop]).format('YYYY-MM-DD HH:mm:ss') }}
</template>
</el-table-column>
<el-table-column
v-if="col.prop === 'status'"
:label="col.label" :label="col.label"
:min-width="col.minWidth" :min-width="col.minWidth"
> >
<template #default="scope"> <template #default="scope">
{{ dayjs(scope.row.createTime).format('YYYY-MM-DD HH:mm:ss') }} {{
statusList.find(item => item.value === scope.row[col.prop])?.label ?? ''
}}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
@ -72,14 +50,22 @@
/> />
</template> </template>
<el-table-column label="操作" width="190" align="center"> <el-table-column label="操作" width="120" align="center">
<template #default="scope"> <template #default="scope">
<EdfsButton <EdfsButton
link link
type="success" type="success"
inner-text="添加维修信息" inner-text="添加维修信息"
v-if="scope.row.status === 3"
@click="addMaintain(scope.row)" @click="addMaintain(scope.row)"
/> />
<EdfsButton
link
type="success"
v-else-if="scope.row.status === 2"
inner-text="添加出库信息"
@click="addOutStorage(scope.row)"
/>
</template> </template>
</el-table-column> </el-table-column>
</EdfsTable> </EdfsTable>
@ -91,10 +77,13 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import EdfsWrap from '@/components/dashboard/Edfs-wrap.vue' import EdfsWrap from '@/components/dashboard/Edfs-wrap.vue'
import EdfsTable from '@/components/dashboard/Edfs-table/index.vue' import EdfsTable from '@/components/dashboard/Edfs-table/index.vue'
import { tableCol } from './utils' import { statusList, tableCol } from './utils'
import { isResError } from '@/hooks/useMessage' import { isResError } from '@/hooks/useMessage'
import { getStorageOutPage } from '@/api/module/eam/device/storage' import LeftFilter from './components/data-filter.vue'
import { getStoragePage, getStorageStatics } from '@/api/module/eam/device/storage'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import PieChart from './components/pie-chart.vue'
import { getSimpleCustomerList, type CustomerVO } from '@/api/module/eam/customer'
const router = useRouter() const router = useRouter()
const loading = ref(true) const loading = ref(true)
@ -103,17 +92,12 @@ const list = ref<any>([])
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
// number: undefined,
// deviceName: undefined,
// status: undefined,
// code: undefined,
// createTime: [],
}) })
const getList = async () => { const getList = async () => {
loading.value = true loading.value = true
const res = await getStorageOutPage(queryParams) const res = await getStoragePage(Object.assign({}, queryParams, filter.value))
if (!isResError(res)) { if (!isResError(res)) {
list.value = res.data.list list.value = res.data.list
@ -123,10 +107,17 @@ const getList = async () => {
loading.value = false loading.value = false
} }
const handleQuery = () => { const storageStatus = computed(
() => statusList.find(item => item.value === filter.value.status)?.colName ?? ''
)
const filter = ref<any>({})
function onSearch(search: any) {
queryParams.pageNo = 1 queryParams.pageNo = 1
filter.value = search
getList() getList()
} }
function handleJump(page: number) { function handleJump(page: number) {
queryParams.pageNo = page queryParams.pageNo = page
getList() getList()
@ -138,8 +129,31 @@ function addMaintain(row: any) {
query: { action: 'update', type: 'maintain', id: row.deviceId }, query: { action: 'update', type: 'maintain', id: row.deviceId },
}) })
} }
function addOutStorage(row: any) {
router.push({
path: '/device/deviceOperation',
query: { action: 'update', type: 'delivery', id: row.deviceId },
})
}
const customerList = ref<Array<Pick<CustomerVO, 'id' | 'name'>>>([])
async function loadCustomerList() {
const res = await getSimpleCustomerList()
if (isResError(res)) return
customerList.value = res.data
}
const chartData = ref([]) as Ref<any>
async function loadChartData() {
const res = await getStorageStatics()
if (!isResError(res)) {
chartData.value = res.data
}
}
onMounted(() => { onMounted(() => {
getList() loadCustomerList()
loadChartData()
}) })
</script> </script>
@ -148,37 +162,47 @@ onMounted(() => {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; column-gap: 12px;
row-gap: 10px; box-sizing: border-box;
.storage-info-from-wrap { font-size: 16px;
height: auto; .left-wrap {
width: 100%; width: 280px;
.from { min-width: 100px;
display: flex; height: 100%;
flex-direction: column; background: var(--warp-bg);
justify-content: space-around; display: flex;
height: 100%; flex-direction: column;
width: 100%;
.from-input { box-sizing: border-box;
display: flex; .station {
width: 100%; position: relative;
column-gap: 20px; height: 60px;
row-gap: 10px; padding-left: 20px;
flex-wrap: wrap; border-bottom: 1px solid var(--pagination-border-color);
.from-row { :deep(.el-input__inner) {
min-width: 270px; color: #666;
display: flex; }
align-items: center;
column-gap: 4px; :deep(.el-input__wrapper) {
.label { padding: 0;
white-space: nowrap; }
width: 70px;
color: var(--label-text); :deep(.el-input__suffix) {
} display: none;
}
} }
} }
.chart-box {
height: 300px;
}
.filter {
height: calc(100% - 300px);
overflow: hidden;
}
:deep(.edfs-wrap) {
box-shadow: none;
}
} }
.storage-info-table { .storage-info-table {
flex: 1; flex: 1;
.table { .table {

14
src/pages/deviceStorage/utils.ts

@ -1,7 +1,13 @@
export const tableCol = [ export const tableCol = [
{ label: '出库设备', prop: 'deviceName', minWidth: '12%' }, { label: '设备名称', prop: 'deviceName', minWidth: '10%' },
{ label: '客户', prop: 'customerName', minWidth: '10%' }, { label: '设备sn', prop: 'sn', minWidth: '10%' },
{ label: '库存状态', prop: 'status', minWidth: '10%' },
{ label: '客户', prop: 'customerName', minWidth: '14%' },
{ label: '出库价格', prop: 'price', minWidth: '10%' }, { label: '出库价格', prop: 'price', minWidth: '10%' },
// { label: '出库单名称', prop: 'name', minWidth: '10%' }, { label: '时间', prop: 'updateTime', minWidth: '18%' },
{ label: '描述', prop: 'description', minWidth: '16%' }, ]
export const statusList = [
{ label: '已入库', value: 2, colName: '入库' },
{ label: '已出库', value: 3, colName: '出库' },
] ]

Loading…
Cancel
Save