feature: 使用deepseek进行了代码和页面优化
This commit is contained in:
@@ -1,5 +1,116 @@
|
||||
# Vue 3 + Vite
|
||||
# 标签打印系统(Label Print System)
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
一个基于 **Vue 3 + Vite + Tailwind CSS + Headless UI** 的标签排版与打印工具。把一张 A4 标签纸按 **4 列 × 11 行 = 44 格** 排版,每格对应一枚实际标签,可批量排版、逐格编辑,并按打印边距输出。
|
||||
|
||||
Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).
|
||||
---
|
||||
|
||||
## 功能简介
|
||||
|
||||
### 标签版面
|
||||
- 一张纸固定 **44 格(4 × 11)**,每格尺寸 **48.5mm × 25.4mm**,版面宽 194mm;
|
||||
- 屏幕预览模拟真实标签纸,格子带序号角标(1–44),打印时不输出角标;
|
||||
- 空格显示为浅色虚线圈,有内容的格子显示内容边框。
|
||||
|
||||
### 内置标签模板
|
||||
| 模板 | 说明 |
|
||||
| --- | --- |
|
||||
| `I5_10400F_16G_256G_1060` / `I5_10400F_16G_512G_2060` / `I5_9400F_16G_256G_1060` | 台式机配置标签(CPU / 内存 / 硬盘 / 显卡) |
|
||||
| `I5_4590_8G_120G` / `I3_4130_8G_120G` / `G3260_8G_120G` | 台式机配置标签(CPU / 内存 / 硬盘 / 系统) |
|
||||
| `22寸一体机_4G_120G` / `24寸一体机_I5_3代_8G_256G` | 一体机标签(标题 / CPU / 内存 / 硬盘) |
|
||||
| `HardDisk` | 硬盘标签(系统 / 分区 / 硬盘 / 品牌) |
|
||||
| `Error` | 售后/故障标签(标题 / 说明 / 时间) |
|
||||
| `CustomTag` | **自定义文字标签**:行数可自由增删 |
|
||||
|
||||
> 模板都包含左侧固定信息区(二维码图片、店铺名、电话)与右侧规格区。
|
||||
> 二维码图片需放在站点根目录(如 `public/qrcode.png`)才会显示。
|
||||
|
||||
### 批量填充
|
||||
- 顶栏选择“批量模板”→ 在「① 批量填充设置」中配置内容 → 点「保存模板配置」→ 点「批量添加」;
|
||||
- “批量添加”会按设置的数量,从**第一个空格**开始依次填入模板副本,适合快速铺满同类标签。
|
||||
|
||||
### 逐格自定义编辑
|
||||
- **单击**任意格子:在「② 编辑第 N 格」中为该格选择模板并单独填写内容(「填入该格」/「保存到该格」),每格内容相互独立;
|
||||
- 有内容的格子会自动带出它的模板与内容,便于直接修改;
|
||||
- **双击**格子:隐藏 / 恢复该格内容(打印时该格留空但保留格子框),也可在编辑面板中用按钮操作;
|
||||
- 「删除该格」把格子恢复为空格,其余格子的位置不受影响。
|
||||
|
||||
### CustomTag 自定义文字
|
||||
- 每行可输入一段文字(支持换行、自动居中折行显示);
|
||||
- 每行可单独设置:**字号 (mm)**、**颜色**(黑 / 深灰 / 红 / 橙 / 蓝 / 绿)、**加粗**;
|
||||
- 空行自动跳过不显示,也可以借此“临时停用”某一行;
|
||||
- 添加 / 删除行随时进行(最多 20 行)。
|
||||
|
||||
### 打印
|
||||
- 可设置打印数量(批量添加用)与 **上下 / 左右边距 (cm)**;
|
||||
- 点「打印」按设置动态注入 `@page` 边距并调用 `window.print()`;若运行环境提供 `window.electronAPI.silentPrint` 则走静默打印;
|
||||
- 打印时只输出标签版面:工具栏、面板、序号角标、空格边框均不打印。
|
||||
|
||||
---
|
||||
|
||||
## 使用步骤
|
||||
|
||||
1. **安装依赖**
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. **启动开发服务器**
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
浏览器打开终端提示的地址(默认 http://localhost:5173)。
|
||||
|
||||
3. **批量排版同类标签**
|
||||
- 顶部“批量模板”下拉选择模板(如 `HardDisk`);
|
||||
- 在左侧「① 批量填充设置」中修改字段(可先点「保存模板配置」);
|
||||
- 顶栏输入“数量”,点「批量添加」,标签会依次填入空格。
|
||||
|
||||
4. **单独排版某一张标签**
|
||||
- **单击**目标格子,左侧出现「② 编辑第 N 格」;
|
||||
- 选择“该格使用模板”,填写内容;
|
||||
- 空格点「填入该格」,已有内容点「保存到该格」;
|
||||
- 需要跳过该格打印:双击格子或点「隐藏(不打印)」。
|
||||
|
||||
5. **自定义文字标签(CustomTag)**
|
||||
- 模板选 `CustomTag`(批量或单格均可);
|
||||
- 逐行输入文字,设置字号 / 颜色 / 加粗,用「+ 添加一行」增行;
|
||||
- 保存并添加后,格子内按行居中显示对应颜色与字号的文字。
|
||||
|
||||
6. **调整打印边距并打印**
|
||||
- 顶栏修改“上下边距 / 左右边距”(cm);
|
||||
- 点「打印」,在打印预览中确认后输出。
|
||||
|
||||
7. **清空版面**
|
||||
- 点「清空全部」一次性恢复全部 44 格为空。
|
||||
|
||||
---
|
||||
|
||||
## 生产构建
|
||||
|
||||
```bash
|
||||
npm run build # 产物输出到 dist/
|
||||
npm run preview # 本地预览构建产物
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
src/
|
||||
├─ main.js / App.vue / style.css # 入口与全局样式
|
||||
└─ components/
|
||||
├─ Main.vue # 主页面:44 格模型、批量填充、单格编辑
|
||||
├─ LabelCell.vue # 单个标签格外壳(角标/选中/占位/隐藏/打印)
|
||||
├─ DynamicForm.vue # 通用动态表单(input/select/textarea/lines)
|
||||
├─ Input/ Select/ # 自绘输入框与下拉选择
|
||||
├─ CustomTag.vue # 自定义文字模板(行数/字号/颜色/加粗)
|
||||
├─ Error.vue … # 各规格标签内容模板(config 驱动)
|
||||
└─ (LabelShell.vue 已废弃移除)
|
||||
```
|
||||
|
||||
## 技术要点
|
||||
- 每个格子是固定 44 个 `LabelCell`,与真实标签纸物理位置一一对应,打印时仅输出有内容的区域;
|
||||
- 模板通过“模板注册表”(`templates` 数组)统一注册,新增模板只需在 `Main.vue` 中加一项;
|
||||
- 数据一律**深拷贝**快照,保证批量与逐格之间互不干扰;
|
||||
- 打印样式使用 `@page margin` + `@media print` 控制,屏幕上的一切编辑辅助元素均不进入打印。
|
||||
|
||||
@@ -1,34 +1,22 @@
|
||||
<template>
|
||||
<div :class="'print-area ' + (isShow ? 'border' : '')" @dblclick="onDblClick">
|
||||
<div :class="'item ' + (isShow ? 'show' : 'hide')">
|
||||
<div class='left-content'>
|
||||
<img class='qrcode' src='\qrcode.png'>
|
||||
<div>长沙加零</div>
|
||||
<div>17373135368</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div>{{ config.name }}</div>
|
||||
<div>{{ config.cpu }}</div>
|
||||
<div>{{ config.memory }}</div>
|
||||
<div>{{ config.hardDisk }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="id">
|
||||
<span>
|
||||
{{ id }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="left-content">
|
||||
<img class="qrcode" :src="logoUrl" alt="">
|
||||
<div>长沙加零</div>
|
||||
<div>17373135368</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div>{{ config.name }}</div>
|
||||
<div>{{ config.cpu }}</div>
|
||||
<div>{{ config.memory }}</div>
|
||||
<div>{{ config.hardDisk }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
const isShow = ref(true);
|
||||
defineProps(['config']);
|
||||
|
||||
const props = defineProps(['id', 'config']);
|
||||
|
||||
const onDblClick = () => {
|
||||
isShow.value = !isShow.value;
|
||||
}
|
||||
// 二维码图片路径:运行时拼接,避免被打包器当作模块导入
|
||||
const logoUrl = '/qrcode.png';
|
||||
</script>
|
||||
|
||||
<script>
|
||||
@@ -61,124 +49,8 @@ export const formSchema = {
|
||||
|
||||
export const initialValues = {
|
||||
name: "22寸一体机",
|
||||
cpu:"M380",
|
||||
cpu: "M380",
|
||||
memory: "8G",
|
||||
hardDisk: "256G"
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.print-area {
|
||||
/* border: 0.1mm dashed #87CEFA; */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: flex-start;
|
||||
margin: 0.25mm;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.border {
|
||||
border: 0.1mm dashed #87CEFA;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41),
|
||||
.print-area:nth-child(42),
|
||||
.print-area:nth-child(43),
|
||||
.print-area:nth-child(44) {
|
||||
padding-bottom: 4mm;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41) .qrcode,
|
||||
.print-area:nth-child(42) .qrcode,
|
||||
.print-area:nth-child(43) .qrcode,
|
||||
.print-area:nth-child(44) .qrcode {
|
||||
height: calc(100% - 5mm);
|
||||
}
|
||||
|
||||
.item {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: red;
|
||||
font-size: 0.8cm;
|
||||
}
|
||||
|
||||
.reason,
|
||||
.time {
|
||||
font-size: 4mm;
|
||||
}
|
||||
|
||||
/*Styling below is used just for positioning the items inside the label. */
|
||||
|
||||
.left-content {
|
||||
width: 24mm;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: inherit;
|
||||
font-size: 3mm;
|
||||
line-height: 3mm;
|
||||
color: #87CEFA;
|
||||
}
|
||||
|
||||
.right-content {
|
||||
display: grid;
|
||||
font-size: 4mm;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
height: calc(100% - 6mm);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.id {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2cm;
|
||||
pointer-events: none;
|
||||
color: #87CEFA60;
|
||||
}
|
||||
|
||||
.id span {
|
||||
/* display: block;
|
||||
margin: 0 auto; */
|
||||
}
|
||||
|
||||
@media print {
|
||||
.id * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.hide * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,34 +1,22 @@
|
||||
<template>
|
||||
<div :class="'print-area ' + (isShow ? 'border' : '')" @dblclick="onDblClick">
|
||||
<div :class="'item ' + (isShow ? 'show' : 'hide')">
|
||||
<div class='left-content'>
|
||||
<img class='qrcode' src='\qrcode.png'>
|
||||
<div>长沙加零</div>
|
||||
<div>17373135368</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div>{{ config.name }}</div>
|
||||
<div>{{ config.cpu }}</div>
|
||||
<div>{{ config.memory }}</div>
|
||||
<div>{{ config.hardDisk }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="id">
|
||||
<span>
|
||||
{{ id }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="left-content">
|
||||
<img class="qrcode" :src="logoUrl" alt="">
|
||||
<div>长沙加零</div>
|
||||
<div>17373135368</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div>{{ config.name }}</div>
|
||||
<div>{{ config.cpu }}</div>
|
||||
<div>{{ config.memory }}</div>
|
||||
<div>{{ config.hardDisk }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
const isShow = ref(true);
|
||||
defineProps(['config']);
|
||||
|
||||
const props = defineProps(['id', 'config']);
|
||||
|
||||
const onDblClick = () => {
|
||||
isShow.value = !isShow.value;
|
||||
}
|
||||
// 二维码图片路径:运行时拼接,避免被打包器当作模块导入
|
||||
const logoUrl = '/qrcode.png';
|
||||
</script>
|
||||
|
||||
<script>
|
||||
@@ -61,124 +49,8 @@ export const formSchema = {
|
||||
|
||||
export const initialValues = {
|
||||
name: "24寸一体机",
|
||||
cpu:"I5 3代",
|
||||
cpu: "I5 3代",
|
||||
memory: "8G",
|
||||
hardDisk: "256G"
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.print-area {
|
||||
/* border: 0.1mm dashed #87CEFA; */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: flex-start;
|
||||
margin: 0.25mm;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.border {
|
||||
border: 0.1mm dashed #87CEFA;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41),
|
||||
.print-area:nth-child(42),
|
||||
.print-area:nth-child(43),
|
||||
.print-area:nth-child(44) {
|
||||
padding-bottom: 4mm;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41) .qrcode,
|
||||
.print-area:nth-child(42) .qrcode,
|
||||
.print-area:nth-child(43) .qrcode,
|
||||
.print-area:nth-child(44) .qrcode {
|
||||
height: calc(100% - 5mm);
|
||||
}
|
||||
|
||||
.item {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: red;
|
||||
font-size: 0.8cm;
|
||||
}
|
||||
|
||||
.reason,
|
||||
.time {
|
||||
font-size: 4mm;
|
||||
}
|
||||
|
||||
/*Styling below is used just for positioning the items inside the label. */
|
||||
|
||||
.left-content {
|
||||
width: 24mm;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: inherit;
|
||||
font-size: 3mm;
|
||||
line-height: 3mm;
|
||||
color: #87CEFA;
|
||||
}
|
||||
|
||||
.right-content {
|
||||
display: grid;
|
||||
font-size: 4mm;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
height: calc(100% - 6mm);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.id {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2cm;
|
||||
pointer-events: none;
|
||||
color: #87CEFA60;
|
||||
}
|
||||
|
||||
.id span {
|
||||
/* display: block;
|
||||
margin: 0 auto; */
|
||||
}
|
||||
|
||||
@media print {
|
||||
.id * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.hide * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+56
-138
@@ -1,164 +1,82 @@
|
||||
<template>
|
||||
<div :class="'print-area ' + (isShow ? 'border' : '')" @dblclick="onDblClick">
|
||||
<div :class="'item ' + (isShow ? 'show' : 'hide')">
|
||||
|
||||
<div>{{ config.content1 }}</div>
|
||||
<div>{{ config.content2 }}</div>
|
||||
<div>{{ config.content3 }}</div>
|
||||
<div>{{ config.content4 }}</div>
|
||||
<div>{{ config.content5 }}</div>
|
||||
|
||||
</div>
|
||||
<div class="id">
|
||||
<span>
|
||||
{{ id }}
|
||||
</span>
|
||||
<div v-if="visibleLines.length" class="ct-lines">
|
||||
<div v-for="(line, idx) in visibleLines" :key="idx" class="ct-line" :style="lineStyle(line)">
|
||||
{{ line.text }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
const isShow = ref(true);
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps(['id', 'config']);
|
||||
const props = defineProps(['config']);
|
||||
|
||||
const onDblClick = () => {
|
||||
isShow.value = !isShow.value;
|
||||
}
|
||||
// 只显示填了内容的行:空行既不在格子上占位,也方便“临时停用”某一行
|
||||
const visibleLines = computed(() =>
|
||||
(props.config?.lines || []).filter((line) => String(line?.text ?? '').trim() !== '')
|
||||
);
|
||||
|
||||
// 每行可单独控制字号(mm)、颜色与加粗
|
||||
const lineStyle = (line) => {
|
||||
const size = Number(line?.size) || 4;
|
||||
return {
|
||||
fontSize: `${size}mm`,
|
||||
lineHeight: `${Math.max(1.1, size * 1.25).toFixed(2)}mm`,
|
||||
fontWeight: line?.bold ? 'bold' : 'normal',
|
||||
color: line?.color || '#000000'
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// 自定义内容模板:行数可自由增删,每行文字显示在标签格中
|
||||
export const formSchema = {
|
||||
formItems: [
|
||||
{ type: 'input', label: '文字1', key: 'content1' },
|
||||
{ type: 'input', label: '文字2', key: 'content2' },
|
||||
{ type: 'input', label: '文字3', key: 'content3' },
|
||||
{ type: 'input', label: '文字4', key: 'content4' },
|
||||
{ type: 'input', label: '文字5', key: 'content5' }
|
||||
{
|
||||
type: 'lines',
|
||||
label: '标签内容(每行显示为一个文本行)',
|
||||
key: 'lines',
|
||||
lineDefaults: { text: '', size: 4, bold: false, color: '#000000' },
|
||||
sizeOptions: [2, 2.5, 3, 3.5, 4, 4.5, 5, 6, 7, 8],
|
||||
colorOptions: [
|
||||
{ label: '黑色', value: '#000000' },
|
||||
{ label: '深灰', value: '#374151' },
|
||||
{ label: '红色', value: '#dc2626' },
|
||||
{ label: '橙色', value: '#ea580c' },
|
||||
{ label: '蓝色', value: '#2563eb' },
|
||||
{ label: '绿色', value: '#16a34a' }
|
||||
],
|
||||
maxLines: 20
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const initialValues = {
|
||||
content1: "",
|
||||
content2: "",
|
||||
content3: "",
|
||||
content4: "",
|
||||
content5: ""
|
||||
}
|
||||
lines: [
|
||||
{ text: '', size: 5, bold: true, color: '#000000' },
|
||||
{ text: '', size: 3.5, bold: false, color: '#dc2626' }
|
||||
]
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.print-area {
|
||||
/* border: 0.1mm dashed #87CEFA; */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: flex-start;
|
||||
margin: 0.25mm;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.border {
|
||||
border: 0.1mm dashed #87CEFA;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41),
|
||||
.print-area:nth-child(42),
|
||||
.print-area:nth-child(43),
|
||||
.print-area:nth-child(44) {
|
||||
padding-bottom: 4mm;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41) .qrcode,
|
||||
.print-area:nth-child(42) .qrcode,
|
||||
.print-area:nth-child(43) .qrcode,
|
||||
.print-area:nth-child(44) .qrcode {
|
||||
height: calc(100% - 5mm);
|
||||
}
|
||||
|
||||
.item {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: red;
|
||||
font-size: 0.8cm;
|
||||
}
|
||||
|
||||
.reason,
|
||||
.time {
|
||||
font-size: 4mm;
|
||||
}
|
||||
|
||||
/*Styling below is used just for positioning the items inside the label. */
|
||||
|
||||
.left-content {
|
||||
width: 24mm;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: inherit;
|
||||
font-size: 3mm;
|
||||
line-height: 3mm;
|
||||
color: #87CEFA;
|
||||
}
|
||||
|
||||
.right-content {
|
||||
display: grid;
|
||||
font-size: 4mm;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
height: calc(100% - 6mm);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.id {
|
||||
position: absolute;
|
||||
.ct-lines {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2cm;
|
||||
pointer-events: none;
|
||||
color: #87CEFA60;
|
||||
gap: 0.4mm;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.id span {
|
||||
/* display: block;
|
||||
margin: 0 auto; */
|
||||
.ct-line {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.id * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.hide * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
+209
-22
@@ -1,31 +1,32 @@
|
||||
<template>
|
||||
<form @submit.prevent="submitForm">
|
||||
<div v-for="(item, index) in formConfig.formItems" :key="index">
|
||||
<!-- 文本输入框 -->
|
||||
<!-- 单行文本框 -->
|
||||
<div v-if="item.type === 'input' || item.type === 'password'">
|
||||
<!-- <label>{{ item.label }}</label>
|
||||
<input
|
||||
:type="item.type"
|
||||
v-model="formData[item.key]"
|
||||
:placeholder="item.placeholder"
|
||||
> -->
|
||||
<Input :title="item.label" :type="item.type" v-model="formData[item.key]"></Input>
|
||||
<Input :title="item.label" :type="item.type" v-model="formData[item.key]" />
|
||||
<span v-if="errors[item.key]" class="error">{{ errors[item.key] }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 多行文本框 -->
|
||||
<div v-else-if="item.type === 'textarea'">
|
||||
<label>{{ item.label }}</label>
|
||||
<textarea v-model="formData[item.key]" :rows="item.rows || 3"
|
||||
:placeholder="item.placeholder || `请输入${item.label}`"></textarea>
|
||||
<span v-if="errors[item.key]" class="error">{{ errors[item.key] }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 下拉选择 -->
|
||||
<div v-if="item.type === 'select'">
|
||||
<div v-else-if="item.type === 'select'">
|
||||
<label>{{ item.label }}</label>
|
||||
<select v-model="formData[item.key]">
|
||||
<option v-for="(opt, idx) in item.options" :key="idx" :value="opt.value">
|
||||
{{ opt.label }}
|
||||
</option>
|
||||
</select>
|
||||
<!-- <Select :data="item.options" v-model="formData[item.key]"></Select> -->
|
||||
</div>
|
||||
|
||||
<!-- 复选框 -->
|
||||
<div v-if="item.type === 'checkbox'">
|
||||
<div v-else-if="item.type === 'checkbox'">
|
||||
<label>{{ item.label }}</label>
|
||||
<div v-for="(opt, idx) in item.options" :key="idx">
|
||||
<input type="checkbox" :value="opt.value" v-model="formData[item.key]">
|
||||
@@ -34,27 +35,68 @@
|
||||
</div>
|
||||
|
||||
<!-- 单选框 -->
|
||||
<div v-if="item.type === 'radio'">
|
||||
<div v-else-if="item.type === 'radio'">
|
||||
<label>{{ item.label }}</label>
|
||||
<div v-for="(opt, idx) in item.options" :key="idx">
|
||||
<input type="radio" :value="opt.value" v-model="formData[item.key]">
|
||||
{{ opt.label }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 动态内容行:行数可增删,每行可设置文字 / 字号 / 加粗 -->
|
||||
<div v-else-if="item.type === 'lines'" class="lines-editor">
|
||||
<label class="lines-label">{{ item.label }}</label>
|
||||
|
||||
<div v-for="(row, rIdx) in formData[item.key] || []" :key="rIdx" class="line-row">
|
||||
<div class="line-head">
|
||||
<span class="line-no">第 {{ rIdx + 1 }} 行</span>
|
||||
|
||||
<label class="line-opt">字号(mm)
|
||||
<select v-model="row.size">
|
||||
<option v-for="s in (item.sizeOptions || defaultSizeOptions)" :key="s" :value="Number(s)">
|
||||
{{ s }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label v-if="item.colorOptions" class="line-opt">颜色
|
||||
<span class="color-dot" :style="{ backgroundColor: row.color || '#000000' }"></span>
|
||||
<select v-model="row.color">
|
||||
<option v-for="c in item.colorOptions" :key="c.value" :value="c.value">{{ c.label }}</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="line-opt" title="是否加粗">
|
||||
<input type="checkbox" v-model="row.bold" /> 加粗
|
||||
</label>
|
||||
|
||||
<button type="button" class="line-del" :disabled="(formData[item.key] || []).length <= 1"
|
||||
@click="removeLine(item.key, rIdx)">删除</button>
|
||||
</div>
|
||||
|
||||
<textarea v-model="row.text" :rows="item.rowRows || 2"
|
||||
:placeholder="`第 ${rIdx + 1} 行内容(可换行)`"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn line-add"
|
||||
:disabled="(formData[item.key] || []).length >= (item.maxLines || 20)"
|
||||
@click="addLine(item.key, item)">+ 添加一行</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" type="submit">提交</button>
|
||||
<button class="btn btn-primary" type="submit">{{ submitText }}</button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Input from "./Input/Index.vue";
|
||||
import Select from "./Select/index.vue";
|
||||
|
||||
// 深拷贝:表单编辑不应反向污染模板默认值 / 已添加的格子数据
|
||||
const clone = (obj) => JSON.parse(JSON.stringify(obj));
|
||||
|
||||
export default {
|
||||
components: {
|
||||
"Input": Input,
|
||||
"Select": Select
|
||||
"Input": Input
|
||||
},
|
||||
props: {
|
||||
formConfig: {
|
||||
@@ -64,24 +106,44 @@ export default {
|
||||
initialData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
submitText: {
|
||||
type: String,
|
||||
default: '提交'
|
||||
}
|
||||
},
|
||||
emits: ['submit'],
|
||||
data() {
|
||||
return {
|
||||
formData: { ...this.initialData },
|
||||
errors: {}
|
||||
formData: clone(this.initialData),
|
||||
errors: {},
|
||||
defaultSizeOptions: [2, 2.5, 3, 3.5, 4, 4.5, 5, 6, 7, 8]
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
submitForm() {
|
||||
if (this.validateForm()) {
|
||||
this.$emit('submit', this.formData)
|
||||
console.log(this.formData);
|
||||
this.$emit('submit', clone(this.formData))
|
||||
}
|
||||
},
|
||||
|
||||
addLine(key, item) {
|
||||
const defaults = item.lineDefaults || { text: '', size: 4, bold: false, color: '#000000' };
|
||||
if (!Array.isArray(this.formData[key])) this.formData[key] = [];
|
||||
this.formData[key].push({
|
||||
text: '',
|
||||
size: Number(defaults.size) || 4,
|
||||
bold: !!defaults.bold,
|
||||
color: defaults.color || '#000000'
|
||||
});
|
||||
},
|
||||
|
||||
removeLine(key, index) {
|
||||
if ((this.formData[key] || []).length <= 1) return;
|
||||
this.formData[key].splice(index, 1);
|
||||
},
|
||||
|
||||
validateForm() {
|
||||
this.errors = {}
|
||||
let isValid = true
|
||||
@@ -100,7 +162,7 @@ export default {
|
||||
watch: {
|
||||
initialData: {
|
||||
handler(newVal) {
|
||||
this.formData = { ...newVal }
|
||||
this.formData = clone(newVal)
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
@@ -168,4 +230,129 @@ select option:checked {
|
||||
color: #1a2a6c;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
||||
/* ====== 多行文本框(textarea 类型) ====== */
|
||||
textarea {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 10px 12px;
|
||||
border: 2px solid #e0e6ed;
|
||||
border-radius: 10px;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
background: #fff;
|
||||
outline: none;
|
||||
color: #333;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
border-color: #3498db;
|
||||
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
|
||||
}
|
||||
|
||||
/* ====== 动态内容行编辑器 ====== */
|
||||
.lines-editor {
|
||||
max-width: 440px;
|
||||
}
|
||||
|
||||
.lines-label {
|
||||
display: block;
|
||||
margin: 0 0 8px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.line-row {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
padding: 8px 10px;
|
||||
margin-bottom: 10px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.line-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 6px;
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.line-no {
|
||||
font-weight: 700;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.color-dot {
|
||||
display: inline-block;
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #cbd5e1;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.line-opt {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.lines-editor select {
|
||||
width: auto;
|
||||
padding: 2px 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
border-radius: 6px;
|
||||
box-shadow: none;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.lines-editor select:focus {
|
||||
box-shadow: 0 0 0 2px rgba(110, 142, 251, 0.2);
|
||||
}
|
||||
|
||||
.line-del {
|
||||
margin-left: auto;
|
||||
border: none;
|
||||
background: #fee2e2;
|
||||
color: #b91c1c;
|
||||
font-size: 12px;
|
||||
padding: 3px 10px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.line-del:hover:not(:disabled) {
|
||||
background: #fecaca;
|
||||
}
|
||||
|
||||
.line-del:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.line-add {
|
||||
width: 100%;
|
||||
border: 1.5px dashed #3498db;
|
||||
background: transparent;
|
||||
color: #3498db;
|
||||
padding: 6px 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.line-add:hover:not(:disabled) {
|
||||
background: #eef6fd;
|
||||
}
|
||||
|
||||
.line-add:disabled {
|
||||
border-color: #cbd5e1;
|
||||
color: #94a3b8;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
||||
+5
-126
@@ -1,26 +1,11 @@
|
||||
<template>
|
||||
<div :class="'print-area ' + (isShow ? 'border' : '')" @dblclick="onDblClick">
|
||||
<div :class="'item ' + (isShow ? 'show' : 'hide')">
|
||||
<div class="tag">{{ config.title }}</div>
|
||||
<div class="reason" style="font-size: 4mm;margin-top: 2mm;">{{ config.reason }}</div>
|
||||
<div class="time">{{ config.time }}</div>
|
||||
</div>
|
||||
<div class="id">
|
||||
<span>
|
||||
{{ id }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tag">{{ config.title }}</div>
|
||||
<div class="reason" style="font-size: 4mm;margin-top: 2mm;">{{ config.reason }}</div>
|
||||
<div class="time">{{ config.time }}</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
const isShow = ref(true);
|
||||
|
||||
const props = defineProps(['id', 'config']);
|
||||
|
||||
const onDblClick = () => {
|
||||
isShow.value = !isShow.value;
|
||||
}
|
||||
defineProps(['config']);
|
||||
</script>
|
||||
|
||||
<script>
|
||||
@@ -48,109 +33,3 @@ export const initialValues = {
|
||||
time: ""
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.print-area {
|
||||
/* border: 0.1mm dashed #87CEFA; */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: flex-start;
|
||||
margin: 0.25mm;
|
||||
}
|
||||
|
||||
.border {
|
||||
border: 0.1mm dashed #87CEFA;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41),
|
||||
.print-area:nth-child(42),
|
||||
.print-area:nth-child(43),
|
||||
.print-area:nth-child(44) {
|
||||
padding-bottom: 4mm;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41) .qrcode,
|
||||
.print-area:nth-child(42) .qrcode,
|
||||
.print-area:nth-child(43) .qrcode,
|
||||
.print-area:nth-child(44) .qrcode {
|
||||
height: calc(100% - 5mm);
|
||||
}
|
||||
|
||||
.item {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: red;
|
||||
font-size: 0.8cm;
|
||||
}
|
||||
|
||||
.reason,
|
||||
.time {
|
||||
font-size: 4mm;
|
||||
}
|
||||
|
||||
/*Styling below is used just for positioning the items inside the label. */
|
||||
|
||||
.left-content {
|
||||
width: 24mm;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: inherit;
|
||||
font-size: 3mm;
|
||||
line-height: 3mm;
|
||||
color: #87CEFA;
|
||||
}
|
||||
|
||||
.right-content {
|
||||
display: grid;
|
||||
font-size: 4mm;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
height: calc(100% - 6mm);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.id {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2cm;
|
||||
pointer-events: none;
|
||||
color: #87CEFA60;
|
||||
}
|
||||
|
||||
.id span {
|
||||
/* display: block;
|
||||
margin: 0 auto; */
|
||||
}
|
||||
|
||||
@media print {
|
||||
.id * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.hide * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,34 +1,22 @@
|
||||
<template>
|
||||
<div :class="'print-area ' + (isShow ? 'border' : '')" @dblclick="onDblClick">
|
||||
<div :class="'item ' + (isShow ? 'show' : 'hide')">
|
||||
<div class='left-content'>
|
||||
<img class='qrcode' src='\qrcode.png'>
|
||||
<div>长沙加零</div>
|
||||
<div>17373135368</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div>{{ config.cpu }}</div>
|
||||
<div>{{ config.memory }}</div>
|
||||
<div>{{ config.hardDisk }}</div>
|
||||
<div>{{ config.system }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="id">
|
||||
<span>
|
||||
{{ id }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="left-content">
|
||||
<img class="qrcode" :src="logoUrl" alt="">
|
||||
<div>长沙加零</div>
|
||||
<div>17373135368</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div>{{ config.cpu }}</div>
|
||||
<div>{{ config.memory }}</div>
|
||||
<div>{{ config.hardDisk }}</div>
|
||||
<div>{{ config.system }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
const isShow = ref(true);
|
||||
defineProps(['config']);
|
||||
|
||||
const props = defineProps(['id', 'config']);
|
||||
|
||||
const onDblClick = () => {
|
||||
isShow.value = !isShow.value;
|
||||
}
|
||||
// 二维码图片路径:运行时拼接,避免被打包器当作模块导入
|
||||
const logoUrl = '/qrcode.png';
|
||||
</script>
|
||||
|
||||
<script>
|
||||
@@ -75,119 +63,3 @@ export const initialValues = {
|
||||
system: ""
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.print-area {
|
||||
/* border: 0.1mm dashed #87CEFA; */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: flex-start;
|
||||
margin: 0.25mm;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.border {
|
||||
border: 0.1mm dashed #87CEFA;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41),
|
||||
.print-area:nth-child(42),
|
||||
.print-area:nth-child(43),
|
||||
.print-area:nth-child(44) {
|
||||
padding-bottom: 4mm;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41) .qrcode,
|
||||
.print-area:nth-child(42) .qrcode,
|
||||
.print-area:nth-child(43) .qrcode,
|
||||
.print-area:nth-child(44) .qrcode {
|
||||
height: calc(100% - 5mm);
|
||||
}
|
||||
|
||||
.item {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: red;
|
||||
font-size: 0.8cm;
|
||||
}
|
||||
|
||||
.reason,
|
||||
.time {
|
||||
font-size: 4mm;
|
||||
}
|
||||
|
||||
/*Styling below is used just for positioning the items inside the label. */
|
||||
|
||||
.left-content {
|
||||
width: 24mm;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: inherit;
|
||||
font-size: 3mm;
|
||||
line-height: 3mm;
|
||||
color: #87CEFA;
|
||||
}
|
||||
|
||||
.right-content {
|
||||
display: grid;
|
||||
font-size: 4mm;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
height: calc(100% - 6mm);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.id {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2cm;
|
||||
pointer-events: none;
|
||||
color: #87CEFA60;
|
||||
}
|
||||
|
||||
.id span {
|
||||
/* display: block;
|
||||
margin: 0 auto; */
|
||||
}
|
||||
|
||||
@media print {
|
||||
.id * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.hide * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+15
-143
@@ -1,34 +1,22 @@
|
||||
<template>
|
||||
<div :class="'print-area ' + (isShow ? 'border' : '')" @dblclick="onDblClick">
|
||||
<div :class="'item ' + (isShow ? 'show' : 'hide')">
|
||||
<div class='left-content'>
|
||||
<img class='qrcode' src='\qrcode.png'>
|
||||
<div>长沙加零</div>
|
||||
<div>17373135368</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div>{{ config.system }}</div>
|
||||
<div>{{ config.partition }}</div>
|
||||
<div>{{ config.hardDisk }}</div>
|
||||
<div>{{ config.brand }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="id">
|
||||
<span>
|
||||
{{ id }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="left-content">
|
||||
<img class="qrcode" :src="logoUrl" alt="">
|
||||
<div>长沙加零</div>
|
||||
<div>17373135368</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div>{{ config.system }}</div>
|
||||
<div>{{ config.partition }}</div>
|
||||
<div>{{ config.hardDisk }}</div>
|
||||
<div>{{ config.brand }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
const isShow = ref(true);
|
||||
defineProps(['config']);
|
||||
|
||||
const props = defineProps(['id', 'config']);
|
||||
|
||||
const onDblClick = () => {
|
||||
isShow.value = !isShow.value;
|
||||
}
|
||||
// 二维码图片路径:运行时拼接,避免被打包器当作模块导入
|
||||
const logoUrl = '/qrcode.png';
|
||||
</script>
|
||||
|
||||
<script>
|
||||
@@ -85,124 +73,8 @@ export const formSchema = {
|
||||
|
||||
export const initialValues = {
|
||||
system: "win10",
|
||||
partition:"MBR",
|
||||
partition: "MBR",
|
||||
hardDisk: "256G",
|
||||
brand: "泽域"
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.print-area {
|
||||
/* border: 0.1mm dashed #87CEFA; */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: flex-start;
|
||||
margin: 0.25mm;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.border {
|
||||
border: 0.1mm dashed #87CEFA;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41),
|
||||
.print-area:nth-child(42),
|
||||
.print-area:nth-child(43),
|
||||
.print-area:nth-child(44) {
|
||||
padding-bottom: 4mm;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41) .qrcode,
|
||||
.print-area:nth-child(42) .qrcode,
|
||||
.print-area:nth-child(43) .qrcode,
|
||||
.print-area:nth-child(44) .qrcode {
|
||||
height: calc(100% - 5mm);
|
||||
}
|
||||
|
||||
.item {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: red;
|
||||
font-size: 0.8cm;
|
||||
}
|
||||
|
||||
.reason,
|
||||
.time {
|
||||
font-size: 4mm;
|
||||
}
|
||||
|
||||
/*Styling below is used just for positioning the items inside the label. */
|
||||
|
||||
.left-content {
|
||||
width: 24mm;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: inherit;
|
||||
font-size: 3mm;
|
||||
line-height: 3mm;
|
||||
color: #87CEFA;
|
||||
}
|
||||
|
||||
.right-content {
|
||||
display: grid;
|
||||
font-size: 4mm;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
height: calc(100% - 6mm);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.id {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2cm;
|
||||
pointer-events: none;
|
||||
color: #87CEFA60;
|
||||
}
|
||||
|
||||
.id span {
|
||||
/* display: block;
|
||||
margin: 0 auto; */
|
||||
}
|
||||
|
||||
@media print {
|
||||
.id * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.hide * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,34 +1,22 @@
|
||||
<template>
|
||||
<div :class="'print-area ' + (isShow ? 'border' : '')" @dblclick="onDblClick">
|
||||
<div :class="'item ' + (isShow ? 'show' : 'hide')">
|
||||
<div class='left-content'>
|
||||
<img class='qrcode' src='\qrcode.png'>
|
||||
<div>长沙加零</div>
|
||||
<div>17373135368</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div>{{ config.cpu }}</div>
|
||||
<div>{{ config.memory }}</div>
|
||||
<div>{{ config.hardDisk }}</div>
|
||||
<div>{{ config.system }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="id">
|
||||
<span>
|
||||
{{ id }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="left-content">
|
||||
<img class="qrcode" :src="logoUrl" alt="">
|
||||
<div>长沙加零</div>
|
||||
<div>17373135368</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div>{{ config.cpu }}</div>
|
||||
<div>{{ config.memory }}</div>
|
||||
<div>{{ config.hardDisk }}</div>
|
||||
<div>{{ config.system }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
const isShow = ref(true);
|
||||
defineProps(['config']);
|
||||
|
||||
const props = defineProps(['id', 'config']);
|
||||
|
||||
const onDblClick = () => {
|
||||
isShow.value = !isShow.value;
|
||||
}
|
||||
// 二维码图片路径:运行时拼接,避免被打包器当作模块导入
|
||||
const logoUrl = '/qrcode.png';
|
||||
</script>
|
||||
|
||||
<script>
|
||||
@@ -75,119 +63,3 @@ export const initialValues = {
|
||||
system: ""
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.print-area {
|
||||
/* border: 0.1mm dashed #87CEFA; */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: flex-start;
|
||||
margin: 0.25mm;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.border {
|
||||
border: 0.1mm dashed #87CEFA;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41),
|
||||
.print-area:nth-child(42),
|
||||
.print-area:nth-child(43),
|
||||
.print-area:nth-child(44) {
|
||||
padding-bottom: 4mm;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41) .qrcode,
|
||||
.print-area:nth-child(42) .qrcode,
|
||||
.print-area:nth-child(43) .qrcode,
|
||||
.print-area:nth-child(44) .qrcode {
|
||||
height: calc(100% - 5mm);
|
||||
}
|
||||
|
||||
.item {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: red;
|
||||
font-size: 0.8cm;
|
||||
}
|
||||
|
||||
.reason,
|
||||
.time {
|
||||
font-size: 4mm;
|
||||
}
|
||||
|
||||
/*Styling below is used just for positioning the items inside the label. */
|
||||
|
||||
.left-content {
|
||||
width: 24mm;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: inherit;
|
||||
font-size: 3mm;
|
||||
line-height: 3mm;
|
||||
color: #87CEFA;
|
||||
}
|
||||
|
||||
.right-content {
|
||||
display: grid;
|
||||
font-size: 4mm;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
height: calc(100% - 6mm);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.id {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2cm;
|
||||
pointer-events: none;
|
||||
color: #87CEFA60;
|
||||
}
|
||||
|
||||
.id span {
|
||||
/* display: block;
|
||||
margin: 0 auto; */
|
||||
}
|
||||
|
||||
@media print {
|
||||
.id * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.hide * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,34 +1,22 @@
|
||||
<template>
|
||||
<div :class="'print-area ' + (isShow ? 'border' : '')" @dblclick="onDblClick">
|
||||
<div :class="'item ' + (isShow ? 'show' : 'hide')">
|
||||
<div class='left-content'>
|
||||
<img class='qrcode' src='\qrcode.png'>
|
||||
<div>长沙加零</div>
|
||||
<div>17373135368</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div>{{ config.cpu }}</div>
|
||||
<div>{{ config.memory }}</div>
|
||||
<div>{{ config.hardDisk }}</div>
|
||||
<div>{{ config.graphicsCard }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="id">
|
||||
<span>
|
||||
{{ id }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="left-content">
|
||||
<img class="qrcode" :src="logoUrl" alt="">
|
||||
<div>长沙加零</div>
|
||||
<div>17373135368</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div>{{ config.cpu }}</div>
|
||||
<div>{{ config.memory }}</div>
|
||||
<div>{{ config.hardDisk }}</div>
|
||||
<div>{{ config.graphicsCard }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
const isShow = ref(true);
|
||||
defineProps(['config']);
|
||||
|
||||
const props = defineProps(['id', 'config']);
|
||||
|
||||
const onDblClick = () => {
|
||||
isShow.value = !isShow.value;
|
||||
}
|
||||
// 二维码图片路径:运行时拼接,避免被打包器当作模块导入
|
||||
const logoUrl = '/qrcode.png';
|
||||
</script>
|
||||
|
||||
<script>
|
||||
@@ -80,119 +68,3 @@ export const initialValues = {
|
||||
graphicsCard: "1060(3,5,6)"
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.print-area {
|
||||
/* border: 0.1mm dashed #87CEFA; */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: flex-start;
|
||||
margin: 0.25mm;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.border {
|
||||
border: 0.1mm dashed #87CEFA;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41),
|
||||
.print-area:nth-child(42),
|
||||
.print-area:nth-child(43),
|
||||
.print-area:nth-child(44) {
|
||||
padding-bottom: 4mm;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41) .qrcode,
|
||||
.print-area:nth-child(42) .qrcode,
|
||||
.print-area:nth-child(43) .qrcode,
|
||||
.print-area:nth-child(44) .qrcode {
|
||||
height: calc(100% - 5mm);
|
||||
}
|
||||
|
||||
.item {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: red;
|
||||
font-size: 0.8cm;
|
||||
}
|
||||
|
||||
.reason,
|
||||
.time {
|
||||
font-size: 4mm;
|
||||
}
|
||||
|
||||
/*Styling below is used just for positioning the items inside the label. */
|
||||
|
||||
.left-content {
|
||||
width: 24mm;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: inherit;
|
||||
font-size: 3mm;
|
||||
line-height: 3mm;
|
||||
color: #87CEFA;
|
||||
}
|
||||
|
||||
.right-content {
|
||||
display: grid;
|
||||
font-size: 4mm;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
height: calc(100% - 6mm);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.id {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2cm;
|
||||
pointer-events: none;
|
||||
color: #87CEFA60;
|
||||
}
|
||||
|
||||
.id span {
|
||||
/* display: block;
|
||||
margin: 0 auto; */
|
||||
}
|
||||
|
||||
@media print {
|
||||
.id * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.hide * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,34 +1,22 @@
|
||||
<template>
|
||||
<div :class="'print-area ' + (isShow ? 'border' : '')" @dblclick="onDblClick">
|
||||
<div :class="'item ' + (isShow ? 'show' : 'hide')">
|
||||
<div class='left-content'>
|
||||
<img class='qrcode' src='\qrcode.png'>
|
||||
<div>长沙加零</div>
|
||||
<div>17373135368</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div>{{ config.cpu }}</div>
|
||||
<div>{{ config.memory }}</div>
|
||||
<div>{{ config.hardDisk }}</div>
|
||||
<div>{{ config.graphicsCard }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="id">
|
||||
<span>
|
||||
{{ id }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="left-content">
|
||||
<img class="qrcode" :src="logoUrl" alt="">
|
||||
<div>长沙加零</div>
|
||||
<div>17373135368</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div>{{ config.cpu }}</div>
|
||||
<div>{{ config.memory }}</div>
|
||||
<div>{{ config.hardDisk }}</div>
|
||||
<div>{{ config.graphicsCard }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
const isShow = ref(true);
|
||||
defineProps(['config']);
|
||||
|
||||
const props = defineProps(['id', 'config']);
|
||||
|
||||
const onDblClick = () => {
|
||||
isShow.value = !isShow.value;
|
||||
}
|
||||
// 二维码图片路径:运行时拼接,避免被打包器当作模块导入
|
||||
const logoUrl = '/qrcode.png';
|
||||
</script>
|
||||
|
||||
<script>
|
||||
@@ -80,119 +68,3 @@ export const initialValues = {
|
||||
graphicsCard: "2060"
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.print-area {
|
||||
/* border: 0.1mm dashed #87CEFA; */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: flex-start;
|
||||
margin: 0.25mm;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.border {
|
||||
border: 0.1mm dashed #87CEFA;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41),
|
||||
.print-area:nth-child(42),
|
||||
.print-area:nth-child(43),
|
||||
.print-area:nth-child(44) {
|
||||
padding-bottom: 4mm;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41) .qrcode,
|
||||
.print-area:nth-child(42) .qrcode,
|
||||
.print-area:nth-child(43) .qrcode,
|
||||
.print-area:nth-child(44) .qrcode {
|
||||
height: calc(100% - 5mm);
|
||||
}
|
||||
|
||||
.item {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: red;
|
||||
font-size: 0.8cm;
|
||||
}
|
||||
|
||||
.reason,
|
||||
.time {
|
||||
font-size: 4mm;
|
||||
}
|
||||
|
||||
/*Styling below is used just for positioning the items inside the label. */
|
||||
|
||||
.left-content {
|
||||
width: 24mm;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: inherit;
|
||||
font-size: 3mm;
|
||||
line-height: 3mm;
|
||||
color: #87CEFA;
|
||||
}
|
||||
|
||||
.right-content {
|
||||
display: grid;
|
||||
font-size: 4mm;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
height: calc(100% - 6mm);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.id {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2cm;
|
||||
pointer-events: none;
|
||||
color: #87CEFA60;
|
||||
}
|
||||
|
||||
.id span {
|
||||
/* display: block;
|
||||
margin: 0 auto; */
|
||||
}
|
||||
|
||||
@media print {
|
||||
.id * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.hide * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,34 +1,22 @@
|
||||
<template>
|
||||
<div :class="'print-area ' + (isShow ? 'border' : '')" @dblclick="onDblClick">
|
||||
<div :class="'item ' + (isShow ? 'show' : 'hide')">
|
||||
<div class='left-content'>
|
||||
<img class='qrcode' src='\qrcode.png'>
|
||||
<div>长沙加零</div>
|
||||
<div>17373135368</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div>{{ config.cpu }}</div>
|
||||
<div>{{ config.memory }}</div>
|
||||
<div>{{ config.hardDisk }}</div>
|
||||
<div>{{ config.system }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="id">
|
||||
<span>
|
||||
{{ id }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="left-content">
|
||||
<img class="qrcode" :src="logoUrl" alt="">
|
||||
<div>长沙加零</div>
|
||||
<div>17373135368</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div>{{ config.cpu }}</div>
|
||||
<div>{{ config.memory }}</div>
|
||||
<div>{{ config.hardDisk }}</div>
|
||||
<div>{{ config.system }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
const isShow = ref(true);
|
||||
defineProps(['config']);
|
||||
|
||||
const props = defineProps(['id', 'config']);
|
||||
|
||||
const onDblClick = () => {
|
||||
isShow.value = !isShow.value;
|
||||
}
|
||||
// 二维码图片路径:运行时拼接,避免被打包器当作模块导入
|
||||
const logoUrl = '/qrcode.png';
|
||||
</script>
|
||||
|
||||
<script>
|
||||
@@ -75,119 +63,3 @@ export const initialValues = {
|
||||
system: ""
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.print-area {
|
||||
/* border: 0.1mm dashed #87CEFA; */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: flex-start;
|
||||
margin: 0.25mm;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.border {
|
||||
border: 0.1mm dashed #87CEFA;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41),
|
||||
.print-area:nth-child(42),
|
||||
.print-area:nth-child(43),
|
||||
.print-area:nth-child(44) {
|
||||
padding-bottom: 4mm;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41) .qrcode,
|
||||
.print-area:nth-child(42) .qrcode,
|
||||
.print-area:nth-child(43) .qrcode,
|
||||
.print-area:nth-child(44) .qrcode {
|
||||
height: calc(100% - 5mm);
|
||||
}
|
||||
|
||||
.item {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: red;
|
||||
font-size: 0.8cm;
|
||||
}
|
||||
|
||||
.reason,
|
||||
.time {
|
||||
font-size: 4mm;
|
||||
}
|
||||
|
||||
/*Styling below is used just for positioning the items inside the label. */
|
||||
|
||||
.left-content {
|
||||
width: 24mm;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: inherit;
|
||||
font-size: 3mm;
|
||||
line-height: 3mm;
|
||||
color: #87CEFA;
|
||||
}
|
||||
|
||||
.right-content {
|
||||
display: grid;
|
||||
font-size: 4mm;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
height: calc(100% - 6mm);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.id {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2cm;
|
||||
pointer-events: none;
|
||||
color: #87CEFA60;
|
||||
}
|
||||
|
||||
.id span {
|
||||
/* display: block;
|
||||
margin: 0 auto; */
|
||||
}
|
||||
|
||||
@media print {
|
||||
.id * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.hide * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,34 +1,22 @@
|
||||
<template>
|
||||
<div :class="'print-area ' + (isShow ? 'border' : '')" @dblclick="onDblClick">
|
||||
<div :class="'item ' + (isShow ? 'show' : 'hide')">
|
||||
<div class='left-content'>
|
||||
<img class='qrcode' src='\qrcode.png'>
|
||||
<div>长沙加零</div>
|
||||
<div>17373135368</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div>{{ config.cpu }}</div>
|
||||
<div>{{ config.memory }}</div>
|
||||
<div>{{ config.hardDisk }}</div>
|
||||
<div>{{ config.graphicsCard }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="id">
|
||||
<span>
|
||||
{{ id }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="left-content">
|
||||
<img class="qrcode" :src="logoUrl" alt="">
|
||||
<div>长沙加零</div>
|
||||
<div>17373135368</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div>{{ config.cpu }}</div>
|
||||
<div>{{ config.memory }}</div>
|
||||
<div>{{ config.hardDisk }}</div>
|
||||
<div>{{ config.graphicsCard }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
const isShow = ref(true);
|
||||
defineProps(['config']);
|
||||
|
||||
const props = defineProps(['id', 'config']);
|
||||
|
||||
const onDblClick = () => {
|
||||
isShow.value = !isShow.value;
|
||||
}
|
||||
// 二维码图片路径:运行时拼接,避免被打包器当作模块导入
|
||||
const logoUrl = '/qrcode.png';
|
||||
</script>
|
||||
|
||||
<script>
|
||||
@@ -80,119 +68,3 @@ export const initialValues = {
|
||||
graphicsCard: "1060(3,5,6)"
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.print-area {
|
||||
/* border: 0.1mm dashed #87CEFA; */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: flex-start;
|
||||
margin: 0.25mm;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.border {
|
||||
border: 0.1mm dashed #87CEFA;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41),
|
||||
.print-area:nth-child(42),
|
||||
.print-area:nth-child(43),
|
||||
.print-area:nth-child(44) {
|
||||
padding-bottom: 4mm;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41) .qrcode,
|
||||
.print-area:nth-child(42) .qrcode,
|
||||
.print-area:nth-child(43) .qrcode,
|
||||
.print-area:nth-child(44) .qrcode {
|
||||
height: calc(100% - 5mm);
|
||||
}
|
||||
|
||||
.item {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: red;
|
||||
font-size: 0.8cm;
|
||||
}
|
||||
|
||||
.reason,
|
||||
.time {
|
||||
font-size: 4mm;
|
||||
}
|
||||
|
||||
/*Styling below is used just for positioning the items inside the label. */
|
||||
|
||||
.left-content {
|
||||
width: 24mm;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: inherit;
|
||||
font-size: 3mm;
|
||||
line-height: 3mm;
|
||||
color: #87CEFA;
|
||||
}
|
||||
|
||||
.right-content {
|
||||
display: grid;
|
||||
font-size: 4mm;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
height: calc(100% - 6mm);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.id {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 2cm;
|
||||
pointer-events: none;
|
||||
color: #87CEFA60;
|
||||
}
|
||||
|
||||
.id span {
|
||||
/* display: block;
|
||||
margin: 0 auto; */
|
||||
}
|
||||
|
||||
@media print {
|
||||
.id * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.hide * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<div class="print-area"
|
||||
:class="{
|
||||
border: !!item?.name,
|
||||
placeholder: !item?.name,
|
||||
selected,
|
||||
hidden: !!item?.hidden
|
||||
}"
|
||||
:title="!item?.name ? '空格:单击选中后可编辑' : (item.hidden ? '已隐藏(双击恢复显示)' : '双击隐藏内容')"
|
||||
@click="emit('select')"
|
||||
@dblclick.prevent="emit('toggle-hide')">
|
||||
<div class="item" :class="item?.hidden ? 'hide' : 'show'">
|
||||
<slot v-if="item?.name"></slot>
|
||||
</div>
|
||||
<div class="id">
|
||||
<span>{{ index + 1 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
index: { type: Number, required: true },
|
||||
item: { type: Object, default: null },
|
||||
selected: { type: Boolean, default: false }
|
||||
});
|
||||
|
||||
const emit = defineEmits(['select', 'toggle-hide']);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* ====== 标签格子通用外观(含占位/选中/隐藏/打印控制) ====== */
|
||||
.print-area {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: flex-start;
|
||||
margin: 0.25mm;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
background: #fff;
|
||||
border: 0.1mm dashed transparent;
|
||||
transition: border-color 0.12s ease, box-shadow 0.12s ease, background-color 0.12s ease;
|
||||
}
|
||||
|
||||
/* 有内容的格子 */
|
||||
.border {
|
||||
border: 0.1mm dashed #87CEFA;
|
||||
}
|
||||
|
||||
/* 空格(屏幕上的编辑引导,打印时不会出现) */
|
||||
.placeholder {
|
||||
border: 0.1mm dashed #dde5ef;
|
||||
}
|
||||
|
||||
.print-area:hover {
|
||||
background-color: #f4f9ff;
|
||||
}
|
||||
|
||||
/* 选中高亮 */
|
||||
.selected {
|
||||
background-color: #eaf3ff;
|
||||
box-shadow: inset 0 0 0 1.6px #3b82f6;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.border.selected {
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
/* 已隐藏(内容不打印) */
|
||||
.print-area.hidden {
|
||||
background-image: repeating-linear-gradient(135deg, #fff 0 7mm, #f2f7fd 7mm 14mm);
|
||||
}
|
||||
|
||||
/* 格子序号角标(屏幕辅助,打印不输出) */
|
||||
.id {
|
||||
position: absolute;
|
||||
top: 0.6mm;
|
||||
right: 1.2mm;
|
||||
font-size: 3mm;
|
||||
line-height: 1;
|
||||
color: #a9b9cc;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.selected .id {
|
||||
color: #2563eb;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.print-area.hidden .id {
|
||||
color: #90a3b8;
|
||||
}
|
||||
|
||||
/* 隐藏/显示 */
|
||||
.hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* 最后一行的 4 个格子留出打印页脚空隙 */
|
||||
.print-area:nth-child(41),
|
||||
.print-area:nth-child(42),
|
||||
.print-area:nth-child(43),
|
||||
.print-area:nth-child(44) {
|
||||
padding-bottom: 4mm;
|
||||
}
|
||||
|
||||
.print-area:nth-child(41) .qrcode,
|
||||
.print-area:nth-child(42) .qrcode,
|
||||
.print-area:nth-child(43) .qrcode,
|
||||
.print-area:nth-child(44) .qrcode {
|
||||
height: calc(100% - 5mm);
|
||||
}
|
||||
|
||||
.item {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: red;
|
||||
font-size: 0.8cm;
|
||||
}
|
||||
|
||||
.reason,
|
||||
.time {
|
||||
font-size: 4mm;
|
||||
}
|
||||
|
||||
/* 规格类标签的内部左右布局 */
|
||||
.left-content {
|
||||
width: 24mm;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: inherit;
|
||||
font-size: 3mm;
|
||||
line-height: 3mm;
|
||||
color: #87CEFA;
|
||||
}
|
||||
|
||||
.right-content {
|
||||
display: grid;
|
||||
font-size: 4mm;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
height: calc(100% - 6mm);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media print {
|
||||
/* 序号角标与空格引导均不打印 */
|
||||
.id,
|
||||
.placeholder {
|
||||
visibility: hidden !important;
|
||||
border-color: transparent !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.print-area.hidden {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.print-area {
|
||||
box-shadow: none !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.hide * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.show {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+537
-124
@@ -1,40 +1,114 @@
|
||||
<template>
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-submit">
|
||||
<label>模板</label>
|
||||
<Select :data="people" v-model="selectedPerson"></Select>
|
||||
<Input type="number" title="数量" v-model="count"></Input>
|
||||
<Input type="number" title="上下边距" v-model="marginHeight"></Input>
|
||||
<Input type="number" title="左右边距" v-model="marginHorizontal"></Input>
|
||||
<button class="btn btn-primary" @click="onAdd">添加</button>
|
||||
<button class="btn btn-primary" @click="onPrint">打印</button>
|
||||
<button class="btn btn-primary" @click="onRest">清空</button>
|
||||
</div>
|
||||
<div class="layout">
|
||||
<!-- 顶部操作栏 -->
|
||||
<header class="topbar">
|
||||
<div class="brand">
|
||||
<span class="brand-icon">🏷️</span>
|
||||
<div>
|
||||
<div class="brand-title">标签打印</div>
|
||||
<div class="brand-sub">4 × 11 标签排版</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DynamicForm :key="selectedPerson.label" v-show="currentFormSchema" :formConfig="currentFormSchema"
|
||||
:initialData="currentConfig" @submit="updateConfig" />
|
||||
</div>
|
||||
<div class="page" :style="marginVars">
|
||||
<template v-for="(item, index) in data" :key="index">
|
||||
<component :is="getComponent(item)" :id="index" :config="item.config"></component>
|
||||
</template>
|
||||
<div class="toolbar">
|
||||
<div class="tool-group">
|
||||
<span class="tool-label">批量模板</span>
|
||||
<Select :data="people" v-model="selectedPerson" />
|
||||
</div>
|
||||
|
||||
<div class="tool-group">
|
||||
<span class="tool-label">打印设置</span>
|
||||
<div class="tool-inputs">
|
||||
<Input type="number" title="数量" v-model="count" />
|
||||
<Input type="number" title="上下边距" v-model="marginHeight" />
|
||||
<Input type="number" title="左右边距" v-model="marginHorizontal" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tool-buttons">
|
||||
<button class="btn btn-primary" :disabled="usedCount >= TOTAL" @click="onAddBatch">批量添加</button>
|
||||
<button class="btn btn-success" :disabled="usedCount === 0" @click="onPrint">打印</button>
|
||||
<button class="btn btn-ghost" :disabled="usedCount === 0" @click="onRest">清空全部</button>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<span class="chip chip-filled">已填 {{ usedCount }}</span>
|
||||
<span class="chip">剩余 {{ TOTAL - usedCount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 主体:左侧编辑面板 + 右侧标签纸 -->
|
||||
<div class="body">
|
||||
<aside class="panel">
|
||||
<!-- 批量填充 -->
|
||||
<section class="card">
|
||||
<h3><span>① 批量填充设置</span></h3>
|
||||
<p class="hint">编辑好模板内容后点「保存模板配置」,再点上方「批量添加」依次填入空格。</p>
|
||||
<DynamicForm :key="'batch-' + selectedPerson.label" :form-config="batchSchema"
|
||||
:initial-data="batchConfig" submit-text="保存模板配置" @submit="onBatchConfig" />
|
||||
</section>
|
||||
|
||||
<!-- 单格编辑 -->
|
||||
<section v-if="selectedIndex >= 0" class="card card-cell">
|
||||
<h3>
|
||||
<span>② 编辑第 {{ selectedIndex + 1 }} 格</span>
|
||||
<button class="btn btn-icon-only" title="关闭面板" @click="selectedIndex = -1">✕</button>
|
||||
</h3>
|
||||
<p v-if="!hasCellContent" class="hint">这是一个空格:选择模板 → 填内容 → 「填入该格」。</p>
|
||||
<p v-else class="hint">当前为「{{ selectedSlot.name }}」模板<template v-if="selectedSlot.hidden">,已隐藏不打印</template>。</p>
|
||||
|
||||
<div class="cell-template">
|
||||
<span class="tool-label">该格使用模板</span>
|
||||
<Select :data="people" v-model="cellTemplate" />
|
||||
</div>
|
||||
|
||||
<DynamicForm :key="cellFormKey" :form-config="cellSchema" :initial-data="cellInitial"
|
||||
:submit-text="hasCellContent ? '保存到该格' : '填入该格'" @submit="saveCell" />
|
||||
|
||||
<div v-if="hasCellContent" class="cell-actions">
|
||||
<button class="btn btn-ghost btn-sm" @click="toggleCellHidden">
|
||||
{{ selectedSlot.hidden ? '恢复显示' : '隐藏(不打印)' }}
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm" @click="clearCell">删除该格</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 未选中时的提示 -->
|
||||
<section v-else class="card card-tip">
|
||||
<h3><span>逐格编辑</span></h3>
|
||||
<ul class="tips">
|
||||
<li>单击任意<span class="hl">格子</span> → 编辑这一格的内容</li>
|
||||
<li>双击格子 → 隐藏 / 恢复内容(打印时留空)</li>
|
||||
<li>上方「批量添加」可一次填满后续空格</li>
|
||||
</ul>
|
||||
</section>
|
||||
</aside>
|
||||
|
||||
<!-- 标签纸 -->
|
||||
<main class="sheet">
|
||||
<div class="page">
|
||||
<LabelCell v-for="(slot, i) in slots" :key="i" :index="i" :item="slot"
|
||||
:selected="selectedIndex === i" @select="onCellSelect(i)" @toggle-hide="toggleHidden(i)">
|
||||
<component :is="templateMap.get(slot.name)" v-if="slot.name" :config="slot.config" />
|
||||
</LabelCell>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, watch, computed } from "vue";
|
||||
// 导入所有组件(确保路径正确)
|
||||
// import Disk_After_Sales from "./Disk_After_Sales.vue";
|
||||
import { computed, reactive, ref, watch } from "vue";
|
||||
|
||||
// 导入所有模板组件(default = 标签内容渲染组件,formSchema/initialValues = 表单配置)
|
||||
import Error, { formSchema as formSchema_Error, initialValues as initialValues_Error } from './Error.vue';
|
||||
import I5_10400F_16G_256G_1060, { formSchema as formSchema_I510400F, initialValues as initialValues_I510400F } from "./I5_10400F_16G_256G_1060.vue";
|
||||
import I5_10400F_16G_512G_2060, { formSchema as formSchema_I510400F2060, initialValues as initialValues_I510400F2060 } from './I5_10400F_16G_512G_2060.vue';
|
||||
import I5_10400F_16G_512G_2060, { formSchema as formSchema_I510400F2060, initialValues as initialValues_I510400F2060 } from "./I5_10400F_16G_512G_2060.vue";
|
||||
import I5_9400F_16G_256G_1060, { formSchema as formSchema_I59400F, initialValues as initialValues_I59400F } from "./I5_9400F_16G_256G_1060.vue";
|
||||
import I5_4590_8G_120G, { formSchema as formSchema_I54590, initialValues as initialValues_I54590 } from './I5_4590_8G_120G.vue';
|
||||
import I3_4130_8G_120G, { formSchema as formSchema_I34130, initialValues as initialValues_I34130 } from './I3_4130_8G_120G.vue';
|
||||
import G3260_8G_120G, {
|
||||
formSchema as formSchema_G3260,
|
||||
initialValues as initialValues_G3260
|
||||
} from './G3260_8G_120G.vue';
|
||||
import G3260_8G_120G, { formSchema as formSchema_G3260, initialValues as initialValues_G3260 } from './G3260_8G_120G.vue';
|
||||
import All_in_one_22_4G_120G, { formSchema as formSchema_22AllInOne, initialValues as initialValues_22AllInOne } from "./22寸一体机_4G_120G.vue";
|
||||
import All_in_one_24_8G_256G, { formSchema as formSchema_24AllInOne, initialValues as initialValues_24AllInOne } from "./24寸一体机_I5_3代_8G_256G.vue";
|
||||
import HardDisk, { formSchema as formSchema_HardDisk, initialValues as initialValues_HardDisk } from "./HardDisk.vue";
|
||||
@@ -43,132 +117,206 @@ import CustomTag, { formSchema as formSchema_CustomTag, initialValues as initial
|
||||
import DynamicForm from './DynamicForm.vue';
|
||||
import Input from './Input/Index.vue';
|
||||
import Select from './Select/index.vue';
|
||||
import LabelCell from './LabelCell.vue';
|
||||
|
||||
// 修复1: 确保所有配置都有表单数据
|
||||
const people = reactive([
|
||||
// 深拷贝:避免编辑表单 / 快照进格子时共享引用
|
||||
const clone = (obj) => JSON.parse(JSON.stringify(obj));
|
||||
|
||||
// 模板注册表:新增模板只需在数组中加一项
|
||||
const templates = [
|
||||
{
|
||||
label: 'I5_10400F_16G_256G_1060',
|
||||
component: I5_10400F_16G_256G_1060,
|
||||
formSchema: formSchema_I510400F,
|
||||
formData: initialValues_I510400F
|
||||
},
|
||||
{
|
||||
label: 'I5_10400F_16G_256G_2060',
|
||||
component: I5_10400F_16G_512G_2060,
|
||||
formSchema: formSchema_I510400F2060,
|
||||
formData: initialValues_I510400F2060
|
||||
},
|
||||
{
|
||||
label: 'I5_9400F_16G_256G_1060',
|
||||
component: I5_9400F_16G_256G_1060,
|
||||
formSchema: formSchema_I59400F,
|
||||
formData: initialValues_I59400F
|
||||
},
|
||||
{
|
||||
label: 'I5_4590_8G_120G',
|
||||
component: I5_4590_8G_120G,
|
||||
formSchema: formSchema_I54590,
|
||||
formData: initialValues_I54590
|
||||
},
|
||||
{
|
||||
label: 'I3_4130_8G_120G',
|
||||
component: I3_4130_8G_120G,
|
||||
formSchema: formSchema_I34130,
|
||||
formData: initialValues_I34130
|
||||
},
|
||||
{
|
||||
label: 'G3260_8G_120G',
|
||||
component: G3260_8G_120G,
|
||||
formSchema: formSchema_G3260,
|
||||
formData: initialValues_G3260
|
||||
},
|
||||
{
|
||||
label: '24寸一体机_I5_3代_8G_256G',
|
||||
component: All_in_one_24_8G_256G,
|
||||
formSchema: formSchema_24AllInOne,
|
||||
formData: initialValues_24AllInOne
|
||||
},
|
||||
{
|
||||
label: '22寸一体机_4G_120G',
|
||||
component: All_in_one_22_4G_120G,
|
||||
formSchema: formSchema_22AllInOne,
|
||||
formData: initialValues_22AllInOne
|
||||
},
|
||||
{
|
||||
label: 'HardDisk',
|
||||
component: HardDisk,
|
||||
formSchema: formSchema_HardDisk,
|
||||
formData: initialValues_HardDisk
|
||||
},
|
||||
{
|
||||
label: 'Error',
|
||||
component: Error,
|
||||
formSchema: formSchema_Error,
|
||||
formData: initialValues_Error
|
||||
},
|
||||
{
|
||||
label: 'CustomTag',
|
||||
component: CustomTag,
|
||||
formSchema: formSchema_CustomTag,
|
||||
formData: initialValues_CustomTag
|
||||
},
|
||||
]);
|
||||
}
|
||||
];
|
||||
|
||||
// 修复2: 初始化选中项为G3260配置
|
||||
// 下拉数据与名称 → 渲染组件映射
|
||||
const people = reactive(
|
||||
templates.map(({ label, formSchema, formData }) => ({ label, formSchema, formData }))
|
||||
);
|
||||
const templateMap = new Map(templates.map(({ label, component }) => [label, component]));
|
||||
|
||||
// ---------- 固定 44 个格子(每格可单独编辑) ----------
|
||||
const TOTAL = 44;
|
||||
const emptySlot = () => ({ name: null, config: null, hidden: false });
|
||||
const slots = ref(Array.from({ length: TOTAL }, emptySlot));
|
||||
|
||||
const usedCount = computed(() => slots.value.filter((s) => s.name).length);
|
||||
|
||||
// 当前选中的格子(-1 = 未选中)
|
||||
const selectedIndex = ref(-1);
|
||||
const selectedSlot = computed(() =>
|
||||
selectedIndex.value >= 0 ? slots.value[selectedIndex.value] : null
|
||||
);
|
||||
const hasCellContent = computed(() => !!selectedSlot.value?.name);
|
||||
|
||||
// ---------- 批量填充(沿用原有语义) ----------
|
||||
const selectedPerson = ref(people[0]);
|
||||
const data = ref([]);
|
||||
// 打印数量
|
||||
const count = ref(1);
|
||||
// 上下边距
|
||||
const marginHeight = ref(0.89);
|
||||
// 左右边距
|
||||
const marginHorizontal = ref(0.8);
|
||||
const currentConfig = ref({ ...initialValues_G3260 });
|
||||
const currentFormSchema = ref(formSchema_G3260);
|
||||
// 打印样式表引用
|
||||
let printStyle = null;
|
||||
const batchSchema = ref(people[0].formSchema);
|
||||
const batchConfig = ref(clone(people[0].formData));
|
||||
|
||||
// 计算样式变量
|
||||
const marginVars = computed(() => ({
|
||||
'--margin-height': `${marginHeight.value}cm`,
|
||||
'--margin-horizontal': `${marginHorizontal.value}cm`
|
||||
}));
|
||||
|
||||
// 修复3: 正确的watch处理
|
||||
watch(selectedPerson, (newVal) => {
|
||||
currentConfig.value = { ...newVal.formData };
|
||||
currentFormSchema.value = newVal.formSchema;
|
||||
watch(selectedPerson, (nv) => {
|
||||
batchSchema.value = nv.formSchema;
|
||||
batchConfig.value = clone(nv.formData);
|
||||
}, { immediate: true });
|
||||
|
||||
const getComponent = (item) => {
|
||||
switch (item.name) {
|
||||
case 'I5_10400F_16G_256G_1060': return I5_10400F_16G_256G_1060;
|
||||
case 'I5_10400F_16G_512G_2060': return I5_10400F_16G_512G_2060;
|
||||
case 'I5_9400F_16G_256G_1060': return I5_9400F_16G_256G_1060;
|
||||
case 'I5_4590_8G_120G': return I5_4590_8G_120G;
|
||||
case 'I3_4130_8G_120G': return I3_4130_8G_120G;
|
||||
case 'G3260_8G_120G': return G3260_8G_120G;
|
||||
case '24寸一体机_I5_3代_8G_256G': return All_in_one_24_8G_256G;
|
||||
case '22寸一体机_4G_120G': return All_in_one_22_4G_120G;
|
||||
case 'Error': return Error;
|
||||
case 'HardDisk': return HardDisk;
|
||||
case 'CustomTag': return CustomTag;
|
||||
default: return Error;
|
||||
// 打印数量 / 边距
|
||||
const count = ref(1);
|
||||
const marginHeight = ref(0.89);
|
||||
const marginHorizontal = ref(0.8);
|
||||
let printStyle = null;
|
||||
|
||||
// ---------- 单格编辑 ----------
|
||||
const cellTemplate = ref(people[0]); // 该格当前使用的模板
|
||||
const cellRev = ref(0); // 保存后递增,用于刷新表单
|
||||
|
||||
const cellSchema = computed(() => cellTemplate.value?.formSchema || people[0].formSchema);
|
||||
const cellInitial = computed(() => {
|
||||
const slot = selectedSlot.value;
|
||||
const tpl = cellTemplate.value;
|
||||
if (!tpl) return {};
|
||||
// 已有内容且模板未变 → 回填该格当前配置;否则显示该模板默认值
|
||||
if (slot?.name && slot.name === tpl.label) return clone(slot.config ?? tpl.formData);
|
||||
return clone(tpl.formData);
|
||||
});
|
||||
const cellFormKey = computed(() =>
|
||||
`${selectedIndex.value}|${cellTemplate.value?.label || ''}|${cellRev.value}`
|
||||
);
|
||||
|
||||
// 单击选择格子;再次单击取消选择
|
||||
const onCellSelect = (i) => {
|
||||
if (selectedIndex.value === i) {
|
||||
selectedIndex.value = -1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
selectedIndex.value = i;
|
||||
const slot = slots.value[i];
|
||||
// 有内容的格子:自动把“该格模板”切到它的模板,方便直接修改
|
||||
if (slot?.name) {
|
||||
const tpl = people.find((p) => p.label === slot.name);
|
||||
if (tpl) cellTemplate.value = tpl;
|
||||
}
|
||||
};
|
||||
|
||||
const onAdd = () => {
|
||||
const maxItems = 44;
|
||||
const availableSlots = maxItems - data.value.length;
|
||||
const addCount = Math.min(count.value, availableSlots);
|
||||
// 双击:隐藏/恢复该格内容(打印时留空,保留格子边框)
|
||||
const toggleHidden = (i) => {
|
||||
const slot = slots.value[i];
|
||||
if (!slot?.name) return;
|
||||
slot.hidden = !slot.hidden;
|
||||
};
|
||||
|
||||
if (addCount <= 0) return;
|
||||
// 单格表单提交:填入(空格)或保存(有内容)
|
||||
const saveCell = (cfg) => {
|
||||
const i = selectedIndex.value;
|
||||
if (i < 0) return;
|
||||
const tpl = cellTemplate.value;
|
||||
slots.value[i] = {
|
||||
name: tpl.label,
|
||||
config: clone(cfg),
|
||||
hidden: slots.value[i]?.hidden || false
|
||||
};
|
||||
cellRev.value++;
|
||||
};
|
||||
|
||||
const newItems = Array.from({ length: addCount }, () => ({
|
||||
name: selectedPerson.value.label,
|
||||
config: { ...currentConfig.value }
|
||||
}));
|
||||
const clearCell = () => {
|
||||
const i = selectedIndex.value;
|
||||
if (i < 0) return;
|
||||
slots.value[i] = emptySlot();
|
||||
cellRev.value++;
|
||||
};
|
||||
|
||||
const toggleCellHidden = () => {
|
||||
const slot = selectedSlot.value;
|
||||
if (slot) slot.hidden = !slot.hidden;
|
||||
};
|
||||
|
||||
// ---------- 批量操作 ----------
|
||||
const onBatchConfig = (cfg) => {
|
||||
batchConfig.value = clone(cfg);
|
||||
};
|
||||
|
||||
const onAddBatch = () => {
|
||||
const n = Math.min(parseInt(count.value, 10) || 1, TOTAL - usedCount.value);
|
||||
if (n <= 0) return;
|
||||
const tpl = selectedPerson.value;
|
||||
let placed = 0;
|
||||
slots.value = slots.value.map((s) => {
|
||||
if (placed < n && !s.name) {
|
||||
placed++;
|
||||
return { name: tpl.label, config: clone(batchConfig.value), hidden: false };
|
||||
}
|
||||
return s;
|
||||
});
|
||||
};
|
||||
|
||||
data.value.push(...newItems);
|
||||
}
|
||||
// 重置打印网格
|
||||
const onRest = () => {
|
||||
data.value = [];
|
||||
}
|
||||
|
||||
const updateConfig = (newConfig) => {
|
||||
currentConfig.value = newConfig;
|
||||
}
|
||||
slots.value = Array.from({ length: TOTAL }, emptySlot);
|
||||
selectedIndex.value = -1;
|
||||
};
|
||||
|
||||
// ---------- 打印 ----------
|
||||
const onPrint = () => {
|
||||
applyPrintStyles();
|
||||
if (window?.electronAPI?.silentPrint) {
|
||||
@@ -176,42 +324,308 @@ const onPrint = () => {
|
||||
} else {
|
||||
window.print();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 应用打印样式
|
||||
const applyPrintStyles = () => {
|
||||
// 移除旧的打印样式
|
||||
if (printStyle) {
|
||||
document.head.removeChild(printStyle);
|
||||
}
|
||||
|
||||
// 创建新的打印样式
|
||||
printStyle = document.createElement('style');
|
||||
printStyle.setAttribute('media', 'print');
|
||||
printStyle.textContent = `
|
||||
@page {
|
||||
margin: ${marginHeight.value}cm ${marginHorizontal.value}cm 0;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
body { margin: 0; padding: 0; }
|
||||
}
|
||||
`;
|
||||
|
||||
document.head.appendChild(printStyle);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 原有样式保持不变 */
|
||||
/* ================= 全局页面布局 ================= */
|
||||
html,
|
||||
body {
|
||||
max-width: none !important;
|
||||
margin: 0 !important;
|
||||
padding: 0rem !important;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "PingFang SC", "Microsoft YaHei", "Segoe UI", system-ui, sans-serif;
|
||||
background: #e9eef5;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
padding: 16px;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ---------------- 顶部操作栏 ---------------- */
|
||||
.topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 22px;
|
||||
flex-wrap: wrap;
|
||||
background: #ffffff;
|
||||
border-radius: 14px;
|
||||
padding: 14px 18px;
|
||||
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.07);
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.brand-icon {
|
||||
font-size: 26px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.brand-title {
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.brand-sub {
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 18px;
|
||||
flex-wrap: wrap;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tool-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.tool-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.tool-inputs {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tool-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
/* 顶栏与面板内的输入框更紧凑 */
|
||||
.topbar .input-group,
|
||||
.panel .input-group {
|
||||
margin-bottom: 0;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.topbar .input-group input,
|
||||
.panel .input-group input {
|
||||
width: 118px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.topbar .input-group i {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.topbar .input-group .floating-label,
|
||||
.panel .input-group .floating-label {
|
||||
left: 12px;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-left: auto;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chip {
|
||||
background: #f1f5f9;
|
||||
color: #475569;
|
||||
font-size: 12.5px;
|
||||
font-weight: 600;
|
||||
padding: 5px 12px;
|
||||
border-radius: 999px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chip-filled {
|
||||
background: #dbeafe;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
/* ---------------- 主体:左面板 + 右纸面 ---------------- */
|
||||
.body {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.panel {
|
||||
width: 356px;
|
||||
flex: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
position: sticky;
|
||||
top: 12px;
|
||||
max-height: calc(100vh - 24px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
padding: 16px 18px;
|
||||
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 0 0 8px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin: 0 0 12px;
|
||||
font-size: 12.5px;
|
||||
color: #94a3b8;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.tips {
|
||||
margin: 6px 0 0;
|
||||
padding-left: 18px;
|
||||
font-size: 13px;
|
||||
line-height: 2;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.tips .hl {
|
||||
color: #2563eb;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.cell-template {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.cell-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* ---------------- 按钮 ---------------- */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 9px;
|
||||
font-size: 13.5px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #2563eb;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background: #1d4ed8;
|
||||
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.35);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #16a34a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-success:hover:not(:disabled) {
|
||||
background: #15803d;
|
||||
box-shadow: 0 4px 12px rgba(22, 163, 74, 0.3);
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
background: #f1f5f9;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.btn-ghost:hover:not(:disabled) {
|
||||
background: #e2e8f0;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #fee2e2;
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.btn-danger:hover:not(:disabled) {
|
||||
background: #fecaca;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 5px 11px;
|
||||
font-size: 12.5px;
|
||||
}
|
||||
|
||||
.btn-icon-only {
|
||||
padding: 2px 8px;
|
||||
background: #f1f5f9;
|
||||
color: #64748b;
|
||||
border-radius: 7px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.btn-icon-only:hover {
|
||||
background: #e2e8f0;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
/* ---------------- 标签纸(屏幕预览) ---------------- */
|
||||
.sheet {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.page {
|
||||
@@ -220,39 +634,37 @@ body {
|
||||
grid-template-columns: repeat(4, 48.5mm);
|
||||
grid-template-rows: repeat(11, 25.4mm);
|
||||
gap: 0;
|
||||
|
||||
/* margin: var(--margin-height) var(--margin-horizontal) 0; */
|
||||
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.toolbar .toolbar-submit {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.toolbar-submit button.btn:nth-of-type(n+2) {
|
||||
margin-left: 20px;
|
||||
background: #fff;
|
||||
border-radius: 3mm;
|
||||
box-shadow: 0 8px 26px rgba(15, 23, 42, 0.16);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ================= 打印 ================= */
|
||||
@media print {
|
||||
|
||||
html,
|
||||
body {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
background: #fff !important;
|
||||
}
|
||||
|
||||
body * {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: none;
|
||||
.layout,
|
||||
.body,
|
||||
.sheet {
|
||||
display: block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.topbar,
|
||||
.panel {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.page,
|
||||
@@ -263,12 +675,13 @@ body {
|
||||
.page {
|
||||
width: 194mm;
|
||||
height: auto;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
@page {
|
||||
/* margin: var(--margin-height) var(--margin-horizontal) 0; */
|
||||
margin: 0.89cm 0.80cm 0;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -47,13 +47,21 @@ import {
|
||||
} from '@headlessui/vue'
|
||||
// import { CheckIcon, ChevronUpDownIcon } from '@heroicons/vue/20/solid'
|
||||
|
||||
const props = defineProps(["data"]);
|
||||
const selectedPerson = ref(props.data?.length > 0 ? props.data[0] : null);
|
||||
const props = defineProps(["data", "modelValue"]);
|
||||
const selectedPerson = ref(props.modelValue ?? (props.data?.length ? props.data[0] : null));
|
||||
|
||||
const emits = defineEmits(["update:modelValue"]);
|
||||
|
||||
watch([selectedPerson], ([newSelected]) => {
|
||||
emits("update:modelValue", newSelected);
|
||||
// 外部值变化时同步内部选中项(如“单格编辑”切换模板)
|
||||
watch(() => props.modelValue, (val) => {
|
||||
if (val) selectedPerson.value = val;
|
||||
});
|
||||
|
||||
// 内部选中变化时通知父组件
|
||||
watch(selectedPerson, (newSelected) => {
|
||||
if (newSelected !== props.modelValue) {
|
||||
emits("update:modelValue", newSelected);
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style>
|
||||
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
@import url("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css");
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import url("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css");
|
||||
|
||||
|
||||
/* 基础按钮样式 */
|
||||
|
||||
Reference in New Issue
Block a user