2 Commits

14 changed files with 1177 additions and 30 deletions

View File

@@ -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')

View File

@@ -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;

View File

@@ -241,17 +241,24 @@ export default {
'content': content
})
}).then(res => {
this.$message({
message: '成功',
type: 'success'
})
if (data && data.code === 0) {
this.$message({
message: '成功',
type: 'success'
})
this.loading = false
this.dataForm.voices = res.data.voices
var voices = { name: '音频文件', url: res.data.voices }
var attr = []
attr.push(voices)
this.fileListVoices = attr
console.log(res)
} else {
this.$message.error(res.msg)
}
}).catch(() => {
this.loading = false
this.dataForm.voices = res.data.voices
var voices = { name: '音频文件', url: res.data.voices }
var attr = []
attr.push(voices)
this.fileListVoices = attr
console.log(res)
this.$message.error('请求失败')
})
},
// 富文本内容赋值

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -0,0 +1,227 @@
<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)
const merchantPrefix = this.merchantId === '2' ? '众妙' : '灵枢'
const recordName = this.bizType === 1 ? '入库记录' : '出库记录'
link.download = `${merchantPrefix}${recordName}.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>

View File

@@ -0,0 +1,238 @@
<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)
const merchantPrefix = this.merchantId === '2' ? '众妙' : '灵枢'
link.download = `${merchantPrefix}库存统计.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>

View 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>

View 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>

View File

@@ -122,6 +122,7 @@
<el-input
v-model="dataForm.productStock"
placeholder="商品库存"
:disabled="dataForm.goodsType !== '05'"
></el-input>
</el-form-item>
</div>

View File

@@ -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: "",

View File

@@ -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>

View File

@@ -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;