|
|
|
<template>
|
|
|
|
<div class="bg-gray-50" style="min-height: calc(100vh - 45px)">
|
|
|
|
<div class="container mx-auto px-4 py-8">
|
|
|
|
<div class="flex gap-6">
|
|
|
|
<!-- 左侧工具栏 -->
|
|
|
|
<div class="w-80 bg-white rounded-lg shadow-lg p-6 space-y-6">
|
|
|
|
<div>
|
|
|
|
<h3 class="text-lg font-medium mb-4">笔触样式</h3>
|
|
|
|
<div class="grid grid-cols-3 gap-3">
|
|
|
|
<button
|
|
|
|
v-for="(style, index) in penStyles"
|
|
|
|
:key="index"
|
|
|
|
:class="{ 'bg-blue-500 text-white': currentPenStyle === style.value }"
|
|
|
|
class="p-3 rounded-lg border !rounded-button whitespace-nowrap"
|
|
|
|
@click="setPenStyle(style.value)"
|
|
|
|
>
|
|
|
|
<el-icon class="text-lg mb-1"><component :is="style.icon" /></el-icon>
|
|
|
|
<div class="text-sm">{{ style.name }}</div>
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
<h3 class="text-lg font-medium mb-4">颜色选择</h3>
|
|
|
|
<div class="grid grid-cols-6 gap-2">
|
|
|
|
<div
|
|
|
|
v-for="color in colors"
|
|
|
|
:key="color"
|
|
|
|
class="w-8 h-8 rounded-full cursor-pointer border-2"
|
|
|
|
:style="{
|
|
|
|
backgroundColor: color,
|
|
|
|
borderColor: currentColor === color ? '#3B82F6' : 'transparent'
|
|
|
|
}"
|
|
|
|
@click="setColor(color)"
|
|
|
|
></div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
<h3 class="text-lg font-medium mb-4">线条粗细</h3>
|
|
|
|
<el-slider v-model="lineWidth" :min="1" :max="20" :step="1" />
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
<h3 class="text-lg font-medium mb-4">特效设置</h3>
|
|
|
|
<div class="space-y-4">
|
|
|
|
<div>
|
|
|
|
<div class="flex justify-between mb-2">
|
|
|
|
<span>阴影</span>
|
|
|
|
<span>{{ shadow }}px</span>
|
|
|
|
</div>
|
|
|
|
<el-slider v-model="shadow" :min="0" :max="20" :step="1" />
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<div class="flex justify-between mb-2">
|
|
|
|
<span>模糊度</span>
|
|
|
|
<span>{{ blur }}px</span>
|
|
|
|
</div>
|
|
|
|
<el-slider v-model="blur" :min="0" :max="20" :step="1" />
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<div class="flex justify-between mb-2">
|
|
|
|
<span>透明度</span>
|
|
|
|
<span>{{ opacity }}%</span>
|
|
|
|
</div>
|
|
|
|
<el-slider v-model="opacity" :min="0" :max="100" :step="1" />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- 右侧画板区域 -->
|
|
|
|
<div class="flex-1">
|
|
|
|
<div class="bg-white rounded-lg shadow-lg p-6">
|
|
|
|
<!-- 顶部工具栏 -->
|
|
|
|
<div class="flex justify-between items-center mb-6">
|
|
|
|
<div class="flex gap-3">
|
|
|
|
<button
|
|
|
|
class="flex items-center px-4 py-2 border rounded !rounded-button whitespace-nowrap"
|
|
|
|
@click="openServer(true)"
|
|
|
|
>
|
|
|
|
<el-icon class="mr-2"><Edit /></el-icon>填写地址
|
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
class="flex items-center px-4 py-2 border rounded !rounded-button whitespace-nowrap"
|
|
|
|
@click="printImage"
|
|
|
|
>
|
|
|
|
<el-icon class="mr-2"><TakeawayBox /></el-icon>打印图片
|
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
class="flex items-center px-4 py-2 border rounded !rounded-button whitespace-nowrap"
|
|
|
|
@click="clearCanvas"
|
|
|
|
>
|
|
|
|
<el-icon class="mr-2"><CloseBold /></el-icon>清除画板
|
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
class="flex items-center px-4 py-2 border rounded !rounded-button whitespace-nowrap"
|
|
|
|
@click="clearPhotoCanvas"
|
|
|
|
>
|
|
|
|
<el-icon class="mr-2"><Delete /></el-icon>清除签名
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<div class="flex gap-3">
|
|
|
|
<button
|
|
|
|
class="flex items-center px-4 py-2 border rounded !rounded-button whitespace-nowrap"
|
|
|
|
@click="uploadSignature"
|
|
|
|
>
|
|
|
|
<el-icon class="mr-2"><Upload /></el-icon>上传
|
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
class="flex items-center px-4 py-2 border rounded !rounded-button whitespace-nowrap"
|
|
|
|
@click="photograph"
|
|
|
|
>
|
|
|
|
<el-icon class="mr-2"><CameraFilled /></el-icon>拍照 / 签名
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<h2 class="title">照相</h2>
|
|
|
|
<div class="flex items-center">
|
|
|
|
<div
|
|
|
|
v-show="!isCamera"
|
|
|
|
class="w-1/2 min-h-80 h-auto flex items-center justify-center text-[100px] border border-solid border-[#eee]"
|
|
|
|
>
|
|
|
|
<el-icon @click="openCamera"><CameraFilled /></el-icon>
|
|
|
|
</div>
|
|
|
|
<video
|
|
|
|
v-show="isCamera"
|
|
|
|
ref="videoRef"
|
|
|
|
class="w-1/2 min-h-80 h-auto"
|
|
|
|
autoplay
|
|
|
|
></video>
|
|
|
|
<canvas id="photoCanvas" ref="photoCanvas" class="w-1/2 h-auto min-h-80"></canvas>
|
|
|
|
</div>
|
|
|
|
<!-- 签名画板 -->
|
|
|
|
<h2 class="title">签名</h2>
|
|
|
|
<div class="relative border rounded-lg" style="height: 30vh">
|
|
|
|
<canvas
|
|
|
|
ref="canvas"
|
|
|
|
class="w-full h-full"
|
|
|
|
:style="{ filter: `blur(${blur}px)`, opacity: opacity / 100 }"
|
|
|
|
@mousedown="startDrawing"
|
|
|
|
@mousemove="draw"
|
|
|
|
@mouseup="stopDrawing"
|
|
|
|
@mouseleave="stopDrawing"
|
|
|
|
@touchstart="startDrawing"
|
|
|
|
@touchmove="draw"
|
|
|
|
@touchend="stopDrawing"
|
|
|
|
>
|
|
|
|
</canvas>
|
|
|
|
<div
|
|
|
|
class="absolute inset-0 pointer-events-none"
|
|
|
|
:style="{ boxShadow: `0 0 ${shadow}px rgba(0,0,0,0.2)` }"
|
|
|
|
></div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
import { ref, onMounted, getCurrentInstance } from 'vue'
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
import { Upload, CloseBold, Edit, Delete, CameraFilled, TakeawayBox } from '@element-plus/icons-vue'
|
|
|
|
import { uploadImage } from '@/utils/base64toFormData'
|
|
|
|
const { proxy } = getCurrentInstance()
|
|
|
|
const isCamera = ref(false)
|
|
|
|
const canvas = ref(null)
|
|
|
|
const photoCanvas = ref(null)
|
|
|
|
const videoRef = ref(null)
|
|
|
|
const ctx = ref(null)
|
|
|
|
const photoCtx = ref(null)
|
|
|
|
const isDrawing = ref(false)
|
|
|
|
const lastX = ref(0) // 上一次的X坐标
|
|
|
|
const lastY = ref(0) // 上一次Y坐标
|
|
|
|
const scaleFactor = 2 // 2倍分辨率 (可设置为3或更高)
|
|
|
|
// 初始画布值
|
|
|
|
const currentPenStyle = ref('normal')
|
|
|
|
const currentColor = ref('#000000')
|
|
|
|
const lineWidth = ref(2)
|
|
|
|
const shadow = ref(0)
|
|
|
|
const blur = ref(0)
|
|
|
|
const opacity = ref(100)
|
|
|
|
const isOpenServer = ref(false)
|
|
|
|
|
|
|
|
// 笔触样式
|
|
|
|
const penStyles = [
|
|
|
|
{ name: '钢笔', value: 'normal', icon: 'Edit' },
|
|
|
|
{ name: '毛笔', value: 'brush', icon: 'Brush' },
|
|
|
|
{ name: '马克笔', value: 'marker', icon: 'Stamp' },
|
|
|
|
{ name: '橡皮擦', value: 'eraser', icon: 'Delete' },
|
|
|
|
{ name: '水彩笔', value: 'watercolor', icon: 'EditPen' }
|
|
|
|
]
|
|
|
|
|
|
|
|
// 颜色列表选择
|
|
|
|
const colors = [
|
|
|
|
'#000000',
|
|
|
|
'#FF0000',
|
|
|
|
'#00FF00',
|
|
|
|
'#0000FF',
|
|
|
|
'#FFFF00',
|
|
|
|
'#FF00FF',
|
|
|
|
'#00FFFF',
|
|
|
|
'#808080',
|
|
|
|
'#C0C0C0',
|
|
|
|
'#800000',
|
|
|
|
'#808000',
|
|
|
|
'#008000'
|
|
|
|
]
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
if (canvas.value) {
|
|
|
|
ctx.value = canvas.value.getContext('2d')
|
|
|
|
photoCtx.value = photoCanvas.value.getContext('2d')
|
|
|
|
resizeCanvas()
|
|
|
|
window.addEventListener('resize', resizeCanvas)
|
|
|
|
}
|
|
|
|
if (!localStorage.getItem('crgx_server')) {
|
|
|
|
openServer(true)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
// 打开相机
|
|
|
|
const openCamera = () => {
|
|
|
|
isCamera.value = true
|
|
|
|
if (videoRef.value != null) {
|
|
|
|
// 1. 请求摄像头访问权限
|
|
|
|
navigator.mediaDevices
|
|
|
|
.getUserMedia({ video: true })
|
|
|
|
.then((stream) => {
|
|
|
|
videoRef.value.srcObject = stream
|
|
|
|
})
|
|
|
|
.catch((err) => {
|
|
|
|
console.error('摄像头访问失败:', err)
|
|
|
|
proxy.$message.error('无法访问摄像头,请检查权限设置')
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const resizeCanvas = () => {
|
|
|
|
if (canvas.value && ctx.value) {
|
|
|
|
const displayWidth = canvas.value.offsetWidth
|
|
|
|
const displayHeight = canvas.value.offsetHeight
|
|
|
|
|
|
|
|
// 设置实际尺寸为显示尺寸的scaleFactor倍
|
|
|
|
canvas.value.width = displayWidth * scaleFactor
|
|
|
|
canvas.value.height = displayHeight * scaleFactor
|
|
|
|
|
|
|
|
// 设置CSS尺寸保持显示大小不变
|
|
|
|
canvas.value.style.width = `${displayWidth}px`
|
|
|
|
canvas.value.style.height = `${displayHeight}px`
|
|
|
|
|
|
|
|
// 缩放绘图上下文以匹配高分辨率
|
|
|
|
ctx.value.scale(scaleFactor, scaleFactor)
|
|
|
|
ctx.value.lineCap = 'round'
|
|
|
|
ctx.value.lineJoin = 'round'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const startDrawing = (e) => {
|
|
|
|
isDrawing.value = true
|
|
|
|
const pos = getPosition(e)
|
|
|
|
lastX.value = pos.x
|
|
|
|
lastY.value = pos.y
|
|
|
|
}
|
|
|
|
// 画
|
|
|
|
const draw = (e) => {
|
|
|
|
if (!isDrawing.value || !ctx.value) return
|
|
|
|
e.preventDefault()
|
|
|
|
|
|
|
|
const pos = getPosition(e)
|
|
|
|
ctx.value.beginPath()
|
|
|
|
ctx.value.moveTo(lastX.value, lastY.value)
|
|
|
|
ctx.value.lineTo(pos.x, pos.y)
|
|
|
|
ctx.value.strokeStyle = currentColor.value
|
|
|
|
ctx.value.lineWidth = lineWidth.value
|
|
|
|
ctx.value.stroke()
|
|
|
|
|
|
|
|
lastX.value = pos.x
|
|
|
|
lastY.value = pos.y
|
|
|
|
}
|
|
|
|
|
|
|
|
// 停止绘画
|
|
|
|
const stopDrawing = () => {
|
|
|
|
isDrawing.value = false
|
|
|
|
}
|
|
|
|
|
|
|
|
// 获取鼠标位置
|
|
|
|
const getPosition = (e) => {
|
|
|
|
if (!canvas.value) return { x: 0, y: 0 }
|
|
|
|
|
|
|
|
const rect = canvas.value.getBoundingClientRect()
|
|
|
|
if (e instanceof MouseEvent) {
|
|
|
|
return {
|
|
|
|
x: e.clientX - rect.left,
|
|
|
|
y: e.clientY - rect.top
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const touch = e.touches[0]
|
|
|
|
return {
|
|
|
|
x: touch.clientX - rect.left,
|
|
|
|
y: touch.clientY - rect.top
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 设置笔触样式
|
|
|
|
const setPenStyle = (style) => {
|
|
|
|
currentPenStyle.value = style
|
|
|
|
if (!ctx.value) return
|
|
|
|
if (style === 'normal') {
|
|
|
|
lineWidth.value = 2
|
|
|
|
ctx.value.lineCap = 'round'
|
|
|
|
ctx.value.lineJoin = 'round'
|
|
|
|
ctx.value.globalCompositeOperation = 'source-over'
|
|
|
|
ctx.value.shadowBlur = 0
|
|
|
|
ctx.value.globalAlpha = 1.0
|
|
|
|
} else if (style === 'brush') {
|
|
|
|
lineWidth.value = 10
|
|
|
|
ctx.value.lineCap = 'round'
|
|
|
|
ctx.value.lineJoin = 'round'
|
|
|
|
ctx.value.globalCompositeOperation = 'multiply'
|
|
|
|
ctx.value.shadowBlur = 5
|
|
|
|
ctx.value.globalAlpha = 0.7
|
|
|
|
ctx.value.shadowColor = 'rgba(0,0,0,0.3)'
|
|
|
|
} else if (style === 'marker') {
|
|
|
|
ctx.value.strokeStyle = 'rgba(0,200,100,0.5)' // 半透明颜色
|
|
|
|
lineWidth.value = 15
|
|
|
|
ctx.value.lineCap = 'square'
|
|
|
|
ctx.value.lineJoin = 'miter'
|
|
|
|
ctx.value.globalCompositeOperation = 'multiply' // 模拟马克笔叠加效果
|
|
|
|
ctx.value.shadowBlur = 0
|
|
|
|
ctx.value.globalAlpha = 0.6
|
|
|
|
} else if (style === 'eraser') {
|
|
|
|
lineWidth.value = 20
|
|
|
|
ctx.value.lineCap = 'round'
|
|
|
|
ctx.value.lineJoin = 'round'
|
|
|
|
ctx.value.globalCompositeOperation = 'destination-out'
|
|
|
|
ctx.value.shadowBlur = 0
|
|
|
|
ctx.value.globalAlpha = 1.0
|
|
|
|
} else if (style === 'watercolor') {
|
|
|
|
lineWidth.value = 20
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const setColor = (color) => {
|
|
|
|
currentColor.value = color
|
|
|
|
}
|
|
|
|
const clearCanvas = () => {
|
|
|
|
if (ctx.value && canvas.value) {
|
|
|
|
ctx.value.clearRect(0, 0, canvas.value.width, canvas.value.height)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const clearPhotoCanvas = () => {
|
|
|
|
if (photoCtx.value && photoCanvas.value) {
|
|
|
|
photoCtx.value.clearRect(0, 0, photoCanvas.value.width, photoCanvas.value.height)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 判断画布是否为空
|
|
|
|
function isCanvasEmpty(canvasValue) {
|
|
|
|
const canvasModule = canvasValue
|
|
|
|
const ctx = canvasModule.getContext('2d')
|
|
|
|
const imageData = ctx.getImageData(0, 0, canvasModule.width, canvasModule.height)
|
|
|
|
const data = imageData.data
|
|
|
|
|
|
|
|
for (let i = 3; i < data.length; i += 4) {
|
|
|
|
if (data[i] !== 0) return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
// 是否打开填写服务器地址
|
|
|
|
const openServer = (flag) => {
|
|
|
|
const server = localStorage.getItem('crgx_server')
|
|
|
|
isOpenServer.value = flag
|
|
|
|
if (isOpenServer.value) {
|
|
|
|
ElMessageBox.prompt('请填写服务器地址以便正常使用', '提示', {
|
|
|
|
confirmButtonText: '确认',
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
inputValue: server,
|
|
|
|
inputPattern: /^http/i,
|
|
|
|
inputErrorMessage: '请正常填写地址'
|
|
|
|
})
|
|
|
|
.then(({ value }) => {
|
|
|
|
localStorage.setItem('crgx_server', value)
|
|
|
|
ElMessage({
|
|
|
|
type: 'success',
|
|
|
|
message: `填写完成,即将重新加载页面`
|
|
|
|
})
|
|
|
|
window.location.reload()
|
|
|
|
})
|
|
|
|
.catch(() => {})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 打印图片
|
|
|
|
const printImage = () => {
|
|
|
|
if (isCanvasEmpty(photoCanvas.value)) {
|
|
|
|
proxy.$message.error('请先绘制签名或拍照再打印')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const imageData = photoCanvas.value.toDataURL('image/png', 1)
|
|
|
|
|
|
|
|
// 创建隐藏的iframe进行打印
|
|
|
|
const iframe = document.createElement('iframe')
|
|
|
|
iframe.style.display = 'none'
|
|
|
|
document.body.appendChild(iframe)
|
|
|
|
|
|
|
|
const doc = iframe.contentDocument || iframe.contentWindow.document
|
|
|
|
doc.open()
|
|
|
|
doc.write(`
|
|
|
|
<!DOCTYPE html>
|
|
|
|
<html>
|
|
|
|
<body style="margin:0; padding:20px;">
|
|
|
|
<img src="${imageData}" style="max-width:100%;">
|
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
`)
|
|
|
|
doc.close()
|
|
|
|
|
|
|
|
// Electron环境中直接调用webContents.print
|
|
|
|
setTimeout(() => {
|
|
|
|
// silent: false 会显示打印机选项
|
|
|
|
iframe.contentWindow.print({ silent: false }, (success) => {
|
|
|
|
if (success) {
|
|
|
|
proxy.$message.success('打印成功')
|
|
|
|
} else {
|
|
|
|
proxy.$message.warning('打印已取消')
|
|
|
|
}
|
|
|
|
// 清理iframe
|
|
|
|
document.body.removeChild(iframe)
|
|
|
|
})
|
|
|
|
}, 500)
|
|
|
|
} catch (error) {
|
|
|
|
console.error('打印失败:', error)
|
|
|
|
proxy.$message.error('打印失败')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 拍照
|
|
|
|
const photograph = () => {
|
|
|
|
// if (!isCamera.value) return proxy.$message.error('请打开相机')
|
|
|
|
if (photoCanvas.value) {
|
|
|
|
photoCanvas.value.width = photoCanvas.value.offsetWidth
|
|
|
|
photoCanvas.value.height = photoCanvas.value.offsetWidth * 0.75
|
|
|
|
if (isCamera.value && !isCanvasEmpty(canvas.value)) {
|
|
|
|
// 将视频和签名制到photoCanvas
|
|
|
|
photoCtx.value.drawImage(
|
|
|
|
videoRef.value,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
photoCanvas.value.width,
|
|
|
|
photoCanvas.value.height
|
|
|
|
)
|
|
|
|
photoCtx.value.drawImage(canvas.value, 0, 260, photoCanvas.value.width, 100)
|
|
|
|
} else if (isCanvasEmpty(canvas.value)) {
|
|
|
|
// 将视频帧绘制到photoCanvas
|
|
|
|
photoCtx.value.drawImage(
|
|
|
|
videoRef.value,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
photoCanvas.value.width,
|
|
|
|
photoCanvas.value.height
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
// 将签名绘制到photoCanvas
|
|
|
|
photoCtx.value.drawImage(
|
|
|
|
canvas.value,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
photoCanvas.value.width,
|
|
|
|
photoCanvas.value.height
|
|
|
|
)
|
|
|
|
}
|
|
|
|
// 获取图片数据(Base64格式)
|
|
|
|
// const imageData = photoCanvas.value.toDataURL('image/png')
|
|
|
|
|
|
|
|
// 保存图片到本地(通过主进程)
|
|
|
|
// window.electronAPI.saveImage(imageData);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 实现保存功能
|
|
|
|
// const saveSignature = () => {
|
|
|
|
// if (isCanvasEmpty(photoCanvas.value)) return proxy.$message.error('请先绘制签名或拍照')
|
|
|
|
// const dataURL = canvas.value.toDataURL('image/png')
|
|
|
|
// // 发送到主进程保存到文件系统
|
|
|
|
// electron.ipcRenderer.send('save-signature', dataURL)
|
|
|
|
// }
|
|
|
|
// 上传签名
|
|
|
|
const uploadSignature = () => {
|
|
|
|
if (isCanvasEmpty(photoCanvas.value)) return proxy.$message.error('请先绘制签名或拍照')
|
|
|
|
// 获取图片数据(Base64格式)
|
|
|
|
const imageData = photoCanvas.value.toDataURL('image/png', 1)
|
|
|
|
uploadImage(imageData).then(() => {
|
|
|
|
ElMessage({
|
|
|
|
type: 'success',
|
|
|
|
message: '上传成功'
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
.container {
|
|
|
|
max-width: 90vw;
|
|
|
|
}
|
|
|
|
.title {
|
|
|
|
font-size: 20px;
|
|
|
|
font-weight: bold;
|
|
|
|
margin-bottom: 20px;
|
|
|
|
&::before {
|
|
|
|
content: '';
|
|
|
|
display: inline-block;
|
|
|
|
width: 4px;
|
|
|
|
height: 20px;
|
|
|
|
margin: -2px 4px;
|
|
|
|
background-color: #1890ff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style> |
...
|
...
|
|