feat: 新增库存管理功能:入库、出库、库存管理
This commit is contained in:
@@ -97,6 +97,9 @@ const mainRoutes = {
|
||||
{ path: '/statisticsBusiness-courseStatistics', component: _import('modules/statisticsBusiness/courseStatistics/index'), name: 'statisticsBusiness-courseStatistics', meta: { title: '课程统计', isTab: true } },
|
||||
{ path: '/statisticsBusiness-vipStatistics', component: _import('modules/statisticsBusiness/vipStatistics/index'), name: 'statisticsBusiness-vipStatistics', meta: { title: 'VIP统计', isTab: true } },
|
||||
{ path: '/statisticsBusiness-userStatistics', component: _import('modules/statisticsBusiness/userStatistics/index'), name: 'statisticsBusiness-userStatistics', meta: { title: '用户统计', isTab: true } },
|
||||
{ path: '/inventoryManagement-stockInRecord', component: _import('modules/inventoryManagement/stockInRecord'), name: 'inventoryManagement-stockInRecord', meta: { title: '入库记录', isTab: true } },
|
||||
{ path: '/inventoryManagement-stockOutRecord', component: _import('modules/inventoryManagement/stockOutRecord'), name: 'inventoryManagement-stockOutRecord', meta: { title: '出库记录', isTab: true } },
|
||||
{ path: '/inventoryManagement-inventoryManage', component: _import('modules/inventoryManagement/inventoryManage'), name: 'inventoryManagement-inventoryManage', meta: { title: '库存管理', isTab: true } },
|
||||
],
|
||||
beforeEnter (to, from, next) {
|
||||
let token = Vue.cookie.get('token')
|
||||
|
||||
@@ -21,6 +21,17 @@
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12" style="margin-top: 30px;">
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover">
|
||||
库存预警
|
||||
<span class="number">{{ stockAlertNum }}</span>
|
||||
<router-link :to="{ name: 'inventoryManagement-inventoryManage', query: { isAlert: 1 } }">
|
||||
<el-button type="primary" size="mini">去处理</el-button>
|
||||
</router-link>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12" style="margin-top: 30px;" v-if="medicalNum > 0||medicalNum2>0">
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover">
|
||||
@@ -50,6 +61,7 @@ export default {
|
||||
medicalNum2: 0,
|
||||
orderNum: 0,
|
||||
workOrderNum: 0,
|
||||
stockAlertNum: 0,
|
||||
// loadAll:true,
|
||||
loaded: 0
|
||||
};
|
||||
@@ -58,6 +70,7 @@ export default {
|
||||
this.getMedicalList();
|
||||
this.getDataList();
|
||||
this.getWorkDataList();
|
||||
this.getStockAlertCount();
|
||||
},
|
||||
methods: {
|
||||
// 待发出列表
|
||||
@@ -140,12 +153,38 @@ export default {
|
||||
}).then(({ data }) => {
|
||||
this.medicalNum2 = data.page.total;
|
||||
});
|
||||
},
|
||||
getStockAlertCount() {
|
||||
Promise.all([1, 2].map(merchantId =>
|
||||
this.$http.request({
|
||||
url: this.$http.adornUrl('/master/inventoryManagement/stockStatistics'),
|
||||
method: 'POST',
|
||||
data: {
|
||||
merchantId,
|
||||
current: 1,
|
||||
limit: 1,
|
||||
isAlert: 1
|
||||
},
|
||||
header: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
)).then(results => {
|
||||
this.stockAlertNum = results.reduce((sum, { data }) => {
|
||||
if (data && data.code === 0) {
|
||||
return sum + (data.result.total || 0)
|
||||
}
|
||||
return sum
|
||||
}, 0)
|
||||
this.loaded += 1
|
||||
}).catch(() => {
|
||||
this.$message.error('获取库存预警数量失败')
|
||||
this.loaded += 1
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
loadAll() {
|
||||
console.log("this.loaded", this.loaded);
|
||||
if (this.loaded == 2) {
|
||||
if (this.loaded == 3) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-if="visible"
|
||||
title="批量出库"
|
||||
:visible.sync="visible"
|
||||
width="520px"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="!submitting"
|
||||
:show-close="!submitting"
|
||||
@closed="handleClosed"
|
||||
>
|
||||
<div v-loading="submitting" element-loading-text="提交中...">
|
||||
<el-form
|
||||
ref="form"
|
||||
:model="form"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item label="出库类型" prop="outboundType">
|
||||
<el-select
|
||||
v-model="form.outboundType"
|
||||
placeholder="请选择出库类型"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="item in outboundTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="文件" prop="file">
|
||||
<el-upload
|
||||
ref="upload"
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:limit="1"
|
||||
accept=".xlsx,.xls"
|
||||
:file-list="fileList"
|
||||
:on-change="handleFileChange"
|
||||
:on-remove="handleFileRemove"
|
||||
:on-exceed="handleExceed"
|
||||
>
|
||||
<el-button size="small" type="primary">选择文件</el-button>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<span slot="footer">
|
||||
<el-button :disabled="submitting" @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="handleSubmit">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const EXCLUDED_OUTBOUND_TYPES = ['App', 'donation']
|
||||
|
||||
export default {
|
||||
name: 'BatchOutboundDialog',
|
||||
data() {
|
||||
const validateFile = (rule, value, callback) => {
|
||||
if (!this.form.file) {
|
||||
callback(new Error('请选择文件'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
visible: false,
|
||||
submitting: false,
|
||||
merchantId: 1,
|
||||
outboundTypeOptions: [],
|
||||
fileList: [],
|
||||
form: {
|
||||
outboundType: '',
|
||||
file: null
|
||||
},
|
||||
formRules: {
|
||||
outboundType: [{ required: true, message: '请选择出库类型', trigger: 'change' }],
|
||||
file: [{ required: true, validator: validateFile, trigger: 'change' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getOutboundTypeOptions() {
|
||||
return this.$http({
|
||||
url: this.$http.adornUrl('/book/sysdictdata/selectByType/inventory_outbound_type'),
|
||||
method: 'get'
|
||||
}).then(({ data }) => {
|
||||
const list = (data && data.dataList) || []
|
||||
this.outboundTypeOptions = list
|
||||
.filter(item => !EXCLUDED_OUTBOUND_TYPES.includes(item.dictType))
|
||||
.map(item => ({
|
||||
label: item.dictValue,
|
||||
value: item.dictType
|
||||
}))
|
||||
}).catch(() => {
|
||||
this.outboundTypeOptions = []
|
||||
})
|
||||
},
|
||||
open(merchantId) {
|
||||
this.merchantId = Number(merchantId)
|
||||
this.submitting = false
|
||||
this.getOutboundTypeOptions().finally(() => {
|
||||
this.resetForm()
|
||||
this.visible = true
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.form) {
|
||||
this.$refs.form.clearValidate()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
resetForm() {
|
||||
this.form = {
|
||||
outboundType: '',
|
||||
file: null
|
||||
}
|
||||
this.fileList = []
|
||||
if (this.$refs.upload) {
|
||||
this.$refs.upload.clearFiles()
|
||||
}
|
||||
},
|
||||
handleFileChange(file, fileList) {
|
||||
this.fileList = fileList.slice(-1)
|
||||
this.form.file = file.raw || null
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.form) {
|
||||
this.$refs.form.validateField('file')
|
||||
}
|
||||
})
|
||||
},
|
||||
handleFileRemove() {
|
||||
this.fileList = []
|
||||
this.form.file = null
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.form) {
|
||||
this.$refs.form.validateField('file')
|
||||
}
|
||||
})
|
||||
},
|
||||
handleExceed() {
|
||||
this.$message.warning('只能上传一个文件')
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (!valid) return
|
||||
this.submitting = true
|
||||
const formData = new FormData()
|
||||
formData.append('file', this.form.file)
|
||||
formData.append('outboundType', this.form.outboundType)
|
||||
formData.append('merchantId', this.merchantId)
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/master/inventoryManagement/batchOutbound'),
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 0) {
|
||||
this.$message.success('操作成功')
|
||||
this.visible = false
|
||||
this.$emit('success')
|
||||
} else {
|
||||
this.$message.error(data.msg || '操作失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$message.error('操作失败')
|
||||
}).finally(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
})
|
||||
},
|
||||
handleClosed() {
|
||||
this.submitting = false
|
||||
this.resetForm()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<el-select
|
||||
:value="value"
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
reserve-keyword
|
||||
:placeholder="placeholder"
|
||||
:remote-method="searchProducts"
|
||||
:loading="loading"
|
||||
:style="selectStyle"
|
||||
@input="$emit('input', $event)"
|
||||
@change="handleChange"
|
||||
@clear="handleClear"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.productId"
|
||||
:label="item.productName"
|
||||
:value="item.productId"
|
||||
>
|
||||
<span>{{ item.productName }}</span>
|
||||
<span style="color: #999; margin-left: 8px">ID:{{ item.productId }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import debounce from 'lodash/debounce'
|
||||
|
||||
export default {
|
||||
name: 'ProductRemoteSelect',
|
||||
props: {
|
||||
value: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
merchantId: {
|
||||
type: [Number, String],
|
||||
default: '1'
|
||||
},
|
||||
goodsType: {
|
||||
type: String,
|
||||
default: '00'
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请输入商品名称检索'
|
||||
},
|
||||
selectStyle: {
|
||||
type: String,
|
||||
default: 'width: 240px'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
options: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
merchantId() {
|
||||
this.resetSelection()
|
||||
},
|
||||
goodsType() {
|
||||
this.resetSelection()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.debouncedSearch = debounce(this.fetchProducts, 300)
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.debouncedSearch && this.debouncedSearch.cancel) {
|
||||
this.debouncedSearch.cancel()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clear() {
|
||||
if (this.debouncedSearch && this.debouncedSearch.cancel) {
|
||||
this.debouncedSearch.cancel()
|
||||
}
|
||||
this.options = []
|
||||
this.loading = false
|
||||
},
|
||||
resetSelection() {
|
||||
this.options = []
|
||||
this.$emit('input', '')
|
||||
this.$emit('change', null)
|
||||
},
|
||||
searchProducts(query) {
|
||||
if (!query) {
|
||||
this.options = []
|
||||
return
|
||||
}
|
||||
this.debouncedSearch(query)
|
||||
},
|
||||
fetchProducts(query) {
|
||||
this.loading = true
|
||||
const params = {
|
||||
current: 1,
|
||||
limit: 20,
|
||||
goodsType: this.goodsType
|
||||
}
|
||||
if (query) {
|
||||
params.productName = query
|
||||
}
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/master/shopProduct/listByPage'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(params)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 0) {
|
||||
this.options = data.result.records || []
|
||||
} else {
|
||||
this.options = []
|
||||
}
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.options = []
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleChange(productId) {
|
||||
const product = this.options.find(item => item.productId === productId) || null
|
||||
this.$emit('change', product)
|
||||
},
|
||||
handleClear() {
|
||||
this.options = []
|
||||
this.$emit('change', null)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,289 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-if="visible"
|
||||
:title="dialogTitle"
|
||||
:visible.sync="visible"
|
||||
width="520px"
|
||||
:close-on-click-modal="false"
|
||||
@closed="handleClosed"
|
||||
>
|
||||
<el-form
|
||||
ref="form"
|
||||
:model="form"
|
||||
:rules="formRules"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item v-if="bizType === 2" label="出库类型" prop="outboundType">
|
||||
<el-select
|
||||
v-model="form.outboundType"
|
||||
placeholder="请选择出库类型"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="item in outboundTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品类型" prop="goodsType">
|
||||
<el-radio-group v-model="form.goodsType" @change="handleGoodsTypeChange">
|
||||
<el-radio
|
||||
v-for="item in goodsTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.value"
|
||||
>{{ item.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品" prop="productId">
|
||||
<product-remote-select
|
||||
v-model="form.productId"
|
||||
:merchant-id="merchantId"
|
||||
:goods-type="form.goodsType"
|
||||
placeholder="请输入商品名称检索"
|
||||
select-style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="bizType === 1" label="供应商类型" prop="source">
|
||||
<el-select
|
||||
v-model="form.source"
|
||||
placeholder="请选择供应商类型"
|
||||
style="width: 100%"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="item in sourceOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="数量" prop="quantity">
|
||||
<el-input-number
|
||||
v-model="form.quantity"
|
||||
:min="1"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="单价" prop="unitCost">
|
||||
<el-input-number
|
||||
v-model="form.unitCost"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="选填"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="合计价格">
|
||||
<span class="total-price">{{ totalPrice }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<span slot="footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="handleSubmit">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProductRemoteSelect from './productRemoteSelect.vue'
|
||||
|
||||
const GOODS_TYPE_LABELS = ['书籍', '预售书', '仪器']
|
||||
const DEFAULT_GOODS_TYPE_OPTIONS = [
|
||||
{ label: '书籍', value: '00' },
|
||||
{ label: '预售书', value: '01' },
|
||||
{ label: '仪器', value: '02' }
|
||||
]
|
||||
|
||||
export default {
|
||||
name: 'PurchaseDialog',
|
||||
components: { ProductRemoteSelect },
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
submitting: false,
|
||||
bizType: 1,
|
||||
merchantId: '1',
|
||||
goodsTypeOptions: DEFAULT_GOODS_TYPE_OPTIONS,
|
||||
sourceOptions: [],
|
||||
outboundTypeOptions: [],
|
||||
form: {
|
||||
goodsType: '00',
|
||||
productId: '',
|
||||
source: '',
|
||||
outboundType: '',
|
||||
quantity: 1,
|
||||
unitCost: 0,
|
||||
remark: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dialogTitle() {
|
||||
return this.bizType === 1 ? '入库' : '出库'
|
||||
},
|
||||
totalPrice() {
|
||||
const qty = this.form.quantity || 0
|
||||
const cost = this.form.unitCost || 0
|
||||
return (qty * cost).toFixed(2)
|
||||
},
|
||||
formRules() {
|
||||
const rules = {
|
||||
goodsType: [{ required: true, message: '请选择商品类型', trigger: 'change' }],
|
||||
productId: [{ required: true, message: '请选择商品', trigger: 'change' }],
|
||||
quantity: [{ required: true, message: '请输入数量', trigger: 'blur' }],
|
||||
unitCost: [{ required: true, message: '请输入单价', trigger: 'blur' }]
|
||||
}
|
||||
if (this.bizType === 1) {
|
||||
rules.source = [{ required: true, message: '请选择供应商类型', trigger: 'change' }]
|
||||
}
|
||||
if (this.bizType === 2) {
|
||||
rules.outboundType = [{ required: true, message: '请选择出库类型', trigger: 'change' }]
|
||||
}
|
||||
return rules
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getGoodsTypeOptions() {
|
||||
return this.$http({
|
||||
url: this.$http.adornUrl('/book/sysdictdata/selectByType/goodsType'),
|
||||
method: 'get'
|
||||
}).then(({ data }) => {
|
||||
const list = (data && data.dataList) || []
|
||||
const options = list
|
||||
.filter(item => GOODS_TYPE_LABELS.includes(item.dictValue))
|
||||
.map(item => ({ label: item.dictValue, value: item.dictType }))
|
||||
if (options.length) {
|
||||
this.goodsTypeOptions = options
|
||||
}
|
||||
}).catch(() => {})
|
||||
},
|
||||
getSourceTypeOptions() {
|
||||
return this.$http({
|
||||
url: this.$http.adornUrl('/book/sysdictdata/selectByType/inventory_source_type'),
|
||||
method: 'get'
|
||||
}).then(({ data }) => {
|
||||
const list = (data && data.dataList) || []
|
||||
this.sourceOptions = list.map(item => ({
|
||||
label: item.dictValue,
|
||||
value: item.dictType
|
||||
}))
|
||||
}).catch(() => {
|
||||
this.sourceOptions = []
|
||||
})
|
||||
},
|
||||
getOutboundTypeOptions() {
|
||||
return this.$http({
|
||||
url: this.$http.adornUrl('/book/sysdictdata/selectByType/inventory_outbound_type'),
|
||||
method: 'get'
|
||||
}).then(({ data }) => {
|
||||
const list = (data && data.dataList) || []
|
||||
this.outboundTypeOptions = list.map(item => ({
|
||||
label: item.dictValue,
|
||||
value: item.dictType
|
||||
}))
|
||||
}).catch(() => {
|
||||
this.outboundTypeOptions = []
|
||||
})
|
||||
},
|
||||
getDefaultGoodsType() {
|
||||
const bookType = this.goodsTypeOptions.find(item => item.label === '书籍')
|
||||
return bookType ? bookType.value : '00'
|
||||
},
|
||||
open(bizType, merchantId) {
|
||||
this.bizType = bizType
|
||||
this.merchantId = Number(merchantId)
|
||||
this.submitting = false
|
||||
const requests = [this.getGoodsTypeOptions()]
|
||||
if (bizType === 1) {
|
||||
requests.push(this.getSourceTypeOptions())
|
||||
} else {
|
||||
requests.push(this.getOutboundTypeOptions())
|
||||
}
|
||||
Promise.all(requests).finally(() => {
|
||||
this.form = {
|
||||
goodsType: this.getDefaultGoodsType(),
|
||||
productId: '',
|
||||
source: '',
|
||||
outboundType: '',
|
||||
quantity: 1,
|
||||
unitCost: 0,
|
||||
remark: ''
|
||||
}
|
||||
this.visible = true
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.form) {
|
||||
this.$refs.form.clearValidate()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
handleGoodsTypeChange() {
|
||||
this.form.productId = ''
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (!valid) return
|
||||
this.submitting = true
|
||||
const payload = {
|
||||
productId: this.form.productId,
|
||||
merchantId: this.merchantId,
|
||||
quantity: this.form.quantity,
|
||||
unitCost: this.form.unitCost,
|
||||
biz_type: this.bizType
|
||||
}
|
||||
if (this.form.remark) {
|
||||
payload.remark = this.form.remark
|
||||
}
|
||||
if (this.bizType === 1) {
|
||||
payload.source = this.form.source
|
||||
}
|
||||
if (this.bizType === 2) {
|
||||
payload.outboundType = this.form.outboundType
|
||||
}
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/master/inventoryManagement/purchaseProduct'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(payload)
|
||||
}).then(({ data }) => {
|
||||
this.submitting = false
|
||||
if (data && data.code === 0) {
|
||||
this.$message.success('操作成功')
|
||||
this.visible = false
|
||||
this.$emit('success')
|
||||
} else {
|
||||
this.$message.error(data.msg || '操作失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
})
|
||||
},
|
||||
handleClosed() {
|
||||
this.submitting = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.total-price {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #17b3a3;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<div class="mod-config">
|
||||
<el-tabs v-model="merchantId" type="card" @tab-click="changeTab">
|
||||
<el-tab-pane label="众妙之门" name="2" />
|
||||
<el-tab-pane label="灵枢教育科技" name="1" />
|
||||
</el-tabs>
|
||||
|
||||
<el-form
|
||||
:inline="true"
|
||||
:model="dataForm"
|
||||
@keyup.enter.native="handleSearch"
|
||||
>
|
||||
<el-form-item>
|
||||
<el-input
|
||||
v-model="dataForm.productName"
|
||||
placeholder="商品名称"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-date-picker
|
||||
v-model="dataForm.dateRange"
|
||||
type="daterange"
|
||||
value-format="yyyy-MM-dd"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleSearch">查询</el-button>
|
||||
<el-button type="success" :loading="exportLoading" @click="handleExport">导出</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- <div v-if="remainTotalStock !== null" class="summary-bar">
|
||||
库存合计:<span class="summary-value">{{ remainTotalStock }}</span>
|
||||
</div> -->
|
||||
|
||||
<el-table
|
||||
:data="dataList"
|
||||
border
|
||||
v-loading="dataListLoading"
|
||||
height="65vh"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column prop="id" label="ID" width="70" align="center" />
|
||||
<el-table-column prop="productId" label="商品ID" width="90" align="center" />
|
||||
<el-table-column prop="productName" label="商品名称" align="center" min-width="160" />
|
||||
<el-table-column prop="quantity" :label="quantityLabel" width="100" align="center" />
|
||||
<el-table-column v-if="bizType === 1" prop="remainQuantity" label="剩余数量" width="100" align="center" />
|
||||
<el-table-column prop="unitCost" label="成本单价" width="100" align="center" />
|
||||
<el-table-column label="时间" width="170" align="center">
|
||||
<template slot-scope="scope">
|
||||
{{ formatTime(scope.row.create_time) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="bizType === 1" label="供应商类型" width="100" align="center">
|
||||
<template slot-scope="scope">
|
||||
{{ formatSource(scope.row.source) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="bizType === 2" prop="outboundTypeLabel" label="出库类型" width="100" align="center" />
|
||||
<el-table-column label="备注" align="center" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.remark || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="bizType === 2" label="订单号" width="200" align="center">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.orderSn != null ? scope.row.orderSn : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
@size-change="sizeChangeHandle"
|
||||
@current-change="currentChangeHandle"
|
||||
:current-page="pageIndex"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size="pageSize"
|
||||
:total="totalPage"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'StockRecordList',
|
||||
props: {
|
||||
bizType: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
quantityLabel: {
|
||||
type: String,
|
||||
default: '数量'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
merchantId: '2',
|
||||
dataForm: {
|
||||
productName: '',
|
||||
dateRange: null
|
||||
},
|
||||
dataList: [],
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
totalPage: 0,
|
||||
remainTotalStock: null,
|
||||
dataListLoading: false,
|
||||
exportLoading: false
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
this.getDataList()
|
||||
},
|
||||
methods: {
|
||||
handleSearch() {
|
||||
this.pageIndex = 1
|
||||
this.getDataList()
|
||||
},
|
||||
changeTab() {
|
||||
this.pageIndex = 1
|
||||
this.getDataList()
|
||||
},
|
||||
getQueryParams() {
|
||||
const params = {
|
||||
current: this.pageIndex,
|
||||
limit: this.pageSize,
|
||||
merchantId: Number(this.merchantId),
|
||||
bizType: this.bizType
|
||||
}
|
||||
if (this.dataForm.productName) {
|
||||
params.productName = this.dataForm.productName
|
||||
}
|
||||
if (this.dataForm.dateRange && this.dataForm.dateRange.length === 2) {
|
||||
params.startTime = this.dataForm.dateRange[0]
|
||||
params.endTime = this.dataForm.dateRange[1]
|
||||
}
|
||||
return params
|
||||
},
|
||||
getDataList() {
|
||||
this.dataListLoading = true
|
||||
this.$http.request({
|
||||
url: this.$http.adornUrl('/master/inventoryManagement/getStockRecordList'),
|
||||
method: 'POST',
|
||||
data: this.getQueryParams(),
|
||||
header: { 'Content-Type': 'application/json' }
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 0) {
|
||||
this.dataList = data.result.records || []
|
||||
this.totalPage = data.result.total || 0
|
||||
this.pageIndex = data.result.current || this.pageIndex
|
||||
this.remainTotalStock = data.result.remainTotalStock
|
||||
} else {
|
||||
this.dataList = []
|
||||
this.totalPage = 0
|
||||
this.remainTotalStock = null
|
||||
}
|
||||
this.dataListLoading = false
|
||||
}).catch(() => {
|
||||
this.dataList = []
|
||||
this.totalPage = 0
|
||||
this.remainTotalStock = null
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
sizeChangeHandle(val) {
|
||||
this.pageSize = val
|
||||
this.pageIndex = 1
|
||||
this.getDataList()
|
||||
},
|
||||
currentChangeHandle(val) {
|
||||
this.pageIndex = val
|
||||
this.getDataList()
|
||||
},
|
||||
formatTime(time) {
|
||||
if (!time) return '-'
|
||||
return String(time).replace('T', ' ').slice(0, 19)
|
||||
},
|
||||
formatSource(source) {
|
||||
const map = { other: '其他', zm: '众妙', ls: '灵枢' }
|
||||
return map[source] || source || '-'
|
||||
},
|
||||
handleExport() {
|
||||
this.exportLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/master/inventoryManagement/exportStockRecordList'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(this.getQueryParams()),
|
||||
responseType: 'blob'
|
||||
}).then(res => {
|
||||
const blob = new Blob([res.data], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
})
|
||||
const link = document.createElement('a')
|
||||
link.href = window.URL.createObjectURL(blob)
|
||||
link.download = this.bizType === 1 ? '入库记录.xlsx' : '出库记录.xlsx'
|
||||
link.click()
|
||||
window.URL.revokeObjectURL(link.href)
|
||||
this.$message.success('导出成功')
|
||||
}).catch(() => {
|
||||
this.$message.error('导出失败')
|
||||
}).finally(() => {
|
||||
this.exportLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.summary-bar {
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.summary-value {
|
||||
color: #17b3a3;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
237
src/views/modules/inventoryManagement/inventoryManage.vue
Normal file
237
src/views/modules/inventoryManagement/inventoryManage.vue
Normal file
@@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<div class="mod-config">
|
||||
<el-tabs v-model="merchantId" type="card" @tab-click="changeTab">
|
||||
<el-tab-pane label="众妙之门" name="2" />
|
||||
<el-tab-pane label="灵枢教育科技" name="1" />
|
||||
</el-tabs>
|
||||
|
||||
<el-form
|
||||
:inline="true"
|
||||
:model="dataForm"
|
||||
@keyup.enter.native="handleSearch"
|
||||
>
|
||||
<el-form-item label="商品名称">
|
||||
<el-input
|
||||
v-model="dataForm.productName"
|
||||
placeholder="商品名称"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="库存状态">
|
||||
<el-select v-model="dataForm.isAlert" placeholder="请选择" style="width: 120px">
|
||||
<el-option label="全部" :value="0" />
|
||||
<el-option label="仅预警" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-date-picker
|
||||
v-model="dataForm.dateRange"
|
||||
type="daterange"
|
||||
value-format="yyyy-MM-dd"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleSearch">查询</el-button>
|
||||
<el-button type="success" :loading="exportLoading" @click="handleExport">导出</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="action-bar">
|
||||
<el-button type="primary" @click="openPurchase(1)">入库</el-button>
|
||||
<el-button type="warning" @click="openPurchase(2)">出库</el-button>
|
||||
<el-button type="warning" @click="openBatchOutbound">批量出库</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
:data="dataList"
|
||||
border
|
||||
v-loading="dataListLoading"
|
||||
height="60vh"
|
||||
style="width: 100%"
|
||||
:row-class-name="tableRowClassName"
|
||||
>
|
||||
<el-table-column prop="productId" label="商品ID" width="90" align="center" />
|
||||
<el-table-column prop="productName" label="商品名称" align="center" min-width="180" />
|
||||
<el-table-column prop="merchantLabel" label="商户" width="100" align="center" />
|
||||
<el-table-column prop="purchaseTotal" label="入库总量" width="100" align="center" />
|
||||
<el-table-column prop="saleTotal" label="出库总量" width="100" align="center" />
|
||||
<el-table-column prop="remainTotal" label="剩余库存" width="100" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span class="remain-total">{{ scope.row.remainTotal }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="剩余比率" width="100" align="center">
|
||||
<template slot-scope="scope">
|
||||
{{ formatRate(scope.row.remainRate) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
@size-change="sizeChangeHandle"
|
||||
@current-change="currentChangeHandle"
|
||||
:current-page="pageIndex"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size="pageSize"
|
||||
:total="totalPage"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
/>
|
||||
|
||||
<purchase-dialog ref="purchaseDialog" @success="getDataList" />
|
||||
<batch-outbound-dialog ref="batchOutboundDialog" @success="getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PurchaseDialog from './components/purchaseDialog.vue'
|
||||
import BatchOutboundDialog from './components/batchOutboundDialog.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PurchaseDialog,
|
||||
BatchOutboundDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
merchantId: '2',
|
||||
dataForm: {
|
||||
productName: '',
|
||||
isAlert: 0,
|
||||
dateRange: null
|
||||
},
|
||||
dataList: [],
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
totalPage: 0,
|
||||
dataListLoading: false,
|
||||
exportLoading: false
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
this.applyRouteQuery()
|
||||
this.getDataList()
|
||||
},
|
||||
methods: {
|
||||
applyRouteQuery() {
|
||||
const isAlert = this.$route.query.isAlert
|
||||
if (isAlert !== undefined && isAlert !== '') {
|
||||
this.dataForm.isAlert = Number(isAlert) || 0
|
||||
}
|
||||
},
|
||||
handleSearch() {
|
||||
this.pageIndex = 1
|
||||
this.getDataList()
|
||||
},
|
||||
changeTab() {
|
||||
this.dataForm.productName = ''
|
||||
this.pageIndex = 1
|
||||
this.getDataList()
|
||||
},
|
||||
getQueryParams() {
|
||||
const params = {
|
||||
merchantId: Number(this.merchantId),
|
||||
current: this.pageIndex,
|
||||
limit: this.pageSize,
|
||||
isAlert: this.dataForm.isAlert
|
||||
}
|
||||
if (this.dataForm.productName) {
|
||||
params.productName = this.dataForm.productName
|
||||
}
|
||||
if (this.dataForm.dateRange && this.dataForm.dateRange.length === 2) {
|
||||
params.startTime = this.dataForm.dateRange[0]
|
||||
params.endTime = this.dataForm.dateRange[1]
|
||||
}
|
||||
return params
|
||||
},
|
||||
getDataList() {
|
||||
this.dataListLoading = true
|
||||
this.$http.request({
|
||||
url: this.$http.adornUrl('/master/inventoryManagement/stockStatistics'),
|
||||
method: 'POST',
|
||||
data: this.getQueryParams(),
|
||||
header: { 'Content-Type': 'application/json' }
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 0) {
|
||||
this.dataList = data.result.records || []
|
||||
this.totalPage = data.result.total || 0
|
||||
this.pageIndex = data.result.current || this.pageIndex
|
||||
} else {
|
||||
this.dataList = []
|
||||
this.totalPage = 0
|
||||
}
|
||||
this.dataListLoading = false
|
||||
}).catch(() => {
|
||||
this.dataList = []
|
||||
this.totalPage = 0
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
sizeChangeHandle(val) {
|
||||
this.pageSize = val
|
||||
this.pageIndex = 1
|
||||
this.getDataList()
|
||||
},
|
||||
currentChangeHandle(val) {
|
||||
this.pageIndex = val
|
||||
this.getDataList()
|
||||
},
|
||||
formatRate(rate) {
|
||||
if (rate == null) return '-'
|
||||
return (Number(rate) * 100).toFixed(2) + '%'
|
||||
},
|
||||
tableRowClassName({ row }) {
|
||||
if (row.remainRate != null && Number(row.remainRate) <= 0.3) {
|
||||
return 'row-low-stock'
|
||||
}
|
||||
return ''
|
||||
},
|
||||
openPurchase(bizType) {
|
||||
this.$refs.purchaseDialog.open(bizType, Number(this.merchantId))
|
||||
},
|
||||
openBatchOutbound() {
|
||||
this.$refs.batchOutboundDialog.open(Number(this.merchantId))
|
||||
},
|
||||
handleExport() {
|
||||
this.exportLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/master/inventoryManagement/exportStockStatistics'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(this.getQueryParams()),
|
||||
responseType: 'blob'
|
||||
}).then(res => {
|
||||
const blob = new Blob([res.data], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
})
|
||||
const link = document.createElement('a')
|
||||
link.href = window.URL.createObjectURL(blob)
|
||||
link.download = '库存统计.xlsx'
|
||||
link.click()
|
||||
window.URL.revokeObjectURL(link.href)
|
||||
this.$message.success('导出成功')
|
||||
}).catch(() => {
|
||||
this.$message.error('导出失败')
|
||||
}).finally(() => {
|
||||
this.exportLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.action-bar {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
/deep/ .el-table .row-low-stock{
|
||||
td {
|
||||
background-color: #fffaf2 !important;
|
||||
}
|
||||
.remain-total {
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
11
src/views/modules/inventoryManagement/stockInRecord.vue
Normal file
11
src/views/modules/inventoryManagement/stockInRecord.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<stock-record-list :biz-type="1" quantity-label="入库数量" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StockRecordList from './components/stockRecordList.vue'
|
||||
|
||||
export default {
|
||||
components: { StockRecordList }
|
||||
}
|
||||
</script>
|
||||
11
src/views/modules/inventoryManagement/stockOutRecord.vue
Normal file
11
src/views/modules/inventoryManagement/stockOutRecord.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<stock-record-list :biz-type="2" quantity-label="出库数量" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StockRecordList from './components/stockRecordList.vue'
|
||||
|
||||
export default {
|
||||
components: { StockRecordList }
|
||||
}
|
||||
</script>
|
||||
@@ -122,6 +122,7 @@
|
||||
<el-input
|
||||
v-model="dataForm.productStock"
|
||||
placeholder="商品库存"
|
||||
:disabled="dataForm.goodsType !== '05'"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
@@ -172,6 +172,7 @@
|
||||
<el-input
|
||||
v-model="dataForm.productStock"
|
||||
placeholder="商品库存"
|
||||
:disabled="dataForm.goodsType !== '05'"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
@@ -323,7 +324,7 @@ export default {
|
||||
showBtnDealImg: true,
|
||||
noneBtnImg: false,
|
||||
productName: "",
|
||||
productStock: null, // 商品库存
|
||||
productStock: 0, // 商品库存
|
||||
price: "",
|
||||
author: "",
|
||||
publisher: "",
|
||||
|
||||
@@ -210,13 +210,6 @@
|
||||
@showchooseBookf="showchooseBookf"
|
||||
></curriculum>
|
||||
|
||||
<add-or-update
|
||||
v-if="addOrUpdateVisible"
|
||||
ref="addOrUpdate"
|
||||
@refreshDataList="getDataList"
|
||||
@showchooseBookf="showchooseBookf"
|
||||
></add-or-update>
|
||||
|
||||
<commonTags ref="commonTags"></commonTags>
|
||||
<correlation ref="correlation"></correlation>
|
||||
<choose-book v-if="chooseBookVisible" :bookIds = bookIds ref="chooseBook" :chooseBookVisible = chooseBookVisible @closeBookf = "closeBookf"></choose-book>
|
||||
|
||||
@@ -16,17 +16,20 @@
|
||||
<el-popover
|
||||
ref="menuListPopover"
|
||||
placement="bottom-start"
|
||||
trigger="click">
|
||||
<el-tree
|
||||
:data="menuList"
|
||||
:props="menuListTreeProps"
|
||||
node-key="menuId"
|
||||
ref="menuListTree"
|
||||
@current-change="menuListTreeCurrentChangeHandle"
|
||||
:default-expand-all="true"
|
||||
:highlight-current="true"
|
||||
:expand-on-click-node="false">
|
||||
</el-tree>
|
||||
trigger="click"
|
||||
popper-class="mod-menu__tree-popover">
|
||||
<div class="mod-menu__tree-inner">
|
||||
<el-tree
|
||||
:data="menuList"
|
||||
:props="menuListTreeProps"
|
||||
node-key="menuId"
|
||||
ref="menuListTree"
|
||||
@current-change="menuListTreeCurrentChangeHandle"
|
||||
:default-expand-all="true"
|
||||
:highlight-current="true"
|
||||
:expand-on-click-node="false">
|
||||
</el-tree>
|
||||
</div>
|
||||
</el-popover>
|
||||
<el-input v-model="dataForm.parentName" v-popover:menuListPopover :readonly="true" placeholder="点击选择上级菜单" class="menu-list__input"></el-input>
|
||||
</el-form-item>
|
||||
@@ -228,6 +231,14 @@
|
||||
width: 458px;
|
||||
overflow: hidden;
|
||||
}
|
||||
&__tree-popover {
|
||||
overflow: hidden;
|
||||
}
|
||||
&__tree-inner {
|
||||
max-height: 300px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
&__icon-inner {
|
||||
width: 478px;
|
||||
max-height: 258px;
|
||||
|
||||
Reference in New Issue
Block a user