SOURCE

console 命令行工具 X clear

                    
>
console
const { createApp } = PetiteVue

const add = (x, y) => (x || 0) + (y || 0)
const sumBy = (arr, key) => arr.map(x => x[key]).reduce(add, 0)

class Table {
    constructor(options) {
        const { columns, key, data, caption, feet, style, init, ...rest } = options
        columns.forEach(x => {
            if (!x.trans) x.trans = x => x
        })
        this.$template = '#table-template'
        if (style) this.style = style
        if (init) this.init = init
        this.columns = columns || []
        this.key = key || 'id'
        this.caption = caption || ''
        this.feet = feet || []
        this.data = data || []
        this.rest = rest
    }
    style() { }
    init() { }
    static formatters = (() => {
        const makeFormatter = (options, locales = 'en-US') => new Intl.NumberFormat(locales, options).format
        const fracDigits = n => ({
            minimumFractionDigits: n,
            maximumFractionDigits: n
        })
        return {
            int: makeFormatter(fracDigits(0)),
            dec: digit => makeFormatter(fracDigits(digit)),
            money: currency => makeFormatter({ style: 'currency', currency }),
            percent: makeFormatter({ style: 'percent', ...fracDigits(2) })
        }
    })()
}

function getData(currency = 'USD', projFilter = 'All', rank = true) {
    const columns = [
        { id: 'rank', name: '排名', align: 'center' },
        { id: 'proj', name: '子项目', align: 'center' },
        { id: 'skuId', name: 'SKU' },
        { id: 'sale', name: '销售额', trans: x => isFinite(x) ? Table.formatters.money(currency)(x) : '-', align: 'right' },
        { id: 'quantity', name: '销量', trans: Table.formatters.int, align: 'right' },
        { id: 'order', name: ' 订单数', trans: Table.formatters.int, align: 'right' }
    ]
    return {
        columns: columns.slice(1 - rank),
        date: [],
        data: [],
        feet: [],
        style(col, row) {
            const styleObj = {}
            if (col.align) styleObj.textAlign = col.align
            return {
                ...styleObj,
                backgroundColor: row.bgColor
            }
        },
        showRank() {
            this.columns = columns
        },
        hideRank() {
            this.columns = columns.slice(1)
        },
        async init() {
            const { date, sales, projs } = await fetch('http://localhost:3001').then(x => x.json())
            this.caption = `销售情况播报 ${date}`
            const salesC = sales.filter(x => {
                if (x.currency !== currency) return false
                if (projFilter === 'All') return true
                return x.proj.includes(projFilter)
            })
            const sums = {}
            const total = { proj: '所有项目', skuId: '汇总', sale: 0, quantity: 0, order: 0 }
            salesC.forEach(({ proj, sale, quantity, order }, i) => {
                salesC[i].bgColor = projs[proj]
                if (!sums[proj]) sums[proj] = { proj, bgColor: projs[proj], skuId: '汇总', sale: 0, quantity: 0, order: 0 }
                const p = sums[proj]
                p.sale = add(p.sale, sale)
                p.quantity = add(p.quantity, quantity)
                p.order = add(p.order, order)
                total.sale = add(total.sale, sale)
                total.quantity = add(total.quantity, quantity)
                total.order = add(total.order, order)
            })
            const ssums = Object.values(sums)
            if (rank) {
                salesC.sort((x, y) => {
                    const diffs = ['sale', 'quantity', 'order'].map(k => add(-x[k], y[k]))
                    return diffs[0] || diffs[1] || diffs[2]
                })
                salesC.forEach((x, i) => {
                    x.rank = i + 1
                    if (i > 0 && x.sale === salesC[i - 1].sale) x.rank = ''
                })
            }
            this.data = [...salesC, ...ssums]
            this.feet = [total]
        }
    }
}

const general = {
    ranking: false,
    currency: 'USD',
    projFilter: 'All',
    showRank () {
        this.ranking = true
        // console.log(this)
    },
    hideRank () {
        this.ranking = false
    },
    genOptions () {
        this.tbOptions = getData(this.currency, this.projFilter, this.ranking)
    },
    tbOptions: getData('USD', 'All', true)
}

createApp({
    Table,
    getData
}).mount()
<div v-scope="general"> 
    <button @click="showRank">排名展示</button>
    <button @click="hideRank">不排名展示</button>
    <div v-scope="new Table(tbOptions)" v-effect="init()"></div>
</div>


<template id="table-template">
  <table>
    <caption v-if="caption">{{ caption }}</caption>
    <thead v-if="columns.length">
      <tr>
        <th v-for="c in columns" key="c.id">{{ c.name }}</th>
      </tr>
    </thead>
    <tbody v-if="columns.length * data.length">
      <tr v-for="r in data" key="r[key]">
        <td v-for="c in columns" key="c.id" :style="style(c, r)">{{ c.trans(r[c.id]) }}</td>
      </tr>
    </tbody>
    <tfoot v-if="columns.length * feet.length">
      <tr v-for="r in feet" key="r[key]">
        <td v-for="c in columns" key="c.id" :style="style(c, r)">{{ c.trans(r[c.id]) }}</td>
      </tr>
    </tfoot>
  </table>
</template>
body {
  font-family: Menlo, Consolas, monospace;
  color: #444;
}
thead, tfoot {
    background-color: #3f87a6;
    color: #fff;
}

caption {
    padding: 10px;
    caption-side: top;
    font-size: 1rem;
}

table {
    border-collapse: collapse;
    border: 2px solid rgb(100, 100, 100);
    letter-spacing: 1px;
    font-family: sans-serif;
    font-size: .8rem;
}

td,
th {
    border: 1px solid rgb(100, 100, 100);
    padding: 10px 10px;
}

本项目引用的自定义外部资源