no message

main
HUOJIN\92525 2025-11-28 16:34:24 +08:00
parent 9300da4988
commit 63224c8786
12 changed files with 964 additions and 71 deletions

View File

@ -1,18 +1,20 @@
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from "/@/hooks/web/useMessage";
import { useMessage } from '/@/hooks/web/useMessage';
const { createConfirm } = useMessage();
enum Api {
list = '/agvTask/list',
save='/agvTask/add',
edit='/agvTask/edit',
save = '/agvTask/add',
edit = '/agvTask/edit',
deleteOne = '/agvTask/delete',
deleteBatch = '/agvTask/deleteBatch',
importExcel = '/agvTask/importExcel',
exportXls = '/agvTask/exportXls',
taskReporter = '/api/robot/reporter/task',
callBackTask='/tes/apiv2/callBackTask',
callBackTask = '/tes/apiv2/callBackTask',
cancelTask = '/tes/apiv2/cancelTask',
resendTesTask = '/tes/apiv2/resendTesTask',
}
/**
@ -37,11 +39,11 @@ export const list = (params) => defHttp.get({ url: Api.list, params });
* @param params
* @param handleSuccess
*/
export const deleteOne = (params,handleSuccess) => {
return defHttp.delete({url: Api.deleteOne, params}, {joinParamsToUrl: true}).then(() => {
export const deleteOne = (params, handleSuccess) => {
return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
}
};
/**
*
@ -56,12 +58,20 @@ export const batchDelete = (params, handleSuccess) => {
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp.delete({url: Api.deleteBatch, data: params}, {joinParamsToUrl: true}).then(() => {
handleSuccess();
});
}
return defHttp
.delete(
{
url: Api.deleteBatch,
data: params,
},
{ joinParamsToUrl: true }
)
.then(() => {
handleSuccess();
});
},
});
}
};
/**
*
@ -71,26 +81,53 @@ export const batchDelete = (params, handleSuccess) => {
export const saveOrUpdate = (params, isUpdate) => {
let url = isUpdate ? Api.edit : Api.save;
return defHttp.post({ url: url, params }, { isTransformResponse: false });
}
};
/**
* AGV
* @param params
*/
export const taskReporter = (params) => {
return defHttp.post({url: Api.taskReporter, params}, {
joinParamsToUrl: false,
isTransformResponse: false
});
}
return defHttp.post(
{ url: Api.taskReporter, params },
{
isTransformResponse: false,
}
);
};
/**
* TES
* @param params
*/
export const callBackTask = (params) => {
return defHttp.post({url: Api.callBackTask, params}, {
joinParamsToUrl: false,
isTransformResponse: false
return defHttp.post(
{ url: Api.callBackTask, params },
{
isTransformResponse: false,
}
);
};
/**
*
* @param params
*/
export const cancelTask = (params) => {
return defHttp.post(
{ url: Api.cancelTask, params },
{
isTransformResponse: false,
}
);
};
/**
*
* @param params
*/
export const resendTesTask = (params) => {
return defHttp.post({ url: Api.resendTesTask, params },{
isTransformResponse: false,
});
}
};

View File

@ -45,6 +45,7 @@
</BasicTable>
<!-- 表单区域 -->
<TesAgvModal ref="registerModal" @success="handleSuccess"></TesAgvModal>
<ResendTesAgvModal ref="registerResendModal" @success="handleSuccess"></ResendTesAgvModal>
</div>
</template>
@ -53,9 +54,9 @@
import { BasicTable, TableAction } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import { columns } from './TesAgv.data';
import { list, deleteOne, batchDelete, getImportUrl, getExportUrl, callBackTask } from './AgvTask.api';
import { list, deleteOne, batchDelete, getImportUrl, getExportUrl, callBackTask, cancelTask } from './AgvTask.api';
import TesAgvModal from './components/TesAgvModal.vue';
import { useUserStore } from '/@/store/modules/user';
import ResendTesAgvModal from './components/ResendTesAgvModal.vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { getDateByPicker } from '/@/utils';
@ -65,12 +66,11 @@
const queryParam = reactive<any>({
agvVendor: 'TES',
});
const toggleSearchStatus = ref<boolean>(false);
const registerModal = ref();
const userStore = useUserStore();
const registerResendModal = ref();
const { createMessage } = useMessage();
//table
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
const { tableContext, onExportXls, onImportXls } = useListPage({
tableProps: {
title: 'TES任务表',
api: list,
@ -100,8 +100,7 @@
success: handleSuccess,
},
});
const [registerTable, { reload, collapseAll, updateTableDataRecord, findTableDataRecord, getDataSource }, { rowSelection, selectedRowKeys }] =
tableContext;
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
const labelCol = reactive({
xs: 24,
sm: 4,
@ -168,15 +167,45 @@
const res = await callBackTask(params);
if (res && res.returnCode === 0) {
createMessage.success('操作成功');
handleSuccess();
} else {
createMessage.error(res.message || '任务处理失败');
createMessage.error(res.returnMsg || '任务处理失败');
}
} catch (error) {
createMessage.error('请求异常: ' + error.message);
} finally {
handleSuccess();
}
}
/**
* 任务取消事件
*/
async function hanndleCancel(record) {
const params = {
taskID: record.id,
};
try {
const res = await cancelTask(params);
if (res && res.returnCode === 0) {
createMessage.success('操作成功');
} else {
createMessage.error(res.returnMsg || '任务处理失败');
}
} catch (error) {
createMessage.error('请求异常: ' + error.message);
} finally {
handleSuccess();
}
}
/**
* 任务重发
*/
function hanndleResend(record) {
registerResendModal.value.disableSubmit = false;
registerResendModal.value.resend(record);
}
/**
* 成功回调
*/
@ -214,10 +243,26 @@
placement: 'topLeft',
},
auth: 'agvTask:data_agv_task:edit',
disabled: record.status === 4 || record.status === 1,
disabled: record.status === 4 || record.status === 1 || record.status === 5,
},
{
label: '删除任务',
label: '任务取消',
popConfirm: {
title: '是否确认取消?',
confirm: hanndleCancel.bind(null, record),
placement: 'topLeft',
},
auth: 'agvTask:data_agv_task:edit',
disabled: record.status != 2,
},
{
label: '任务重送',
onClick: hanndleResend.bind(null, record),
auth: 'agvTask:data_agv_task:edit',
disabled: record.status != 5,
},
{
label: '任务删除',
popConfirm: {
title: '是否确认删除?',
confirm: handleDelete.bind(null, record),

View File

@ -0,0 +1,156 @@
<template>
<a-spin :spinning="confirmLoading">
<JFormContainer>
<template #detail>
<a-form ref="formRef" class="antd-modal-form" :labelCol="labelCol" :wrapperCol="wrapperCol" name="AgvTaskForm">
<a-row>
<a-col :span="24">
<a-form-item label="容器" v-bind="validateInfos.carrierCode" id="AgvTaskForm-carrierCode" name="carrierCode">
<JDictSelectTag
v-model:value="formData.carrierCode"
placeholder="请选择容器"
dictCode="base_stock where iz_active=1 and del_flag=0,stock_code,stock_code"
allowClear
disabled
/>
</a-form-item>
</a-col>
<!-- <a-col :span="24">
<a-form-item label="任务类型" v-bind="validateInfos.taskType" id="AgvTaskForm-taskType" name="taskType">
<a-input v-model:value="formData.taskType" placeholder="请输入任务类型" allow-clear ></a-input>
</a-form-item>
</a-col>-->
<a-col :span="24">
<a-form-item label="业务类型" v-bind="validateInfos.type" id="AgvTaskForm-type" name="type">
<JDictSelectTag type="select" v-model:value="formData.type" dictCode="business_type" placeholder="请选择业务类型" disabled />
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="任务状态" v-bind="validateInfos.status" id="AgvTaskForm-status" name="status">
<JDictSelectTag
type="select"
v-model:value="formData.status"
dictCode="agv_task_status"
placeholder="请选择任务状态"
:string-to-number="true"
disabled
/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="优先级" v-bind="validateInfos.priority" id="AgvTaskForm-priority" name="priority">
<a-input-number v-model:value="formData.priority" placeholder="请输入优先级" style="width: 100%" />
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="目标库位" v-bind="validateInfos.endCode" id="AgvTaskForm-endCode" name="endCode">
<JSearchSelect
v-model:value="formData.endCode"
placeholder="请选择目标库位"
dict="base_point where iz_active=1 and del_flag=0 ,point_code,point_code"
allowClear
/>
</a-form-item>
</a-col>
</a-row>
</a-form>
</template>
</JFormContainer>
</a-spin>
</template>
<script lang="ts" setup>
import { ref, reactive, defineExpose, nextTick } from 'vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { getTenantId } from '@/utils/auth';
import { cancelTask, resendTesTask } from '../AgvTask.api';
import { Form } from 'ant-design-vue';
import JFormContainer from '/@/components/Form/src/container/JFormContainer.vue';
import { JSearchSelect, JDictSelectTag } from '@/components/Form';
const formRef = ref();
const useForm = Form.useForm;
const emit = defineEmits(['register', 'ok']);
// ID
let tenantId = getTenantId();
const formData = reactive<Record<string, any>>({
id: '',
carrierCode: '',
carrierType: 'TRAY',
taskType: '',
type: '',
status: 'CREATED',
priority: 3,
startCode: '',
endCode: '',
agvVendor: 'TES',
tenantId: tenantId,
});
const { createMessage } = useMessage();
const labelCol = ref<any>({ xs: { span: 24 }, sm: { span: 5 } });
const wrapperCol = ref<any>({ xs: { span: 24 }, sm: { span: 16 } });
const confirmLoading = ref<boolean>(false);
//
const validatorRules = reactive({
carrierCode: [{ required: true, message: '请选择容器!' }],
type: [{ required: true, message: '请选择业务类型!' }],
priority: [{ required: true, message: '请输入优先级!' }],
endCode: [{ required: true, message: '请选择目标库位!' }],
status: [{ required: true, message: '请选择任务状态!' }],
});
const { resetFields, validate, validateInfos } = useForm(formData, validatorRules, { immediate: false });
/**
* 编辑
*/
function edit(record) {
nextTick(() => {
resetFields();
const tmpData = {};
Object.keys(formData).forEach((key) => {
if (record.hasOwnProperty(key)) {
tmpData[key] = record[key];
}
});
//
Object.assign(formData, tmpData);
});
}
/**
* 提交数据
*/
async function submitForm() {
try {
//
await validate();
} catch (error) {
console.error(error);
}
confirmLoading.value = true;
try {
const res = await resendTesTask(formData);
if (res && res.returnCode === 0) {
createMessage.success('操作成功');
emit('ok');
} else {
createMessage.error(res.returnMsg || '任务处理失败');
}
} catch (error) {
createMessage.error('请求异常: ' + error.message);
} finally {
confirmLoading.value = false;
}
}
defineExpose({
edit,
submitForm,
});
</script>
<style lang="less" scoped>
.antd-modal-form {
padding: 14px 20px;
}
</style>

View File

@ -0,0 +1,76 @@
<template>
<j-modal
:title="title"
:maxHeight="400"
:width="600"
:visible="visible"
@ok="handleOk"
:okButtonProps="{ class: { 'jee-hidden': disableSubmit } }"
@cancel="handleCancel"
cancelText="关闭"
>
<ResendTesAgvForm ref="registerForm" @ok="submitCallback" :formDisabled="disableSubmit" :formBpm="false"></ResendTesAgvForm>
<template #footer>
<a-button @click="handleCancel"></a-button>
<a-button :class="{ 'jee-hidden': disableSubmit }" type="primary" @click="handleOk"> </a-button>
</template>
</j-modal>
</template>
<script lang="ts" setup>
import { ref, nextTick, defineExpose } from 'vue';
import JModal from '/@/components/Modal/src/JModal/JModal.vue';
import ResendTesAgvForm from '@/views/agvTask/components/ResendTesAgvForm.vue';
const title = ref<string>('');
const visible = ref<boolean>(false);
const disableSubmit = ref<boolean>(false);
const registerForm = ref();
const emit = defineEmits(['register', 'success']);
/**
* 重送
*/
function resend(record) {
title.value = 'TES任务重送';
visible.value = true;
nextTick(() => {
registerForm.value.edit(record);
});
}
/**
* 确定按钮点击事件
*/
function handleOk() {
registerForm.value.submitForm();
}
/**
* form保存回调事件
*/
function submitCallback() {
handleCancel();
emit('success');
}
/**
* 取消按钮回调事件
*/
function handleCancel() {
visible.value = false;
}
defineExpose({
resend,
disableSubmit,
});
</script>
<style lang="less">
/**隐藏样式-modal确定按钮 */
.jee-hidden {
display: none !important;
}
</style>
<style lang="less" scoped></style>

View File

@ -26,7 +26,7 @@
</a-col>
<a-col :span="24">
<a-form-item label="任务状态" v-bind="validateInfos.status" id="AgvTaskForm-status" name="status">
<JDictSelectTag type="select" v-model:value="formData.status" dictCode="agv_task_status" placeholder="请选择任务状态" />
<JDictSelectTag type="select" v-model:value="formData.status" dictCode="agv_task_status" placeholder="请选择任务状态" :string-to-number="true" />
</a-form-item>
</a-col>
<a-col :span="24">

View File

@ -21,11 +21,8 @@
import { ref, nextTick, defineExpose } from 'vue';
import TesAgvForm from './TesAgvForm.vue';
import JModal from '/@/components/Modal/src/JModal/JModal.vue';
import { useMessage } from '/@/hooks/web/useMessage';
const { createMessage } = useMessage();
const title = ref<string>('');
const width = ref<number>(800);
const visible = ref<boolean>(false);
const disableSubmit = ref<boolean>(false);
const registerForm = ref();

View File

@ -0,0 +1,15 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
showConveyorLine = '/api/conveyorLine/showConveyorLine',
}
export const showConveyorLine = (conveyorLine) => {
return defHttp.get(
{
url: Api.showConveyorLine,
params: { conveyorLine },
},
{ joinParamsToUrl: true }
);
};

View File

@ -0,0 +1,438 @@
<template>
<div class="monitor-container">
<header class="header">
<div class="header-left">
<div class="logo-box">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="logo-icon">
<rect x="2" y="7" width="20" height="14" rx="2" ry="2"></rect>
<path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"></path>
</svg>
</div>
<div class="title-group">
<h1 class="system-title">WCS 输送线监控大屏</h1>
<div class="system-sub">CONVEYOR LINE MONITORING SYSTEM V4.2</div>
</div>
</div>
<div class="header-right">
<div class="status-group">
<span class="status-dot online"></span>
<span class="status-text">SYSTEM ONLINE</span>
</div>
<div class="divider">|</div>
<div class="node-info">Node: CN-SH-01</div>
<div class="time-display">
<span class="time-big">{{ timeOnly }}</span>
<span class="date-small">{{ dateOnly }}</span>
</div>
</div>
</header>
<main class="dashboard-card">
<section class="scan-section">
<div class="section-header">
<span class="label-cn">当前扫描</span>
<span class="label-en">CURRENT SCAN</span>
</div>
<div class="scan-content">
<div class="scan-code">{{ scanData.stockCode }}</div>
<button class="pallet-btn">
<svg viewBox="0 0 24 24" width="14" height="14" stroke="currentColor" stroke-width="2" fill="none">
<path
d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"
></path>
</svg>
{{ scanData.izAll }}
</button>
</div>
</section>
<section class="info-grid">
<div class="info-card type-card">
<div class="card-label">
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M17 1l4 4-4 4"></path>
<path d="M3 11V9a4 4 0 0 1 4-4h14"></path>
<path d="M7 23l-4-4 4-4"></path>
<path d="M21 13v2a4 4 0 0 1-4 4H3"></path>
</svg>
<span>任务类型 TASK TYPE</span>
</div>
<div class="card-value value-purple">{{ scanData.taskType }}</div>
</div>
<div class="info-card dest-card">
<div class="card-label">
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="10" r="3"></circle>
<path d="M12 21.7C17.3 17 20 13 20 10a8 8 0 1 0-16 0c0 3 2.7 7 8 11.7z"></path>
</svg>
<span>目的站 DESTINATION</span>
</div>
<div class="card-value value-pink">{{ scanData.endCode }}</div>
</div>
</section>
<section class="remark-section">
<div class="section-header">
<svg class="icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="16" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12.01" y2="8"></line>
</svg>
<span class="label-mix">特殊描述 / REMARKS</span>
</div>
<div class="remark-text">{{ scanData.description===''?'无到站任务!!!':scanData.description }}</div>
</section>
</main>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue';
import dayjs from 'dayjs';
import { showConveyorLine } from '@/views/conveyorLine/ConveyorLine.api';
// ( Props API )
const scanData = ref({
stockCode: '',
taskType: '',
endCode: '',
izAll: '',
description: '',
});
//
const now = ref(dayjs());
const updateTime = () => {
now.value = dayjs();
};
const timeOnly = computed(() => now.value.format('HH:mm:ss'));
const dateOnly = computed(() => {
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
return `${now.value.format('YYYY/MM/DD')} ${weekDays[now.value.day()]}`;
});
let timer;
onMounted(() => {
timer = setInterval(updateTime, 1000);
});
onUnmounted(() => {
clearInterval(timer);
});
async function queryData() {
const conveyorLine = 'CKJBK01';
const res = await showConveyorLine(conveyorLine);
scanData.value = {
stockCode: res.stockCode,
taskType: res.taskType,
endCode: res.endCode,
izAll: res.izAll,
description: res.description,
};
console.log('数据:' , res);
}
//
setInterval(() => {
console.log('更新数据');
queryData();
}, 3000);
</script>
<style scoped>
/* 引入等宽字体 (可选,如果有本地字体更好) */
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@500;700&family=Inter:wght@400;600;800&display=swap');
/* --- 全局容器 --- */
.monitor-container {
width: 100%;
height: 100vh;
background-color: #02050e; /* 极深黑背景 */
color: #ffffff;
padding: 20px 32px;
box-sizing: border-box;
font-family:
'Inter',
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
'Helvetica Neue',
Arial,
sans-serif;
overflow: hidden;
}
/* --- 1. Header 样式 --- */
.header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 20px;
}
.header-left {
display: flex;
gap: 12px;
align-items: center;
}
.logo-box {
width: 48px;
height: 48px;
background: linear-gradient(135deg, #007bff, #00d4ff);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
}
.logo-icon {
width: 24px;
height: 24px;
stroke: #fff;
}
.title-group {
display: flex;
flex-direction: column;
}
.system-title {
font-size: 22px;
font-weight: 800;
margin: 0;
letter-spacing: 0.5px;
color: #fff;
}
.system-sub {
font-size: 11px;
color: #8b9bb4; /* 稍微偏蓝灰 */
font-weight: 600;
letter-spacing: 0.5px;
margin-top: 2px;
}
.header-right {
display: flex;
align-items: center;
gap: 16px;
font-size: 14px;
}
.status-group {
display: flex;
align-items: center;
gap: 6px;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #00e676;
box-shadow: 0 0 8px #00e676;
}
.status-text {
color: #00e676;
font-weight: 700;
font-size: 14px;
display: block;
}
.divider {
color: #343a40;
}
.node-info {
color: #5d6e82;
font-size: 12px;
}
.time-display {
text-align: right;
line-height: 1.1;
}
.time-big {
display: block;
font-size: 28px;
font-weight: 700;
font-family: 'Roboto Mono', monospace;
letter-spacing: -1px;
}
.date-small {
font-size: 12px;
color: #8b9bb4;
}
/* --- 2. 主卡片样式 (深蓝框) --- */
.dashboard-card {
background-color: #101524;
background-image: radial-gradient(circle at 100% 0%, rgba(56, 96, 246, 0.12) 0%, transparent 40%);
border: 1px solid rgba(47, 54, 77, 0.5);
border-radius: 12px;
padding: 36px 48px;
height: calc(100vh - 110px);
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: flex-start; /* 内容上对齐 */
gap: 30px; /* 模块间距 */
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
/* 当前扫描 */
.section-header {
font-size: 14px;
color: #495057;
font-weight: 700;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
text-transform: uppercase;
}
.label-cn {
color: #8b9bb4;
}
.scan-content {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #1f2029;
padding-bottom: 30px;
}
.scan-code {
font-family: 'Roboto Mono', monospace;
font-size: 80px; /* 超大字体 */
font-weight: 700;
color: #fff;
letter-spacing: 2px;
line-height: 1;
text-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
}
.pallet-btn {
background: rgba(30, 36, 56, 0.6);
border: 1px solid #3d5afe;
color: #536dfe;
padding: 10px 24px;
border-radius: 30px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.pallet-btn:hover {
background: #3d5afe;
color: #fff;
}
/* 任务卡片栅格 */
.info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
.info-card {
background-color: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 12px;
padding: 24px 32px;
height: 160px;
display: flex;
flex-direction: column;
justify-content: center;
position: relative;
overflow: hidden;
}
/* !* *!
.type-card {
background: radial-gradient(circle at 100% 0%, rgba(111, 66, 193, 0.1) 0%, transparent 50%), #11111a;
}
.dest-card {
background: radial-gradient(circle at 100% 0%, rgba(232, 62, 140, 0.1) 0%, transparent 50%), #11111a;
}*/
.card-label {
display: flex;
align-items: center;
gap: 10px;
color: #6c7a92;
font-size: 14px;
margin-bottom: 12px;
}
.card-label .icon {
width: 18px;
height: 18px;
opacity: 0.7;
}
.card-value {
font-size: 40px;
font-weight: 700;
letter-spacing: 1px;
}
/* 高亮颜色 */
.value-purple {
color: #00d2ff;
text-shadow: 0 0 15px rgba(0, 210, 255, 0.15);
}
.value-pink {
color: #c780ff; /* 浅紫 */
text-shadow: 0 0 15px rgba(199, 128, 255, 0.15);
font-family: 'Roboto Mono', monospace;
}
/* 底部备注 */
.remark-section {
background-color: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 8px;
padding: 24px 32px;
flex: 1; /* 占满剩余高度 */
display: flex;
flex-direction: column;
margin-bottom: 30px;
}
.label-mix {
font-size: 12px;
color: #6c757d;
letter-spacing: 0.5px;
}
.icon-sm {
width: 14px;
height: 14px;
color: #6c7a92;
}
.remark-text {
font-size: 30px;
color: #d1d5db; /* 灰白色,不刺眼 */
font-weight: bold;
}
</style>

View File

@ -137,6 +137,10 @@
width: 120,
fixed: 'right',
},
defSort: {
column: 'id',
order: 'desc',
},
showActionColumn: false,
beforeFetch: async (params) => {
for (let key in fieldPickers) {

View File

@ -1,19 +1,22 @@
import {defHttp} from '/@/utils/http/axios';
import { useMessage } from "/@/hooks/web/useMessage";
import { defHttp } from '/@/utils/http/axios';
import { useMessage } from '/@/hooks/web/useMessage';
const { createConfirm } = useMessage();
enum Api {
list = '/shipping/pick/list',
save='/shipping/pick/add',
edit='/shipping/pick/edit',
save = '/shipping/pick/add',
edit = '/shipping/pick/edit',
deleteOne = '/shipping/pick/delete',
deleteBatch = '/shipping/pick/deleteBatch',
importExcel = '/shipping/pick/importExcel',
exportXls = '/shipping/pick/exportXls',
queryDataById = '/shipping/pick/queryById',
pickDetailList = '/shipping/pick/queryPickDetailByMainId',
allocatePick = '/shipping/pick/allocatePick',
cancelAllocate = '/shipping/pick/cancelAllocate',
}
/**
* api
* @param params
@ -29,23 +32,26 @@ export const getImportUrl = Api.importExcel;
*
* @param params
*/
export const queryPickDetailListByMainId = (id) => defHttp.get({url: Api.pickDetailList, params:{ id }});
export const queryPickDetailListByMainId = (id) =>
defHttp.get({
url: Api.pickDetailList,
params: { id },
});
/**
*
* @param params
*/
export const list = (params) =>
defHttp.get({url: Api.list, params});
export const list = (params) => defHttp.get({ url: Api.list, params });
/**
*
*/
export const deleteOne = (params,handleSuccess) => {
return defHttp.delete({url: Api.deleteOne, params}, {joinParamsToUrl: true}).then(() => {
export const deleteOne = (params, handleSuccess) => {
return defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
}
};
/**
*
* @param params
@ -58,24 +64,51 @@ export const batchDelete = (params, handleSuccess) => {
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp.delete({url: Api.deleteBatch, data: params}, {joinParamsToUrl: true}).then(() => {
handleSuccess();
});
}
return defHttp
.delete(
{
url: Api.deleteBatch,
data: params,
},
{ joinParamsToUrl: true }
)
.then(() => {
handleSuccess();
});
},
});
}
};
/**
*
* @param params
*/
export const saveOrUpdate = (params, isUpdate) => {
let url = isUpdate ? Api.edit : Api.save;
return defHttp.post({url: url, params});
}
return defHttp.post({ url: url, params });
};
/**
* id
* @param params
*/
export const queryDataById = (id) => defHttp.get({url: Api.queryDataById, params:{ id }});
* id
* @param params
*/
export const queryDataById = (id) => defHttp.get({ url: Api.queryDataById, params: { id } });
/**
*
* @param params
*/
export const allocatePick = (ids,handleSuccess) => {
return defHttp.get({ url: Api.allocatePick, params: { ids } }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
};
/**
*
* @param params
*/
export const cancelAllocate = (ids,handleSuccess) => {
return defHttp.get({ url: Api.cancelAllocate, params: { ids } }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
};

View File

@ -7,21 +7,25 @@ export const columns: BasicColumn[] = [
title: '系统单号',
align: 'center',
dataIndex: 'orderNo',
width: '130px',
},
{
title: '外部单号',
align: 'center',
dataIndex: 'thirdOrderNo',
width: '130px',
},
{
title: '任务号',
align: 'center',
dataIndex: 'no',
width: '130px',
},
{
title: '订单状态',
align: 'center',
dataIndex: 'status_dictText',
width: '100px',
customRender: ({ text }) => {
//出库状态:1.已创建;2.部分分配;3.已分配;4.拣货中;5.拣货完成;6.已关闭;7.已取消。
const statusColorMap = {
@ -41,38 +45,38 @@ export const columns: BasicColumn[] = [
title: '单据类型',
align: 'center',
dataIndex: 'orderType_dictText',
width: '100px',
},
{
title: '需求数量',
align: 'center',
dataIndex: 'orderQty',
width: '80px',
},
{
title: '分配数量',
align: 'center',
dataIndex: 'allocatedQty',
width: '80px',
},
{
title: '拣货数量',
align: 'center',
dataIndex: 'pickedQty',
width: '80px',
},
{
title: '外部仓库',
align: 'center',
dataIndex: 'whCode',
width: '80px',
},
{
title: '客户代码',
title: '客户',
align: 'center',
dataIndex: 'customerCode',
},
{
title: '外部仓库',
align: 'center',
dataIndex: 'whCode',
},
{
title: '订单日期',
align: 'center',
@ -108,7 +112,7 @@ export const pickDetailColumns: JVxeColumn[] = [
key: 'unit',
type: JVxeTypes.select,
dictCode: 'package_unit',
width: '130px',
width: '80px',
placeholder: '请选择${title}',
defaultValue: '托',
},
@ -116,7 +120,7 @@ export const pickDetailColumns: JVxeColumn[] = [
title: '需求数量',
key: 'orderQty',
type: JVxeTypes.inputNumber,
width: '130px',
width: '110px',
validateRules: [
{
required: true, // 必填
@ -128,7 +132,7 @@ export const pickDetailColumns: JVxeColumn[] = [
title: '分配数量',
key: 'allocatedQty',
type: JVxeTypes.normal,
width: '130px',
width: '80px',
defaultValue: '0',
disabled: true,
},
@ -136,7 +140,7 @@ export const pickDetailColumns: JVxeColumn[] = [
title: '拣货数量',
key: 'pickedQty',
type: JVxeTypes.normal,
width: '130px',
width: '80px',
placeholder: '请输入${title}',
defaultValue: '0',
disabled: true,
@ -145,7 +149,7 @@ export const pickDetailColumns: JVxeColumn[] = [
title: '明细状态',
key: 'status',
type: JVxeTypes.normal,
width: '120px',
width: '80px',
defaultValue: '1',
formatter: ({ cellValue }) => {
//入库状态:1.已创建;2.部分收货;3.收货完成;4.已取消。
@ -205,4 +209,10 @@ export const pickDetailColumns: JVxeColumn[] = [
placeholder: '请输入${title}',
defaultValue: null,
},
{
title: '返回报文',
key: 'resMessage',
type: JVxeTypes.normal,
width: '200px',
},
];

View File

@ -56,6 +56,25 @@
<!--插槽:table标题-->
<template #tableTitle>
<a-button type="primary" v-auth="'shipping:data_pick:add'" @click="handleAdd" preIcon="ant-design:plus-outlined"> </a-button>
<a-button
type="primary"
:loading="allocate_loading"
v-auth="'shipping:data_pick:allocatePick'"
@click="handleAllocatePick"
preIcon="ant-design:edit-outlined"
>
分配
</a-button>
<a-button
type="primary"
danger
:loading="cancel_loading"
v-auth="'shipping:data_pick:cancelAllocate'"
@click="handleCancelAllocate"
preIcon="ant-design:edit-outlined"
>
取消分配
</a-button>
<a-button type="primary" v-auth="'shipping:data_pick:exportXls'" preIcon="ant-design:export-outlined" @click="onExportXls"> </a-button>
<j-upload-button type="primary" v-auth="'shipping:data_pick:importExcel'" preIcon="ant-design:import-outlined" @click="onImportXls"
>导入
@ -94,7 +113,7 @@
import { useModal } from '/@/components/Modal';
import PickModal from './components/PickModal.vue';
import { columns } from './Pick.data';
import { list, deleteOne, batchDelete, getImportUrl, getExportUrl } from './Pick.api';
import { list, deleteOne, batchDelete, getImportUrl, getExportUrl, allocatePick, cancelAllocate } from './Pick.api';
import { useMessage } from '/@/hooks/web/useMessage';
import { getDateByPicker } from '/@/utils';
import { useUserStore } from '/@/store/modules/user';
@ -102,6 +121,8 @@
import { JInput, JDictSelectTag } from '@/components/Form';
import JRangeDate from '@/components/Form/src/jeecg/components/JRangeDate.vue';
const allocate_loading = ref(false);
const cancel_loading = ref(false);
const fieldPickers = reactive({});
const formRef = ref();
const queryParam = reactive<any>({});
@ -156,7 +177,7 @@
},
});
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
const [registerTable, { reload }, { rowSelection, selectedRowKeys, selectedRows }] = tableContext;
/**
* 新增事件
@ -168,6 +189,67 @@
});
}
/**
* 分配事件
*/
async function handleAllocatePick() {
if (selectedRowKeys.value.length === 0) {
return createMessage.error('请选择出库单');
}
// 124 true,false
const validStatuses = [1, 2, 4];
const allValidStatus = selectedRows.value.every((row: any) => validStatuses.includes(row.status));
if (!allValidStatus) {
return createMessage.error('【已创建、 部分分配、部分拣货】状态的出库单才允许分配');
}
if (allocate_loading.value) {
return;
}
//
allocate_loading.value = true;
try {
await allocatePick(selectedRowKeys.value, handleSuccess);
} catch (e) {
console.error('分配失败:', e);
handleSuccess()
} finally {
//
allocate_loading.value = false;
}
}
/**
* 取消分配
*/
async function handleCancelAllocate() {
if (selectedRowKeys.value.length === 0) {
return createMessage.error('请选择出库单');
}
// 23 true,false
const validStatuses = [2, 3];
const allValidStatus = selectedRows.value.every((row: any) => validStatuses.includes(row.status));
if (!allValidStatus) {
return createMessage.error('【部分分配、已分配】状态的出库单才允许取消分配');
}
if (cancel_loading.value) {
return;
}
//
cancel_loading.value = true;
try {
await cancelAllocate(selectedRowKeys.value, handleSuccess);
} catch (e) {
console.error('取消失败:', e);
handleSuccess()
} finally {
//
cancel_loading.value = false;
}
}
/**
* 编辑事件
*/