Auto commit

上级 63b5a6c5
:root,body {height: 100%}
* {box-sizing: border-box}
h2,h3,p {display: inline}
button,input {outline: none}
button {
background-color: var(--interfaceColor);
color: #000000;
}
button:hover {background-color: var(--hoverColor)}
button:active {background-color: var(--activeColor)}
body {
margin: 0;
background-color: #111111;
color: #FFFFFF;
}
body>div {
height: 100%;
display: none;
overflow: hidden;
}
body.work>#work {display: grid}
#open {grid-template-rows: 5rem 1fr}
#top {
height: 100%;
display: grid;
place-content: center start;
padding: 0 2em;
background-color: #00BFFF;
}
body.open>#open {display: grid}
#work {
grid-template-rows: 2rem 1.5rem 1fr;
height: 100%;
}
#open,#work {
width: 100%;
height: 100%;
overflow: hidden;
}
#select_file {
place-self: center;
font-size: 1rem;
font-weight: bold;
padding: 1rem;
border: none;
border-radius: 1rem;
}
#ui_frame,.ui_frame {
display: grid;
gap: 0.5rem;
height: 100%;
overflow: hidden;
}
#ui_frame {
grid-template-columns: 1fr 1fr;
padding: 0.5rem;
}
.ui_frame {grid-template-rows: auto 1fr}
#file_name_frame,#menu {
display: grid;
height: 100%;
padding: 0 0.5rem;
white-space: nowrap;
}
#file_name_frame{
background-color: #00BFFF;
place-content: center start;
}
#file_name {
overflow: hidden;
text-overflow: ellipsis;
color: #000000;
}
#menu {
background-color: #222222;
grid-auto-flow: column;
grid-auto-columns: 5.1875rem;
}
.menu_item,.menu_option {
height: 100%;
display: grid;
place-content: center start;
padding: 0 0.25rem;
font-size: 0.9375rem;
}
.menu_item {position: relative}
.menu_item:hover {background-color: #444444}
.menu_item:focus-within {background-color: #333333}
.menu_expand {
display: none;
position: absolute;
top: 100%;
min-width: 100%;
max-width: 15.625rem;
background-color: #333333;
border: solid 0.0625rem #555555;
}
.menu_item:focus-within>.menu_expand {
display: grid;
grid-auto-rows: 1.5rem;
}
.menu_option {
background-color: transparent;
color: #FFFFFF;
border: 0;
border-radius: 0;
}
.menu_option:hover {background-color: #0060A0}
.menu_option:active:not(:hover) {background-color: #D08000}
.ui_frame>h3 {margin: 0 0.5rem}
#preview,#editor {
border: solid #777777 0.125rem;
height: 100%;
background-color: #222222;
}
#preview {
display: grid;
padding: 0.5rem;
grid-auto-rows: 1.375rem;
line-height: 1.375rem;
color: #FFFFFF;
overflow: auto;
white-space: nowrap;
}
.preview_key {
margin-inline-end: 0.5rem;
user-select: text;
}
.preview_key::after {content: ":"}
.preview_value {
min-width: 100%;
width: max-content;
user-select: text;
}
.preview_value.string {color: #FFFF00}
.preview_value.number {color: #00FF00}
.preview_value.boolean {color: #0060FF}
.preview_value.null,.preview_value.undefined {color: #FF00FF}
.preview_value.object {
border: 0;
padding: 0;
border-radius: 0;
}
.preview_value.object>summary {
border: 0;
margin: 0;
padding: 0;
}
.preview_children {
display: grid;
margin-left: 2rem;
}
.preview_type.Array::before {content: "数组["}
.preview_type.Array::after {content: "]"}
.preview_type.Array {color: #FF0000}
.preview_type.Object::before {content: "对象{"}
.preview_type.Object::after {content: "}"}
.preview_type.Object {color: #00BFFF}
#editor {
display: grid;
grid-template-rows: 2rem 2rem 1fr;
overflow: hidden;
}
#editor>div {
display: grid;
height: 100%;
}
#editor_index,#editor_indexor {
background-color: #333333;
padding: 0 0.5rem;
grid-template-columns: auto auto;
justify-content: start;
align-items: center;
gap: 0.5rem;
}
#editor_index_set {
border: solid #000000 0.125rem;
border-radius: 0.25rem;
height: 1.5rem;
width: 7rem;
font-size: 0.875rem;
text-align: end;
}
#editor_indexor_add {
width: 1.5rem;
height: 1.5rem;
}
#indexor_frame {
padding: 0.5rem;
overflow-y: auto;
grid-auto-rows: min-content;
gap: 0.5rem;
}
.indexor {
display: grid;
background-color: #444444;
padding: 0.5rem;
border-radius: 0.5rem;
gap: 0.5rem;
grid-template-columns: auto 1fr auto;
grid-template-rows: repeat(3, 1.75rem);
grid-template-areas: "title title remove""pathd path path""content content content";
align-items: center;
}
.indexor>input {
height: 1.75rem;
border: 0;
border-radius: 0.25rem;
font-size: 1rem;
padding: 0 0.375rem;
background-color: #222222;
color: #FFFFFF;
}
.indexor_title {
grid-area: title;
color: #00BFFF !important;
}
.indexor_remove {
grid-area: remove;
width: 1.75rem;
height: 1.75rem;
border: 0;
background-color: #FF0000;
display: block;
position: relative;
}
.indexor_remove:before,.indexor_remove:after {
content: "";
position: absolute;
display: block;
background-color: #000000;
left: 0.25rem;
right: 0.25rem;
top: 0.8125rem;
bottom: 0.8125rem;
transform: rotate(45deg);
border-radius: 0.0625rem;
}
.indexor_remove:after {transform: rotate(135deg)}
.indexor_path_d {grid-area: pathd}
.indexor_path {
grid-area: path;
border: solid 0.125rem transparent !important;
padding: 0 0.25rem !important;
}
.indexor_content {grid-area: content}
.indexor_content:disabled {background-color: #800000}
.indexor_content:disabled::placeholder {color: #808080}
.indexor_content.invalid {color:#FF0000 !important}
.indexor_path.string {border-color: #FFFF00 !important}
.indexor_path.number {border-color: #00FF00 !important}
.indexor_path.boolean {border-color: #0060FF !important}
.indexor_data_frame {
display: grid;
grid-template-rows: auto 1fr;
gap: 0.5rem;
height: 100%;
overflow: hidden;
}
.indexor_data {
display: grid;
padding: 0 0.5rem;
gap: 0.5rem;
max-height: 16rem;
overflow: hidden auto;
font-size: 0.875rem;
word-break: break-all;
}
\ No newline at end of file
此差异已折叠。
<!DOCTYPE html>
<html lang="en">
<!DOCTYPE HTML>
<html lang="zh-cmn-Hans">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="style.css" rel="stylesheet" type="text/css" />
<title>InsCode</title>
<meta charset=UTF-8>
<title>JSON 索引化编辑工具</title>
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<link rel="stylesheet" type="text/css" href="//bsif.netlify.app/css/BSIF_style.css">
<link rel="stylesheet" type="text/css" href="editor.css">
<script src="main.mjs" type="module"></script>
</head>
<body>
<div class="container">
<img src="src/assets/logo.svg" alt="InsCode">
<div>欢迎来到 InsCode</div>
</div>
<script src="script.js"></script>
<body class="bs-loading">
<div id="open">
<header id="top">
<h1>JSON 索引化编辑工具</h1>
</header>
<button id="select_file">打开 JSON 文件</button>
</div>
<div id="work">
<div id="file_name_frame"><span id="file_name">文件名</span></div>
<div id="menu"></div>
<div id="ui_frame">
<div class="ui_frame">
<h3>预览</h3>
<div id="preview"></div>
</div>
<div class="ui_frame">
<h3>编辑</h3>
<div id="editor">
<div id="editor_index">索引编号<input id="editor_index_set" type="number" value="0" min="0" step="1" max="4294967295" title="索引编号"></div>
<div id="editor_indexor">索引器<button id="editor_indexor_add" title="增加一个索引器">+</button></div>
<div id="indexor_frame"></div>
</div>
</div>
</div>
</div>
</body>
</html>
\ No newline at end of file
import { open, read, readableTypes } from "./module/FileIO.mjs";
import { MiniWindow } from "./module/MiniWindow.mjs";
import { openFile } from "./editor.mjs";
const body = document.body;
var working = false, pending = false;
document.getElementById("select_file").addEventListener("click", async function () { try { loadFile(await open({ types: [{ accept: { "application/json": [".json"] } }] })) } catch (e) { } });
async function loadFile(fileHandle) {
if (pending || working) return;
pending = true;
const waitWin = new MiniWindow("正在加载,请稍等……", "请稍等", { noManualClose: true });
try {
const data = JSON.parse(await read(await fileHandle.getFile(), readableTypes.TEXT));
fileHandle.requestPermission({ mode: "readwrite" });
startWork(data, fileHandle);
} catch (error) {
new MiniWindow("无法解读该文件,请选择正确的 JSON 文件。", "错误!");
}
pending = false;
waitWin.close();
}
function preventDefault(event) { event.preventDefault() }
document.addEventListener("dragover", preventDefault);
document.addEventListener("drop", preventDefault);
body.className = "open";
launchQueue.setConsumer(function (launchParams) {
const file = launchParams.files[0];
if (file) loadFile(file);
});
function startWork(data, fileHandle) {
if (working) {
new MiniWindow("此实例已经打开了文件,请启动一个新实例。");
return;
}
working = true;
openFile(data, fileHandle);
body.className = "work";
}
function endWork() {
working = false;
body.className = "open";
}
export { endWork }
\ No newline at end of file
function decodeCollection(data,outer,collector) {
if (!Array.isArray(data)) throw new TypeError("Decode error: Detected non-Array object");
for (let item of data) {
switch (typeof item) {
case "string":
case "number":
case "bigint":
case "boolean":
outer.appendChild(document.createTextNode(item));
break;
case "object":
if (!item) break;
if (item instanceof Node) {
outer.appendChild(item);
break;
}
decodeNode(item,outer,collector)
default:
}
}
}
function decodeStyleObject(node,object) {
const styleObject=node.style;
for (let index in object) styleObject[index]=[object[index]];
}
function decodeAttribute(node,attributeName,data) {
switch (attributeName) {
case "style":
if (data instanceof Object) {
decodeStyleObject(node,data);
return;
}
break;
case "class":
node.setAttribute("class",Array.isArray(data)?data.join(" "):data);
return;
default:
}
node.setAttribute(attributeName,data)
}
function decodeContent(node,content,collector) {
switch (typeof content) {
case "string":
case "number":
case "bigint":
case "boolean":
node.innerText=content;
return;
case "object":
if (!content) return;
if (content instanceof Node) {
node.appendChild(content);
return;
}
try {decodeCollection(content,node,collector)} catch(error){console.warn(error)}
default:
}
}
function decodeNode(data,outer,collector) {
if (!Array.isArray(data)) throw new TypeError("Decode error: Detected non-Array object");
var content=data[1],node;
switch (data[0]) {
case "#comment":
outer.appendChild(node=document.createComment(content));
break;
case "#text":
outer.appendChild(node=document.createTextNode(content));
break;
case "#shadow":
try {
if (!(outer instanceof Element)) throw new TypeError("Container is not an Element.")
node=outer.attachShadow(data[2])
} catch(error) {
console.warn("Decode error: Failed to attach shadow DOM\n",data,"\non",outer,`\n${error.name}: ${error.message}`);
return;
}
decodeContent(node,content,collector);
break;
default:
outer.appendChild(node=document.createElement(data[0]));
if (data[2] instanceof Object) {
for (let attribute in data[2]) try {decodeAttribute(node,attribute,data[2][attribute])} catch (error) {console.warn("Decode error: Failed to set attribute",attribute,"to",data[2][attribute],"on",node,`\n${error.name}: ${error.message}`)}
}
decodeContent(node,content,collector);
}
if (collector&&(3 in data)) collector[data[3]]=node;
}
function decode(ArrayHTML) {
const documentFragment=document.createDocumentFragment();
decodeCollection(ArrayHTML,documentFragment);
return documentFragment;
}
function decodeAndGetNodes(ArrayHTML) {
const nodes={},documentFragment=document.createDocumentFragment();
decodeCollection(ArrayHTML,documentFragment,nodes);
return {documentFragment,nodes};
}
function encodeIterator(node,outer) {
if (node.nodeName=="#text") {
outer.push(node.textContent);
} else {
for (let child of node.childNodes) {encodeNode(child,outer)}
}
}
function encodeNode(node,outer){
switch (node.nodeName) {
case "#text":
outer.push(node.textContent);
break;
case "#comment":
outer.push(["#comment",node.textContent]);
break;
case "#document":
encodeIterator(node,outer);
case "html":
break;
default:
let child=[node.nodeName];
try {
if (node.hasAttributes()) {
child[2]={};
for (let attribute of node.attributes) {
child[2][attribute.name]=attribute.value;
}
}
} catch(error) {console.warn("Encode exception: Failed to get attributes of node",node)}
if (node.hasChildNodes()) {
encodeIterator(node,child[1]=[]);
}
outer.push(child);
}
}
function encode(node,onlyChildren=true) {
if (!(node instanceof Node)) throw new TypeError("Encode failed: Argument 'node' is not type of Node");
const ArrayHtml=[];
if (onlyChildren) {encodeIterator(node,ArrayHtml)} else {encodeNode(node,ArrayHtml)}
return ArrayHtml;
}
export {decode,encode,decodeAndGetNodes}
\ No newline at end of file
const TypedArray=Object.getPrototypeOf(Uint8Array);
function splitBytes(data,splitLength) {
if (arguments.length<2) throw new TypeError(`Failed to execute 'sliceByte': 2 arguments required, but only ${arguments.length} present.`);
if (!(data instanceof TypedArray)) throw new TypeError("Failed to execute 'sliceByte': Argument 'data' is not type of TypedArray.");
if (!Array.isArray(splitLength)) throw new TypeError("Failed to execute 'sliceByte': Argument 'splitLength' is not an Array.");
var totalBits=0;
for (let i of splitLength) {
if (!Number.isInteger(i)) throw new Error("Failed to execute 'sliceByte': Length of bits must be integer.");
if (i<1) throw new Error("Failed to execute 'sliceByte': Length of bits connot less than 1.");
totalBits+=i;
}
const bitsPerElement=data.BYTES_PER_ELEMENT*8;
if (totalBits!=data.length*bitsPerElement) throw new Error("Failed to execute 'sliceByte': SliceRule's total value is not equal number of data's bits.");
data=data.constructor.from(data);
const result=new Array(splitLength.length);
var currentByte=0,byteRemainBits=bitsPerElement;
for (let i in splitLength) {
let length=splitLength[i];
if (length>32) throw new Error(`Failed to execute 'sliceByte': Cannot process values with length ${length}.`);
let value=0;
while (length>0) {
let spliceBits=length>7?8:length;
if (spliceBits>byteRemainBits) spliceBits=byteRemainBits;
let temp=data[currentByte];
if (byteRemainBits-=spliceBits) {
data[currentByte]%=2**byteRemainBits;
temp>>>=byteRemainBits;
}
if(!byteRemainBits) {
++currentByte;
byteRemainBits=bitsPerElement;
}
value+=(length-=spliceBits)?temp<<length:temp;
}
result[i]=value;
}
return result;
}
function littleEndianToNumber(data) {
if (arguments.length<1) throw new TypeError("Failed to execute 'littleEndianToNumber': 1 argument required, but only 0 present.");
if (!(data instanceof Uint8Array)) throw new TypeError("Failed to execute 'littleEndianToNumber': Argument 'data' is not type of Uint8Array.");
const bitsPerElement=data.BYTES_PER_ELEMENT*8,length=data.length;
if (length>4) throw new Error(`Failed to execute 'littleEndianToNumber': Cannot process data with length greater then 4.`);
var result=0;
for (let i=0;i<length;++i) result+=data[i]<<bitsPerElement*i;
return result;
}
function littleEndianToBigInt(data) {
if (arguments.length<1) throw new TypeError("Failed to execute 'littleEndianToBigInt': 1 argument required, but only 0 present.");
if (!(data instanceof Uint8Array)) throw new TypeError("Failed to execute 'littleEndianToBigInt': Argument 'data' is not type of Uint8Array.");
const bitsPerElement=BigInt(data.BYTES_PER_ELEMENT*8),length=BigInt(data.length);
var result=0n;
for (let i=0n;i<length;++i) result+=BigInt(data[i])<<bitsPerElement*i;
return result;
}
function bigEndianToNumber(data) {
if (arguments.length<1) throw new TypeError("Failed to execute 'bigEndianToNumber': 1 argument required, but only 0 present.");
if (!(data instanceof Uint8Array)) throw new TypeError("Failed to execute 'bigEndianToNumber': Argument 'data' is not type of Uint8Array.");
return littleEndianToNumber(Uint8Array.from(data).reverse());
}
function bigEndianToBigInt(data) {
if (arguments.length<1) throw new TypeError("Failed to execute 'bigEndianToBigInt': 1 argument required, but only 0 present.");
if (!(data instanceof Uint8Array)) throw new TypeError("Failed to execute 'bigEndianToBigInt': Argument 'data' is not type of Uint8Array.");
return littleEndianToBigInt(Uint8Array.from(data).reverse());
}
function numberToLittleEndian(value,size) {
if (arguments.length<2) throw new TypeError(`Failed to execute 'numberToLittleEndian': 2 arguments required, but only ${arguments.length} present.`);
if (typeof value!="number") throw new TypeError("Failed to execute 'numberToLittleEndian': Argument 'value' is not a number.");
if (value<0||!Number.isInteger(value)) throw new Error("Failed to execute 'numberToLittleEndian': Argument 'value' must be unsign integer.");
if (typeof size!="number") throw new TypeError("Failed to execute 'numberToLittleEndian': Argument 'size' is not a number.");
if (!Number.isInteger(size)) throw new Error("Failed to execute 'numberToLittleEndian': Argument 'size' must be integer.");
if (size<1) throw new Error("Failed to execute 'numberToLittleEndian': Argument 'size' connot less than 1.");
const result=new Uint8Array(size);
for (let i=0;i<size&&value;++i) {
result[i]=value%256;
value>>>=8;
}
return result;
}
function numberToBigEndian(value,size) {
if (arguments.length<2) throw new TypeError(`Failed to execute 'numberToBigEndian': 2 arguments required, but only ${arguments.length} present.`);
if (typeof value!="number") throw new TypeError("Failed to execute 'numberToBigEndian': Argument 'value' is not a number.");
if (value<0||!Number.isInteger(value)) throw new Error("Failed to execute 'numberToBigEndian': Argument 'value' must be unsign integer.");
if (typeof size!="number") throw new TypeError("Failed to execute 'numberToBigEndian': Argument 'size' is not a number.");
if (!Number.isInteger(size)) throw new Error("Failed to execute 'numberToBigEndian': Argument 'size' must be integer.");
if (size<1) throw new Error("Failed to execute 'numberToBigEndian': Argument 'size' connot less than 1.");
return numberToLittleEndian(value,size).reverse();
}
function bigIntToLittleEndian(value,size) {
if (arguments.length<2) throw new TypeError(`Failed to execute 'bigIntToLittleEndian': 2 arguments required, but only ${arguments.length} present.`);
if (typeof value!="bigint") throw new TypeError("Failed to execute 'bigIntToLittleEndian': Argument 'value' is not a bigint.");
if (value<0) throw new Error("Failed to execute 'bigIntToLittleEndian': Argument 'value' connot less than 0.");
if (typeof size!="number") throw new TypeError("Failed to execute 'bigIntToLittleEndian': Argument 'size' is not a number.");
if (size<1) throw new Error("Failed to execute 'bigIntToLittleEndian': Argument 'size' connot less than 1.");
const result=new Uint8Array(size);
for (let i=0;i<size&&value;++i) {
result[i]=Number(value%256n);
value>>=8n;
}
return result;
}
function bigIntToBigEndian(value,size) {
if (arguments.length<2) throw new TypeError(`Failed to execute 'bigIntToBigEndian': 2 arguments required, but only ${arguments.length} present.`);
if (typeof value!="bigint") throw new TypeError("Failed to execute 'bigIntToBigEndian': Argument 'value' is not a bigint.");
if (value<0) throw new Error("Failed to execute 'bigIntToBigEndian': Argument 'value' connot less than 0.");
if (typeof size!="number") throw new TypeError("Failed to execute 'bigIntToBigEndian': Argument 'size' is not a number.");
if (size<1) throw new Error("Failed to execute 'bigIntToBigEndian': Argument 'size' connot less than 1.");
return bigIntToLittleEndian(value,size).reverse();
}
function bitsOf(value) {
if (typeof value!="number") throw new TypeError("Failed to execute 'bitsOf': Argument 'value' is not a number.");
if (!isFinite(value)) throw new TypeError("Failed to execute 'bitsOf': Argument 'value' is not finite.");
if (value<0||!Number.isInteger(value)) throw new Error("Failed to execute 'bitsOf': Argument 'value' must be unsign integer.");
if (value>4294967295) return bitsOfBigInt(BigInt(value));
for (var result=0;value;value>>>=1) ++result;
return result;
}
function bitsOfBigInt(value) {
if (typeof value!="bigint") throw new TypeError("Failed to execute 'bitsOfBigInt': Argument 'value' is not a bigint.");
if (value<0n) throw new Error("Failed to execute 'bitsOfBigInt': Argument 'value' must be unsign integer.");
for (var result=0;value;value>>=1n) ++result;
return result;
}
export {
splitBytes,
littleEndianToNumber,
littleEndianToBigInt,
bigEndianToNumber,
bigEndianToBigInt,
numberToLittleEndian,
bigIntToLittleEndian,
numberToBigEndian,
bigIntToBigEndian,
bitsOf,
bitsOfBigInt,
TypedArray
}
\ No newline at end of file
import { TypedArray } from "./BinaryOperate.mjs";
const readableTypes = Object.freeze({ TEXT: 0, DATA_URL: 1, ARRAY_BUFFER: 2 }), readFunctions = [
FileReader.prototype.readAsText,
FileReader.prototype.readAsDataURL,
FileReader.prototype.readAsArrayBuffer
];
function read(file, readType) {
if (arguments.length < 2) throw new TypeError("Failed to execute 'read': 2 arguments required, but only " + arguments.length + " present.");
if (!(file instanceof Blob)) throw new TypeError("Failed to execute 'read': Argument 'file' is not a binary object.");
if (!(readType in readFunctions)) throw new Error("Failed to execute 'read': Argument 'readtype' is not one of FileIO.readableTypes.");
return new Promise(function (resolve) {
var Operator = new FileReader;
Operator.addEventListener("load", function () { resolve(Operator.result) });
readFunctions[readType].apply(Operator, [file]);
});
}
function fileHandleMap(item) { return item.getFile() }
async function get(options) {
if (arguments.length && !(options instanceof Object)) throw new TypeError("Failed to execute 'get': The provided value is not an object.");
var temp;
try { temp = await showOpenFilePicker(options) } catch (error) {
if (error instanceof TypeError) throw error;
return null;
}
temp = await Promise.all(temp.map(fileHandleMap));
return options?.multiple ? temp : temp[0];
}
async function open(options) {
const result = await showOpenFilePicker(options);
return options?.multiple ? result : result[0];
}
const openDirectory = showDirectoryPicker.bind(window);
async function save(data, options) {
if (arguments.length < 1) throw new TypeError("Failed to execute 'save': 1 argument required, but only 0 present.");
if (!(data instanceof TypedArray || data instanceof Blob || data instanceof DataView || data instanceof ArrayBuffer || typeof data == "string")) throw new TypeError("Failed to execute 'save': Argument 'data' is not valid type.");
if (arguments.length > 1 && !(options instanceof Object)) throw new TypeError("Failed to execute 'save': Argument 'options' is not an object.");
try {
const operator = await (await showSaveFilePicker(options)).createWritable();
await operator.write(data);
await operator.close();
return true;
} catch (error) {
if (error instanceof TypeError) throw error;
return false;
}
}
function downloadSave(file, saveName) {
const objectURL = URL.createObjectURL(file), address = document.createElement("a");
address.href = objectURL;
address.download = typeof saveName == "string" ? saveName : "";
address.dispatchEvent(new MouseEvent("click"));
URL.revokeObjectURL(objectURL);
}
export { get, open, openDirectory, save, downloadSave, read, readableTypes }
\ No newline at end of file
此差异已折叠。
此差异已折叠。
html,
body {
height: 100%;
width: 100%;
}
.container {
text-align: center;
padding: 64px;
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册