Skip to content

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;
}