6 changed files with 919 additions and 5 deletions
@ -0,0 +1,691 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="zh-CN"> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<title>数据卡</title> |
|||
<link rel="stylesheet" href="./element-ui.css"> |
|||
<style> |
|||
* { |
|||
margin: 0; |
|||
padding: 0; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
body { |
|||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; |
|||
background: #fafafa; |
|||
} |
|||
|
|||
#app { |
|||
width: 100%; |
|||
height: 100vh; |
|||
} |
|||
|
|||
.data-card-container { |
|||
display: flex; |
|||
flex-direction: column; |
|||
height: 100vh; |
|||
background: #fafafa; |
|||
} |
|||
|
|||
.data-card-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 0 32px; |
|||
height: 56px; |
|||
background: #fff; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.header-left { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 16px; |
|||
} |
|||
|
|||
.back-btn { |
|||
padding: 8px 12px; |
|||
font-size: 14px; |
|||
color: #666; |
|||
cursor: pointer; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 4px; |
|||
} |
|||
|
|||
.back-btn:hover { |
|||
color: #409EFF; |
|||
} |
|||
|
|||
.data-card-title { |
|||
font-size: 18px; |
|||
font-weight: 600; |
|||
color: #333; |
|||
} |
|||
|
|||
.header-right { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 12px; |
|||
} |
|||
|
|||
.data-card-toolbar { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 12px; |
|||
padding: 16px 32px; |
|||
background: #fff; |
|||
border-bottom: 1px solid #e8e8e8; |
|||
} |
|||
|
|||
.toolbar-group { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 8px; |
|||
} |
|||
|
|||
.toolbar-label { |
|||
font-size: 14px; |
|||
color: #666; |
|||
} |
|||
|
|||
.data-card-content { |
|||
flex: 1; |
|||
padding: 24px 32px; |
|||
overflow: auto; |
|||
} |
|||
|
|||
.table-container { |
|||
background: #fff; |
|||
border-radius: 8px; |
|||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
|||
padding: 24px; |
|||
display: inline-block; |
|||
} |
|||
|
|||
.data-table { |
|||
border-collapse: collapse; |
|||
table-layout: fixed; |
|||
} |
|||
|
|||
.data-table td { |
|||
border: 1px solid #ddd; |
|||
min-width: 60px; |
|||
height: 30px; |
|||
position: relative; |
|||
cursor: cell; |
|||
transition: background-color 0.2s; |
|||
} |
|||
|
|||
.data-table td:hover { |
|||
background-color: #f5f7fa; |
|||
} |
|||
|
|||
.data-table td.selected { |
|||
background-color: #ecf5ff; |
|||
border-color: #409EFF; |
|||
} |
|||
|
|||
.resize-handle-col { |
|||
position: absolute; |
|||
right: 0; |
|||
top: 0; |
|||
width: 5px; |
|||
height: 100%; |
|||
cursor: col-resize; |
|||
background: transparent; |
|||
z-index: 10; |
|||
} |
|||
|
|||
.resize-handle-col:hover { |
|||
background: #409EFF; |
|||
} |
|||
|
|||
.resize-handle-row { |
|||
position: absolute; |
|||
bottom: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 5px; |
|||
cursor: row-resize; |
|||
background: transparent; |
|||
z-index: 10; |
|||
} |
|||
|
|||
.resize-handle-row:hover { |
|||
background: #409EFF; |
|||
} |
|||
|
|||
.cell-input { |
|||
width: 100%; |
|||
height: 100%; |
|||
border: none; |
|||
outline: none; |
|||
padding: 4px 8px; |
|||
font-size: 14px; |
|||
background: transparent; |
|||
} |
|||
|
|||
.color-picker-panel { |
|||
display: flex; |
|||
flex-wrap: wrap; |
|||
gap: 4px; |
|||
width: 180px; |
|||
} |
|||
|
|||
.color-option { |
|||
width: 24px; |
|||
height: 24px; |
|||
border-radius: 4px; |
|||
cursor: pointer; |
|||
border: 2px solid transparent; |
|||
} |
|||
|
|||
.color-option:hover { |
|||
border-color: #409EFF; |
|||
} |
|||
|
|||
.color-option.selected { |
|||
border-color: #409EFF; |
|||
} |
|||
</style> |
|||
</head> |
|||
<body> |
|||
<div id="app"> |
|||
<div class="data-card-container"> |
|||
<div class="data-card-header"> |
|||
<div class="header-left"> |
|||
<div class="back-btn" @click="goBack"> |
|||
<i class="el-icon-arrow-left"></i> |
|||
返回 |
|||
</div> |
|||
<div class="data-card-title">数据卡</div> |
|||
</div> |
|||
<div class="header-right"> |
|||
<el-button type="primary" size="small" @click="exportImage"> |
|||
<i class="el-icon-picture-outline"></i> |
|||
导出图片 |
|||
</el-button> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="data-card-toolbar"> |
|||
<div class="toolbar-group"> |
|||
<span class="toolbar-label">行数:</span> |
|||
<el-input-number v-model="rows" :min="1" :max="50" size="small" @change="updateTableSize"></el-input-number> |
|||
</div> |
|||
<div class="toolbar-group"> |
|||
<span class="toolbar-label">列数:</span> |
|||
<el-input-number v-model="cols" :min="1" :max="20" size="small" @change="updateTableSize"></el-input-number> |
|||
</div> |
|||
<el-divider direction="vertical"></el-divider> |
|||
<div class="toolbar-group"> |
|||
<span class="toolbar-label">单元格背景色:</span> |
|||
<el-popover |
|||
placement="bottom" |
|||
width="200" |
|||
trigger="click" |
|||
v-model="colorPopoverVisible"> |
|||
<div class="color-picker-panel"> |
|||
<div |
|||
v-for="color in colors" |
|||
:key="color" |
|||
class="color-option" |
|||
:style="{ backgroundColor: color }" |
|||
:class="{ selected: selectedColor === color }" |
|||
@click="selectColor(color)"> |
|||
</div> |
|||
</div> |
|||
<el-button slot="reference" size="small"> |
|||
<span :style="{ display: 'inline-block', width: '16px', height: '16px', backgroundColor: selectedColor, marginRight: '8px', verticalAlign: 'middle', borderRadius: '2px' }"></span> |
|||
选择颜色 |
|||
</el-button> |
|||
</el-popover> |
|||
</div> |
|||
<el-divider direction="vertical"></el-divider> |
|||
<div class="toolbar-group"> |
|||
<el-button size="small" @click="undo">撤回</el-button> |
|||
<el-button size="small" @click="mergeCells">合并单元格</el-button> |
|||
<el-button type="primary" size="small" @click="saveTable">保存</el-button> |
|||
<el-button type="danger" size="small" @click="clearCell">清空选中</el-button> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="data-card-content"> |
|||
<div class="table-container" ref="tableContainer"> |
|||
<table class="data-table" ref="dataTable"> |
|||
<tbody> |
|||
<tr v-for="(row, rowIndex) in tableData" :key="rowIndex"> |
|||
<td |
|||
v-for="(cell, colIndex) in row" |
|||
:key="colIndex" |
|||
:class="{ selected: selectedCells.some(c => c.row === rowIndex && c.col === colIndex) }" |
|||
:rowspan="getRowspan(rowIndex, colIndex)" |
|||
:colspan="getColspan(rowIndex, colIndex)" |
|||
:style="getCellStyle(rowIndex, colIndex)" |
|||
@click="editCell(rowIndex, colIndex)" |
|||
@mousedown="startSelect($event, rowIndex, colIndex)" |
|||
@mouseover="continueSelect($event, rowIndex, colIndex)" |
|||
@mouseup="endSelect"> |
|||
<div class="resize-handle-col" @mousedown.stop="startResizeCol($event, colIndex)"></div> |
|||
<div class="resize-handle-row" @mousedown.stop="startResizeRow($event, rowIndex)"></div> |
|||
<span v-if="!editingCell || editingCell.row !== rowIndex || editingCell.col !== colIndex">{{ cell.content }}</span> |
|||
<input |
|||
v-else |
|||
class="cell-input" |
|||
v-model="tableData[rowIndex][colIndex].content" |
|||
@blur="finishEditing" |
|||
@keyup.enter="finishEditing" |
|||
ref="cellInput" |
|||
autofocus> |
|||
</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<script src="https://unpkg.com/vue@2.6.12/dist/vue.js"></script> |
|||
<script src="https://unpkg.com/element-ui/lib/index.js"></script> |
|||
<script src="https://unpkg.com/html2canvas@1.4.1/dist/html2canvas.min.js"></script> |
|||
<script> |
|||
new Vue({ |
|||
el: '#app', |
|||
data() { |
|||
return { |
|||
rows: 10, |
|||
cols: 8, |
|||
tableData: [], |
|||
cellWidths: [], |
|||
cellHeights: [], |
|||
selectedCells: [], |
|||
isSelecting: false, |
|||
startCell: null, |
|||
editingCell: null, |
|||
selectedColor: '#ffffff', |
|||
colorPopoverVisible: false, |
|||
colors: [ |
|||
'#ffffff', '#f5f7fa', '#e6a23c', '#67c23a', '#409eff', |
|||
'#909399', '#f0f9eb', '#fdf6ec', '#ecf5ff', '#f4f4f5', |
|||
'#ff6b6b', '#51cf66', '#339af0', '#cc5de8', '#ff922b', |
|||
'#ffd43b', '#20c997', '#22b8cf', '#748ffc', '#be4bdb' |
|||
], |
|||
resizingCol: null, |
|||
resizingRow: null, |
|||
startX: 0, |
|||
startY: 0, |
|||
startWidth: 0, |
|||
startHeight: 0, |
|||
mergedCells: [], |
|||
history: [], |
|||
historyIndex: -1 |
|||
}; |
|||
}, |
|||
mounted() { |
|||
this.loadTable(); |
|||
if (this.tableData.length === 0) { |
|||
this.generateTable(); |
|||
// 初始化历史记录 |
|||
this.saveState(); |
|||
} |
|||
document.addEventListener('mouseup', this.endSelect); |
|||
document.addEventListener('mousemove', this.handleResize); |
|||
document.addEventListener('mouseup', this.endResize); |
|||
}, |
|||
beforeDestroy() { |
|||
document.removeEventListener('mouseup', this.endSelect); |
|||
document.removeEventListener('mousemove', this.handleResize); |
|||
document.removeEventListener('mouseup', this.endResize); |
|||
}, |
|||
methods: { |
|||
goBack() { |
|||
window.close(); |
|||
}, |
|||
generateTable() { |
|||
this.tableData = []; |
|||
this.cellWidths = []; |
|||
this.cellHeights = []; |
|||
this.mergedCells = []; |
|||
for (let i = 0; i < this.rows; i++) { |
|||
const row = []; |
|||
for (let j = 0; j < this.cols; j++) { |
|||
row.push({ content: '', bgColor: '#ffffff' }); |
|||
} |
|||
this.tableData.push(row); |
|||
this.cellHeights.push(30); |
|||
} |
|||
for (let j = 0; j < this.cols; j++) { |
|||
this.cellWidths.push(100); |
|||
} |
|||
this.selectedCells = []; |
|||
// 重置历史记录 |
|||
this.history = []; |
|||
this.historyIndex = -1; |
|||
}, |
|||
|
|||
updateTableSize() { |
|||
const oldRows = this.tableData.length; |
|||
const oldCols = oldRows > 0 ? this.tableData[0].length : 0; |
|||
|
|||
if (this.rows !== oldRows || this.cols !== oldCols) { |
|||
// 保存当前状态 |
|||
this.saveState(); |
|||
|
|||
// 处理行的增减 |
|||
if (this.rows > oldRows) { |
|||
// 增加行 |
|||
for (let i = oldRows; i < this.rows; i++) { |
|||
const row = []; |
|||
for (let j = 0; j < this.cols; j++) { |
|||
row.push({ content: '', bgColor: '#ffffff' }); |
|||
} |
|||
this.tableData.push(row); |
|||
this.cellHeights.push(30); |
|||
} |
|||
} else if (this.rows < oldRows) { |
|||
// 减少行 |
|||
this.tableData.splice(this.rows); |
|||
this.cellHeights.splice(this.rows); |
|||
} |
|||
|
|||
// 处理列的增减 |
|||
if (this.cols > oldCols) { |
|||
// 增加列 |
|||
for (let i = 0; i < this.tableData.length; i++) { |
|||
for (let j = oldCols; j < this.cols; j++) { |
|||
this.tableData[i].push({ content: '', bgColor: '#ffffff' }); |
|||
} |
|||
} |
|||
for (let j = oldCols; j < this.cols; j++) { |
|||
this.cellWidths.push(100); |
|||
} |
|||
} else if (this.cols < oldCols) { |
|||
// 减少列 |
|||
for (let i = 0; i < this.tableData.length; i++) { |
|||
this.tableData[i].splice(this.cols); |
|||
} |
|||
this.cellWidths.splice(this.cols); |
|||
} |
|||
} |
|||
}, |
|||
|
|||
mergeCells() { |
|||
if (this.selectedCells.length < 2) { |
|||
this.$message.warning('请至少选择两个单元格进行合并'); |
|||
return; |
|||
} |
|||
|
|||
// 保存当前状态 |
|||
this.saveState(); |
|||
|
|||
// 计算合并区域的最小和最大行、列 |
|||
const minRow = Math.min(...this.selectedCells.map(c => c.row)); |
|||
const maxRow = Math.max(...this.selectedCells.map(c => c.row)); |
|||
const minCol = Math.min(...this.selectedCells.map(c => c.col)); |
|||
const maxCol = Math.max(...this.selectedCells.map(c => c.col)); |
|||
|
|||
// 获取左上角单元格的内容和背景色 |
|||
const firstCell = this.tableData[minRow][minCol]; |
|||
const content = firstCell.content; |
|||
const bgColor = firstCell.bgColor; |
|||
|
|||
// 清除其他单元格的内容和背景色 |
|||
for (let r = minRow; r <= maxRow; r++) { |
|||
for (let c = minCol; c <= maxCol; c++) { |
|||
if (r !== minRow || c !== minCol) { |
|||
this.tableData[r][c].content = ''; |
|||
this.tableData[r][c].bgColor = '#ffffff'; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 记录合并信息 |
|||
this.mergedCells.push({ |
|||
startRow: minRow, |
|||
startCol: minCol, |
|||
endRow: maxRow, |
|||
endCol: maxCol |
|||
}); |
|||
|
|||
this.$message.success('单元格合并成功'); |
|||
}, |
|||
|
|||
saveTable() { |
|||
const tableData = { |
|||
rows: this.rows, |
|||
cols: this.cols, |
|||
tableData: this.tableData, |
|||
cellWidths: this.cellWidths, |
|||
cellHeights: this.cellHeights, |
|||
mergedCells: this.mergedCells |
|||
}; |
|||
localStorage.setItem('dataCardTable', JSON.stringify(tableData)); |
|||
this.$message.success('表格保存成功!'); |
|||
}, |
|||
|
|||
loadTable() { |
|||
const savedData = localStorage.getItem('dataCardTable'); |
|||
if (savedData) { |
|||
try { |
|||
const data = JSON.parse(savedData); |
|||
this.rows = data.rows; |
|||
this.cols = data.cols; |
|||
this.tableData = data.tableData; |
|||
this.cellWidths = data.cellWidths; |
|||
this.cellHeights = data.cellHeights; |
|||
this.mergedCells = data.mergedCells || []; |
|||
this.$message.success('表格加载成功!'); |
|||
// 初始化历史记录 |
|||
this.saveState(); |
|||
} catch (e) { |
|||
console.error('加载表格失败:', e); |
|||
} |
|||
} |
|||
}, |
|||
|
|||
saveState() { |
|||
// 保存当前状态到历史记录 |
|||
const state = { |
|||
rows: this.rows, |
|||
cols: this.cols, |
|||
tableData: JSON.parse(JSON.stringify(this.tableData)), |
|||
cellWidths: [...this.cellWidths], |
|||
cellHeights: [...this.cellHeights], |
|||
mergedCells: JSON.parse(JSON.stringify(this.mergedCells)) |
|||
}; |
|||
|
|||
// 如果当前不是在历史记录的最后,删除后面的历史记录 |
|||
if (this.historyIndex < this.history.length - 1) { |
|||
this.history = this.history.slice(0, this.historyIndex + 1); |
|||
} |
|||
|
|||
// 添加新状态到历史记录 |
|||
this.history.push(state); |
|||
|
|||
// 限制历史记录长度,防止内存占用过大 |
|||
if (this.history.length > 50) { |
|||
this.history.shift(); |
|||
// 调整历史记录索引 |
|||
if (this.historyIndex > 0) { |
|||
this.historyIndex--; |
|||
} |
|||
} else { |
|||
this.historyIndex++; |
|||
} |
|||
}, |
|||
|
|||
undo() { |
|||
if (this.historyIndex > 0) { |
|||
this.historyIndex--; |
|||
const state = this.history[this.historyIndex]; |
|||
this.rows = state.rows; |
|||
this.cols = state.cols; |
|||
this.tableData = state.tableData; |
|||
this.cellWidths = state.cellWidths; |
|||
this.cellHeights = state.cellHeights; |
|||
this.mergedCells = state.mergedCells; |
|||
this.$message.success('操作已撤回'); |
|||
} else { |
|||
this.$message.info('没有可撤回的操作'); |
|||
} |
|||
}, |
|||
|
|||
startSelect(event, row, col) { |
|||
if (event.button !== 0) return; |
|||
// 只有在按下鼠标并移动时才开始选择 |
|||
this.isSelecting = true; |
|||
this.startCell = { row, col }; |
|||
this.selectedCells = [{ row, col }]; |
|||
}, |
|||
continueSelect(event, row, col) { |
|||
if (!this.isSelecting || !this.startCell) return; |
|||
const minRow = Math.min(this.startCell.row, row); |
|||
const maxRow = Math.max(this.startCell.row, row); |
|||
const minCol = Math.min(this.startCell.col, col); |
|||
const maxCol = Math.max(this.startCell.col, col); |
|||
this.selectedCells = []; |
|||
for (let r = minRow; r <= maxRow; r++) { |
|||
for (let c = minCol; c <= maxCol; c++) { |
|||
this.selectedCells.push({ row: r, col: c }); |
|||
} |
|||
} |
|||
}, |
|||
endSelect() { |
|||
this.isSelecting = false; |
|||
}, |
|||
editCell(row, col) { |
|||
// 停止任何正在进行的选择 |
|||
this.isSelecting = false; |
|||
this.selectedCells = [{ row, col }]; |
|||
|
|||
// 进入编辑模式 |
|||
this.editingCell = { row, col }; |
|||
this.$nextTick(() => { |
|||
const cellInputs = this.$refs.cellInput; |
|||
if (cellInputs) { |
|||
// 确保输入框获得焦点 |
|||
if (Array.isArray(cellInputs)) { |
|||
cellInputs.forEach(input => { |
|||
if (input) input.focus(); |
|||
}); |
|||
} else if (cellInputs) { |
|||
cellInputs.focus(); |
|||
} |
|||
} |
|||
}); |
|||
}, |
|||
finishEditing() { |
|||
// 保存当前状态 |
|||
this.saveState(); |
|||
this.editingCell = null; |
|||
}, |
|||
selectColor(color) { |
|||
if (this.selectedCells.length === 0) return; |
|||
|
|||
// 保存当前状态 |
|||
this.saveState(); |
|||
|
|||
this.selectedColor = color; |
|||
this.colorPopoverVisible = false; |
|||
this.selectedCells.forEach(cell => { |
|||
this.tableData[cell.row][cell.col].bgColor = color; |
|||
}); |
|||
}, |
|||
clearCell() { |
|||
if (this.selectedCells.length === 0) return; |
|||
|
|||
// 保存当前状态 |
|||
this.saveState(); |
|||
|
|||
this.selectedCells.forEach(cell => { |
|||
this.tableData[cell.row][cell.col].content = ''; |
|||
this.tableData[cell.row][cell.col].bgColor = '#ffffff'; |
|||
}); |
|||
}, |
|||
startResizeCol(event, col) { |
|||
this.resizingCol = col; |
|||
this.startX = event.clientX; |
|||
this.startWidth = this.cellWidths[col]; |
|||
}, |
|||
startResizeRow(event, row) { |
|||
this.resizingRow = row; |
|||
this.startY = event.clientY; |
|||
this.startHeight = this.cellHeights[row]; |
|||
}, |
|||
handleResize(event) { |
|||
if (this.resizingCol !== null) { |
|||
const deltaX = event.clientX - this.startX; |
|||
const newWidth = Math.max(60, this.startWidth + deltaX); |
|||
this.$set(this.cellWidths, this.resizingCol, newWidth); |
|||
} |
|||
if (this.resizingRow !== null) { |
|||
const deltaY = event.clientY - this.startY; |
|||
const newHeight = Math.max(30, this.startHeight + deltaY); |
|||
this.$set(this.cellHeights, this.resizingRow, newHeight); |
|||
} |
|||
}, |
|||
endResize() { |
|||
this.resizingCol = null; |
|||
this.resizingRow = null; |
|||
}, |
|||
getRowspan(row, col) { |
|||
// 检查当前单元格是否是合并单元格的起始单元格 |
|||
const mergedCell = this.mergedCells.find(mc => mc.startRow === row && mc.startCol === col); |
|||
return mergedCell ? mergedCell.endRow - mergedCell.startRow + 1 : 1; |
|||
}, |
|||
|
|||
getColspan(row, col) { |
|||
// 检查当前单元格是否是合并单元格的起始单元格 |
|||
const mergedCell = this.mergedCells.find(mc => mc.startRow === row && mc.startCol === col); |
|||
return mergedCell ? mergedCell.endCol - mergedCell.startCol + 1 : 1; |
|||
}, |
|||
|
|||
getCellStyle(row, col) { |
|||
// 检查当前单元格是否是合并单元格的一部分 |
|||
const mergedCell = this.mergedCells.find(mc => |
|||
row >= mc.startRow && row <= mc.endRow && |
|||
col >= mc.startCol && col <= mc.endCol |
|||
); |
|||
|
|||
// 如果是合并单元格的非起始单元格,隐藏它 |
|||
if (mergedCell && (row !== mergedCell.startRow || col !== mergedCell.startCol)) { |
|||
return { |
|||
display: 'none', |
|||
width: this.cellWidths[col] + 'px', |
|||
height: this.cellHeights[row] + 'px' |
|||
}; |
|||
} |
|||
|
|||
// 正常单元格的样式 |
|||
return { |
|||
width: this.cellWidths[col] + 'px', |
|||
height: this.cellHeights[row] + 'px', |
|||
backgroundColor: this.tableData[row][col].bgColor || '#fff' |
|||
}; |
|||
}, |
|||
|
|||
exportImage() { |
|||
const container = this.$refs.tableContainer; |
|||
html2canvas(container, { |
|||
backgroundColor: '#ffffff', |
|||
scale: 2, |
|||
useCORS: true |
|||
}).then(canvas => { |
|||
const link = document.createElement('a'); |
|||
link.download = '数据卡.png'; |
|||
link.href = canvas.toDataURL('image/png'); |
|||
link.click(); |
|||
this.$message.success('图片导出成功!'); |
|||
}).catch(err => { |
|||
this.$message.error('图片导出失败:' + err.message); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
</script> |
|||
</body> |
|||
</html> |
|||
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
Loading…
Reference in new issue