add junks produced these days
This commit is contained in:
parent
9d7eae14db
commit
1985566ae5
29 changed files with 1724 additions and 0 deletions
47
entemu-legacy/index.html
Normal file
47
entemu-legacy/index.html
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>产品规划</title>
|
||||||
|
<link rel="stylesheet" href="main.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table class="dashboard">
|
||||||
|
<tr>
|
||||||
|
<td>厂房建成日:</td>
|
||||||
|
<td><input type="text" id="datestart" value="1/4/21" placeholder="1/5/21" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>生产效率:</td>
|
||||||
|
<td><input type="number" id="efficiency" value="56" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>模拟次数:</td>
|
||||||
|
<td><input type="number" id="limit" placeholder="limit" value="30" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>产出量:</td>
|
||||||
|
<td><input type="number" id="amount" value="1" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>产品:</td>
|
||||||
|
<td>
|
||||||
|
<select id="product">
|
||||||
|
<option value="P1">P1</option>
|
||||||
|
<option value="P2" selected>P2</option>
|
||||||
|
<option value="P3">P3</option>
|
||||||
|
<option value="P4">P4</option>
|
||||||
|
<option value="P5">P5</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td><button id="ok">进行模拟</button></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div id="tables"></div>
|
||||||
|
<script src="main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
36
entemu-legacy/main.css
Normal file
36
entemu-legacy/main.css
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
body {
|
||||||
|
font-size: large;
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 29.7cm;
|
||||||
|
}
|
||||||
|
input, button, select {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
table.dashboard {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
table.dashboard td:nth-child(1) {
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
|
#tables {
|
||||||
|
display: flex;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
table.result {
|
||||||
|
text-align: center;
|
||||||
|
margin: 0 1em;
|
||||||
|
}
|
||||||
|
table.result td {
|
||||||
|
min-width: 6em;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
table.result tr.highlight {
|
||||||
|
background-color: yellow !important;
|
||||||
|
}
|
||||||
|
table.result thead tr {
|
||||||
|
background-color: aquamarine;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
table.result tbody tr:nth-of-type(2n) {
|
||||||
|
background-color: #ededed;
|
||||||
|
}
|
189
entemu-legacy/main.js
Normal file
189
entemu-legacy/main.js
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} s
|
||||||
|
* @returns {HTMLElement}
|
||||||
|
*/
|
||||||
|
function E(s) {
|
||||||
|
return document.querySelector(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} tag
|
||||||
|
* @returns {HTMLElement}
|
||||||
|
*/
|
||||||
|
function A(tag) {
|
||||||
|
return document.createElement(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} s
|
||||||
|
* @returns {[number, number, number]}
|
||||||
|
*/
|
||||||
|
function parse_date(s) {
|
||||||
|
return s.split("/").map(x => parseInt(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {[number, number, number]} d
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function fmt_date(d) {
|
||||||
|
return "" + d[0] + "/" + d[1] + "/" + d[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {[number, number, number]} d
|
||||||
|
* @param {number} n
|
||||||
|
* @returns {[number, number, number]}
|
||||||
|
*/
|
||||||
|
function add_day(d, n) {
|
||||||
|
d = d.concat();
|
||||||
|
d[2] += n;
|
||||||
|
d[1] += d[2] / 30 | 0;
|
||||||
|
d[2] = d[2] % 30;
|
||||||
|
d[0] += d[1] / 12 | 0;
|
||||||
|
d[1] = d[1] % 12;
|
||||||
|
if (d[2] < 1) {
|
||||||
|
d[2] += 30;
|
||||||
|
d[1] -= 1;
|
||||||
|
}
|
||||||
|
if (d[1] < 1) {
|
||||||
|
d[1] += 12;
|
||||||
|
d[0] -= 1;
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = {
|
||||||
|
rprice: {
|
||||||
|
R1: 12,
|
||||||
|
R2: 12,
|
||||||
|
R3: 12,
|
||||||
|
R4: 12,
|
||||||
|
},
|
||||||
|
rdelay: {
|
||||||
|
R1: 30,
|
||||||
|
R2: 30,
|
||||||
|
R3: 60,
|
||||||
|
R4: 60,
|
||||||
|
},
|
||||||
|
pprice: {
|
||||||
|
P1: 50,
|
||||||
|
P2: 70,
|
||||||
|
P3: 90,
|
||||||
|
P4: 100,
|
||||||
|
P5: 100,
|
||||||
|
},
|
||||||
|
pdelay: {
|
||||||
|
P1: 56,
|
||||||
|
P2: 56,
|
||||||
|
P3: 56,
|
||||||
|
P4: 56,
|
||||||
|
P5: 56,
|
||||||
|
},
|
||||||
|
pr: {
|
||||||
|
P1: { R1: 1 },
|
||||||
|
P2: { R2: 1, R3: 1 },
|
||||||
|
P3: { R1: 1, R3: 1, R4: 1 },
|
||||||
|
P4: { R2: 1, R3: 1, R4: 2 },
|
||||||
|
P5: { P2: 1, R4: 1 },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} p
|
||||||
|
* @param {[number, number, number]} d
|
||||||
|
* @return {{ [r: string]: [number, [number, number, number]] }}
|
||||||
|
*/
|
||||||
|
function buy_date_of(p, d) {
|
||||||
|
const r = db.pr[p];
|
||||||
|
const s = {};
|
||||||
|
for (const key in r) {
|
||||||
|
if (key[0] === "R") {
|
||||||
|
s[key] = [r[key], add_day(d, -db.rdelay[key])];
|
||||||
|
} else if (key[0] === "P") {
|
||||||
|
s[key] = [r[key], add_day(d, -db.pdelay[key])];
|
||||||
|
Object.assign(s, buy_date_of(key, add_day(d, -db.pdelay[key])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
const order = ["P2", "P4", "P1", "P5", "P3"];
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
const datestart_input = E("#datestart");
|
||||||
|
const efficiency_input = E("#efficiency");
|
||||||
|
const product_select = E("#product");
|
||||||
|
const limit_input = E("#limit");
|
||||||
|
const amount_input = E("#amount");
|
||||||
|
const ok_button = E("#ok");
|
||||||
|
const tables = E("#tables");
|
||||||
|
function generate() {
|
||||||
|
tables.innerHTML = "";
|
||||||
|
const datestart = parse_date(datestart_input.value);
|
||||||
|
const limit = parseInt(limit_input.value);
|
||||||
|
const efficiency = parseInt(efficiency_input.value);
|
||||||
|
const amount = parseInt(amount_input.value);
|
||||||
|
const p = product_select.value;
|
||||||
|
if (order.indexOf(p) === -1) return;
|
||||||
|
// for (const p of order) {
|
||||||
|
const table = A("table");
|
||||||
|
table.classList.add("result");
|
||||||
|
tables.appendChild(table);
|
||||||
|
const thead = A("thead");
|
||||||
|
table.appendChild(thead);
|
||||||
|
let tr = A("tr");
|
||||||
|
thead.append(tr);
|
||||||
|
const plabel = A("td");
|
||||||
|
plabel.textContent = p + " 规划";
|
||||||
|
E("title").textContent = p + ": " + fmt_date(datestart);
|
||||||
|
tr.appendChild(plabel);
|
||||||
|
tr = A("tr");
|
||||||
|
thead.appendChild(tr);
|
||||||
|
const d0 = buy_date_of(p, datestart);
|
||||||
|
plabel.colSpan = Object.keys(d0).length + 2;
|
||||||
|
for (const r in d0) {
|
||||||
|
const td = A("td");
|
||||||
|
td.textContent = "购买 " + r + " * " + (d0[r][0] * amount);
|
||||||
|
tr.appendChild(td);
|
||||||
|
}
|
||||||
|
let td = A("td");
|
||||||
|
td.textContent = p + " 开产日";
|
||||||
|
tr.appendChild(td);
|
||||||
|
td = A("td");
|
||||||
|
td.textContent = p + " 产出 * " + amount;
|
||||||
|
tr.appendChild(td);
|
||||||
|
const tbody = A("tbody");
|
||||||
|
table.appendChild(tbody);
|
||||||
|
let next_date = datestart;
|
||||||
|
for (let i = 0; i < limit; ++i) {
|
||||||
|
const tr = A("tr");
|
||||||
|
tr.addEventListener("click", () => tr.classList.toggle("highlight"));
|
||||||
|
tbody.appendChild(tr);
|
||||||
|
const d = buy_date_of(p, next_date);
|
||||||
|
for (const r in d) {
|
||||||
|
const td = A("td");
|
||||||
|
td.textContent = fmt_date(d[r][1]);
|
||||||
|
tr.appendChild(td);
|
||||||
|
}
|
||||||
|
let td = A("td");
|
||||||
|
td.textContent = fmt_date(next_date);
|
||||||
|
tr.appendChild(td);
|
||||||
|
td = A("td");
|
||||||
|
td.textContent = fmt_date(add_day(next_date, efficiency /*db.pdelay[p]*/));
|
||||||
|
tr.appendChild(td);
|
||||||
|
next_date = add_day(next_date, efficiency /*db.pdelay[p]*/);
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
ok_button.addEventListener("click", () => generate());
|
||||||
|
product_select.addEventListener("change", () => generate());
|
||||||
|
generate();
|
||||||
|
});
|
13
entemu/.gitignore
vendored
Normal file
13
entemu/.gitignore
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# Fresh build directory
|
||||||
|
_fresh/
|
||||||
|
# npm + other dependencies
|
||||||
|
node_modules/
|
||||||
|
vendor/
|
||||||
|
|
2
entemu/.npmrc
Normal file
2
entemu/.npmrc
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# for China Mainland users
|
||||||
|
registry=https://registry.npmmirror.com
|
5
entemu/.vscode/extensions.json
vendored
Normal file
5
entemu/.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"denoland.vscode-deno"
|
||||||
|
]
|
||||||
|
}
|
17
entemu/.vscode/settings.json
vendored
Normal file
17
entemu/.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"deno.enable": true,
|
||||||
|
"deno.lint": true,
|
||||||
|
"editor.defaultFormatter": "denoland.vscode-deno",
|
||||||
|
"[typescriptreact]": {
|
||||||
|
"editor.defaultFormatter": "denoland.vscode-deno"
|
||||||
|
},
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "denoland.vscode-deno"
|
||||||
|
},
|
||||||
|
"[javascriptreact]": {
|
||||||
|
"editor.defaultFormatter": "denoland.vscode-deno"
|
||||||
|
},
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.defaultFormatter": "denoland.vscode-deno"
|
||||||
|
}
|
||||||
|
}
|
16
entemu/README.md
Normal file
16
entemu/README.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Fresh project
|
||||||
|
|
||||||
|
Your new Fresh project is ready to go. You can follow the Fresh "Getting
|
||||||
|
Started" guide here: https://fresh.deno.dev/docs/getting-started
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Make sure to install Deno: https://deno.land/manual/getting_started/installation
|
||||||
|
|
||||||
|
Then start the project in development mode:
|
||||||
|
|
||||||
|
```
|
||||||
|
deno task dev
|
||||||
|
```
|
||||||
|
|
||||||
|
This will watch the project directory and restart as necessary.
|
14
entemu/common/date.ts
Normal file
14
entemu/common/date.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
export type DateTuple = [number, number, number];
|
||||||
|
|
||||||
|
export function datetuple(d: number): DateTuple {
|
||||||
|
return [d / 12 / 30 | 0, (d / 30 | 0) % 12, d % 30]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function datefmt(d: number): string {
|
||||||
|
return datetuple(d).map(n => n + 1).join("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function datefrom(y: number, m: number, d: number) {
|
||||||
|
return (y - 1) * 12 * 30 + (m - 1) * 30 + (d - 1);
|
||||||
|
}
|
365
entemu/common/plan.ts
Normal file
365
entemu/common/plan.ts
Normal file
|
@ -0,0 +1,365 @@
|
||||||
|
import { datefrom } from "./date.ts";
|
||||||
|
|
||||||
|
export interface Plan {
|
||||||
|
name: string;
|
||||||
|
times: number;
|
||||||
|
price: number;
|
||||||
|
start: number;
|
||||||
|
amount: number;
|
||||||
|
period: number;
|
||||||
|
nextonce: boolean;
|
||||||
|
role: string;
|
||||||
|
next: Partial<Plan>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dplan: Plan = {
|
||||||
|
name: "新策划",
|
||||||
|
times: 1,
|
||||||
|
price: 0,
|
||||||
|
start: 0,
|
||||||
|
amount: 1,
|
||||||
|
period: 0,
|
||||||
|
nextonce: false,
|
||||||
|
role: "",
|
||||||
|
next: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IPlan {
|
||||||
|
date: number;
|
||||||
|
plan: Plan;
|
||||||
|
order: number;
|
||||||
|
cost: number;
|
||||||
|
current: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bplan(...plan: Partial<Plan>[]): Plan {
|
||||||
|
const p = { ...dplan };
|
||||||
|
for (const p1 of plan) {
|
||||||
|
Object.assign(p, p1);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addplan(cal: Record<number, IPlan[]>, p: Plan): number[] {
|
||||||
|
const e: number[] = [];
|
||||||
|
let d = p.start;
|
||||||
|
for (let i = 0; i < p.times; i++) {
|
||||||
|
if (cal[d] === undefined)
|
||||||
|
cal[d] = [];
|
||||||
|
cal[d].push({
|
||||||
|
date: d,
|
||||||
|
plan: p,
|
||||||
|
order: i,
|
||||||
|
cost: p.price * p.amount,
|
||||||
|
current: 0,
|
||||||
|
});
|
||||||
|
if (!p.nextonce || (p.nextonce && i === 0))
|
||||||
|
for (const n of p.next) {
|
||||||
|
e.push(...addplan(cal, bplan(n, {
|
||||||
|
start: d + (n.start || 0),
|
||||||
|
amount: (p.amount || 1) * (n.amount || 1),
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
e.push(d);
|
||||||
|
d += p.period;
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function expandplan(d: number, p: Plan): Plan[] {
|
||||||
|
const l: Plan[] = [];
|
||||||
|
for (let i = 0; i < p.times; ++i) {
|
||||||
|
l.push({ ...p, times: 1, start: d + p.period * i });
|
||||||
|
}
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const plandbpre: Record<string, Partial<Plan>> = {
|
||||||
|
r1_order: {
|
||||||
|
name: "采购 R1",
|
||||||
|
start: -30,
|
||||||
|
period: 30,
|
||||||
|
next: [{
|
||||||
|
name: "收取 R1",
|
||||||
|
price: 12,
|
||||||
|
start: 30,
|
||||||
|
period: 0,
|
||||||
|
role: "r1",
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
r2_order: {
|
||||||
|
name: "采购 R2",
|
||||||
|
start: -30,
|
||||||
|
period: 30,
|
||||||
|
next: [{
|
||||||
|
name: "收取 R2",
|
||||||
|
price: 12,
|
||||||
|
start: 30,
|
||||||
|
period: 0,
|
||||||
|
role: "r2",
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
r3_order: {
|
||||||
|
name: "采购 R3",
|
||||||
|
start: -60,
|
||||||
|
period: 60,
|
||||||
|
next: [{
|
||||||
|
name: "收取 R3",
|
||||||
|
price: 12,
|
||||||
|
start: 60,
|
||||||
|
period: 0,
|
||||||
|
role: "r3",
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
r4_order: {
|
||||||
|
name: "采购 R4",
|
||||||
|
start: -60,
|
||||||
|
period: 60,
|
||||||
|
next: [{
|
||||||
|
name: "收取 R4",
|
||||||
|
price: 12,
|
||||||
|
start: 60,
|
||||||
|
period: 0,
|
||||||
|
role: "r4",
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const storage_keys: Record<string, string> = {
|
||||||
|
// factory: "工厂",
|
||||||
|
// linea: "自动线",
|
||||||
|
// optimizea: "自动线技改",
|
||||||
|
p1: "P1",
|
||||||
|
p2: "P2",
|
||||||
|
p3: "P3",
|
||||||
|
p4: "P4",
|
||||||
|
p5: "P5",
|
||||||
|
r1: "R1",
|
||||||
|
r2: "R2",
|
||||||
|
r3: "R3",
|
||||||
|
r4: "R4",
|
||||||
|
r5: "R5",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const plandb: Record<string, Partial<Plan>> = {
|
||||||
|
ads: {
|
||||||
|
name: "广告费",
|
||||||
|
price: 80,
|
||||||
|
},
|
||||||
|
loan: {
|
||||||
|
name: "贷款",
|
||||||
|
price: 0,
|
||||||
|
start: 0,
|
||||||
|
role: "loan",
|
||||||
|
},
|
||||||
|
loanback: {
|
||||||
|
name: "还贷",
|
||||||
|
price: 0,
|
||||||
|
start: 0,
|
||||||
|
role: "-loan",
|
||||||
|
},
|
||||||
|
strategy_ads: {
|
||||||
|
name: "战略广告",
|
||||||
|
price: 20,
|
||||||
|
start: datefrom(1, 12, 30),
|
||||||
|
period: 360,
|
||||||
|
times: 5,
|
||||||
|
},
|
||||||
|
iso9000: {
|
||||||
|
name: "ISO9000资质开发",
|
||||||
|
price: 10,
|
||||||
|
times: 2,
|
||||||
|
period: 360,
|
||||||
|
nextonce: true,
|
||||||
|
next: [{
|
||||||
|
name: "ISO9000资质开发完成",
|
||||||
|
price: 0,
|
||||||
|
start: datefrom(3, 1, 1),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
iso14000: {
|
||||||
|
name: "ISO14000资质开发",
|
||||||
|
price: 10,
|
||||||
|
times: 3,
|
||||||
|
period: 360,
|
||||||
|
nextonce: true,
|
||||||
|
next: [{
|
||||||
|
name: "ISO14000资质开发完成",
|
||||||
|
price: 0,
|
||||||
|
start: datefrom(4, 1, 1),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
market_domestic: {
|
||||||
|
name: "国内市场资质开发",
|
||||||
|
price: 10,
|
||||||
|
times: 2,
|
||||||
|
period: 360,
|
||||||
|
nextonce: true,
|
||||||
|
next: [{
|
||||||
|
name: "国内市场资质开发完成",
|
||||||
|
price: 0,
|
||||||
|
start: datefrom(3, 1, 1),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
market_asia: {
|
||||||
|
name: "亚洲市场资质开发",
|
||||||
|
price: 10,
|
||||||
|
times: 3,
|
||||||
|
period: 360,
|
||||||
|
nextonce: true,
|
||||||
|
next: [{
|
||||||
|
name: "亚洲市场资质开发完成",
|
||||||
|
price: 0,
|
||||||
|
start: datefrom(4, 1, 1),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
market_intl: {
|
||||||
|
name: "国际市场资质开发",
|
||||||
|
price: 10,
|
||||||
|
times: 4,
|
||||||
|
period: 360,
|
||||||
|
nextonce: true,
|
||||||
|
next: [{
|
||||||
|
name: "国际市场资质开发完成",
|
||||||
|
price: 0,
|
||||||
|
start: datefrom(5, 1, 1),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
rent_factory: {
|
||||||
|
name: "租用厂房",
|
||||||
|
price: 40,
|
||||||
|
times: 4,
|
||||||
|
period: 360,
|
||||||
|
role: "factory",
|
||||||
|
},
|
||||||
|
p1_cert: {
|
||||||
|
name: "P1 资质开发",
|
||||||
|
price: 10,
|
||||||
|
period: 30,
|
||||||
|
nextonce: true,
|
||||||
|
next: [{
|
||||||
|
name: "P1 资质拥有",
|
||||||
|
start: 30,
|
||||||
|
price: 0,
|
||||||
|
period: 0,
|
||||||
|
role: "p1cert",
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
p2_cert: {
|
||||||
|
name: "P2 资质开发",
|
||||||
|
price: 10,
|
||||||
|
period: 30,
|
||||||
|
times: 2,
|
||||||
|
nextonce: true,
|
||||||
|
next: [{
|
||||||
|
name: "P2 资质拥有",
|
||||||
|
start: 60,
|
||||||
|
price: 0,
|
||||||
|
period: 0,
|
||||||
|
role: "p2cert",
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
p4_cert: {
|
||||||
|
name: "P4 资质开发",
|
||||||
|
price: 10,
|
||||||
|
period: 60,
|
||||||
|
times: 4,
|
||||||
|
nextonce: true,
|
||||||
|
next: [{
|
||||||
|
name: "P4 资质拥有",
|
||||||
|
start: 4*60,
|
||||||
|
price: 0,
|
||||||
|
period: 0,
|
||||||
|
role: "p4cert",
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
build_line_manual: {
|
||||||
|
name: "建设手工线",
|
||||||
|
price: 50,
|
||||||
|
period: 0,
|
||||||
|
role: "linem",
|
||||||
|
},
|
||||||
|
build_line_auto: {
|
||||||
|
name: "建设自动线",
|
||||||
|
times: 3,
|
||||||
|
price: 50,
|
||||||
|
period: 30,
|
||||||
|
role: "linea",
|
||||||
|
},
|
||||||
|
optimize_auto: {
|
||||||
|
name: "自动线技改",
|
||||||
|
times: 1,
|
||||||
|
price: 20,
|
||||||
|
period: 20,
|
||||||
|
role: "optimizea",
|
||||||
|
},
|
||||||
|
p1_produce: {
|
||||||
|
name: "P1 生产",
|
||||||
|
times: 30,
|
||||||
|
price: 9,
|
||||||
|
period: 0,
|
||||||
|
role: "-r1",
|
||||||
|
next: [plandbpre.r1_order, {
|
||||||
|
name: "收取 P1",
|
||||||
|
times: 1,
|
||||||
|
start: 56,
|
||||||
|
price: 0,
|
||||||
|
period: 0,
|
||||||
|
role: "p1",
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
p2_produce: {
|
||||||
|
name: "P2 生产",
|
||||||
|
times: 30,
|
||||||
|
price: 9,
|
||||||
|
period: 0,
|
||||||
|
role: "-r2,-r3",
|
||||||
|
next: [plandbpre.r2_order, plandbpre.r3_order, {
|
||||||
|
name: "收取 P2",
|
||||||
|
times: 1,
|
||||||
|
start: 56,
|
||||||
|
price: 0,
|
||||||
|
period: 0,
|
||||||
|
role: "p2",
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
p4_produce: {
|
||||||
|
name: "P4 生产",
|
||||||
|
times: 30,
|
||||||
|
price: 9,
|
||||||
|
period: 0,
|
||||||
|
role: "-r2,-r3,-r4,-r4",
|
||||||
|
next: [
|
||||||
|
plandbpre.r2_order,
|
||||||
|
plandbpre.r3_order,
|
||||||
|
bplan(plandbpre.r4_order, { amount: 2 }), {
|
||||||
|
name: "收取 P4",
|
||||||
|
times: 1,
|
||||||
|
start: 56,
|
||||||
|
price: 0,
|
||||||
|
period: 0,
|
||||||
|
role: "p4",
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
p2_sell: {
|
||||||
|
name: "卖出 P2",
|
||||||
|
price: 0,
|
||||||
|
role: "-p2",
|
||||||
|
next: [{
|
||||||
|
name: "收到 P2 账款",
|
||||||
|
price: -52,
|
||||||
|
start: 30,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
p4_sell: {
|
||||||
|
name: "卖出 P4",
|
||||||
|
price: 0,
|
||||||
|
amount: 1,
|
||||||
|
role: "-p4",
|
||||||
|
next: [{
|
||||||
|
name: "收到 P4 账款",
|
||||||
|
price: -91,
|
||||||
|
start: 30,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
};
|
44
entemu/deno.json
Normal file
44
entemu/deno.json
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx",
|
||||||
|
"dev": "deno run -A --watch=static/,routes/ dev.ts",
|
||||||
|
"build": "deno run -A dev.ts build",
|
||||||
|
"start": "deno run -A main.ts",
|
||||||
|
"update": "deno run -A -r jsr:@fresh/update ."
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"rules": {
|
||||||
|
"tags": [
|
||||||
|
"fresh",
|
||||||
|
"recommended"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"**/_fresh/*"
|
||||||
|
],
|
||||||
|
"imports": {
|
||||||
|
"fresh": "jsr:@fresh/core@^2.0.0-alpha.25",
|
||||||
|
"@fresh/plugin-tailwind": "jsr:@fresh/plugin-tailwind@^0.0.1-alpha.7",
|
||||||
|
"preact": "npm:preact@^10.24.3",
|
||||||
|
"@preact/signals": "npm:@preact/signals@^1.3.0"
|
||||||
|
},
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.asynciterable",
|
||||||
|
"dom.iterable",
|
||||||
|
"deno.ns"
|
||||||
|
],
|
||||||
|
"jsx": "precompile",
|
||||||
|
"jsxImportSource": "preact",
|
||||||
|
"jsxPrecompileSkipElements": [
|
||||||
|
"a",
|
||||||
|
"img",
|
||||||
|
"source",
|
||||||
|
"body",
|
||||||
|
"html",
|
||||||
|
"head"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
229
entemu/deno.lock
Normal file
229
entemu/deno.lock
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
{
|
||||||
|
"version": "4",
|
||||||
|
"specifiers": {
|
||||||
|
"jsr:@fresh/core@^2.0.0-alpha.25": "2.0.0-alpha.25",
|
||||||
|
"jsr:@luca/esbuild-deno-loader@0.11": "0.11.0",
|
||||||
|
"jsr:@std/bytes@^1.0.2": "1.0.4",
|
||||||
|
"jsr:@std/crypto@1": "1.0.3",
|
||||||
|
"jsr:@std/datetime@~0.225.2": "0.225.2",
|
||||||
|
"jsr:@std/encoding@1": "1.0.5",
|
||||||
|
"jsr:@std/encoding@^1.0.5": "1.0.5",
|
||||||
|
"jsr:@std/fmt@1": "1.0.3",
|
||||||
|
"jsr:@std/fs@1": "1.0.6",
|
||||||
|
"jsr:@std/html@1": "1.0.3",
|
||||||
|
"jsr:@std/jsonc@1": "1.0.1",
|
||||||
|
"jsr:@std/media-types@1": "1.1.0",
|
||||||
|
"jsr:@std/path@1": "1.0.8",
|
||||||
|
"jsr:@std/path@^1.0.6": "1.0.8",
|
||||||
|
"jsr:@std/path@^1.0.8": "1.0.8",
|
||||||
|
"jsr:@std/semver@1": "1.0.3",
|
||||||
|
"npm:@preact/signals@^1.2.3": "1.3.1_preact@10.25.1",
|
||||||
|
"npm:@preact/signals@^1.3.0": "1.3.1_preact@10.25.1",
|
||||||
|
"npm:esbuild-wasm@0.23.1": "0.23.1",
|
||||||
|
"npm:esbuild@0.23.1": "0.23.1",
|
||||||
|
"npm:preact-render-to-string@^6.5.11": "6.5.11_preact@10.25.1",
|
||||||
|
"npm:preact@^10.24.1": "10.25.1",
|
||||||
|
"npm:preact@^10.24.3": "10.25.1"
|
||||||
|
},
|
||||||
|
"jsr": {
|
||||||
|
"@fresh/core@2.0.0-alpha.25": {
|
||||||
|
"integrity": "1069232989c4bc7f69ad424f6b97cdba1a7631d1307e1c2964aed748cd0cf74b",
|
||||||
|
"dependencies": [
|
||||||
|
"jsr:@luca/esbuild-deno-loader",
|
||||||
|
"jsr:@std/crypto",
|
||||||
|
"jsr:@std/datetime",
|
||||||
|
"jsr:@std/encoding@1",
|
||||||
|
"jsr:@std/fmt",
|
||||||
|
"jsr:@std/fs",
|
||||||
|
"jsr:@std/html",
|
||||||
|
"jsr:@std/jsonc",
|
||||||
|
"jsr:@std/media-types",
|
||||||
|
"jsr:@std/path@1",
|
||||||
|
"jsr:@std/semver",
|
||||||
|
"npm:@preact/signals@^1.2.3",
|
||||||
|
"npm:esbuild",
|
||||||
|
"npm:esbuild-wasm",
|
||||||
|
"npm:preact-render-to-string",
|
||||||
|
"npm:preact@^10.24.1",
|
||||||
|
"npm:preact@^10.24.3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@luca/esbuild-deno-loader@0.11.0": {
|
||||||
|
"integrity": "c05a989aa7c4ee6992a27be5f15cfc5be12834cab7ff84cabb47313737c51a2c",
|
||||||
|
"dependencies": [
|
||||||
|
"jsr:@std/bytes",
|
||||||
|
"jsr:@std/encoding@^1.0.5",
|
||||||
|
"jsr:@std/path@^1.0.6"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@std/bytes@1.0.4": {
|
||||||
|
"integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc"
|
||||||
|
},
|
||||||
|
"@std/crypto@1.0.3": {
|
||||||
|
"integrity": "a2a32f51ddef632d299e3879cd027c630dcd4d1d9a5285d6e6788072f4e51e7f"
|
||||||
|
},
|
||||||
|
"@std/datetime@0.225.2": {
|
||||||
|
"integrity": "45f0100554a912cd65f48089ef0a33aa1eb6ea21f08090840b539ab582827eaa"
|
||||||
|
},
|
||||||
|
"@std/encoding@1.0.5": {
|
||||||
|
"integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04"
|
||||||
|
},
|
||||||
|
"@std/fmt@1.0.3": {
|
||||||
|
"integrity": "97765c16aa32245ff4e2204ecf7d8562496a3cb8592340a80e7e554e0bb9149f"
|
||||||
|
},
|
||||||
|
"@std/fs@1.0.6": {
|
||||||
|
"integrity": "42b56e1e41b75583a21d5a37f6a6a27de9f510bcd36c0c85791d685ca0b85fa2",
|
||||||
|
"dependencies": [
|
||||||
|
"jsr:@std/path@^1.0.8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@std/html@1.0.3": {
|
||||||
|
"integrity": "7a0ac35e050431fb49d44e61c8b8aac1ebd55937e0dc9ec6409aa4bab39a7988"
|
||||||
|
},
|
||||||
|
"@std/jsonc@1.0.1": {
|
||||||
|
"integrity": "6b36956e2a7cbb08ca5ad7fbec72e661e6217c202f348496ea88747636710dda"
|
||||||
|
},
|
||||||
|
"@std/media-types@1.1.0": {
|
||||||
|
"integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4"
|
||||||
|
},
|
||||||
|
"@std/path@1.0.8": {
|
||||||
|
"integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be"
|
||||||
|
},
|
||||||
|
"@std/semver@1.0.3": {
|
||||||
|
"integrity": "7c139c6076a080eeaa4252c78b95ca5302818d7eafab0470d34cafd9930c13c8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"npm": {
|
||||||
|
"@esbuild/aix-ppc64@0.23.1": {
|
||||||
|
"integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ=="
|
||||||
|
},
|
||||||
|
"@esbuild/android-arm64@0.23.1": {
|
||||||
|
"integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw=="
|
||||||
|
},
|
||||||
|
"@esbuild/android-arm@0.23.1": {
|
||||||
|
"integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ=="
|
||||||
|
},
|
||||||
|
"@esbuild/android-x64@0.23.1": {
|
||||||
|
"integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg=="
|
||||||
|
},
|
||||||
|
"@esbuild/darwin-arm64@0.23.1": {
|
||||||
|
"integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q=="
|
||||||
|
},
|
||||||
|
"@esbuild/darwin-x64@0.23.1": {
|
||||||
|
"integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw=="
|
||||||
|
},
|
||||||
|
"@esbuild/freebsd-arm64@0.23.1": {
|
||||||
|
"integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA=="
|
||||||
|
},
|
||||||
|
"@esbuild/freebsd-x64@0.23.1": {
|
||||||
|
"integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-arm64@0.23.1": {
|
||||||
|
"integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-arm@0.23.1": {
|
||||||
|
"integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-ia32@0.23.1": {
|
||||||
|
"integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-loong64@0.23.1": {
|
||||||
|
"integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-mips64el@0.23.1": {
|
||||||
|
"integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-ppc64@0.23.1": {
|
||||||
|
"integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-riscv64@0.23.1": {
|
||||||
|
"integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-s390x@0.23.1": {
|
||||||
|
"integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw=="
|
||||||
|
},
|
||||||
|
"@esbuild/linux-x64@0.23.1": {
|
||||||
|
"integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ=="
|
||||||
|
},
|
||||||
|
"@esbuild/netbsd-x64@0.23.1": {
|
||||||
|
"integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA=="
|
||||||
|
},
|
||||||
|
"@esbuild/openbsd-arm64@0.23.1": {
|
||||||
|
"integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q=="
|
||||||
|
},
|
||||||
|
"@esbuild/openbsd-x64@0.23.1": {
|
||||||
|
"integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA=="
|
||||||
|
},
|
||||||
|
"@esbuild/sunos-x64@0.23.1": {
|
||||||
|
"integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA=="
|
||||||
|
},
|
||||||
|
"@esbuild/win32-arm64@0.23.1": {
|
||||||
|
"integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A=="
|
||||||
|
},
|
||||||
|
"@esbuild/win32-ia32@0.23.1": {
|
||||||
|
"integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ=="
|
||||||
|
},
|
||||||
|
"@esbuild/win32-x64@0.23.1": {
|
||||||
|
"integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg=="
|
||||||
|
},
|
||||||
|
"@preact/signals-core@1.8.0": {
|
||||||
|
"integrity": "sha512-OBvUsRZqNmjzCZXWLxkZfhcgT+Fk8DDcT/8vD6a1xhDemodyy87UJRJfASMuSD8FaAIeGgGm85ydXhm7lr4fyA=="
|
||||||
|
},
|
||||||
|
"@preact/signals@1.3.1_preact@10.25.1": {
|
||||||
|
"integrity": "sha512-nNvSF2O7RDzxp1Rm7SkA5QhN1a2kN8pGE8J5o6UjgDof0F0Vlg6d6HUUVxxqZ1uJrN9xnH2DpL6rpII3Es0SsQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"@preact/signals-core",
|
||||||
|
"preact"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"esbuild-wasm@0.23.1": {
|
||||||
|
"integrity": "sha512-L3vn7ctvBrtScRfoB0zG1eOCiV4xYvpLYWfe6PDZuV+iDFDm4Mt3xeLIDllG8cDHQ8clUouK3XekulE+cxgkgw=="
|
||||||
|
},
|
||||||
|
"esbuild@0.23.1": {
|
||||||
|
"integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
|
||||||
|
"dependencies": [
|
||||||
|
"@esbuild/aix-ppc64",
|
||||||
|
"@esbuild/android-arm",
|
||||||
|
"@esbuild/android-arm64",
|
||||||
|
"@esbuild/android-x64",
|
||||||
|
"@esbuild/darwin-arm64",
|
||||||
|
"@esbuild/darwin-x64",
|
||||||
|
"@esbuild/freebsd-arm64",
|
||||||
|
"@esbuild/freebsd-x64",
|
||||||
|
"@esbuild/linux-arm",
|
||||||
|
"@esbuild/linux-arm64",
|
||||||
|
"@esbuild/linux-ia32",
|
||||||
|
"@esbuild/linux-loong64",
|
||||||
|
"@esbuild/linux-mips64el",
|
||||||
|
"@esbuild/linux-ppc64",
|
||||||
|
"@esbuild/linux-riscv64",
|
||||||
|
"@esbuild/linux-s390x",
|
||||||
|
"@esbuild/linux-x64",
|
||||||
|
"@esbuild/netbsd-x64",
|
||||||
|
"@esbuild/openbsd-arm64",
|
||||||
|
"@esbuild/openbsd-x64",
|
||||||
|
"@esbuild/sunos-x64",
|
||||||
|
"@esbuild/win32-arm64",
|
||||||
|
"@esbuild/win32-ia32",
|
||||||
|
"@esbuild/win32-x64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"preact-render-to-string@6.5.11_preact@10.25.1": {
|
||||||
|
"integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==",
|
||||||
|
"dependencies": [
|
||||||
|
"preact"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"preact@10.25.1": {
|
||||||
|
"integrity": "sha512-frxeZV2vhQSohQwJ7FvlqC40ze89+8friponWUFeVEkaCfhC6Eu4V0iND5C9CXz8JLndV07QRDeXzH1+Anz5Og=="
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"workspace": {
|
||||||
|
"dependencies": [
|
||||||
|
"jsr:@fresh/core@^2.0.0-alpha.25",
|
||||||
|
"jsr:@fresh/plugin-tailwind@^0.0.1-alpha.7",
|
||||||
|
"npm:@preact/signals@^1.3.0",
|
||||||
|
"npm:preact@^10.24.3"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
15
entemu/dev.ts
Normal file
15
entemu/dev.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/env -S deno run -A --watch=static/,routes/
|
||||||
|
|
||||||
|
import { Builder } from "fresh/dev";
|
||||||
|
import { app } from "./main.ts";
|
||||||
|
|
||||||
|
const builder = new Builder();
|
||||||
|
|
||||||
|
if (Deno.args.includes("build")) {
|
||||||
|
await builder.build(app);
|
||||||
|
} else {
|
||||||
|
await builder.listen(app, {
|
||||||
|
hostname: Deno.env.get("junkhost") || "127.0.0.1",
|
||||||
|
port: parseInt(Deno.env.get("junkport") || "11001"),
|
||||||
|
});
|
||||||
|
}
|
161
entemu/islands/Calendar.tsx
Normal file
161
entemu/islands/Calendar.tsx
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
import { useComputed, type Signal } from "@preact/signals";
|
||||||
|
import { addplan, bplan, IPlan, Plan, storage_keys } from "../common/plan.ts";
|
||||||
|
import { datefmt, datefrom } from "../common/date.ts";
|
||||||
|
|
||||||
|
export interface CalendarProps {
|
||||||
|
date: Signal<number>;
|
||||||
|
plans: Signal<Plan[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const casht_a = -800;
|
||||||
|
const casht_b = -950;
|
||||||
|
|
||||||
|
const calendar_span = 30;
|
||||||
|
|
||||||
|
const calendar_days = new Array(calendar_span).fill(0).map((_, i) => i);
|
||||||
|
|
||||||
|
export default function Calendar({ date, plans }: CalendarProps) {
|
||||||
|
const _calendar = useComputed(() => {
|
||||||
|
// cal
|
||||||
|
let e: number[] = [];
|
||||||
|
const cal: Record<number, IPlan[]> = {};
|
||||||
|
for (const p of plans.value) {
|
||||||
|
e = e.concat(addplan(cal, p));
|
||||||
|
}
|
||||||
|
e = Array.from(new Set(e)).sort((a, b) => a - b);
|
||||||
|
let c = 600;
|
||||||
|
let current = 600;
|
||||||
|
let alert = -1;
|
||||||
|
let die = -1;
|
||||||
|
/*
|
||||||
|
const cal_ex: Record<number, IPlan[]> = {};
|
||||||
|
const e_ex: number[] = [];
|
||||||
|
*/
|
||||||
|
// cash
|
||||||
|
for (const d of e) {
|
||||||
|
for (const p of cal[d]) {
|
||||||
|
c -= p.cost;
|
||||||
|
p.current = c;
|
||||||
|
if (alert === -1 && c < casht_a && d < datefrom(6, 1, 1))
|
||||||
|
alert = d;
|
||||||
|
if (die === -1 && c < casht_b && d < datefrom(6, 1, 1))
|
||||||
|
die = d;
|
||||||
|
/*
|
||||||
|
if (c < 0) {
|
||||||
|
e_ex.push(...(addplan(cal_ex, bplan({
|
||||||
|
name: "借款",
|
||||||
|
start: d,
|
||||||
|
price: -p.cost,
|
||||||
|
})).concat(addplan(cal_ex, bplan({
|
||||||
|
name: "还贷",
|
||||||
|
start: d + 360,
|
||||||
|
price: p.cost * 1.05,
|
||||||
|
})))));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
if (d < date.value + 30) {
|
||||||
|
current = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
for (const k in cal_ex) {
|
||||||
|
if (cal[k])
|
||||||
|
cal[k].push(...cal_ex[k]);
|
||||||
|
else
|
||||||
|
cal[k] = cal_ex[k];
|
||||||
|
}
|
||||||
|
e.push(...e_ex);
|
||||||
|
*/
|
||||||
|
return {
|
||||||
|
cal: cal,
|
||||||
|
e: e,
|
||||||
|
cash: current,
|
||||||
|
alert: alert,
|
||||||
|
die: die,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const calendar = useComputed(() => _calendar.value.cal);
|
||||||
|
const event_days = useComputed(() => _calendar.value.e);
|
||||||
|
const cash = useComputed(() => _calendar.value.cash);
|
||||||
|
const date_alert = useComputed(() => _calendar.value.alert);
|
||||||
|
const date_die = useComputed(() => _calendar.value.die);
|
||||||
|
const storage = useComputed(() => {
|
||||||
|
const st: Record<string, number> = {};
|
||||||
|
for (const d of event_days.value) {
|
||||||
|
if (d >= date.value + 30)
|
||||||
|
break;
|
||||||
|
for (const p of calendar.value[d]) {
|
||||||
|
if (!p.plan.role) continue;
|
||||||
|
for (let role of p.plan.role.split(",")) {
|
||||||
|
let amount = p.plan.amount;
|
||||||
|
// if (!role) continue;
|
||||||
|
if (role[0] === "-") {
|
||||||
|
amount = -amount;
|
||||||
|
role = role.slice(1);
|
||||||
|
}
|
||||||
|
if (st[role] === undefined)
|
||||||
|
st[role] = 0;
|
||||||
|
st[role] += amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return st;
|
||||||
|
});
|
||||||
|
let last_date = -1;
|
||||||
|
return <div class="calendar">
|
||||||
|
<div class="calendar__calendar">
|
||||||
|
<div class="calender__cash">¥{cash.value}</div>
|
||||||
|
<div class="calendar__head">
|
||||||
|
<button onClick={() => date.value -= 360}><<<</button>
|
||||||
|
<button onClick={() => date.value -= 90}><<</button>
|
||||||
|
<button onClick={() => date.value -= 30}><</button>
|
||||||
|
<span class="calendar__date">{datefmt(date.value)}</span>
|
||||||
|
<button onClick={() => date.value += 30}>></button>
|
||||||
|
<button onClick={() => date.value += 90}>>></button>
|
||||||
|
<button onClick={() => date.value += 360}>>>></button>
|
||||||
|
</div>
|
||||||
|
<div class="calendar__days">
|
||||||
|
{calendar_days.map(d => <span class={calendar.value[d + date.value] ? "p" : ""}>{d + 1}</span>)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="calendar__alert" hidden>
|
||||||
|
<p>{date_alert.value === -1 ? "" : "资金周转风险 (现金 < " + casht_a + "): " + datefmt(date_alert.value)}</p>
|
||||||
|
<p>{date_die.value === -1 ? "" : "资金链断裂 (现金 < " + casht_b + "): " + datefmt(date_die.value)}</p>
|
||||||
|
</div>
|
||||||
|
<table class="calendar__storage">
|
||||||
|
<thead>
|
||||||
|
<tr><td>库存</td><td>数量</td></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{Object.entries(storage.value).filter(st => storage_keys[st[0]] && st[1]).map(st => <tr><td>{storage_keys[st[0]]}</td><td>{st[1]}</td></tr>)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<table class="calendar__events" cellSpacing="0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>日期</td>
|
||||||
|
<td>描述</td>
|
||||||
|
<td>数量</td>
|
||||||
|
<td>次序</td>
|
||||||
|
<td>收支</td>
|
||||||
|
<td>余额</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{calendar_days.map(d => d + date.value).filter(d => calendar.value[d]).map(d => calendar.value[d].map(p => {
|
||||||
|
const e = <tr>
|
||||||
|
<td>{last_date === p.date ? "" : datefmt(p.date)}</td>
|
||||||
|
<td>{p.plan.name}</td>
|
||||||
|
<td>{p.plan.amount}</td>
|
||||||
|
<td>{p.plan.times === 1 ? "1" : "" + (p.order + 1) + " / " + p.plan.times}</td>
|
||||||
|
<td>{p.cost !== 0 ? "¥" + (-p.cost) : ""}</td>
|
||||||
|
<td>¥{p.current}</td>
|
||||||
|
</tr>
|
||||||
|
last_date = p.date;
|
||||||
|
return e;
|
||||||
|
}))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>;
|
||||||
|
}
|
35
entemu/islands/PlanDB.tsx
Normal file
35
entemu/islands/PlanDB.tsx
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { useSignal, type Signal } from "@preact/signals";
|
||||||
|
import { bplan, dplan, Plan, plandb } from "../common/plan.ts";
|
||||||
|
|
||||||
|
export interface PlanDBProps {
|
||||||
|
date: Signal<number>;
|
||||||
|
plans: Signal<Plan[]>;
|
||||||
|
cplan: Signal<Plan>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlanDisplayProps {
|
||||||
|
plan: Plan;
|
||||||
|
add?: (plan: Plan) => any;
|
||||||
|
modify?: (plan: Plan) => any;
|
||||||
|
del?: (plan: Plan) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PlanDisplay({ plan, add, modify, del }: PlanDisplayProps) {
|
||||||
|
const cplan: Signal<Plan> = useSignal({ ...dplan, ...plan });
|
||||||
|
return <div class="plan">
|
||||||
|
<pre>{JSON.stringify(cplan.value)}</pre>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PlanDB({ date, plans, cplan }: PlanDBProps) {
|
||||||
|
return <div class="plandb">
|
||||||
|
<div>所有</div>
|
||||||
|
<div class="plandb_current">
|
||||||
|
{Object.entries(plans.value).map(([k, v]) => <PlanDisplay plan={v} />)}
|
||||||
|
</div>
|
||||||
|
<div>预设</div>
|
||||||
|
<div class="plandb__presets">
|
||||||
|
{Object.entries(plandb).map(([k, v]) => <PlanDisplay plan={bplan(v)} />)}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
72
entemu/islands/Planner.tsx
Normal file
72
entemu/islands/Planner.tsx
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import { type Signal, useSignal } from "@preact/signals";
|
||||||
|
import { bplan, Plan, plandb } from "../common/plan.ts";
|
||||||
|
import Calendar from "./Calendar.tsx";
|
||||||
|
import { datefrom } from "../common/date.ts";
|
||||||
|
|
||||||
|
export default function Planner() {
|
||||||
|
const date = useSignal(self.localStorage ? parseInt(localStorage.getItem("entemu.date") || "0") : 0);
|
||||||
|
date.subscribe(d => self.localStorage ? localStorage.setItem("entemu.date", d.toString()) : void 0);
|
||||||
|
const plans: Signal<Plan[]> = useSignal([
|
||||||
|
// base
|
||||||
|
bplan(plandb.iso9000),
|
||||||
|
bplan(plandb.iso14000),
|
||||||
|
bplan(plandb.market_domestic),
|
||||||
|
bplan(plandb.market_asia),
|
||||||
|
bplan({ name: "管理费", times: 5*12*30, period: 30, price: 5 }),
|
||||||
|
bplan({ name: "维修费", start: datefrom(2, 4, 1), times: 4, amount: 4, period: 360, price: 15 }),
|
||||||
|
|
||||||
|
// start 1
|
||||||
|
bplan(plandb.ads),
|
||||||
|
bplan(plandb.strategy_ads),
|
||||||
|
bplan(plandb.rent_factory, { times: 4, amount: 1 }),
|
||||||
|
// bplan(plandb.p1_cert),
|
||||||
|
bplan(plandb.p2_cert),
|
||||||
|
bplan(plandb.p4_cert),
|
||||||
|
bplan(plandb.build_line_auto, { amount: 4 }),
|
||||||
|
bplan(plandb.optimize_auto, { start: datefrom(1, 4, 1), amount: 4 }),
|
||||||
|
// bplan(plandb.loan, { price: -500, start: datefrom(1, 3, 1) }),
|
||||||
|
// bplan(plandb.loanback, { price: 525, start: datefrom(1, 11, 1) }),
|
||||||
|
// bplan(plandb.loan, { price: -30, start: datefrom(1, 6, 17) }),
|
||||||
|
// bplan(plandb.loanback, { price: 31.5, start: datefrom(2, 6, 17) }),
|
||||||
|
|
||||||
|
// bplan(plandb.loan, { price: -10, start: datefrom(1, 7, 1) }),
|
||||||
|
// bplan(plandb.loanback, { price: 10.5, start: datefrom(2, 7, 1) }),
|
||||||
|
|
||||||
|
// bplan(plandb.loan, { price: -40, start: datefrom(1, 8, 13) }),
|
||||||
|
// bplan(plandb.loanback, { price: 42, start: datefrom(2, 8, 13) }),
|
||||||
|
|
||||||
|
bplan(plandb.loan, { price: -480-360, start: datefrom(1, 12, 30) }),
|
||||||
|
bplan(plandb.loanback, { price: 480*1.1 + 360*1.05, start: datefrom(3, 1, 1) }),
|
||||||
|
|
||||||
|
bplan(plandb, { name: "贴现", price: 40-40*1.1, start: datefrom(2, 1, 1) }),
|
||||||
|
|
||||||
|
bplan(plandb.p4_sell, { name: "贱卖 P4", start: datefrom(1, 11, 1), price: -50, role: "-p4" }),
|
||||||
|
|
||||||
|
// prod 1
|
||||||
|
// bplan(plandb.p1_produce, { start: 110, period: 56, amount: 30 }),
|
||||||
|
bplan(plandb.p2_produce, { start: datefrom(1, 4, 21), period: 56, times: 30, amount: 3 }),
|
||||||
|
bplan(plandb.p4_produce, { start: datefrom(1, 9, 1), period: 56, times: 30 }),
|
||||||
|
|
||||||
|
bplan(plandb.p2_sell, { start: datefrom(1, 7, 15), amount: 3, price: 0, next: [{ name: "收到 P2 账款", price: -60, start: 30, }] }),
|
||||||
|
bplan(plandb.p2_sell, { start: datefrom(1, 12, 21), amount: 9, price: 0, next: [{ name: "收到 P2 账款", price: -54, start: 30, }] }),
|
||||||
|
|
||||||
|
// year 2
|
||||||
|
bplan(plandb.ad),
|
||||||
|
bplan(plandb.p2_sell, { start: datefrom(2, 3, 27), amount: 6, next: [{ name: "收到 P2 账款", price: -51, start: 30, }] }),
|
||||||
|
bplan(plandb.p2_sell, { start: datefrom(2, 7, 19), amount: 6, next: [{ name: "收到 P2 账款", price: -58, start: 30, }] }),
|
||||||
|
|
||||||
|
bplan(plandb.p4_sell, { start: datefrom(2, 2, 19), amount: 2, next: [{ name: "收到 P4 账款", price: -92, start: 30, }] }),
|
||||||
|
bplan(plandb.p4_sell, { start: datefrom(2, 6, 11), amount: 2, next: [{ name: "收到 P4 账款", price: -91, start: 30, }] }),
|
||||||
|
|
||||||
|
// year 3
|
||||||
|
bplan(plandb.p2_sell, { start: datefrom(3, 11, 19), amount: 25, next: [{ name: "收到 P2 账款", price: -58, start: 30, }] }),
|
||||||
|
bplan(plandb.p4_sell, { start: datefrom(3, 11, 19), amount: 8, next: [{ name: "收到 P4 账款", price: -92, start: 30, }] }),
|
||||||
|
|
||||||
|
// year 4
|
||||||
|
bplan(plandb.p2_sell, { start: datefrom(4, 12, 21), amount: 19, next: [{ name: "收到 P2 账款", price: -58, start: 30, }] }),
|
||||||
|
bplan(plandb.p4_sell, { start: datefrom(4, 12, 21), amount: 8, next: [{ name: "收到 P4 账款", price: -92, start: 30, }] }),
|
||||||
|
]);
|
||||||
|
return <div>
|
||||||
|
<Calendar date={date} plans={plans} />
|
||||||
|
</div>;
|
||||||
|
}
|
8
entemu/junkrc
Executable file
8
entemu/junkrc
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/sh
|
||||||
|
#;proxy=http://localhost:11001
|
||||||
|
export deno=/mnt/data/app/deno
|
||||||
|
export junkhost=127.0.0.1
|
||||||
|
export junkport=11001
|
||||||
|
|
||||||
|
$deno task build
|
||||||
|
exec $deno run --allow-read --allow-net --allow-env main.ts
|
33
entemu/main.ts
Normal file
33
entemu/main.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { App, fsRoutes, staticFiles } from "fresh";
|
||||||
|
import { define, type State } from "./utils.ts";
|
||||||
|
|
||||||
|
export const app = new App<State>();
|
||||||
|
app.use(staticFiles());
|
||||||
|
|
||||||
|
// this is the same as the /api/:name route defined via a file. feel free to delete this!
|
||||||
|
app.get("/api2/:name", (ctx) => {
|
||||||
|
const name = ctx.params.name;
|
||||||
|
return new Response(
|
||||||
|
`Hello, ${name.charAt(0).toUpperCase() + name.slice(1)}!`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// this can also be defined via a file. feel free to delete this!
|
||||||
|
const exampleLoggerMiddleware = define.middleware((ctx) => {
|
||||||
|
console.log(`${ctx.req.method} ${ctx.req.url}`);
|
||||||
|
return ctx.next();
|
||||||
|
});
|
||||||
|
app.use(exampleLoggerMiddleware);
|
||||||
|
|
||||||
|
await fsRoutes(app, {
|
||||||
|
dir: "./",
|
||||||
|
loadIsland: (path) => import(`./islands/${path}`),
|
||||||
|
loadRoute: (path) => import(`./routes/${path}`),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (import.meta.main) {
|
||||||
|
await app.listen({
|
||||||
|
hostname: Deno.env.get("junkhost") || "127.0.0.1",
|
||||||
|
port: parseInt(Deno.env.get("junkport") || "11001"),
|
||||||
|
});
|
||||||
|
}
|
17
entemu/routes/_app.tsx
Normal file
17
entemu/routes/_app.tsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import type { PageProps } from "fresh";
|
||||||
|
|
||||||
|
export default function App({ Component }: PageProps) {
|
||||||
|
return (
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>entemu</title>
|
||||||
|
<link rel="stylesheet" href="/styles.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<Component />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
10
entemu/routes/api/[name].tsx
Normal file
10
entemu/routes/api/[name].tsx
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { define } from "../../utils.ts";
|
||||||
|
|
||||||
|
export const handler = define.handlers({
|
||||||
|
GET(ctx) {
|
||||||
|
const name = ctx.params.name;
|
||||||
|
return new Response(
|
||||||
|
`Hello, ${name.charAt(0).toUpperCase() + name.slice(1)}!`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
6
entemu/routes/index.tsx
Normal file
6
entemu/routes/index.tsx
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import Planner from "../islands/Planner.tsx";
|
||||||
|
import { define } from "../utils.ts";
|
||||||
|
|
||||||
|
export default define.page(function Home() {
|
||||||
|
return <Planner />;
|
||||||
|
});
|
BIN
entemu/static/favicon.ico
Normal file
BIN
entemu/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
6
entemu/static/logo.svg
Normal file
6
entemu/static/logo.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<svg width="40" height="40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M34.092 8.845C38.929 20.652 34.092 27 30 30.5c1 3.5-2.986 4.222-4.5 2.5-4.457 1.537-13.512 1.487-20-5C2 24.5 4.73 16.714 14 11.5c8-4.5 16-7 20.092-2.655Z" fill="#FFDB1E"/>
|
||||||
|
<path d="M14 11.5c6.848-4.497 15.025-6.38 18.368-3.47C37.5 12.5 21.5 22.612 15.5 25c-6.5 2.587-3 8.5-6.5 8.5-3 0-2.5-4-5.183-7.75C2.232 23.535 6.16 16.648 14 11.5Z" fill="#fff" stroke="#FFDB1E"/>
|
||||||
|
<path d="M28.535 8.772c4.645 1.25-.365 5.695-4.303 8.536-3.732 2.692-6.606 4.21-7.923 4.83-.366.173-1.617-2.252-1.617-1 0 .417-.7 2.238-.934 2.326-1.365.512-4.223 1.29-5.835 1.29-3.491 0-1.923-4.754 3.014-9.122.892-.789 1.478-.645 2.283-.645-.537-.773-.534-.917.403-1.546C17.79 10.64 23 8.77 25.212 8.42c.366.014.82.35.82.629.41-.14 2.095-.388 2.503-.278Z" fill="#FFE600"/>
|
||||||
|
<path d="M14.297 16.49c.985-.747 1.644-1.01 2.099-2.526.566.121.841-.08 1.29-.701.324.466 1.657.608 2.453.701-.715.451-1.057.852-1.452 2.106-1.464-.611-3.167-.302-4.39.42Z" fill="#fff"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1 KiB |
160
entemu/static/styles.css
Normal file
160
entemu/static/styles.css
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
:root {
|
||||||
|
--fore: #202020;
|
||||||
|
--back: #fdfdfd;
|
||||||
|
--back2: #f0f0f0;
|
||||||
|
--shade: rgba(0, 0, 0, 0.05);
|
||||||
|
--highlight: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
color: inherit;
|
||||||
|
background: none;
|
||||||
|
border: 1px solid var(--fore);
|
||||||
|
border-radius: 4px;
|
||||||
|
/*
|
||||||
|
font: inherit;
|
||||||
|
font-size: 1rem;
|
||||||
|
border: none;
|
||||||
|
padding: 4px;
|
||||||
|
background-color: var(--back);
|
||||||
|
border-radius: 1em;
|
||||||
|
width: 2.2rem;
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
button, [role="button"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
font-family:
|
||||||
|
ui-monospace,
|
||||||
|
SFMono-Regular,
|
||||||
|
Menlo,
|
||||||
|
Monaco,
|
||||||
|
Consolas,
|
||||||
|
"Liberation Mono",
|
||||||
|
"Courier New",
|
||||||
|
monospace;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
img,
|
||||||
|
svg {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
img,
|
||||||
|
video {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
line-height: 1.5;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
color: var(--fore);
|
||||||
|
background-color: var(--back);
|
||||||
|
padding-bottom: 4em;
|
||||||
|
font-family:
|
||||||
|
ui-sans-serif,
|
||||||
|
system-ui,
|
||||||
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
"Segoe UI",
|
||||||
|
Roboto,
|
||||||
|
"Helvetica Neue",
|
||||||
|
Arial,
|
||||||
|
"Noto Sans",
|
||||||
|
sans-serif,
|
||||||
|
"Apple Color Emoji",
|
||||||
|
"Segoe UI Emoji",
|
||||||
|
"Segoe UI Symbol",
|
||||||
|
"Noto Color Emoji";
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar__calendar {
|
||||||
|
width: 18em;
|
||||||
|
margin: 1em auto;
|
||||||
|
border-radius: 2em;
|
||||||
|
padding: 1em;
|
||||||
|
background-color: var(--back2);
|
||||||
|
}
|
||||||
|
.calender__cash {
|
||||||
|
margin: 4px 0.5em;
|
||||||
|
}
|
||||||
|
.calendar__head {
|
||||||
|
text-align: center;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
.calendar__days {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 16em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.calendar__date {
|
||||||
|
display: inline-block;
|
||||||
|
width: 4em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.calendar__days > span {
|
||||||
|
width: 3.2em;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
.calendar__days > span.p {
|
||||||
|
background-color: var(--highlight);
|
||||||
|
}
|
||||||
|
.calendar__events {
|
||||||
|
width: 21cm;
|
||||||
|
margin: 1em auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar__alert > * {
|
||||||
|
color: orange;
|
||||||
|
font-weight: bold;
|
||||||
|
width: 21cm;
|
||||||
|
margin: 0.5em auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar__storage {
|
||||||
|
width: 12em;
|
||||||
|
margin: 1em auto;
|
||||||
|
border-radius: 2em;
|
||||||
|
padding: 0.5em;
|
||||||
|
background-color: var(--back2);
|
||||||
|
}
|
||||||
|
.calendar__storage td {
|
||||||
|
padding: 0 8px;
|
||||||
|
width: 12em;
|
||||||
|
}
|
||||||
|
.calendar__storage td:nth-child(1) {
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .calendar__events {} */
|
||||||
|
.calendar__events thead > tr,
|
||||||
|
.calendar__events tr:nth-of-type(2n) {
|
||||||
|
background-color: var(--shade);
|
||||||
|
}
|
||||||
|
.calendar__events tr:hover {
|
||||||
|
background-color: rgba(0, 255, 0, 0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:any-link {
|
||||||
|
color: var(--fore);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--fore: #a0a0a0;
|
||||||
|
--back: #303030;
|
||||||
|
--back2: #383838;
|
||||||
|
--shade: rgba(255, 255, 255, 0.05);
|
||||||
|
--highlight: rgba(255, 255, 0, 0.15);
|
||||||
|
}
|
||||||
|
}
|
6
entemu/utils.ts
Normal file
6
entemu/utils.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { createDefine } from "fresh";
|
||||||
|
|
||||||
|
// deno-lint-ignore no-empty-interface
|
||||||
|
export interface State {}
|
||||||
|
|
||||||
|
export const define = createDefine<State>();
|
3
go.mod
Normal file
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module junk
|
||||||
|
|
||||||
|
go 1.21.0
|
26
index.html
Normal file
26
index.html
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Junk</title>
|
||||||
|
<link rel="stylesheet" href="junk.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>Historical Interests</h1>
|
||||||
|
<dl>
|
||||||
|
<dt><a href="/entemu/">entemu</a></dt>
|
||||||
|
<dd>Planning for a sandbox enterprise game, made in one week. Ugly code. Change plans by changing the code.</dd>
|
||||||
|
</dl>
|
||||||
|
<dl>
|
||||||
|
<dt><a href="/entemu-legacy/">entemu-legacy</a></dt>
|
||||||
|
<dd>Legacy version of entemu, made overnight. Ugly code. Have been used together with entemu.</dd>
|
||||||
|
</dl>
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<p><a href="https://git.yozora.tech/maki/junk">This service itself</a> is junk, too.</p>
|
||||||
|
<p>Everything here are licensed under <a href="/LICENSE">WTFPL, version 2</a>.</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
23
junk.css
Normal file
23
junk.css
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
:root {
|
||||||
|
--fore: #202020;
|
||||||
|
--back: #fdfdfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 21cm;
|
||||||
|
line-height: 1.5;
|
||||||
|
background-color: var(--back);
|
||||||
|
color: var(--fore);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:any-link {
|
||||||
|
color: var(--fore);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--fore: #a0a0a0;
|
||||||
|
--back: #303030;
|
||||||
|
}
|
||||||
|
}
|
166
main.go
Normal file
166
main.go
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const junkaddr = "[::]:8012"
|
||||||
|
const proxybufsize = 4 << 10
|
||||||
|
|
||||||
|
type JunkConf struct {
|
||||||
|
Proxy string
|
||||||
|
Cmd *exec.Cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
status := cmain(len(os.Args), os.Args)
|
||||||
|
os.Setenv("status", status)
|
||||||
|
if status != "" {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func proxy(w http.ResponseWriter, r *http.Request, proxy_path string) {
|
||||||
|
var err error
|
||||||
|
r.RequestURI = ""
|
||||||
|
r.URL, err = url.Parse(proxy_path)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := http.DefaultClient.Do(r)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for k, v := range resp.Header {
|
||||||
|
for _, x := range v {
|
||||||
|
w.Header().Add(k, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.WriteHeader(resp.StatusCode)
|
||||||
|
buf := make([]byte, proxybufsize)
|
||||||
|
for {
|
||||||
|
n, err := resp.Body.Read(buf)
|
||||||
|
w.Write(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmain(argc int, argv []string) string {
|
||||||
|
fileserver := http.FileServer(http.Dir(""))
|
||||||
|
cmdmap := map[string]JunkConf{}
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
p1 := r.URL.Path
|
||||||
|
if p1 == "/" {
|
||||||
|
fileserver.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p := strings.Split(p1, "/")[1:]
|
||||||
|
seg := p[0]
|
||||||
|
if ref := r.Header.Get("Referer"); ref != "" {
|
||||||
|
rurl, err := url.Parse(ref)
|
||||||
|
rp := strings.Split(rurl.Path, "/")[1:]
|
||||||
|
if rurl.Path != "/" {
|
||||||
|
rseg := rp[0]
|
||||||
|
if err == nil && seg != rseg && (len(p) < 2 || p[1] != rseg) {
|
||||||
|
p1 = "/" + rseg + r.URL.Path
|
||||||
|
w.Header().Set("Location", p1)
|
||||||
|
w.WriteHeader(http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if conf, ok := cmdmap[seg]; ok {
|
||||||
|
proxy_path := conf.Proxy + "/" + strings.Join(p[1:], "/")
|
||||||
|
proxy(w, r, proxy_path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if f, err := os.Open(seg + "/junkrc"); err == nil {
|
||||||
|
// info, err := f.Stat()
|
||||||
|
s := bufio.NewScanner(f)
|
||||||
|
conf := JunkConf{}
|
||||||
|
for s.Scan() {
|
||||||
|
line := s.Text()
|
||||||
|
if len(line) > 2 && line[0:2] == "#;" {
|
||||||
|
note := line[2:]
|
||||||
|
i := strings.IndexByte(note, '=')
|
||||||
|
if i == -1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key, value := note[:i], note[i+1:]
|
||||||
|
switch key {
|
||||||
|
case "proxy":
|
||||||
|
conf.Proxy = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
println("exec:", seg+"/junkrc")
|
||||||
|
cmd := exec.Command("./junkrc")
|
||||||
|
cmd.Dir = seg
|
||||||
|
// cmd.Stdout = os.Stdout
|
||||||
|
conf.Cmd = cmd
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
waits := 0
|
||||||
|
for {
|
||||||
|
_, err := http.Head(conf.Proxy)
|
||||||
|
if err != nil {
|
||||||
|
if cmd.ProcessState != nil {
|
||||||
|
cmd.Wait()
|
||||||
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
|
w.Write([]byte("process exited early: " + cmd.ProcessState.String()))
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
waits++
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
if waits > 10 {
|
||||||
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
|
w.Write([]byte("starting process: timeout"))
|
||||||
|
cmd.Process.Kill()
|
||||||
|
cmd.Wait()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmdmap[seg] = conf
|
||||||
|
break
|
||||||
|
}
|
||||||
|
proxy_path := conf.Proxy + "/" + strings.Join(p[1:], "/")
|
||||||
|
proxy(w, r, proxy_path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fileserver.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
lock := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(lock, os.Interrupt, syscall.SIGTERM)
|
||||||
|
go func() {
|
||||||
|
println("started:", "http://"+junkaddr+"/")
|
||||||
|
http.ListenAndServe(junkaddr, nil)
|
||||||
|
}()
|
||||||
|
<-lock
|
||||||
|
for k, v := range cmdmap {
|
||||||
|
println("killing:", k)
|
||||||
|
v.Cmd.Process.Kill()
|
||||||
|
v.Cmd.Wait()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
Loading…
Reference in a new issue