Vue3 单元格例子
在线预览: https://docs.ffffee.com/examples/cell/index.html
importmap 实战
vue 实战
index.html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="style.css" />
<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3.3.4/dist/vue.esm-browser.js",
"vue/server-renderer": "https://unpkg.com/@vue/server-renderer@3.3.4/dist/server-renderer.esm-browser.js"
}
}
</script>
<script type="module">
import { createApp } from "vue";
import Cell from "./Cell.js";
import { cells } from "./store.js";
createApp({
components: {
Cell,
},
data() {
return {
cols: cells.map((_, i) => String.fromCharCode(65 + i)),
cells,
};
},
}).mount("#app");
</script>
</head>
<body>
<div id="app">
<table>
<thead>
<tr>
<th></th>
<th v-for="c in cols">{{ c }}</th>
</tr>
</thead>
<tbody>
<tr v-for="i in cells[0].length">
<th>{{ i - 1 }}</th>
<td v-for="(c, j) in cols">
<cell :r="i - 1" :c="j"></cell>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
store.js
js
import { reactive } from "vue";
const COLS = 26;
const ROWS = 101;
export const cells = reactive(
Array.from(Array(COLS).keys()).map((i) =>
Array.from(Array(ROWS).keys()).map((i) => "")
)
);
// 原版 https://codesandbox.io/s/jotai-7guis-task7-cells-mzoit?file=/src/atoms.ts
// 作者 @dai-shi
export function evalCell(exp) {
if (!exp.startsWith("=")) {
return exp;
}
// = A1 + B2 ---> get(0,1) + get(1,2)
exp = exp
.slice(1)
.replace(
/\b([A-Z])(\d{1,2})\b/g,
(_, c, r) => `get(${c.charCodeAt(0) - 65},${r})`
);
try {
return new Function("get", `return ${exp}`)(getCellValue);
} catch (e) {
return `#ERROR ${e}`;
}
}
function getCellValue(c, r) {
const val = evalCell(cells[c][r]);
const num = Number(val);
return Number.isFinite(num) ? num : val;
}
Cell.js
js
import { cells, evalCell } from "./store.js";
export default {
props: {
c: Number,
r: Number,
},
data() {
return {
editing: false,
cells,
};
},
methods: {
evalCell,
update(e) {
this.editing = false;
cells[this.c][this.r] = e.target.value.trim();
},
},
template: `
<div class="cell" :title="cells[c][r]" @click="editing = true">
<input
v-if="editing"
:value="cells[c][r]"
@change="update"
@blur="update"
@vue:mounted="({ el }) => el.focus()"
>
<span v-else>{{ evalCell(cells[c][r]) }}</span>
</div>
`,
};
style.css
css
body {
margin: 0;
}
table {
border-collapse: collapse;
table-layout: fixed;
width: 100%;
}
th {
background-color: #eee;
}
tr:first-of-type th {
width: 100px;
}
tr:first-of-type th:first-of-type {
width: 25px;
}
td {
border: 1px solid #ccc;
height: 1.5em;
overflow: hidden;
}
.cell,
.cell input {
height: 1.5em;
line-height: 1.5;
font-size: 15px;
}
.cell span {
padding: 0 6px;
}
.cell input {
width: 100%;
box-sizing: border-box;
}