增加溯源功能
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { TraceabilityApi } from '#/api';
|
import type { TraceabilityApi } from '#/api';
|
||||||
|
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
import { getTraceabilityBatches, getTraceabilityPreviewDetail } from '#/api';
|
import { getTraceabilityBatches, getTraceabilityPreviewDetail } from '#/api';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
buildColoredQrDataUrl,
|
||||||
buildCoordinateEmbedUrl,
|
buildCoordinateEmbedUrl,
|
||||||
buildCoordinateMapUrl,
|
buildCoordinateMapUrl,
|
||||||
formatFieldValue,
|
formatFieldValue,
|
||||||
@@ -38,9 +39,27 @@ const qrCode = useQRCode(publicLink, {
|
|||||||
margin: 2,
|
margin: 2,
|
||||||
width: 220,
|
width: 220,
|
||||||
});
|
});
|
||||||
const qrDownloadName = computed(
|
const qrColor = ref('#000000');
|
||||||
() => `${detail.value?.batch.batchCode || 'batch'}.png`,
|
const customQrCode = ref('');
|
||||||
|
|
||||||
|
function buildQrFileName(
|
||||||
|
code?: string,
|
||||||
|
name?: string,
|
||||||
|
fallback: string = 'batch',
|
||||||
|
) {
|
||||||
|
const raw =
|
||||||
|
[code?.trim(), name?.trim()].filter(Boolean).join(' - ') || fallback;
|
||||||
|
return `${raw.replaceAll(/[\\/:*?"<>|]/g, '_')}.png`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const qrDownloadName = computed(() =>
|
||||||
|
buildQrFileName(
|
||||||
|
detail.value?.batch.batchCode,
|
||||||
|
detail.value?.batch.batchName,
|
||||||
|
'batch',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
const displayedQrCode = computed(() => customQrCode.value || qrCode.value);
|
||||||
|
|
||||||
function downloadQrCode(dataUrl: string, fileName: string) {
|
function downloadQrCode(dataUrl: string, fileName: string) {
|
||||||
if (!dataUrl) return;
|
if (!dataUrl) return;
|
||||||
@@ -53,9 +72,26 @@ function downloadQrCode(dataUrl: string, fileName: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function downloadConsumerQrCode() {
|
function downloadConsumerQrCode() {
|
||||||
downloadQrCode(qrCode.value, qrDownloadName.value);
|
downloadQrCode(displayedQrCode.value, qrDownloadName.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function generateConsumerQrCode() {
|
||||||
|
customQrCode.value = qrCode.value
|
||||||
|
? await buildColoredQrDataUrl(qrCode.value, {
|
||||||
|
color: qrColor.value,
|
||||||
|
})
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
qrCode,
|
||||||
|
() => {
|
||||||
|
customQrCode.value = '';
|
||||||
|
qrColor.value = '#000000';
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
async function loadBatches() {
|
async function loadBatches() {
|
||||||
batches.value = await getTraceabilityBatches();
|
batches.value = await getTraceabilityBatches();
|
||||||
if (!queryCode.value && batches.value[0]) {
|
if (!queryCode.value && batches.value[0]) {
|
||||||
@@ -160,17 +196,26 @@ onMounted(loadBatches);
|
|||||||
<Col :lg="8" :xs="24">
|
<Col :lg="8" :xs="24">
|
||||||
<Card class="panel-card qr-panel" title="二维码">
|
<Card class="panel-card qr-panel" title="二维码">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<Button
|
<div v-if="publicLink" class="qr-panel__actions">
|
||||||
v-if="publicLink"
|
<input
|
||||||
size="small"
|
v-model="qrColor"
|
||||||
type="primary"
|
class="qr-color-input"
|
||||||
@click="downloadConsumerQrCode"
|
type="color"
|
||||||
>
|
/>
|
||||||
保存二维码
|
<Button size="small" @click="generateConsumerQrCode">
|
||||||
</Button>
|
生成二维码
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
@click="downloadConsumerQrCode"
|
||||||
|
>
|
||||||
|
保存二维码
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="qr-wrap">
|
<div class="qr-wrap">
|
||||||
<img :src="qrCode" alt="溯源二维码" class="qr-image" />
|
<img :src="displayedQrCode" alt="溯源二维码" class="qr-image" />
|
||||||
<div class="qr-meta">
|
<div class="qr-meta">
|
||||||
<strong>扫码查看溯源页</strong>
|
<strong>扫码查看溯源页</strong>
|
||||||
<p>{{ publicLink }}</p>
|
<p>{{ publicLink }}</p>
|
||||||
@@ -272,13 +317,9 @@ onMounted(loadBatches);
|
|||||||
:href="buildCoordinateMapUrl(entry.value)"
|
:href="buildCoordinateMapUrl(entry.value)"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>打开地图</a
|
>打开地图</a>
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<strong
|
<strong v-else :style="getFieldDisplayStyle(entry.field)">
|
||||||
v-else
|
|
||||||
:style="getFieldDisplayStyle(entry.field)"
|
|
||||||
>
|
|
||||||
{{ formatFieldValue(entry.value) }}
|
{{ formatFieldValue(entry.value) }}
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
@@ -349,10 +390,12 @@ onMounted(loadBatches);
|
|||||||
:href="buildCoordinateMapUrl(entry.value)"
|
:href="buildCoordinateMapUrl(entry.value)"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>打开地图</a
|
>打开地图</a>
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<strong v-else :style="getFieldDisplayStyle(entry.field)">
|
<strong
|
||||||
|
v-else
|
||||||
|
:style="getFieldDisplayStyle(entry.field)"
|
||||||
|
>
|
||||||
{{ formatFieldValue(entry.value) }}
|
{{ formatFieldValue(entry.value) }}
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
@@ -459,6 +502,21 @@ onMounted(loadBatches);
|
|||||||
min-height: 138px;
|
min-height: 138px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.qr-panel :deep(.ant-card-head-wrapper) {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-panel :deep(.ant-card-extra) {
|
||||||
|
padding-inline-start: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-panel__actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding-inline: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.qr-wrap {
|
.qr-wrap {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
@@ -616,6 +674,16 @@ onMounted(loadBatches);
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.qr-color-input {
|
||||||
|
width: 36px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid #d7e1f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.coordinate-preview-card {
|
.coordinate-preview-card {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { TraceabilityApi } from '#/api';
|
import type { TraceabilityApi } from '#/api';
|
||||||
|
|
||||||
import { computed, onMounted, reactive, ref } from 'vue';
|
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { useQRCode } from '@vueuse/integrations/useQRCode';
|
import { useQRCode } from '@vueuse/integrations/useQRCode';
|
||||||
import {
|
import {
|
||||||
@@ -35,6 +35,7 @@ import {
|
|||||||
import CoordinateFieldEditor from './components/CoordinateFieldEditor.vue';
|
import CoordinateFieldEditor from './components/CoordinateFieldEditor.vue';
|
||||||
import {
|
import {
|
||||||
buildOssStoredValue,
|
buildOssStoredValue,
|
||||||
|
buildColoredQrDataUrl,
|
||||||
formatFieldValue,
|
formatFieldValue,
|
||||||
getFieldTypeLabel,
|
getFieldTypeLabel,
|
||||||
getImagePreviewSrc,
|
getImagePreviewSrc,
|
||||||
@@ -113,9 +114,18 @@ const allStepsCompleted = computed(() =>
|
|||||||
const canEditBaseInfo = computed(() => !isPublished.value || batchEditMode.value);
|
const canEditBaseInfo = computed(() => !isPublished.value || batchEditMode.value);
|
||||||
const canPublishBatch = computed(() => !isPublished.value && allStepsCompleted.value);
|
const canPublishBatch = computed(() => !isPublished.value && allStepsCompleted.value);
|
||||||
const publishButtonText = computed(() => (isPublished.value ? '更新批次' : '发布批次'));
|
const publishButtonText = computed(() => (isPublished.value ? '更新批次' : '发布批次'));
|
||||||
const batchQrDownloadName = computed(
|
|
||||||
() => `${batchDetail.value?.batchCode || 'batch'}.png`,
|
function buildQrFileName(code?: string, name?: string, fallback: string = 'batch') {
|
||||||
|
const raw = [code?.trim(), name?.trim()].filter(Boolean).join(' - ') || fallback;
|
||||||
|
return `${raw.replace(/[\\/:*?"<>|]/g, '_')}.png`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const batchQrDownloadName = computed(() =>
|
||||||
|
buildQrFileName(batchDetail.value?.batchCode, batchDetail.value?.batchName, 'batch'),
|
||||||
);
|
);
|
||||||
|
const qrColor = ref('#000000');
|
||||||
|
const customBatchQrCode = ref('');
|
||||||
|
const displayedBatchQrCode = computed(() => customBatchQrCode.value || batchQrCode.value);
|
||||||
|
|
||||||
const hasBaseInfoChanges = computed(() => {
|
const hasBaseInfoChanges = computed(() => {
|
||||||
if (!selectedBatchId.value || !batchDetail.value) return false;
|
if (!selectedBatchId.value || !batchDetail.value) return false;
|
||||||
@@ -180,9 +190,26 @@ function downloadQrCode(dataUrl: string, fileName: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function downloadBatchQrCode() {
|
function downloadBatchQrCode() {
|
||||||
downloadQrCode(batchQrCode.value, batchQrDownloadName.value);
|
downloadQrCode(displayedBatchQrCode.value, batchQrDownloadName.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function generateBatchQrCode() {
|
||||||
|
customBatchQrCode.value = batchQrCode.value
|
||||||
|
? await buildColoredQrDataUrl(batchQrCode.value, {
|
||||||
|
color: qrColor.value,
|
||||||
|
})
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
batchQrCode,
|
||||||
|
() => {
|
||||||
|
customBatchQrCode.value = '';
|
||||||
|
qrColor.value = '#000000';
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
async function loadLists() {
|
async function loadLists() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
@@ -731,6 +758,19 @@ onMounted(async () => {
|
|||||||
>
|
>
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<Space>
|
<Space>
|
||||||
|
<input
|
||||||
|
v-if="batchDetail.publicUrl"
|
||||||
|
v-model="qrColor"
|
||||||
|
class="qr-color-input"
|
||||||
|
type="color"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
v-if="batchDetail.publicUrl"
|
||||||
|
size="small"
|
||||||
|
@click="generateBatchQrCode"
|
||||||
|
>
|
||||||
|
生成二维码
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
v-if="batchDetail.publicUrl"
|
v-if="batchDetail.publicUrl"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -769,7 +809,7 @@ onMounted(async () => {
|
|||||||
<div class="publish-qr">
|
<div class="publish-qr">
|
||||||
<img
|
<img
|
||||||
v-if="batchDetail.publicUrl"
|
v-if="batchDetail.publicUrl"
|
||||||
:src="batchQrCode"
|
:src="displayedBatchQrCode"
|
||||||
alt="批次二维码"
|
alt="批次二维码"
|
||||||
/>
|
/>
|
||||||
<Empty v-else description="暂无可下载二维码" />
|
<Empty v-else description="暂无可下载二维码" />
|
||||||
@@ -1427,6 +1467,15 @@ onMounted(async () => {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.qr-color-input {
|
||||||
|
width: 36px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid #d7e1f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.readonly-box {
|
.readonly-box {
|
||||||
min-height: 54px;
|
min-height: 54px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, reactive, ref } from 'vue';
|
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
||||||
import { useQRCode } from '@vueuse/integrations/useQRCode';
|
import { useQRCode } from '@vueuse/integrations/useQRCode';
|
||||||
import { Button, Card, Col, DatePicker, Empty, Input, message, Modal, Row, Select, Space, Switch, Tabs, Tag } from 'ant-design-vue';
|
import { Button, Card, Col, DatePicker, Empty, Input, message, Modal, Row, Select, Space, Switch, Tabs, Tag } from 'ant-design-vue';
|
||||||
import {
|
import {
|
||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
} from '#/api';
|
} from '#/api';
|
||||||
import type { TraceabilityApi } from '#/api';
|
import type { TraceabilityApi } from '#/api';
|
||||||
import CoordinateFieldEditor from './components/CoordinateFieldEditor.vue';
|
import CoordinateFieldEditor from './components/CoordinateFieldEditor.vue';
|
||||||
import { buildOssStoredValue, clonePreviewForSave, createEmptyField, createEmptyPreviewNode, fieldTypeOptions, getFieldTypeLabel, getImagePreviewSrc, normalizeFieldInput } from './shared';
|
import { buildColoredQrDataUrl, buildOssStoredValue, clonePreviewForSave, createEmptyField, createEmptyPreviewNode, fieldTypeOptions, getFieldTypeLabel, getImagePreviewSrc, normalizeFieldInput } from './shared';
|
||||||
|
|
||||||
const previews = ref<TraceabilityApi.PreviewPageSummary[]>([]);
|
const previews = ref<TraceabilityApi.PreviewPageSummary[]>([]);
|
||||||
const selectedPreviewId = ref('');
|
const selectedPreviewId = ref('');
|
||||||
@@ -42,7 +42,16 @@ const editor = reactive<Partial<TraceabilityApi.PreviewPageDetail> & { coverImag
|
|||||||
themeColor: '#1f4fd6', tags: [], publicUrl: '', updatedAt: '', nodes: [],
|
themeColor: '#1f4fd6', tags: [], publicUrl: '', updatedAt: '', nodes: [],
|
||||||
});
|
});
|
||||||
const qrCode = useQRCode(computed(() => editor.publicUrl || ''), { errorCorrectionLevel: 'M', margin: 1, width: 220 });
|
const qrCode = useQRCode(computed(() => editor.publicUrl || ''), { errorCorrectionLevel: 'M', margin: 1, width: 220 });
|
||||||
const qrDownloadName = computed(() => `${editor.previewCode || editor.publicUrl || editor.name || 'preview'}.png`);
|
const qrColor = ref('#000000');
|
||||||
|
const customQrCode = ref('');
|
||||||
|
|
||||||
|
function buildQrFileName(code?: string, name?: string, fallback: string = 'preview') {
|
||||||
|
const raw = [code?.trim(), name?.trim()].filter(Boolean).join(' - ') || fallback;
|
||||||
|
return `${raw.replace(/[\\/:*?"<>|]/g, '_')}.png`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const qrDownloadName = computed(() => buildQrFileName(editor.previewCode, editor.name, 'preview'));
|
||||||
|
const displayedQrCode = computed(() => customQrCode.value || qrCode.value);
|
||||||
|
|
||||||
function downloadQrCode(dataUrl: string, fileName: string) {
|
function downloadQrCode(dataUrl: string, fileName: string) {
|
||||||
if (!dataUrl) return;
|
if (!dataUrl) return;
|
||||||
@@ -386,8 +395,25 @@ async function handleTemplateCoverUpload(event: Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function downloadPreviewQrCode() {
|
function downloadPreviewQrCode() {
|
||||||
downloadQrCode(qrCode.value, qrDownloadName.value);
|
downloadQrCode(displayedQrCode.value, qrDownloadName.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function generatePreviewQrCode() {
|
||||||
|
customQrCode.value = qrCode.value
|
||||||
|
? await buildColoredQrDataUrl(qrCode.value, {
|
||||||
|
color: qrColor.value,
|
||||||
|
})
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
qrCode,
|
||||||
|
() => {
|
||||||
|
customQrCode.value = '';
|
||||||
|
qrColor.value = '#000000';
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
onMounted(loadPreviews);
|
onMounted(loadPreviews);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -465,11 +491,15 @@ onMounted(loadPreviews);
|
|||||||
</div>
|
</div>
|
||||||
<div class="preview-link-card__qr">
|
<div class="preview-link-card__qr">
|
||||||
<div class="preview-link-card__qr-head">
|
<div class="preview-link-card__qr-head">
|
||||||
|
<input v-if="editor.publicUrl" v-model="qrColor" class="qr-color-input" type="color">
|
||||||
|
<Button v-if="editor.publicUrl" size="small" @click="generatePreviewQrCode">
|
||||||
|
生成二维码
|
||||||
|
</Button>
|
||||||
<Button v-if="editor.publicUrl" size="small" type="primary" @click="downloadPreviewQrCode">
|
<Button v-if="editor.publicUrl" size="small" type="primary" @click="downloadPreviewQrCode">
|
||||||
保存二维码
|
保存二维码
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<img v-if="editor.publicUrl" :src="qrCode" alt="预演二维码">
|
<img v-if="editor.publicUrl" :src="displayedQrCode" alt="预演二维码">
|
||||||
<Empty v-else description="保存后生成二维码" />
|
<Empty v-else description="保存后生成二维码" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -722,6 +752,7 @@ onMounted(loadPreviews);
|
|||||||
.preview-link-card__qr { position: relative; flex-direction: column; gap: 12px; padding: 12px; }
|
.preview-link-card__qr { position: relative; flex-direction: column; gap: 12px; padding: 12px; }
|
||||||
.preview-link-card__qr-head { display: flex; justify-content: flex-end; width: 100%; }
|
.preview-link-card__qr-head { display: flex; justify-content: flex-end; width: 100%; }
|
||||||
.preview-link-card__qr img { width: 220px; height: 220px; }
|
.preview-link-card__qr img { width: 220px; height: 220px; }
|
||||||
|
.qr-color-input { width: 36px; height: 32px; padding: 0; border: 1px solid #d7e1f0; border-radius: 8px; background: #fff; }
|
||||||
.node-editor { padding: 18px; border: 1px solid #edf1f7; border-radius: 18px; background: linear-gradient(180deg, #fcfdff 0%, #ffffff 100%); }
|
.node-editor { padding: 18px; border: 1px solid #edf1f7; border-radius: 18px; background: linear-gradient(180deg, #fcfdff 0%, #ffffff 100%); }
|
||||||
.node-editor__summary { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 16px; padding: 14px 16px; border: 1px solid #ebf0f7; border-radius: 16px; background: #f8fbff; }
|
.node-editor__summary { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 16px; padding: 14px 16px; border: 1px solid #ebf0f7; border-radius: 16px; background: #f8fbff; }
|
||||||
.node-editor__summary strong { color: #111827; font-size: 16px; }
|
.node-editor__summary strong { color: #111827; font-size: 16px; }
|
||||||
|
|||||||
@@ -430,6 +430,69 @@ export function getFieldDisplayStyle(field?: TraceabilityApi.FieldDefinition) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function buildColoredQrDataUrl(
|
||||||
|
source: string,
|
||||||
|
options: {
|
||||||
|
color?: string;
|
||||||
|
backgroundColor?: string;
|
||||||
|
} = {},
|
||||||
|
) {
|
||||||
|
const dataUrl = source.trim();
|
||||||
|
if (!dataUrl) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const color = options.color || '#000000';
|
||||||
|
const backgroundColor = options.backgroundColor || '#ffffff';
|
||||||
|
|
||||||
|
return await new Promise<string>((resolve) => {
|
||||||
|
const image = new Image();
|
||||||
|
image.onload = () => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const width = image.naturalWidth || image.width || 220;
|
||||||
|
const height = image.naturalHeight || image.height || 220;
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
if (!context) {
|
||||||
|
resolve(dataUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.clearRect(0, 0, width, height);
|
||||||
|
context.fillStyle = backgroundColor;
|
||||||
|
context.fillRect(0, 0, width, height);
|
||||||
|
context.drawImage(image, 0, 0, width, height);
|
||||||
|
|
||||||
|
const qrImageData = context.getImageData(0, 0, width, height);
|
||||||
|
for (let index = 0; index < qrImageData.data.length; index += 4) {
|
||||||
|
const alpha = qrImageData.data[index + 3];
|
||||||
|
const isDark =
|
||||||
|
alpha > 0
|
||||||
|
&& qrImageData.data[index] < 200
|
||||||
|
&& qrImageData.data[index + 1] < 200
|
||||||
|
&& qrImageData.data[index + 2] < 200;
|
||||||
|
|
||||||
|
if (isDark) {
|
||||||
|
qrImageData.data[index] = Number.parseInt(color.slice(1, 3), 16);
|
||||||
|
qrImageData.data[index + 1] = Number.parseInt(color.slice(3, 5), 16);
|
||||||
|
qrImageData.data[index + 2] = Number.parseInt(color.slice(5, 7), 16);
|
||||||
|
qrImageData.data[index + 3] = 255;
|
||||||
|
} else {
|
||||||
|
qrImageData.data[index] = 255;
|
||||||
|
qrImageData.data[index + 1] = 255;
|
||||||
|
qrImageData.data[index + 2] = 255;
|
||||||
|
qrImageData.data[index + 3] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.putImageData(qrImageData, 0, 0);
|
||||||
|
resolve(canvas.toDataURL('image/png'));
|
||||||
|
};
|
||||||
|
image.onerror = () => resolve(dataUrl);
|
||||||
|
image.src = dataUrl;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function formatFieldValue(value: any) {
|
export function formatFieldValue(value: any) {
|
||||||
if (value === null || value === undefined || value === '') {
|
if (value === null || value === undefined || value === '') {
|
||||||
return '未填写';
|
return '未填写';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# push_docker.ps1
|
# push_docker.ps1
|
||||||
|
|
||||||
# Set version
|
# Set version
|
||||||
$env:VERSION = "1.5.4"
|
$env:VERSION = "1.5.5"
|
||||||
|
|
||||||
# Docker registry/repository
|
# Docker registry/repository
|
||||||
$registry = "ai.ronsunny.cn:13011/bbit_ai/ce_vue"
|
$registry = "ai.ronsunny.cn:13011/bbit_ai/ce_vue"
|
||||||
|
|||||||
Reference in New Issue
Block a user