1 Commits
Author SHA1 Message Date
funjia 1896d1032b feature: 使用deepseek进行了代码和页面优化 2026-09-05 00:07:47 +08:00
17 changed files with 1255 additions and 1699 deletions
+114 -3
View File
@@ -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` 控制,屏幕上的一切编辑辅助元素均不进入打印。
+15 -143
View File
@@ -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>
+15 -143
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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>
+14 -142
View File
@@ -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
View File
@@ -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>
+14 -142
View File
@@ -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>
+14 -142
View File
@@ -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>
+14 -142
View File
@@ -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>
+14 -142
View File
@@ -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>
+14 -142
View File
@@ -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>
+191
View File
@@ -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
View File
@@ -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>
+12 -4
View File
@@ -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
View File
@@ -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");
/* 基础按钮样式 */