feat: 完成了物料总表,生产发料,入料,以及部分采购计划。
This commit is contained in:
@@ -69,7 +69,6 @@ defineExpose({
|
||||
tableMainRef.value?.toggleRowExpansion(row, expanded);
|
||||
},
|
||||
sort: (field: string, order: string) => {
|
||||
console.log(tableMainRef.value?.tableRef);
|
||||
tableMainRef.value?.tableRef?.sort(field, order);
|
||||
},
|
||||
});
|
||||
@@ -78,6 +77,7 @@ onMounted(loadData);
|
||||
</script>
|
||||
<template>
|
||||
<TableHeader
|
||||
v-if="searchers !== undefined"
|
||||
:searchers="searchers"
|
||||
:tool-buttons="toolButtons"
|
||||
v-model:searcher-params="searcherParams"
|
||||
|
||||
188
src/components/base/base-qrcode/QrCodeDialog.vue
Normal file
188
src/components/base/base-qrcode/QrCodeDialog.vue
Normal file
@@ -0,0 +1,188 @@
|
||||
<script lang="ts" setup>
|
||||
import QRCode from "qrcode";
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
qrCodeContent: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
qrCodeSize: {
|
||||
type: Number,
|
||||
default: 200,
|
||||
},
|
||||
showLabel: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
|
||||
const visible = defineModel<boolean>("visible");
|
||||
const qrCodeDataUrl = ref("");
|
||||
const loading = ref(false);
|
||||
|
||||
const generateQrCode = async () => {
|
||||
if (!props.qrCodeContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
qrCodeDataUrl.value = await QRCode.toDataURL(props.qrCodeContent, {
|
||||
width: props.qrCodeSize,
|
||||
margin: 2,
|
||||
color: {
|
||||
dark: "#000000",
|
||||
light: "#ffffff",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Generate QR code failed:", error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handlePrint = () => {
|
||||
const printWindow = window.open("", "_blank");
|
||||
if (!printWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
const imgHtml = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>${props.title || "QR Code"}</title>
|
||||
<style>
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
.label {
|
||||
margin-top: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
@media print {
|
||||
body {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<img src="${qrCodeDataUrl.value}" alt="QR Code" />
|
||||
${props.showLabel ? `<div class="label">${props.label || props.qrCodeContent}</div>` : ""}
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
printWindow.document.write(imgHtml);
|
||||
printWindow.document.close();
|
||||
printWindow.focus();
|
||||
setTimeout(() => {
|
||||
printWindow.print();
|
||||
printWindow.close();
|
||||
}, 250);
|
||||
};
|
||||
|
||||
const handleDownload = () => {
|
||||
const link = document.createElement("a");
|
||||
link.download = `${props.label || props.qrCodeContent || "qrcode"}.png`;
|
||||
link.href = qrCodeDataUrl.value;
|
||||
link.click();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.qrCodeContent,
|
||||
() => {
|
||||
if (visible.value && props.qrCodeContent) {
|
||||
generateQrCode();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(visible, newVal => {
|
||||
if (newVal && props.qrCodeContent) {
|
||||
generateQrCode();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="title"
|
||||
v-model="visible"
|
||||
width="400px"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<div v-loading="loading" class="qrcode-container">
|
||||
<div v-if="qrCodeDataUrl" class="qrcode-display">
|
||||
<img :src="qrCodeDataUrl" alt="QR Code" />
|
||||
<div v-if="showLabel" class="qrcode-label">{{ label || qrCodeContent }}</div>
|
||||
</div>
|
||||
<div v-else class="qrcode-placeholder">
|
||||
{{ $t("_message.warehouse.warehouse_item.no_qrcode_content") }}
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">{{ $t("_button.cancel") }}</el-button>
|
||||
<el-button type="primary" @click="handleDownload" :disabled="!qrCodeDataUrl">
|
||||
{{ $t("_button.download") }}
|
||||
</el-button>
|
||||
<el-button type="success" @click="handlePrint" :disabled="!qrCodeDataUrl">
|
||||
{{ $t("_button.print") }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.qrcode-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.qrcode-display {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.qrcode-display img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.qrcode-label {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.qrcode-placeholder {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
263
src/components/base/base-transfer/VendorTransferDialog.vue
Normal file
263
src/components/base/base-transfer/VendorTransferDialog.vue
Normal file
@@ -0,0 +1,263 @@
|
||||
<script lang="ts" setup>
|
||||
import { $t } from "@/common/languages";
|
||||
import { ElMessage, type FormInstance, type FormRules } from "element-plus";
|
||||
import { get, post } from "@/common/http/request";
|
||||
import { Delete, Edit, Plus } from "@element-plus/icons-vue";
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
warehouseItemId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
selectedVendorUrl: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
saveVendorUrl: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:visible": [value: boolean];
|
||||
saved: [];
|
||||
}>();
|
||||
|
||||
const visible = defineModel<boolean>("visible");
|
||||
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const formRef = ref<FormInstance>();
|
||||
const vendorList = ref<any[]>([]);
|
||||
const allVendors = ref<any[]>([]);
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
vendorId: [{ required: true, message: $t("_message.warehouse.warehouse_item.select_vendor"), trigger: "change" }],
|
||||
costPrice: [{ required: true, message: $t("_message.warehouse.warehouse_item.input_costPrice"), trigger: "blur" }],
|
||||
});
|
||||
|
||||
const loadVendorList = async () => {
|
||||
if (!props.warehouseItemId) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await get(props.selectedVendorUrl, { warehouseItemId: props.warehouseItemId });
|
||||
vendorList.value = response.data || [];
|
||||
} catch (error) {
|
||||
console.error("Load vendor list failed:", error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadAllVendors = async () => {
|
||||
try {
|
||||
const response = await get("/systemset/vendor/getVendorPage", { current: 1, size: 1000 });
|
||||
allVendors.value = response.data.records || [];
|
||||
} catch (error) {
|
||||
console.error("Load all vendors failed:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const addRow = () => {
|
||||
vendorList.value.push({
|
||||
id: null,
|
||||
vendorId: null,
|
||||
vendorName: "",
|
||||
costPrice: null,
|
||||
procureDate: null,
|
||||
});
|
||||
};
|
||||
|
||||
const editRow = (row: any) => {
|
||||
row.isEditing = true;
|
||||
};
|
||||
|
||||
const deleteRow = (index: number) => {
|
||||
vendorList.value.splice(index, 1);
|
||||
};
|
||||
|
||||
const saveRow = (row: any) => {
|
||||
row.isEditing = false;
|
||||
};
|
||||
|
||||
const cancelEdit = (row: any, originalRow: any) => {
|
||||
row.vendorId = originalRow.vendorId;
|
||||
row.costPrice = originalRow.costPrice;
|
||||
row.procureDate = originalRow.procureDate;
|
||||
row.isEditing = false;
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (vendorList.value.length === 0) {
|
||||
ElMessage.warning($t("_message.warehouse.warehouse_item.select_vendor"));
|
||||
return;
|
||||
}
|
||||
|
||||
saving.value = true;
|
||||
try {
|
||||
const vendorListData = vendorList.value.map(item => ({
|
||||
vendorId: item.vendorId,
|
||||
costPrice: item.costPrice,
|
||||
procureDate: item.procureDate,
|
||||
}));
|
||||
|
||||
await post(props.saveVendorUrl, {
|
||||
warehouseItemId: props.warehouseItemId,
|
||||
vendorList: vendorListData,
|
||||
});
|
||||
|
||||
ElMessage.success($t("_message.common.edit_success"));
|
||||
visible.value = false;
|
||||
emit("saved");
|
||||
} catch (error) {
|
||||
console.error("Save vendor list failed:", error);
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const getVendorName = (vendorId: number) => {
|
||||
const vendor = allVendors.value.find(v => v.id === vendorId);
|
||||
return vendor ? vendor.vendorName : "";
|
||||
};
|
||||
|
||||
watch(
|
||||
() => visible.value,
|
||||
val => {
|
||||
if (val) {
|
||||
loadVendorList();
|
||||
loadAllVendors();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="title"
|
||||
v-model="visible"
|
||||
width="900px"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<el-form ref="formRef" :model="{ vendorList }" label-width="120px">
|
||||
<el-table :data="vendorList" border style="width: 100%" max-height="50vh" v-loading="loading">
|
||||
<el-table-column :label="$t('_prop.warehouse.warehouse_item.vendorName')" width="200">
|
||||
<template #default="{ row, $index }">
|
||||
<el-form-item v-if="row.isEditing" :prop="`vendorList.${$index}.vendorId`" :rules="rules.vendorId">
|
||||
<el-select
|
||||
v-model="row.vendorId"
|
||||
:placeholder="$t('_message.warehouse.warehouse_item.select_vendor')"
|
||||
filterable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="vendor in allVendors"
|
||||
:key="vendor.id"
|
||||
:label="vendor.vendorName"
|
||||
:value="vendor.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<span v-else>{{ getVendorName(row.vendorId) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="$t('_prop.warehouse.warehouse_item.costPrice')" width="150">
|
||||
<template #default="{ row, $index }">
|
||||
<el-form-item v-if="row.isEditing" :prop="`vendorList.${$index}.costPrice`" :rules="rules.costPrice">
|
||||
<el-input-number
|
||||
v-model="row.costPrice"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:placeholder="$t('_message.warehouse.warehouse_item.input_costPrice')"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<span v-else>{{ row.costPrice }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="$t('_prop.warehouse.warehouse_item.procureDate')" width="200">
|
||||
<template #default="{ row, $index }">
|
||||
<el-form-item v-if="row.isEditing" :prop="`vendorList.${$index}.procureDate`">
|
||||
<el-date-picker
|
||||
v-model="row.procureDate"
|
||||
type="datetime"
|
||||
:placeholder="$t('_message.warehouse.warehouse_item.select_procureDate')"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<span v-else>{{ row.procureDate }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="$t('_prop.common.operate')" width="150" align="center">
|
||||
<template #default="{ row, $index }">
|
||||
<template v-if="row.isEditing">
|
||||
<el-button type="primary" link size="small" @click="saveRow(row)">
|
||||
{{ $t("_button.confirm") }}
|
||||
</el-button>
|
||||
<el-button link size="small" @click="cancelEdit(row, { ...row })">
|
||||
{{ $t("_button.cancel") }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button type="primary" link size="small" @click="editRow(row)">
|
||||
<el-icon><Edit /></el-icon>
|
||||
</el-button>
|
||||
<el-button type="danger" link size="small" @click="deleteRow($index)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form>
|
||||
|
||||
<div class="table-actions">
|
||||
<el-button type="primary" @click="addRow">
|
||||
<el-icon><Plus /></el-icon>
|
||||
{{ $t("_button.add") }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">{{ $t("_button.cancel") }}</el-button>
|
||||
<el-button type="primary" @click="handleSave" :loading="saving">
|
||||
{{ $t("_button.confirm") }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.table-actions {
|
||||
margin-top: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,22 +1,34 @@
|
||||
<script lang="ts" setup>
|
||||
import { $t } from "@/common/languages";
|
||||
|
||||
const props = defineProps({
|
||||
authShowFunc: Function,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{ "operate-button-click": [eventName: string, row: any] }>();
|
||||
|
||||
const buttonList = useRoute().meta.toolButtonAuth ?? [];
|
||||
|
||||
const authShowFunc = (row: any, button: globalThis.ButtonProp) => {
|
||||
if (props.authShowFunc !== undefined) {
|
||||
return props.authShowFunc(row, button);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<el-table-column :label="$t('_prop.common.operate')" v-if="buttonList?.length !== 0">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-for="button of buttonList"
|
||||
:key="button.buttonName"
|
||||
:type="button.colorType || 'primary'"
|
||||
@click="emit('operate-button-click', button.eventName, scope.row)"
|
||||
link
|
||||
>
|
||||
{{ $t("_button." + button.eventName) }}
|
||||
</el-button>
|
||||
<template #default="{ row }">
|
||||
<template v-for="button of buttonList" :key="button.buttonName">
|
||||
<el-button
|
||||
v-if="authShowFunc(row, button)"
|
||||
:type="button.colorType || 'primary'"
|
||||
@click="emit('operate-button-click', button.eventName, row)"
|
||||
link
|
||||
>
|
||||
{{ $t("_button." + button.eventName) }}
|
||||
</el-button>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
|
||||
@@ -1,37 +1,36 @@
|
||||
<script lang="ts" setup>
|
||||
import { post } from "@/common/http/request";
|
||||
import StatusSwitch from "../base-switch/StatusSwitch.vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { $t } from "@/common/languages";
|
||||
import { useStatus } from "@/common/languages/mapping/base-info-mapping";
|
||||
|
||||
const props = defineProps({
|
||||
url: String,
|
||||
tableEl: ref,
|
||||
statusLabelMapping: Function,
|
||||
statusParamName: {
|
||||
type: String,
|
||||
default: "status",
|
||||
},
|
||||
switchOnValue: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
switchOffValue: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [val: any, row: any];
|
||||
}>();
|
||||
|
||||
const { getCommonStatusLabel } = useStatus();
|
||||
|
||||
const buttonList = useRoute().meta.statusButtonAuth ?? [];
|
||||
|
||||
const change = (val: any, row: any) => {
|
||||
if (props.url !== undefined)
|
||||
post(props.url, { id: row.id, status: val })
|
||||
.then(res => {
|
||||
if (res.code === 0) {
|
||||
ElMessage.success((val === 1 ? $t("_button.enable") : $t("_button.disable")) + "成功");
|
||||
} else {
|
||||
ElMessage.error((val === 1 ? $t("_button.enable") : $t("_button.disable")) + "失败,信息为:" + res.msg);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
ElMessage.error((val === 1 ? $t("_button.enable") : $t("_button.disable")) + "失败,信息为:" + err);
|
||||
});
|
||||
emit("change", val, row);
|
||||
};
|
||||
|
||||
const getLabel = (code: number | null) => {
|
||||
@@ -49,7 +48,10 @@ const getLabel = (code: number | null) => {
|
||||
<StatusSwitch
|
||||
v-model="scope.row[statusParamName]"
|
||||
@change="val => change(val, scope.row)"
|
||||
v-if="button.buttonType === 'switch'"
|
||||
v-if="
|
||||
(button.eventName === 'enable' && scope.row[statusParamName] === switchOffValue) ||
|
||||
(button.eventName === 'disable' && scope.row[statusParamName] === switchOnValue)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -186,7 +186,7 @@ const wrappedParse = (rawData: any) => {
|
||||
|
||||
defineExpose({
|
||||
reload: () => {
|
||||
baseTableRef.value?.reload;
|
||||
baseTableRef.value?.reload();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -236,18 +236,19 @@ defineExpose({
|
||||
|
||||
<style scoped>
|
||||
.item-container {
|
||||
min-height: 50px;
|
||||
padding: 16px;
|
||||
background-color: #fafafa;
|
||||
border-radius: 4px;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.loading-state,
|
||||
.empty-state {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
gap: 8px;
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user