| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789 |
- <template>
- <div :style="{ backgroundColor: bgColor }">
- <q-dialog
- v-model="storage_dialog"
- transition-show="jump-down"
- transition-hide="jump-up"
- @show="prepareDialog()"
- >
- <q-card style="min-width: 1000px">
- <q-bar
- class="bg-light-blue-10 text-white rounded-borders"
- style="height: 50px"
- >
- <div>
- {{ "托盘" }}
- </div>
- <q-space></q-space>
- {{ containerCode }}
- </q-bar>
- <q-card-section class="q-pt-md">
- <q-tabs v-model="activeTab">
- <q-tab name="tab2" label="托盘信息" />
- <q-tab name="tab3" label="托盘详情" />
- <q-tab name="tab1" label="操作记录" />
- </q-tabs>
- </q-card-section>
- <q-tab-panels v-model="activeTab" animated>
- <q-tab-panel name="tab2" style="height: 600px">
- <template>
- <div class="text-h6 q-mb-md">{{ "托盘信息" }}</div>
- <q-table
- v-if="storage_form.length > 0"
- :data="storage_form"
- :columns="columns_batch"
- row-key="id"
- flat
- bordered
- hide-pagination
- class="my-sticky-table scrollable-table"
- :style="{ 'max-height': '400px' }"
- :container-style="{ height: 'auto' }"
- :pagination="{ rowsPerPage: 0 }"
- >
- </q-table>
- <div style="float: right; min-width: 100%" flow="row wrap">
- <q-card class="q-mb-md" bordered>
- <q-card-actions
- class="q-px-none"
- style="
- position: absolute;
- right: 20px;
- top: 10px;
- z-index: 100;
- "
- >
- <q-btn
- flat
- dense
- color="primary"
- @click="showInventoryDetails = !showInventoryDetails"
- >
- {{ showInventoryDetails ? "收起" : "展开" }}
- </q-btn>
- </q-card-actions>
- <q-card-section>
- <div class="text-h6 q-mb-md">
- {{ "物料详情" }}
- </div>
- <q-table
- v-if="showInventoryDetails"
- :data="results"
- :columns="columns_results"
- row-key="id"
- flat
- bordered
- hide-pagination
- class="my-sticky-table scrollable-table"
- :style="{ 'max-height': '400px' }"
- :container-style="{ height: 'auto' }"
- :pagination="{ rowsPerPage: 0 }"
- >
- </q-table>
- </q-card-section>
- </q-card>
- </div>
- </template>
- </q-tab-panel>
- <q-tab-panel name="tab3" style="height: 600px">
- <q-table
- :data="container_table"
- :columns="coloums_container"
- row-key="id"
- flat
- bordered
- hide-pagination
- class="my-sticky-table scrollable-table"
- :style="{ 'max-height': '600px' }"
- :container-style="{ height: 'auto' }"
- :pagination="{ rowsPerPage: 0 }"
- >
- <!-- 入库重量列可编辑 - 添加name属性 -->
- <template v-slot:body-cell-goods_qty="props">
- <q-td :props="props">
- <q-input
- v-if="props.row.editing && can_edit_detail"
- v-model.number="props.row.goods_qty"
- type="number"
- dense
- outlined
- @keyup.enter="handleRowSave(props.row)"
- />
- <span v-else>
- {{ props.row.goods_qty }}
- </span>
- </q-td>
- </template>
- <!-- 出库重量列可编辑 - 添加name属性 -->
- <template v-slot:body-cell-goods_out_qty="props">
- <q-td :props="props">
- <q-input
- v-if="props.row.editing && can_edit_detail"
- v-model.number="props.row.goods_out_qty"
- type="number"
- dense
- outlined
- @keyup.enter="handleRowSave(props.row)"
- />
- <span v-else>
- {{ props.row.goods_out_qty }}
- </span>
- </q-td>
- </template>
- <!-- 操作列按钮 -->
- <template v-slot:body-cell-actions="props">
- <q-td :props="props">
- <template v-if="props.row.editing">
- <q-btn
- dense
- round
- color="positive"
- icon="save"
- @click="handleRowSave(props.row)"
- >
- <q-tooltip>保存修改</q-tooltip>
- </q-btn>
- <q-btn
- dense
- round
- color="negative"
- icon="cancel"
- @click="handleRowCancel(props.row)"
- >
- <q-tooltip>取消编辑</q-tooltip>
- </q-btn>
- </template>
- <template v-else>
- <q-btn
- v-if="hasPermission('edit')"
- dense
- round
- color="primary"
- icon="edit"
- @click="handleRowEdit(props.row)"
- >
- <q-tooltip>编辑该行数据</q-tooltip>
- </q-btn>
- <q-btn
- dense
- v-if="hasPermission('edit')"
- round
- color="negative"
- icon="delete"
- @click="handleRowdelete(props.row)"
- >
- <q-tooltip>删除该行数据</q-tooltip>
- </q-btn>
- </template>
- </q-td>
- </template>
- </q-table>
- </q-tab-panel>
- <q-tab-panel name="tab1" style="height: 600px">
- <div
- style="
- float: right;
- padding: 15px 15px 50px 15px;
- min-width: 100%;
- "
- flow="row wrap"
- >
- <q-card class="q-mb-md" bordered>
- <q-card-section>
- <template>
- <div
- class="text-h6 q-mb-md"
- style="display: flex; justify-content: space-between"
- >
- {{ "操作记录" }}
- </div>
- <q-btn
- class="q-ml-sm"
- color="primary"
- icon="event"
- @click="sortoperatedetail"
- >近一个月操作记录</q-btn
- >
- <template v-if="listSize > 0">
- <q-list bordered class="rounded-borders">
- <q-expansion-item
- v-for="(node, index) in nodeList"
- v-if="node.value && node.value.length > 0"
- :key="index"
- group="op-group"
- :caption="`操作类型: ${formatType(
- node.value[0].operation_type
- )} ----- 操作时间:${node.value[0].timestamp}`"
- >
- <q-card>
- <q-card-section>
- <q-table
- :data="node.value"
- :columns="columns_operate"
- row-key="id"
- flat
- bordered
- hide-pagination
- class="my-sticky-table scrollable-table"
- :style="{ 'max-height': '400px' }"
- :container-style="{ height: 'auto' }"
- :pagination="{ rowsPerPage: 0 }"
- >
- </q-table>
- </q-card-section>
- </q-card>
- </q-expansion-item>
- </q-list>
- </template>
- <div v-else class="text-grey-8">暂无操作记录</div>
- </template>
- </q-card-section>
- </q-card>
- </div>
- </q-tab-panel>
- </q-tab-panels>
- </q-card>
- </q-dialog>
- </div>
- </template>
- <script>
- class ListNode {
- constructor (value) {
- this.value = value
- this.next = null
- }
- }
- class LinkedList {
- constructor () {
- this.head = null
- this.size = 0
- }
- append (value) {
- const newNode = new ListNode(value)
- if (!this.head) {
- this.head = newNode
- } else {
- let current = this.head
- while (current.next) {
- current = current.next
- }
- current.next = newNode
- }
- this.size++
- }
- delete (value) {
- if (!this.head) return
- if (this.head.value === value) {
- this.head = this.head.next
- this.size--
- return
- }
- let current = this.head
- while (current.next) {
- if (current.next.value === value) {
- current.next = current.next.next
- this.size--
- return
- }
- current = current.next
- }
- }
- toArray () {
- const result = []
- let current = this.head
- while (current) {
- result.push({ value: current.value })
- current = current.next
- }
- return result
- }
- clear () {
- this.head = null
- this.size = 0
- }
- }
- import { getauth, putauth, deleteauth, postauth } from 'boot/axios_request'
- import { LocalStorage } from 'quasar'
- export default {
- props: {
- containerCode: Number,
- containerNumber: Number
- },
- data () {
- return {
- pathnamecontainer: 'container/locationdetail/',
- pathnamecontainer_detail: 'container/containerdetail/',
- container_id: 123456,
- results: [],
- container_table: [],
- storage_form: [],
- showInventoryDetails: true,
- columns_batch: [
- {
- name: 'bound_number',
- label: '批次',
- field: (row) => row.bound_number,
- align: 'center'
- },
- {
- name: 'plan_weight',
- label: '当前库位容纳重量',
- field: (row) => row.total_batch_qty,
- align: 'center'
- }
- ],
- columns_results: [
- {
- label: '物料编码',
- field: (row) => row.goods_code,
- align: 'center'
- },
- {
- label: '物料名称',
- field: (row) => row.goods_desc,
- align: 'center'
- },
- {
- label: '物料批次',
- field: (row) => row.bound_number,
- align: 'center'
- },
- {
- label: '件数',
- field: (row) => row.group_qty,
- align: 'center'
- },
- {
- label: '每件重量',
- field: (row) => row.goods_qty,
- align: 'center'
- },
- {
- label: '批次计划重量',
- field: (row) => row.batch_total_qty,
- align: 'center'
- },
- {
- label: '在库重量',
- field: (row) => row.batch_total_in_qty,
- align: 'center'
- },
- {
- label: '录入时间',
- field: (row) => row.create_time.slice(0, 10),
- align: 'center'
- }
- ],
- coloums_container: [
- {
- label: '物料编码',
- field: (row) => row.goods_code,
- align: 'center'
- },
- {
- label: '物料名称',
- field: (row) => row.goods_desc,
- align: 'center'
- },
- {
- label: '物料批次',
- field: (row) => row.batch,
- align: 'center'
- },
- {
- name: 'goods_qty', // 添加 name 属性
- label: '入库重量',
- field: (row) => row.goods_qty,
- align: 'center',
- sortable: true
- },
- {
- name: 'goods_out_qty', // 添加 name 属性
- label: '出库重量',
- field: (row) => row.goods_out_qty,
- align: 'center',
- sortable: true
- },
- {
- name: 'actions',
- label: '编辑',
- align: 'center',
- field: 'actions',
- sortable: false,
- headerStyle: 'width: 80px'
- }
- ],
- columns_operate: [
- {
- name: 'timestamp',
- label: '操作时间',
- field: (row) => row.timestamp,
- align: 'center'
- },
- {
- name: 'operator',
- label: '经手人',
- field: (row) => row.operator,
- align: 'center'
- },
- {
- name: 'batch',
- label: '批次',
- field: (row) => row.batch.bound_number,
- align: 'center'
- },
- {
- name: 'memo',
- label: '备注',
- field: (row) => row.memo,
- align: 'center'
- },
- {
- label: '当前位置',
- field: (row) => row.from_location,
- align: 'center'
- },
- {
- label: '目标位置',
- field: (row) => row.to_location,
- align: 'center'
- }
- ],
- user_id: '',
- auth_id: '',
- can_edit_detail: false,
- storage_dialog: false,
- bgColor: 'transparent',
- error1: this.$t('stock.shelf.error1'),
- shelfLocal: '',
- activeTab: 'tab2',
- operate_detail: [],
- userComponentPermissions: [], // 用户权限
- login_mode: LocalStorage.getItem('login_mode'), // 登录模式
- linkedList: new LinkedList()
- }
- },
- created () {
- this.handleclick()
- this.loadUserPermissions()
- },
- computed: {
- nodeList () {
- return this.linkedList.toArray()
- },
- listSize () {
- return this.linkedList.size
- }
- },
- methods: {
- // 检查用户是否有指定页面的组件访问权限
- loadUserPermissions () {
- postauth('staff/role-comPermissions/' + this.login_mode + '/', {
- page: '/container/containerlist'
- }).then(
- (response) => {
- // 处理分组权限结构
- this.userComponentPermissions = response
- },
- (error) => {
- this.$q.notify({
- type: 'negative',
- message: '加载用户权限失败,' + error.message
- })
- }
- )
- },
- hasPermission (components) {
- if (!this.userComponentPermissions) return false
- const permission = this.userComponentPermissions.find(
- (perm) => perm.component === components
- )
- return permission && permission.enabled
- },
- // 进入编辑模式
- handleRowEdit (row) {
- if (this.can_edit_detail) {
- // 保存原始数据用于取消操作时恢复
- row.originalData = {
- goods_qty: row.goods_qty,
- goods_out_qty: row.goods_out_qty
- }
- // 设置编辑状态
- row.editing = true
- // 强制更新视图,确保输入框显示
- this.$set(this.container_table, this.container_table.indexOf(row), row)
- } else {
- this.$q.notify({
- message: '权限不足,请联系管理员',
- icon: 'close',
- color: 'negative'
- })
- }
- },
- // 保存修改
- async handleRowSave (row) {
- try {
- const payload = {
- goods_code: row.goods_code,
- goods_desc: row.goods_desc,
- goods_qty: row.goods_qty,
- goods_weight: 1,
- goods_out_qty: row.goods_out_qty
- }
- await putauth(`container/detail/${row.id}/`, payload)
- this.$q.notify({
- message: '更新成功',
- color: 'positive',
- icon: 'check',
- timeout: 1000
- })
- // 退出编辑模式
- row.editing = false
- delete row.originalData
- // 强制更新视图
- this.$set(this.container_table, this.container_table.indexOf(row), row)
- } catch (err) {
- console.error('更新失败:', err)
- this.$q.notify({
- message: `更新失败: ${
- err.response?.data?.detail || err.message || '服务器错误'
- }`,
- color: 'negative',
- icon: 'warning',
- timeout: 3000
- })
- }
- },
- // 取消编辑
- handleRowCancel (row) {
- // 恢复原始数据
- if (row.originalData) {
- row.goods_qty = row.originalData.goods_qty
- row.goods_out_qty = row.originalData.goods_out_qty
- delete row.originalData
- }
- // 退出编辑模式
- row.editing = false
- // 强制更新视图
- this.$set(this.container_table, this.container_table.indexOf(row), row)
- },
- sortoperatedetail () {
- const oneMonthAgo = new Date()
- oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1)
- // 过滤出近一个月的操作记录
- const filteredList = this.linkedList.toArray().filter((node) => {
- return node.value.some(
- (item) => new Date(item.timestamp) > oneMonthAgo
- )
- })
- // 使用Vue的响应式特性更新显示
- this.$nextTick(() => {
- this.visibleNodes = filteredList
- this.$q.notify({
- message: `已筛选出近一个月操作记录 (共${filteredList.length}组)`,
- color: 'info',
- timeout: 1500
- })
- })
- },
- formatType (type) {
- switch (type) {
- case 'container':
- return '组盘'
- case 'outbound':
- return '出库'
- case 'inbound':
- return '入库'
- case 'adjust':
- return '移库'
- case '5':
- return '调拨'
- case '6':
- return '其他'
- default:
- return '未知'
- }
- },
- getOperationRecord () {
- var _this = this
- _this.operate_detail = []
- var operate_detail_container = []
- _this.linkedList.clear()
- getauth('container/operate/?status=1&container=' + _this.containerNumber)
- .then((res) => {
- _this.operate_detail = res.results
- if (_this.operate_detail.length === 0) return
- // 初始化第一个元素
- operate_detail_container.push(_this.operate_detail[0])
- for (let i = 0; i < _this.operate_detail.length - 1; i++) {
- const current = _this.operate_detail[i]
- const next = _this.operate_detail[i + 1]
- if (current.operation_type === next.operation_type) {
- operate_detail_container.push(next)
- } else {
- _this.linkedList.append([...operate_detail_container])
- operate_detail_container = [next]
- }
- }
- // 添加最后一个分组
- if (operate_detail_container.length > 0) {
- _this.linkedList.append([...operate_detail_container])
- }
- })
- .catch((err) => {
- _this.$q.notify({
- message: err.detail || '获取操作记录失败',
- icon: 'close',
- color: 'negative'
- })
- })
- },
- async deleteContainerData (rowData) {
- try {
- const res = await deleteauth(`container/detail/${rowData.id}/`)
- this.$q.notify({
- message: '删除成功',
- color: 'positive'
- })
- this.get_container_table() // 刷新数据
- } catch (err) {
- this.$q.notify({
- message: '删除失败: ' + (err.response?.data?.detail || err.message),
- color: 'negative'
- })
- }
- },
- handleRowdelete (rowData) {
- if (this.can_edit_detail === true) {
- this.$q
- .dialog({
- title: '删除物料信息',
- message: `确定删除物料 ${rowData.goods_code} (批次${rowData.batch}) 吗?`,
- ok: { label: '确定', color: 'negative' },
- cancel: { label: '取消', color: 'primary' }
- })
- .onOk(() => {
- this.deleteContainerData(rowData)
- })
- } else {
- this.$q.notify({
- message: '权限不足,请联系管理员',
- icon: 'close',
- color: 'negative'
- })
- }
- },
- prepareDialog () {
- if (this.hasPermission('edit')) {
- this.can_edit_detail = true
- } else {
- this.can_edit_detail = false
- }
- },
- handleclick () {
- this.getList()
- this.get_container_table()
- this.getOperationRecord()
- this.storage_dialog = true
- },
- get_container_table () {
- var _this = this
- getauth(
- _this.pathnamecontainer_detail + '?container=' + _this.containerNumber
- )
- .then((res) => {
- // 添加编辑状态属性
- _this.container_table = res.data.map((item) => ({
- ...item,
- editing: false
- }))
- })
- .catch((err) => {
- console.error('获取托盘详情失败:', err)
- _this.$q.notify({
- message:
- '获取托盘详情失败:' +
- (err.response?.data?.detail || err.message),
- color: 'negative'
- })
- })
- },
- getList () {
- var _this = this
- _this.storage_form = []
- _this.results = []
- getauth(_this.pathnamecontainer + '?container=' + _this.containerNumber)
- .then((res) => {
- var data = res.data
- this.storage_form = data.batch_totals || []
- this.results = data.results || []
- })
- .catch((err) => {
- console.error('获取托盘信息失败:', err)
- _this.$q.notify({
- message:
- '获取托盘信息失败:' +
- (err.response?.data?.detail || err.message),
- color: 'negative'
- })
- })
- }
- }
- }
- </script>
- <style scoped>
- :deep(.q-field__label) {
- margin-top: 8px;
- align-self: center;
- }
- :deep(.q-field__control-container) {
- padding-left: 50px;
- margin-top: -5px;
- }
- :deep(.q-table) .q-editable:hover {
- background-color: #f0f8ff;
- cursor: pointer;
- }
- :deep(.q-field__native) {
- padding: 5px 8px;
- }
- /* 高亮显示编辑状态的行 */
- :deep(.q-table tr.editing) {
- background-color: #e8f5e9 !important;
- }
- </style>
|