调整界面
调整脚本 增加功能:备份、恢复、清空
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
import {activateRecordService} from "../service/database/activateRecord";
|
||||
import path from 'path';
|
||||
import { app as electronApp, dialog } from 'electron';
|
||||
import { activateRecordService } from "../service/database/activateRecord";
|
||||
|
||||
class ActivateRecordController {
|
||||
|
||||
async list(args: { macAddress: string, modules: string[] }): Promise<any> {
|
||||
const {macAddress, modules} = args
|
||||
return activateRecordService.list(macAddress, modules)
|
||||
const { macAddress, modules } = args;
|
||||
return activateRecordService.list(macAddress, modules);
|
||||
}
|
||||
|
||||
async save(args: {
|
||||
applicant: string,
|
||||
macAddress: string,
|
||||
applicationCode: string,
|
||||
modules: string[],
|
||||
@@ -15,8 +18,76 @@ class ActivateRecordController {
|
||||
createTime: string,
|
||||
remark: string
|
||||
}): Promise<any> {
|
||||
const { modules} = args
|
||||
return activateRecordService.save({...args, module : modules.join(',')})
|
||||
const { modules } = args;
|
||||
return activateRecordService.save({ ...args, module: modules.join(',') });
|
||||
}
|
||||
|
||||
async removeById(args: { id: number }): Promise<any> {
|
||||
const { id } = args;
|
||||
return activateRecordService.removeById(id);
|
||||
}
|
||||
|
||||
async clear(): Promise<any> {
|
||||
return activateRecordService.clear();
|
||||
}
|
||||
|
||||
async backup(): Promise<any> {
|
||||
const defaultFileName = `activate-record-backup-${this.getTimestamp()}.json`;
|
||||
const filePath = dialog.showSaveDialogSync({
|
||||
title: '导出备份',
|
||||
defaultPath: path.join(electronApp.getPath('documents'), defaultFileName),
|
||||
filters: [
|
||||
{ name: 'JSON', extensions: ['json'] }
|
||||
]
|
||||
});
|
||||
|
||||
if (!filePath) {
|
||||
return {
|
||||
canceled: true
|
||||
};
|
||||
}
|
||||
|
||||
const count = await activateRecordService.backup(filePath);
|
||||
return {
|
||||
canceled: false,
|
||||
count,
|
||||
filePath
|
||||
};
|
||||
}
|
||||
|
||||
async importBackup(): Promise<any> {
|
||||
const filePaths = dialog.showOpenDialogSync({
|
||||
title: '导入备份',
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{ name: 'JSON', extensions: ['json'] }
|
||||
]
|
||||
});
|
||||
|
||||
if (!filePaths || filePaths.length === 0) {
|
||||
return {
|
||||
canceled: true
|
||||
};
|
||||
}
|
||||
|
||||
const filePath = filePaths[0];
|
||||
const count = await activateRecordService.importBackup(filePath);
|
||||
return {
|
||||
canceled: false,
|
||||
count,
|
||||
filePath
|
||||
};
|
||||
}
|
||||
|
||||
private getTimestamp(): string {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = `${now.getMonth() + 1}`.padStart(2, '0');
|
||||
const day = `${now.getDate()}`.padStart(2, '0');
|
||||
const hour = `${now.getHours()}`.padStart(2, '0');
|
||||
const minute = `${now.getMinutes()}`.padStart(2, '0');
|
||||
const second = `${now.getSeconds()}`.padStart(2, '0');
|
||||
return `${year}${month}${day}-${hour}${minute}${second}`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,30 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { ElectronEgg } from 'ee-core';
|
||||
import { Lifecycle } from './preload/lifecycle';
|
||||
import { preload } from './preload';
|
||||
import { app as electronApp } from 'electron';
|
||||
|
||||
function writeRuntimeLog(message: string): void {
|
||||
try {
|
||||
const baseDir = electronApp.isPackaged ? path.dirname(process.execPath) : process.cwd();
|
||||
const logFile = path.join(baseDir, 'runtime.log');
|
||||
const line = `[${new Date().toISOString()}] ${message}\n`;
|
||||
fs.appendFileSync(logFile, line, 'utf8');
|
||||
} catch (error) {
|
||||
console.error('[runtime-log] write failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
process.on('uncaughtException', (error) => {
|
||||
writeRuntimeLog(`uncaughtException: ${error?.stack || error}`);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
writeRuntimeLog(`unhandledRejection: ${String(reason)}`);
|
||||
});
|
||||
|
||||
writeRuntimeLog('app bootstrap start');
|
||||
|
||||
// New app
|
||||
const app = new ElectronEgg();
|
||||
@@ -16,4 +40,5 @@ app.register("before-close", life.beforeClose);
|
||||
app.register("preload", preload);
|
||||
|
||||
// Run
|
||||
app.run();
|
||||
app.run();
|
||||
writeRuntimeLog('app bootstrap end');
|
||||
|
||||
@@ -2,6 +2,19 @@ import { app as electronApp, screen } from 'electron';
|
||||
import { logger } from 'ee-core/log';
|
||||
import { getConfig } from 'ee-core/config';
|
||||
import { getMainWindow } from 'ee-core/electron';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
function writeRuntimeLog(message: string): void {
|
||||
try {
|
||||
const baseDir = electronApp.isPackaged ? path.dirname(process.execPath) : process.cwd();
|
||||
const logFile = path.join(baseDir, 'runtime.log');
|
||||
const line = `[${new Date().toISOString()}] ${message}\n`;
|
||||
fs.appendFileSync(logFile, line, 'utf8');
|
||||
} catch (error) {
|
||||
console.error('[runtime-log] write failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
class Lifecycle {
|
||||
/**
|
||||
@@ -36,6 +49,16 @@ class Lifecycle {
|
||||
|
||||
const win = getMainWindow();
|
||||
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
logger.info('[window] did-finish-load');
|
||||
writeRuntimeLog('window did-finish-load');
|
||||
});
|
||||
|
||||
win.webContents.on('did-fail-load', (_event, errorCode, errorDescription, validatedURL) => {
|
||||
logger.error('[window] did-fail-load errorCode:', errorCode, 'errorDescription:', errorDescription, 'url:', validatedURL);
|
||||
writeRuntimeLog(`window did-fail-load errorCode=${errorCode} errorDescription=${errorDescription} url=${validatedURL}`);
|
||||
});
|
||||
|
||||
// The window is centered and scaled proportionally
|
||||
// Obtain the size information of the main screen, calculate the width and height of the window as a percentage of the screen,
|
||||
// and calculate the coordinates of the upper left corner when the window is centered
|
||||
@@ -67,4 +90,4 @@ class Lifecycle {
|
||||
}
|
||||
Lifecycle.toString = () => '[class Lifecycle]';
|
||||
|
||||
export { Lifecycle };
|
||||
export { Lifecycle };
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
import {BasedbService} from './basedb';
|
||||
import fs from 'fs';
|
||||
import { BasedbService } from './basedb';
|
||||
|
||||
interface ActivateRecordItem {
|
||||
applicant: string;
|
||||
macAddress: string;
|
||||
applicationCode: string;
|
||||
module: string;
|
||||
activationCode: string;
|
||||
createTime: string;
|
||||
remark: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* sqlite数据存储
|
||||
* @class
|
||||
* sqlite data storage
|
||||
*/
|
||||
class ActivateRecordService extends BasedbService {
|
||||
tableName: string;
|
||||
@@ -10,75 +20,89 @@ class ActivateRecordService extends BasedbService {
|
||||
constructor() {
|
||||
const options = {
|
||||
dbname: 'pqs9100-tool.db',
|
||||
}
|
||||
};
|
||||
super(options);
|
||||
this.tableName = 'activate_record';
|
||||
}
|
||||
|
||||
/*
|
||||
* 初始化表
|
||||
/**
|
||||
* Initialize table and perform lightweight schema migration.
|
||||
*/
|
||||
init(): void {
|
||||
this._init();
|
||||
|
||||
// 检查表是否存在
|
||||
const masterStmt = this.db.prepare('SELECT * FROM sqlite_master WHERE type=? AND name = ?');
|
||||
let tableExists = masterStmt.get('table', this.tableName);
|
||||
const tableExists = masterStmt.get('table', this.tableName);
|
||||
if (!tableExists) {
|
||||
// 创建表
|
||||
const create_table_sql =
|
||||
const createTableSql =
|
||||
`CREATE TABLE ${this.tableName}
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
applicant CHAR(100) NULL,
|
||||
macAddress CHAR(50) NOT NULL,
|
||||
applicationCode CHAR(2000) NOT NULL,
|
||||
module CHAR(200) NOT NULL,
|
||||
activationCode CHAR(2000) NOT NULL,
|
||||
createTime CHAR(32) NOT NULL,
|
||||
remark CHAR(120) NULL
|
||||
);`
|
||||
this.db.exec(create_table_sql);
|
||||
remark CHAR(120) NULL
|
||||
);`;
|
||||
this.db.exec(createTableSql);
|
||||
return;
|
||||
}
|
||||
|
||||
const columns = this.db.prepare(`PRAGMA table_info(${this.tableName})`).all() as Array<{ name: string }>;
|
||||
const hasApplicant = columns.some((column) => column.name === 'applicant');
|
||||
if (!hasApplicant) {
|
||||
this.db.exec(`ALTER TABLE ${this.tableName} ADD COLUMN applicant CHAR(100) NULL`);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 增 data (sqlite)
|
||||
/**
|
||||
* Insert one record.
|
||||
*/
|
||||
async save(data: {
|
||||
macAddress: string;
|
||||
applicationCode: string;
|
||||
module: string;
|
||||
activationCode: string;
|
||||
createTime: string;
|
||||
remark: string;
|
||||
}) {
|
||||
const insert = this.db.prepare(`INSERT INTO ${this.tableName} (macAddress, applicationCode, module, activationCode, createTime, remark)
|
||||
VALUES (@macAddress, @applicationCode, @module, @activationCode, @createTime, @remark)`);
|
||||
insert.run(data);
|
||||
async save(data: ActivateRecordItem) {
|
||||
const insert = this.db.prepare(
|
||||
`INSERT INTO ${this.tableName} (applicant, macAddress, applicationCode, module, activationCode, createTime, remark)
|
||||
VALUES (@applicant, @macAddress, @applicationCode, @module, @activationCode, @createTime, @remark)`
|
||||
);
|
||||
insert.run({
|
||||
...data,
|
||||
applicant: data.applicant || '',
|
||||
remark: data.remark || ''
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* 删 data
|
||||
/**
|
||||
* Delete one record by id.
|
||||
*/
|
||||
async removeById(name: string = ''): Promise<boolean> {
|
||||
async removeById(id: number): Promise<boolean> {
|
||||
const remove = this.db.prepare(`DELETE
|
||||
FROM ${this.tableName}
|
||||
WHERE id = ?`);
|
||||
remove.run(name);
|
||||
remove.run(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* 查list data (sqlite)
|
||||
/**
|
||||
* Clear all records.
|
||||
*/
|
||||
async clear(): Promise<boolean> {
|
||||
const clearStmt = this.db.prepare(`DELETE FROM ${this.tableName}`);
|
||||
clearStmt.run();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query record list.
|
||||
*/
|
||||
async list(macAddress: string = '', modules: string[] = []): Promise<any[]> {
|
||||
let condition = ''
|
||||
let condition = '';
|
||||
if (macAddress) {
|
||||
condition += ` AND macAddress = '${macAddress}'`
|
||||
condition += ` AND macAddress = '${macAddress}'`;
|
||||
}
|
||||
if (modules.length > 0) {
|
||||
const moduleConditions = modules.map(module => `module LIKE '%${module}%'`).join(' OR ');
|
||||
const moduleConditions = modules.map((module) => `module LIKE '%${module}%'`).join(' OR ');
|
||||
condition += ` AND (${moduleConditions})`;
|
||||
}
|
||||
const select = this.db.prepare(`SELECT *
|
||||
@@ -88,35 +112,88 @@ class ActivateRecordService extends BasedbService {
|
||||
return select.all();
|
||||
}
|
||||
|
||||
/*
|
||||
* all Test data (sqlite)
|
||||
/**
|
||||
* Export all records to a JSON backup file.
|
||||
*/
|
||||
async backup(filePath: string): Promise<number> {
|
||||
const records = await this.getAllTestDataSqlite();
|
||||
const payload = {
|
||||
version: 1,
|
||||
tableName: this.tableName,
|
||||
exportedAt: new Date().toISOString(),
|
||||
records
|
||||
};
|
||||
fs.writeFileSync(filePath, JSON.stringify(payload, null, 2), 'utf8');
|
||||
return records.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import backup and replace current records.
|
||||
*/
|
||||
async importBackup(filePath: string): Promise<number> {
|
||||
const raw = fs.readFileSync(filePath, 'utf8');
|
||||
const parsed = JSON.parse(raw);
|
||||
const inputRecords = Array.isArray(parsed) ? parsed : parsed.records;
|
||||
if (!Array.isArray(inputRecords)) {
|
||||
throw new Error('Invalid backup file');
|
||||
}
|
||||
|
||||
const records = inputRecords.map((item: any) => this.normalizeRecord(item));
|
||||
const clearStmt = this.db.prepare(`DELETE FROM ${this.tableName}`);
|
||||
const insertStmt = this.db.prepare(
|
||||
`INSERT INTO ${this.tableName} (applicant, macAddress, applicationCode, module, activationCode, createTime, remark)
|
||||
VALUES (@applicant, @macAddress, @applicationCode, @module, @activationCode, @createTime, @remark)`
|
||||
);
|
||||
const transaction = this.db.transaction((rows: ActivateRecordItem[]) => {
|
||||
clearStmt.run();
|
||||
for (const row of rows) {
|
||||
insertStmt.run(row);
|
||||
}
|
||||
});
|
||||
|
||||
transaction(records);
|
||||
return records.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all records.
|
||||
*/
|
||||
async getAllTestDataSqlite(): Promise<any[]> {
|
||||
const selectAllUser = this.db.prepare(`SELECT *
|
||||
FROM ${this.tableName} `);
|
||||
const allUser = selectAllUser.all();
|
||||
return allUser;
|
||||
FROM ${this.tableName}
|
||||
ORDER BY id DESC`);
|
||||
return selectAllUser.all();
|
||||
}
|
||||
|
||||
/*
|
||||
* get data dir (sqlite)
|
||||
/**
|
||||
* Get data directory.
|
||||
*/
|
||||
async getDataDir(): Promise<string> {
|
||||
const dir = this.storage.getDbDir();
|
||||
return dir;
|
||||
return this.storage.getDbDir();
|
||||
}
|
||||
|
||||
/*
|
||||
* set custom data dir (sqlite)
|
||||
/**
|
||||
* Set custom data directory.
|
||||
*/
|
||||
async setCustomDataDir(dir: string): Promise<void> {
|
||||
if (dir.length == 0) {
|
||||
if (dir.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.changeDataDir(dir);
|
||||
this.init();
|
||||
return;
|
||||
}
|
||||
|
||||
private normalizeRecord(item: any): ActivateRecordItem {
|
||||
return {
|
||||
applicant: typeof item?.applicant === 'string' ? item.applicant : '',
|
||||
macAddress: typeof item?.macAddress === 'string' ? item.macAddress : '',
|
||||
applicationCode: typeof item?.applicationCode === 'string' ? item.applicationCode : '',
|
||||
module: typeof item?.module === 'string' ? item.module : Array.isArray(item?.modules) ? item.modules.join(',') : '',
|
||||
activationCode: typeof item?.activationCode === 'string' ? item.activationCode : '',
|
||||
createTime: typeof item?.createTime === 'string' ? item.createTime : '',
|
||||
remark: typeof item?.remark === 'string' ? item.remark : '',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user