;
+
+export interface SendMessage> {
+ apiName: string,
+ parameter?: P,
+ displayScale?: number,
+}
+
+export interface PrinterResponse {
+ apiName: string,
+ resultAck: T,
+}
+
+export interface InitCanvasParams {
+ width: number,
+ height: number,
+ rotate: 0 | 90 | 180 | 270,
+ path: "",
+ verticalShift: 0,
+ HorizontalShift: 0,
+}
+
+export interface DrawTextParams {
+ x: number,
+ y: number,
+ width: number,
+ height: number,
+ value: string,
+ fontFamily: "",
+ rotate: 0 | 1 | 2 | 3,
+ fontSize: number,
+ textAlignHorizonral: 0 | 1 | 2,
+ textAlignVertical: 0 | 1 | 2,
+ letterSpacing: number,
+ lineSpacing: number,
+ lineMode: 1 | 2 | 4 | 6,
+ fontStyle: boolean[],
+}
+
+export interface DrawQrCodeParams {
+ x: number,
+ y: number,
+ height: number,
+ width: number,
+ value: string,
+ codeType: 31 | 32 | 33 | 34,
+ rotate: 0 | 90 | 180 | 270,
+}
+
+export interface StartJobParams {
+ printDensity: number,
+ printLabelType: number,
+ printMode: number,
+ count: number,
+}
+
+export interface InitCanvasParams {
+ width: number,
+ height: number,
+}
diff --git a/packages/schema/src/setting.ts b/packages/schema/src/setting.ts
new file mode 100644
index 0000000..f8b843e
--- /dev/null
+++ b/packages/schema/src/setting.ts
@@ -0,0 +1,14 @@
+import nullable from "@repo/util/data/type";
+import {z} from "zod/v4";
+
+export const settingSchema = z.object({
+ address: nullable(z.string()),
+ name: nullable(z.string()),
+ phone: nullable(z.string()),
+ label: nullable(z.string().default("NextMyOrder")),
+ cargo: nullable(z.string()),
+ title: nullable(z.string().default("NextMyOrder")),
+ logo: nullable(z.url()),
+});
+
+export type SettingSchema = z.infer;
diff --git a/packages/schema/src/shipping.ts b/packages/schema/src/shipping.ts
new file mode 100644
index 0000000..c382b83
--- /dev/null
+++ b/packages/schema/src/shipping.ts
@@ -0,0 +1,40 @@
+import {z} from "zod/v4";
+
+export const statusMap = {
+ pending: {
+ text: "待处理",
+ },
+ confirmed: {
+ text: "正在运输",
+ },
+ finished: {
+ text: "已完成",
+ },
+ warning: {
+ text: "需要关注",
+ },
+ failed: {
+ text: "失败",
+ },
+};
+
+export const shippingSchema = z.object({
+ id: z.number(),
+ expressNumber: z.string().nullish().catch(null),
+ tax: z.number().min(0),
+ fee: z.number().min(0),
+ status: z.string().default("pending"),
+ createdAt: z.date().default(new Date()),
+ comment: z.string().nullish().catch(null),
+});
+
+export const shippingData = shippingSchema.extend({
+ orderId: z.number(),
+ orderIds: z.number().array().min(1),
+}).omit({
+ createdAt: true,
+});
+
+export type ShippingSchema = z.infer;
+
+export type ShippingData = z.infer;
diff --git a/packages/schema/src/user.ts b/packages/schema/src/user.ts
new file mode 100644
index 0000000..c03ab7a
--- /dev/null
+++ b/packages/schema/src/user.ts
@@ -0,0 +1,21 @@
+import {z} from "zod/v4";
+
+export const userSchema = z.object({
+ id: z.number(),
+ name: z.string(),
+ qq: z.string().regex(/^\d+$/),
+ email: z.email().nullish().catch(null),
+ phone: z.string().regex(/^1\d{10}$/).nullish().catch(null),
+ address: z.string().nullish().catch(null),
+ createdAt: z.date().default(new Date()),
+});
+
+export const userData = userSchema.omit({
+ createdAt: true,
+}).extend({
+ qqs: z.string().regex(/^\d+$/).array(),
+});
+
+export type UserSchema = z.infer
+
+export type UserData = z.infer;
diff --git a/packages/schema/tsconfig.json b/packages/schema/tsconfig.json
new file mode 100644
index 0000000..26cf637
--- /dev/null
+++ b/packages/schema/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "@repo/config/tsconfig.json",
+ "compilerOptions": {
+ "outDir": "dist"
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/util/eslint.config.mjs b/packages/util/eslint.config.mjs
new file mode 100644
index 0000000..fd683ae
--- /dev/null
+++ b/packages/util/eslint.config.mjs
@@ -0,0 +1,4 @@
+import eslintConfig from "@repo/config/eslint.js";
+
+/** @type {import("eslint").Linter.Config} */
+export default eslintConfig;
diff --git a/packages/util/package.json b/packages/util/package.json
new file mode 100644
index 0000000..12e2098
--- /dev/null
+++ b/packages/util/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "@repo/util",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "prisma:generate": "prisma generate"
+ },
+ "exports": {
+ "./client/*": "./src/client/*.ts",
+ "./data/*": "./src/data/*.ts",
+ "./item/*": "./src/item/*.ts",
+ "./security/*": "./src/security/*.ts",
+ "./printer/*": "./src/printer/*.ts"
+ },
+ "devDependencies": {
+ "@repo/config": "*",
+ "@types/node": "^22.15.3",
+ "@types/nodemailer": "^7.0.1",
+ "prisma": "^6.16.2"
+ },
+ "dependencies": {
+ "@repo/schema": "*",
+ "@prisma/client": "^6.16.2",
+ "@trpc/server": "^11.5.1",
+ "axios": "^1.12.1",
+ "cheerio": "^1.1.2",
+ "jose": "^6.1.0",
+ "nodemailer": "^7.0.6",
+ "zod": "^3.24.4"
+ }
+}
diff --git a/packages/util/prisma/schema.prisma b/packages/util/prisma/schema.prisma
new file mode 100644
index 0000000..88c454f
--- /dev/null
+++ b/packages/util/prisma/schema.prisma
@@ -0,0 +1,172 @@
+generator client {
+ provider = "prisma-client-js"
+ previewFeatures = ["views"]
+ binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
+}
+
+datasource db {
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+}
+
+model User {
+ id Int @id @default(autoincrement())
+ name String @unique
+ qq String @unique
+ email String? @unique
+ phone String?
+ address String?
+ createdAt DateTime @default(now())
+ lists List[]
+ orders Order[]
+ deliveries Delivery[]
+}
+
+model Group {
+ id Int @id @default(autoincrement())
+ name String @unique
+ qq String @unique
+ deadline DateTime
+ ended Boolean @default(false)
+ createdAt DateTime @default(now())
+ lists List[]
+ items Item[]
+}
+
+model List {
+ id Int @id @default(autoincrement())
+ userId Int
+ groupId Int
+ createdAt DateTime @default(now())
+ confirmed Boolean @default(false)
+ finished Boolean @default(false)
+ group Group @relation(fields: [groupId], references: [id], onDelete: Cascade)
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+ @@unique([userId, groupId])
+}
+
+model Item {
+ id Int @id @default(autoincrement())
+ groupId Int
+ name String
+ url String
+ image String?
+ price Float
+ weight Float?
+ allowed Boolean @default(false)
+ group Group @relation(fields: [groupId], references: [id], onDelete: Cascade)
+ orders Order[]
+
+ @@unique([groupId, name, url, price])
+}
+
+model Shipping {
+ id Int @id @default(autoincrement())
+ expressNumber String? @unique
+ tax Float @default(0)
+ fee Float @default(0)
+ status String @default("unknown")
+ createdAt DateTime @default(now())
+ comment String?
+ orders Order[]
+}
+
+model Delivery {
+ id Int @id @default(autoincrement())
+ userId Int
+ name String
+ phone String?
+ address String?
+ company String?
+ status String @default("pending")
+ createdAt DateTime @default(now())
+ comment String?
+ taskId String? @unique
+ expressId String?
+ expressNumber String?
+ queryToken String?
+ orders DeliveryOrder[]
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+}
+
+model Order {
+ id Int @id @default(autoincrement())
+ userId Int
+ itemId Int
+ shippingId Int?
+ count Int @default(1)
+ status String @default("pending")
+ createdAt DateTime @default(now())
+ comment String?
+ item Item @relation(fields: [itemId], references: [id], onDelete: Cascade)
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+ shipping Shipping? @relation(fields: [shippingId], references: [id])
+ deliveries DeliveryOrder[]
+}
+
+model DeliveryOrder {
+ deliveryId Int
+ orderId Int
+ delivery Delivery @relation(fields: [deliveryId], references: [id], onDelete: Cascade)
+ order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
+
+ @@id([deliveryId, orderId])
+}
+
+model Setting {
+ key String @id
+ value String?
+}
+
+view UserSummary {
+ id Int @unique
+ groupId Int
+ name String
+ qq String
+ total Float
+}
+
+view CheckSummary {
+ id Int @unique
+ shippingId Int
+ name String
+ url String
+ image String?
+ groupName String
+ total Int
+ check Int
+}
+
+view ItemSummary {
+ id Int @unique
+ groupId Int
+ name String
+ url String
+ image String?
+ price Float
+ count Int
+ confirmed Int
+ pending Int
+ total Float
+}
+
+view TaxSummary {
+ id Int @unique
+ shippingId Int
+ name String
+ qq String
+ total Float
+ ratio Float
+ tax Float
+}
+
+view WeightSummary {
+ id Int @unique
+ shippingId Int
+ name String
+ qq String
+ total Float
+ ratio Float
+ fee Float
+}
diff --git a/src/util/client/common.ts b/packages/util/src/client/common.ts
similarity index 81%
rename from src/util/client/common.ts
rename to packages/util/src/client/common.ts
index 71c1b49..12983f4 100644
--- a/src/util/client/common.ts
+++ b/packages/util/src/client/common.ts
@@ -1,7 +1,7 @@
import axios from "axios";
const common = axios.create({
- timeout: 15000
+ timeout: 15000,
});
export default common;
diff --git a/src/util/client/kd100.ts b/packages/util/src/client/kd100.ts
similarity index 63%
rename from src/util/client/kd100.ts
rename to packages/util/src/client/kd100.ts
index a540304..1b1dadb 100644
--- a/src/util/client/kd100.ts
+++ b/packages/util/src/client/kd100.ts
@@ -2,25 +2,25 @@ import axios from "axios";
import crypto from "crypto";
const kd100 = axios.create({
- timeout: 15000
+ timeout: 15000,
});
kd100.interceptors.request.use(async (config) => {
if (config.data instanceof URLSearchParams) {
- const data = config.data
+ const data = config.data;
const param = data.get("param")?.toString() ?? "";
const t = (Math.floor(Date.now() / 1000) * 1000).toString();
- const key = process.env.EXPRESS_KEY!
- const secret = process.env.EXPRESS_SECRET!
+ const key = process.env.EXPRESS_KEY!;
+ const secret = process.env.EXPRESS_SECRET!;
const sign = crypto.createHash("MD5")
.update(param + t + key + secret)
.digest("hex")
- .toUpperCase()
- data.append("t", t)
- data.append("key", key)
- data.append("sign", sign)
+ .toUpperCase();
+ data.append("t", t);
+ data.append("key", key);
+ data.append("sign", sign);
}
return config;
-})
+});
export default kd100;
diff --git a/packages/util/src/client/napcat.ts b/packages/util/src/client/napcat.ts
new file mode 100644
index 0000000..58a4b50
--- /dev/null
+++ b/packages/util/src/client/napcat.ts
@@ -0,0 +1,11 @@
+import axios from "axios";
+
+const napcat = axios.create({
+ timeout: 15000,
+ baseURL: process.env.NAPCAT_URL,
+ headers: {
+ Authorization: `Bearer ${process.env.NAPCAT_TOKEN}`,
+ },
+});
+
+export default napcat;
diff --git a/src/util/print/label.ts b/packages/util/src/client/printer.ts
similarity index 55%
rename from src/util/print/label.ts
rename to packages/util/src/client/printer.ts
index aaa321d..59deccc 100644
--- a/src/util/print/label.ts
+++ b/packages/util/src/client/printer.ts
@@ -1,8 +1,8 @@
-import {labelSdk} from "@/util/print/sdk";
-import {methodMap} from "@/type/delivery";
-import {DrawTextParams} from "@/type/print";
+import {labelSdk} from "../printer/niimbot";
+import {companyMap} from "@repo/schema/delivery";
+import {DrawTextParams} from "@repo/schema/printer";
-const printLabel = async (data: Record, isPrint?: boolean) => {
+const printLabel = async (data: Record) => {
const items = [
{
x: 2,
@@ -18,14 +18,14 @@ const printLabel = async (data: Record, isPrint?: boolean) => {
letterSpacing: 0,
lineSpacing: 0,
lineMode: 6,
- fontStyle: [false, false, false, false]
+ fontStyle: [false, false, false, false],
},
{
x: 36,
y: 2,
width: 12,
height: 4.3,
- value: methodMap[data?.method as keyof typeof methodMap].text,
+ value: companyMap[data?.company as keyof typeof companyMap].text,
fontFamily: "",
rotate: 0,
fontSize: 9,
@@ -34,14 +34,14 @@ const printLabel = async (data: Record, isPrint?: boolean) => {
letterSpacing: 0,
lineSpacing: 0,
lineMode: 6,
- fontStyle: [false, false, false, false]
+ fontStyle: [false, false, false, false],
},
{
x: 2,
y: 15,
width: 30,
- height: 8.5,
- value: `${data.name}\n${(data.phone as string).slice(-4)}`,
+ height: 12.8,
+ value: `${data.name}\n${data.city}\n${(data.phone as string).slice(-4)}`,
fontFamily: "",
rotate: 0,
fontSize: 9,
@@ -50,32 +50,24 @@ const printLabel = async (data: Record, isPrint?: boolean) => {
letterSpacing: 0,
lineSpacing: 0,
lineMode: 6,
- fontStyle: [false, false, false, false]
- }
+ fontStyle: [false, false, false, false],
+ },
];
- if (isPrint) await labelSdk.startJob(3, 1, 1, 1)
+ await labelSdk.startJob(3, 1, 1, 1);
await labelSdk.initCanvas({width: 50, height: 30, rotate: 0, path: "", verticalShift: 0, HorizontalShift: 0});
- for (const item of items) await labelSdk.drawText(item as DrawTextParams)
+ for (const item of items) {
+ await labelSdk.drawText(item as DrawTextParams);
+ }
await labelSdk.drawQrCode({
x: 36,
y: 16,
width: 12,
height: 12,
- value: JSON.stringify({
- name: data.name,
- phone: (data.phone as string).slice(-4),
- method: data.method,
- createdAt: Math.floor(Date.now() / 1000),
- }),
+ value: `${window.location.origin}/delivery/${data.id}`,
codeType: 31,
- rotate: 0
- })
- if (isPrint) {
- await labelSdk.commitJob(null, {printQuantity: 1});
- }
- else {
- return await labelSdk.preview(8);
- }
-}
+ rotate: 0,
+ });
+ await labelSdk.commitJob(null, {printQuantity: 1});
+};
export default printLabel;
diff --git a/packages/util/src/client/smtp.ts b/packages/util/src/client/smtp.ts
new file mode 100644
index 0000000..04bfddc
--- /dev/null
+++ b/packages/util/src/client/smtp.ts
@@ -0,0 +1,27 @@
+import nodemailer from "nodemailer";
+
+const mail = nodemailer.createTransport({
+ host: process.env.EMAIL_HOST,
+ port: parseInt(process.env.EMAIL_PORT || "587", 10),
+ requireTLS: true,
+ auth: {
+ user: process.env.EMAIL_USER,
+ pass: process.env.EMAIL_PASSWORD,
+ },
+});
+
+const sendEmail = async (to: string, subject: string, html: string) => {
+ try {
+ await mail.sendMail({
+ from: process.env.EMAIL_FROM,
+ to,
+ subject,
+ html,
+ });
+ } catch (error) {
+ console.error(`Failed to send email to ${to}:`, error);
+ throw error;
+ }
+};
+
+export default sendEmail;
diff --git a/packages/util/src/client/track.ts b/packages/util/src/client/track.ts
new file mode 100644
index 0000000..1fd750e
--- /dev/null
+++ b/packages/util/src/client/track.ts
@@ -0,0 +1,11 @@
+import axios from "axios";
+
+const track = axios.create({
+ timeout: 15000,
+ baseURL: "https://api.17track.net/track/v2.4",
+ headers: {
+ "17token": process.env.TRACKING_KEY,
+ },
+});
+
+export default track;
diff --git a/src/util/data/database.ts b/packages/util/src/data/database.ts
similarity index 90%
rename from src/util/data/database.ts
rename to packages/util/src/data/database.ts
index 3b014c2..2ed62e8 100644
--- a/src/util/data/database.ts
+++ b/packages/util/src/data/database.ts
@@ -1,7 +1,7 @@
import {PrismaClient} from "@prisma/client";
const globalForPrisma = globalThis as unknown as {
- prisma?: PrismaClient;
+ prisma?: PrismaClient,
};
const prisma = globalForPrisma.prisma ?? new PrismaClient();
diff --git a/packages/util/src/data/query.ts b/packages/util/src/data/query.ts
new file mode 100644
index 0000000..6df430a
--- /dev/null
+++ b/packages/util/src/data/query.ts
@@ -0,0 +1,193 @@
+import {z} from "zod/v4";
+import {TRPCError} from "@trpc/server";
+import {Prisma} from "@prisma/client";
+
+type SearchBuilder = {
+ [key: string]: {
+ contains: string,
+ mode: "insensitive",
+ },
+}[]
+
+const expandNestedField = (field: string, value: any) => {
+ const keys = field.split(".");
+ return keys.reduceRight((acc, key) => ({ [key]: acc }), value);
+};
+
+const deepMerge = (target: any, source: any): any => {
+ for (const key of Object.keys(source)) {
+ if (
+ key in target &&
+ typeof target[key] === "object" &&
+ typeof source[key] === "object"
+ ) {
+ target[key] = deepMerge(target[key], source[key]);
+ } else {
+ target[key] = source[key];
+ }
+ }
+ return target;
+};
+
+const buildTree = (paths: string[]): Record => {
+ const result = new Set();
+ for (const path of paths) {
+ const parts = path.split(".");
+ for (let i = 1; i <= parts.length; i++) {
+ result.add(parts.slice(0, i).join("."));
+ }
+ }
+ return Array.from(result).reduce((acc, path) => {
+ return deepMerge(acc, expandNestedField(path, {}));
+ }, {});
+};
+
+export const safeSelect = (
+ name: string,
+ block: string[] = [],
+ include: string[] = [],
+) => {
+ const model = Prisma.dmmf.datamodel.models.find(
+ (m) => m.name.toLowerCase() === name.toLowerCase(),
+ );
+ if (!model) {
+ throw new Error(`Model ${name} not found`);
+ }
+
+ const blockTree = buildTree(block);
+ const includeTree = buildTree(include);
+
+ const buildSelect = (
+ modelName: string,
+ blockNode: Record,
+ includeNode: Record,
+ ): Record => {
+ const mdl = Prisma.dmmf.datamodel.models.find(
+ (m) => m.name.toLowerCase() === modelName.toLowerCase(),
+ );
+ if (!mdl) return {};
+
+ return Object.fromEntries(
+ mdl.fields
+ .filter((f) =>
+ f.kind === "object"
+ ? f.name in includeNode
+ : !(f.name in blockNode),
+ )
+ .map((f) => {
+ if (f.kind === "object") {
+ return [
+ f.name,
+ {
+ select: buildSelect(
+ f.type,
+ blockNode[f.name] || {},
+ includeNode[f.name] || {},
+ ),
+ },
+ ];
+ }
+ return [f.name, true];
+ }),
+ );
+ };
+
+ return {
+ select: buildSelect(name, blockTree, includeTree),
+ };
+};
+
+export const safeRule = z.object({
+ filter: z.object({
+ field: z.string(),
+ operator: z.enum(["eq", "ne", "gt", "gte", "lt", "lte", "contains", "startsWith", "endsWith"]).array(),
+ }).array(),
+ column: z.object({
+ modal: z.string(),
+ block: z.string().array().optional(),
+ include: z.string().array().optional(),
+ }).optional(),
+ sort: z.string().array().default(["id"]).optional(),
+ search: z.string().array().default(["name"]).optional(),
+});
+
+const filter = z.object({
+ field: z.string(),
+ operator: z.enum(["eq", "ne", "gt", "gte", "lt", "lte", "contains", "startsWith", "endsWith"]),
+ value: z.union([z.string(), z.number(), z.boolean(), z.date()]).nullable(),
+}).array().optional();
+
+export const queryDsl = z.object({
+ filter: filter,
+ sort: z.object({
+ field: z.string().default("id"),
+ order: z.enum(["asc", "desc"]).default("asc"),
+ }).optional(),
+ page: z.object({
+ size: z.number().default(20),
+ current: z.number().default(1),
+ }).optional(),
+ search: z.string().optional(),
+});
+
+export const whereBuilder = (
+ query: z.infer,
+ rule: SafeRule,
+ isCount: boolean = false,
+) => {
+ query.filter?.forEach(i => {
+ if (!rule.filter.some(r => r.field === i.field && r.operator.includes(i.operator))) {
+ throw new TRPCError({code: "BAD_REQUEST", message: `Invalid filter: ${i.field} ${i.operator}`});
+ }
+ });
+ if (query.sort?.field && !rule.sort?.includes(query.sort?.field)) {
+ throw new TRPCError({code: "BAD_REQUEST", message: `Invalid sort field: ${query.sort.field}`});
+ }
+ const searchBuilder: SearchBuilder = [];
+ const skip = ((query.page?.current || 1) - 1) * (query.page?.size || 20);
+ const take = query.page?.size || 20;
+ if (query.search) {
+ rule.search?.forEach(i => {
+ return searchBuilder.push(expandNestedField(i, {
+ contains: query.search!,
+ mode: "insensitive",
+ }));
+ });
+ }
+ return {
+ orderBy: query.sort? expandNestedField(query.sort.field, query.sort.order): {},
+ where: {
+ ...(query.filter? {
+ AND: query.filter.map(i => {
+ switch (i.operator) {
+ case "eq":
+ return expandNestedField(i.field, i.value);
+ case "ne":
+ return expandNestedField(i.field, {not: i.value});
+ case "gt":
+ return expandNestedField(i.field, {gt: i.value});
+ case "gte":
+ return expandNestedField(i.field, {gte: i.value});
+ case "lt":
+ return expandNestedField(i.field, {lt: i.value});
+ case "lte":
+ return expandNestedField(i.field, {lte: i.value});
+ case "contains":
+ return expandNestedField(i.field, {contains: i.value});
+ case "startsWith":
+ return expandNestedField(i.field, {startsWith: i.value});
+ case "endsWith":
+ return expandNestedField(i.field, {endsWith: i.value});
+ }
+ }),
+ }: {}),
+ ...(searchBuilder.length > 0 ? {OR: searchBuilder} : {}),
+ },
+ ...(rule.column && !isCount? safeSelect(rule.column!.modal, rule.column?.block, rule.column?.include): {}),
+ ...(isCount? {}: {skip, take}),
+ };
+};
+
+export type SafeRule = z.infer;
+
+export type Filter = z.infer;
diff --git a/packages/util/src/data/setting.ts b/packages/util/src/data/setting.ts
new file mode 100644
index 0000000..dba96d6
--- /dev/null
+++ b/packages/util/src/data/setting.ts
@@ -0,0 +1,24 @@
+import prisma from "./database";
+import {SettingSchema} from "@repo/schema/setting";
+
+export const getSetting = async () => {
+ return Object.fromEntries(
+ await prisma.setting.findMany().then(i => i.map(
+ j => [j.key, j.value],
+ )),
+ ) as SettingSchema;
+};
+
+export const updateSetting = async (data: SettingSchema) => {
+ const entries = Object.entries(data);
+ await Promise.all(entries.map(
+ ([key, value]) => prisma.setting.upsert({
+ where: {key},
+ update: {value},
+ create: {
+ key: key,
+ value: value as string | null,
+ },
+ }),
+ ));
+};
diff --git a/src/util/string.ts b/packages/util/src/data/string.ts
similarity index 72%
rename from src/util/string.ts
rename to packages/util/src/data/string.ts
index 9692696..4b82d1e 100644
--- a/src/util/string.ts
+++ b/packages/util/src/data/string.ts
@@ -1,25 +1,25 @@
export const cStd = (value: number) => {
return Intl.NumberFormat("zh-CN", {
style: "currency",
- currency: "JPY"
+ currency: "JPY",
}).format(Number(value));
-}
+};
export const mStd = (value: number | null) => {
return new Intl.NumberFormat("zh-CN", {
style: "unit",
unit: "gram",
- unitDisplay: "short"
- }).format(value || 0)
-}
+ unitDisplay: "short",
+ }).format(value || 0);
+};
export const rStd = (value: number) => {
return new Intl.NumberFormat("zh-CN", {
style: "percent",
minimumFractionDigits: 2,
- }).format(value)
-}
+ }).format(value);
+};
export const jStd = (value: string) => {
- return value.replace(/[\u3000\u00A0]/g, ' ').trim()
-}
+ return value.replace(/[\u3000\u00A0]/g, ' ').trim();
+};
diff --git a/packages/util/src/data/type.ts b/packages/util/src/data/type.ts
new file mode 100644
index 0000000..36c2745
--- /dev/null
+++ b/packages/util/src/data/type.ts
@@ -0,0 +1,11 @@
+import {z} from "zod/v4";
+
+const nullable = (
+ schema: T,
+) => {
+ return schema.optional().nullable().transform(val =>
+ val === '' || val === undefined ? null : val,
+ );
+};
+
+export default nullable;
diff --git a/src/util/item/animate.ts b/packages/util/src/item/animate.ts
similarity index 68%
rename from src/util/item/animate.ts
rename to packages/util/src/item/animate.ts
index 60c484e..9ac33f2 100644
--- a/src/util/item/animate.ts
+++ b/packages/util/src/item/animate.ts
@@ -1,5 +1,5 @@
import * as cheerio from "cheerio";
-import {jStd} from "@/util/string";
+import {jStd} from "../data/string";
export const match = /^https:\/\/www\.animate-onlineshop\.jp\/(?:pn\/.+\/)?pd\/(\d+)\/?$/;
@@ -10,5 +10,7 @@ export const parse = async (url: URL) => {
const $ = await cheerio.fromURL(url);
const price = Number($("p.price.new_price").text().replace(/[^\d.]/g, ""));
const name = jStd($("div.item_overview_detail > h1").text());
- return {name, price, url: `https://www.animate-onlineshop.jp/pd/${productId}/`};
-}
+ const image = $("ul.itemThumbnails > li > span > img").attr("src");
+ return {name, price, image
+ , url: `https://www.animate-onlineshop.jp/pd/${productId}/`};
+};
diff --git a/src/util/item/au-coop.ts b/packages/util/src/item/au-coop.ts
similarity index 63%
rename from src/util/item/au-coop.ts
rename to packages/util/src/item/au-coop.ts
index 0cad4d8..1daeae6 100644
--- a/src/util/item/au-coop.ts
+++ b/packages/util/src/item/au-coop.ts
@@ -1,6 +1,6 @@
import * as cheerio from "cheerio";
-import {jStd} from "@/util/string";
-import client from "@/util/client/common";
+import {jStd} from "../data/string";
+import client from "../client/common";
export const match = /^https:\/\/au-coop\.jp\/products\/([a-zA-Z0-9_-]+)$/;
@@ -9,5 +9,6 @@ export const parse = async (url: URL) => {
const $ = cheerio.load(http.data);
const price = Number($("span.price").text().replace(/[^\d.]/g, ""));
const name = jStd($("h1.product-meta__title.heading.h1").text());
- return {name, price, url: url.toString()};
-}
+ const image = $("img.product-gallery__image").attr("src")?.split("?")[0];
+ return {name, price, url: url.toString(), image: `https:${image}`};
+};
diff --git a/packages/util/src/item/gamers.ts b/packages/util/src/item/gamers.ts
new file mode 100644
index 0000000..31f28cb
--- /dev/null
+++ b/packages/util/src/item/gamers.ts
@@ -0,0 +1,12 @@
+import * as cheerio from "cheerio";
+import {jStd} from "../data/string";
+
+export const match = /^https:\/\/www\.gamers\.co\.jp\/pd\/\d+\/?$/;
+
+export const parse = async (url: URL) => {
+ const $ = await cheerio.fromURL(url);
+ const price = Number($("p.price > span").text().replace(/[^\d.]/g, ""));
+ const name = jStd($("#item_detail > h1").text());
+ const image = $("img.img_zoom").attr("src");
+ return {name, price, url: url.toString(), image};
+};
diff --git a/src/util/item/ichijinsha.ts b/packages/util/src/item/ichijinsha.ts
similarity index 65%
rename from src/util/item/ichijinsha.ts
rename to packages/util/src/item/ichijinsha.ts
index 8c22af0..d349e00 100644
--- a/src/util/item/ichijinsha.ts
+++ b/packages/util/src/item/ichijinsha.ts
@@ -1,11 +1,12 @@
import * as cheerio from "cheerio";
-import {jStd} from "@/util/string";
+import {jStd} from "../data/string";
-export const match = /https:\/\/www\.ichijin-shop\.jp\/view\/item\/\d{12}/g
+export const match = /https:\/\/www\.ichijin-shop\.jp\/view\/item\/\d{12}/g;
export const parse = async (url: URL) => {
const $ = await cheerio.fromURL(url);
const price = Number($('span[data-id="makeshop-item-price:1"]').text().replace(/[^\d.]/g, ""));
const name = jStd($("h2.item-detail-title").text());
- return {name, price, url: url.toString()};
-}
+ const image = $("img.item-image").attr("src") || null;
+ return {url: url.toString().split("?")[0], name, price, image};
+};
diff --git a/src/util/item/index.ts b/packages/util/src/item/index.ts
similarity index 77%
rename from src/util/item/index.ts
rename to packages/util/src/item/index.ts
index 77f30d2..76ac744 100644
--- a/src/util/item/index.ts
+++ b/packages/util/src/item/index.ts
@@ -3,12 +3,17 @@ import * as fs from "node:fs";
import {fileURLToPath} from "url";
type ItemParser = {
- match: RegExp | RegExp[];
- parse: (url: URL) => Promise<{name: string; price: number; url: string}>;
+ match: RegExp | RegExp[],
+ parse: (url: URL) => Promise<{
+ name: string,
+ price: number,
+ url: string,
+ image: string | null,
+ }>,
};
const parsers: ItemParser[] = [];
-const currentDir = path.dirname(fileURLToPath(import.meta.url)); // 当前路径 /lib/item
+const currentDir = path.dirname(fileURLToPath(import.meta.url));
const loadParsers = async () => {
const files = fs.readdirSync(currentDir);
@@ -21,17 +26,19 @@ const loadParsers = async () => {
parsers.push({match: pattern, parse: mod.parse});
}
}
-}
+};
const parseItem = async (url: string) => {
+ if (parsers.length === 0) {
+ await loadParsers();
+ }
const parser = parsers.find(p => {
if ("test" in p.match) {
+ p.match.lastIndex = 0;
return p.match.test(url);
}
- })
+ });
return parser?.parse(new URL(url));
-}
-
-await loadParsers();
+};
export default parseItem;
diff --git a/src/util/item/mangaoh.ts b/packages/util/src/item/mangaoh.ts
similarity index 93%
rename from src/util/item/mangaoh.ts
rename to packages/util/src/item/mangaoh.ts
index 3fba98a..db0b199 100644
--- a/src/util/item/mangaoh.ts
+++ b/packages/util/src/item/mangaoh.ts
@@ -1,5 +1,5 @@
import * as cheerio from "cheerio";
-import {jStd} from "@/util/string";
+import {jStd} from "../data/string";
export const match = /https:\/\/www\.mangaoh\.co\.jp\/catalog\/\d+\//g;
@@ -12,4 +12,4 @@ export const parse = async (url: URL) => {
.replace(/[^\d.]/g, ""));
const name = jStd($("h1.product-name.my-3.my-md-4.opacity-9").text());
return {name, price, url: url.toString()};
-}
+};
diff --git a/packages/util/src/item/melonbooks.ts b/packages/util/src/item/melonbooks.ts
new file mode 100644
index 0000000..e7fe6f3
--- /dev/null
+++ b/packages/util/src/item/melonbooks.ts
@@ -0,0 +1,12 @@
+import * as cheerio from "cheerio";
+import {jStd} from "../data/string";
+
+export const match = /^https:\/\/www\.melonbooks\.co\.jp\/(?:detail|products)\/detail\.php\?product_id=\d+$/;
+
+export const parse = async (url: URL) => {
+ const $ = await cheerio.fromURL(url);
+ const price = Number($("span.price--value").text().replace(/[^\d.]/g, ""));
+ const name = jStd($("h1.page-header").text());
+ const image = `https:${$(".slider > figure > a").attr("href")}`;
+ return {name, price, image, url: url.toString().replace("/detail/", "/products/")};
+};
diff --git a/src/util/item/suruga-ya.ts b/packages/util/src/item/suruga-ya.ts
similarity index 93%
rename from src/util/item/suruga-ya.ts
rename to packages/util/src/item/suruga-ya.ts
index 93c679b..5259029 100644
--- a/src/util/item/suruga-ya.ts
+++ b/packages/util/src/item/suruga-ya.ts
@@ -1,5 +1,5 @@
import * as cheerio from 'cheerio';
-import {jStd} from "@/util/string";
+import {jStd} from "../data/string";
export const match = /^https:\/\/www\.suruga-ya\.(?:jp|com\/zh-hans)\/product\/(?:detail\/)?([a-zA-Z0-9\-]+)$/;
@@ -10,4 +10,4 @@ export const parse = async (url: URL) => {
const price = Number($("span.text-price-detail.price-buy").text().replace(/[^\d.]/g, ""));
const name = jStd($("#item_title").text());
return {name, price, url: normalizedUrl};
-}
+};
diff --git a/src/util/item/toranoana.ts b/packages/util/src/item/toranoana.ts
similarity index 86%
rename from src/util/item/toranoana.ts
rename to packages/util/src/item/toranoana.ts
index d0f2416..0cde0d5 100644
--- a/src/util/item/toranoana.ts
+++ b/packages/util/src/item/toranoana.ts
@@ -1,6 +1,6 @@
import * as cheerio from "cheerio";
-import client from "@/util/client/common";
-import {jStd} from "@/util/string";
+import client from "../client/common";
+import {jStd} from "../data/string";
export const match = /https:\/\/ecs\.toranoana\.jp\/tora\/ec\/item\/\d{12}\/?/g;
@@ -10,4 +10,4 @@ export const parse = async (url: URL) => {
const price = Number($("li.pricearea__price.pricearea__price--normal.color_price.js-price-area").text().replace(/[^\d.]/g, ""));
const name = jStd($("h1.product-detail-desc-title > span").text());
return {name, price, url: url.toString()};
-}
+};
diff --git a/src/util/print/sdk.ts b/packages/util/src/printer/niimbot.ts
similarity index 96%
rename from src/util/print/sdk.ts
rename to packages/util/src/printer/niimbot.ts
index f41f281..c84f644 100644
--- a/src/util/print/sdk.ts
+++ b/packages/util/src/printer/niimbot.ts
@@ -5,8 +5,8 @@ import {
InitCanvasParams, PrinterRequest,
PrinterResponse,
SendMessage,
- StartJobParams
-} from "@/type/print";
+ StartJobParams,
+} from "@repo/schema/printer";
export class LabelSdk {
private websocket: WebSocket | null = null;
@@ -32,8 +32,7 @@ export class LabelSdk {
cb(msg.resultAck);
this.callbacks.delete(msg.apiName);
}
- }
- catch {
+ } catch {
console.warn("标签机返回非 JSON 格式", event.data);
}
});
@@ -41,13 +40,13 @@ export class LabelSdk {
}
disconnect() {
- this.websocket?.close()
+ this.websocket?.close();
}
send>(
apiName: string,
parameter: P = {} as P,
- displayScale?: number
+ displayScale?: number,
): Promise {
return new Promise((resolve, reject) => {
if (!this.websocket || this.websocket.readyState !== WebSocket.OPEN) {
diff --git a/packages/util/src/security/jwt.ts b/packages/util/src/security/jwt.ts
new file mode 100644
index 0000000..1b3f876
--- /dev/null
+++ b/packages/util/src/security/jwt.ts
@@ -0,0 +1,25 @@
+import * as jose from "jose";
+
+export const createJWT = async (
+ payload: Record,
+ subject: string,
+ audience: string,
+ expired: string = "1d",
+): Promise => {
+ return await new jose.SignJWT(payload)
+ .setProtectedHeader({
+ alg: "HS256",
+ typ: "JWT",
+ })
+ .setAudience(audience)
+ .setIssuedAt()
+ .setExpirationTime(expired)
+ .setSubject(subject)
+ .setJti(crypto.randomUUID())
+ .sign(new TextEncoder().encode(process.env.APP_KEY));
+};
+
+export const decryptJwt = async (token: string) => {
+ const secret = new TextEncoder().encode(process.env.APP_KEY);
+ return await jose.jwtVerify(token, secret);
+};
diff --git a/packages/util/tsconfig.json b/packages/util/tsconfig.json
new file mode 100644
index 0000000..288401f
--- /dev/null
+++ b/packages/util/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "@repo/config/tsconfig.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
deleted file mode 100644
index a1a24ea..0000000
--- a/prisma/schema.prisma
+++ /dev/null
@@ -1,94 +0,0 @@
-generator client {
- binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
- provider = "prisma-client-js"
-}
-
-datasource db {
- provider = "postgresql"
- url = env("DATABASE_URL")
-}
-
-model User {
- id Int @id @default(autoincrement())
- name String @unique
- qq String @unique
- email String? @unique
- phone String?
- address String?
- createAt DateTime @default(now())
-
- joined Join[]
- orders Order[]
-}
-
-model Group {
- id Int @id @default(autoincrement())
- name String @unique
- qq String @unique
- status String @default("activated")
- createAt DateTime @default(now())
-
- users Join[]
- items Item[]
-}
-
-model Join {
- userId Int
- groupId Int
- createAt DateTime @default(now())
-
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
- group Group @relation(fields: [groupId], references: [id], onDelete: Cascade)
-
- @@id([userId, groupId])
-}
-
-model Item {
- id Int @id @default(autoincrement())
- groupId Int
- name String @unique
- url String
- price Float
- weight Float?
- allowed Boolean @default(false)
-
- group Group @relation(fields: [groupId], references: [id], onDelete: Cascade)
- orders Order[]
-}
-
-model Order {
- id Int @id @default(autoincrement())
- userId Int
- itemId Int
- deliveryId Int?
- count Int @default(1)
- status String @default("pending")
- createAt DateTime @default(now())
- comment String?
-
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
- item Item @relation(fields: [itemId], references: [id], onDelete: Cascade)
- delivery Delivery? @relation(fields: [deliveryId], references: [id], onDelete: SetNull)
-}
-
-model Delivery {
- id Int @id @default(autoincrement())
- name String
- phone String
- method String
- address String
- status String @default("pending")
- createAt DateTime @default(now())
- comment String?
- taskId String? @unique
- expressId String?
- expressNumber String?
- token String?
-
- orders Order[]
-}
-
-model Setting {
- key String @unique
- value String
-}
diff --git a/src/app/(ui)/delivery/create/page.tsx b/src/app/(ui)/delivery/create/page.tsx
deleted file mode 100644
index 5ff6795..0000000
--- a/src/app/(ui)/delivery/create/page.tsx
+++ /dev/null
@@ -1,149 +0,0 @@
-"use client";
-import React from "react";
-import DeliveryTable from "@/component/table/delivery";
-import PrintField from "@/component/field/print";
-import printLabel from "@/util/print/label";
-import trpc from "@/server/client";
-import {ProFormText, ProFormTextArea} from "@ant-design/pro-form";
-import {DeliverySchema, methodMap} from "@/type/delivery";
-import {PageContainer} from "@ant-design/pro-layout";
-import {StepsForm} from "@ant-design/pro-components";
-import {CheckCard} from "@ant-design/pro-card";
-import {labelSdk} from "@/util/print/sdk";
-import {useRouter} from "next/navigation";
-import {App, Avatar, Form} from "antd";
-import {UserSchema} from "@/type/user";
-
-const Page = () => {
- const message = App.useApp().message;
- const router = useRouter();
- const [step, setStep] = React.useState(0);
- const [user, setUser] = React.useState([]);
- const [data, setData] = React.useState>();
- const [connect, setConnect] = React.useState("");
- React.useEffect(() => {
- const init = async () => {
- await labelSdk.connect()
- const painters = JSON.parse((await labelSdk.getAllPrinters()).info as string)
- await labelSdk.selectPrinter(Object.keys(painters)[0], Number(Object.values(painters)[0]))
- await labelSdk.initSdk()
- await labelSdk.setPrinterAutoShutDownTime(4)
- }
- if (step !== 2) return;
- init().then(async () => {
- const result = JSON.parse((await printLabel(data!))?.info as string)
- setConnect(result.ImageData)
- }).catch((err) => {
- message.error(err.message);
- });
- return () => {
- labelSdk.disconnect()
- setConnect("");
- };
- }, [data, step, message])
-
- return (
-
- setStep(current)}
- onFormFinish={(name, info) => {
- if (name === "select") {
- info.forms.info.setFieldsValue(user[0]);
- }
- }}
- onFinish={async (values) => {
- try {
- delete values.print
- await trpc.delivery.create.mutate(values as DeliverySchema);
- message.success("创建成功")
- router.back()
- } catch {
- message.error("创建失败")
- }
- return false
- }}
- >
-
- {
- if (value.length === 0) {
- return Promise.reject();
- }
- return Promise.resolve();
- }
- }
- ]}
- >
-
-
-
- {
- try {
- values.label = (await trpc.setting.get.query()).label;
- setData(values);
- return true;
- } catch {
- message.error("发生错误,请稍后再试")
- return false
- }
- }}
- >
-
-
- {
- Object.keys(methodMap).map((method, index) => (
-
- }
- />
- ))
- }
-
-
-
-
-
-
-
- {
- if (!value) {
- return Promise.reject();
- }
- return Promise.resolve();
- }
- }
- ]}
- >
-
-
-
-
-
- );
-}
-
-export default Page;
diff --git a/src/app/(ui)/delivery/page.tsx b/src/app/(ui)/delivery/page.tsx
deleted file mode 100644
index 7e37fff..0000000
--- a/src/app/(ui)/delivery/page.tsx
+++ /dev/null
@@ -1,251 +0,0 @@
-"use client";
-import React from "react";
-import trpc from "@/server/client";
-import Link from "next/link";
-import {ActionType, ProColumns, ProTable} from "@ant-design/pro-table";
-import {CloseOutlined, DeleteOutlined, EditOutlined, MessageOutlined} from "@ant-design/icons";
-import {DeliveryData, DeliverySchema, methodMap, statusMap} from "@/type/delivery";
-import {ModalForm, ProFormText, ProFormTextArea} from "@ant-design/pro-form";
-import {App, Avatar, Button, Form, Popconfirm, Popover} from "antd";
-import {PageContainer} from "@ant-design/pro-layout";
-import {CheckCard} from "@ant-design/pro-card";
-import {TRPCClientError} from "@trpc/client";
-
-const Page = () => {
- const message = App.useApp().message;
- const table = React.useRef(null);
- const columns: ProColumns[] = [
- {
- title: "ID",
- dataIndex: "id",
- sorter: true
- },
- {
- title: "收件人",
- dataIndex: "name",
- sorter: true,
- search: false
- },
- {
- title: "手机号码",
- dataIndex: "phone",
- sorter: true,
- search: false
- },
- {
- title: "地址",
- dataIndex: "address",
- search: false
- },
- {
- title: "快递方式",
- dataIndex: "method",
- sorter: true,
- valueType: "select",
- valueEnum: methodMap
- },
- {
- title: "快递单号",
- dataIndex: "expressNumber"
- },
- {
- title: "状态",
- dataIndex: "status",
- sorter: true,
- valueType: "select",
- valueEnum: statusMap
- },
- {
- title: "创建时间",
- dataIndex: "createAt",
- valueType: "dateTime",
- sorter: true,
- search: false
- },
- {
- title: "操作",
- valueType: "option",
- width: 150,
- render: (_, record, _1, action) => [
-
- ,
- }
- disabled={record.status !== "pending"}
- />
- }
- modalProps={{
- destroyOnHidden: true
- }}
- onFinish={async (values) => {
- try {
- values.id = record.id
- await trpc.delivery.update.mutate(values as DeliverySchema)
- message.success("修改成功")
- action?.reload();
- return true;
- } catch {
- message.error("发生错误,请稍后再试")
- return false;
- }
- }}
- >
-
-
- {
- Object.keys(methodMap).map((method, index) => (
-
- }
- />
- ))
- }
-
-
-
-
-
- ,
- {
- try {
- await trpc.delivery.flow.mutate({
- id: record.id
- });
- message.success("取消成功");
- action?.reload();
- return true;
- } catch {
- message.error("发生错误,请稍后再试");
- return false;
- }
- }}
- okText="确定"
- cancelText="取消"
- >
- ,
- {
- try {
- await trpc.delivery.delete.mutate({
- id: record.id
- });
- message.success("删除成功");
- action?.reload();
- return true;
- } catch {
- message.error("发生错误,请稍后再试");
- return false;
- }
- }}
- okText="确定"
- cancelText="取消"
- >
-
- ]
- }
- ];
-
- return (
-
-
- rowKey="id"
- rowSelection={{}}
- actionRef={table}
- columns={columns}
- search={{filterType: "light"}}
- options={{search: {allowClear: true}}}
- tableAlertOptionRender={({selectedRowKeys}) => [
- {
- try {
- await trpc.delivery.push.mutate({
- ids: selectedRowKeys.map((id) => Number(id)),
- });
- message.success("推送成功,请注意回调通知");
- table.current?.reload()
- return true;
- } catch (e) {
- if (e instanceof TRPCClientError) {
- message.error(e.message);
- } else {
- message.error("发生未知错误");
- }
- return false;
- }
- }}
- >
-
- ,
-
- ]}
- toolBarRender={() => [
-
-
-
- ]}
- request={async (params, sort) => {
- const res = await trpc.delivery.get.query({
- params, sort
- });
- return {
- data: res.items,
- success: true,
- total: res.total
- }
- }}
- />
-
- );
-}
-
-export default Page;
diff --git a/src/app/(ui)/group/[groupId]/page.tsx b/src/app/(ui)/group/[groupId]/page.tsx
deleted file mode 100644
index a3a1f5c..0000000
--- a/src/app/(ui)/group/[groupId]/page.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import React from "react";
-import GroupContainer from "@/component/container/group";
-import database from "@/util/data/database";
-import {notFound} from "next/navigation";
-
-interface Props {
- params: Promise<{groupId: number}>
-}
-
-export const revalidate = 0;
-
-const Page = async (props: Props) => {
- const groupId = Number((await props.params).groupId);
- const group = await database.group.findUnique({
- where: {
- id: groupId
- }
- });
- if (!group) {
- return notFound();
- }
-
- return (
-
- );
-}
-
-export default Page;
diff --git a/src/app/(ui)/group/[groupId]/summary/page.tsx b/src/app/(ui)/group/[groupId]/summary/page.tsx
deleted file mode 100644
index 265d2b0..0000000
--- a/src/app/(ui)/group/[groupId]/summary/page.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import SummaryContainer from "@/component/container/summary";
-import {summaryItem, summaryUser, summaryWeight} from "@/util/summary";
-import database from "@/util/data/database";
-import {notFound} from "next/navigation";
-
-interface Props {
- params: Promise<{
- groupId: number
- }>
-}
-
-export const revalidate = 0;
-
-const Page = async (props: Props) => {
- const groupId = Number((await props.params).groupId)
- const group = await database.group.findUnique({
- where: {
- id: groupId
- }
- });
- if (!group) {
- return notFound();
- }
- const item = await summaryItem(groupId);
- const user = await summaryUser(groupId);
- const weight = await summaryWeight(groupId);
- return (
-
- );
-}
-
-export default Page;
diff --git a/src/app/(ui)/group/[groupId]/user/[userId]/page.tsx b/src/app/(ui)/group/[groupId]/user/[userId]/page.tsx
deleted file mode 100644
index d3d3d71..0000000
--- a/src/app/(ui)/group/[groupId]/user/[userId]/page.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import JoinContainer from "@/component/container/join";
-import database from "@/util/data/database";
-import {notFound} from "next/navigation";
-
-interface Props {
- params: Promise<{
- groupId: number,
- userId: number
- }>
-}
-
-export const revalidate = 0;
-
-const Page = async (props: Props) => {
- const groupId = Number((await props.params).groupId);
- const userId = Number((await props.params).userId);
- const join = await database.join.findUnique({
- where: {
- userId_groupId: {
- userId: userId,
- groupId: groupId
- }
- },
- include: {
- group: true,
- user: true
- }
- })
- if (!join) {
- return notFound();
- }
-
- return (
-
- )
-}
-
-export default Page;
diff --git a/src/app/(ui)/group/page.tsx b/src/app/(ui)/group/page.tsx
deleted file mode 100644
index 73b2ece..0000000
--- a/src/app/(ui)/group/page.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-"use client";
-import React from "react";
-import GroupForm from "@/component/form/group";
-import trpc from "@/server/client";
-import {ActionType, ProColumns, ProTable} from "@ant-design/pro-table";
-import {PageContainer} from "@ant-design/pro-layout";
-import {GroupSchema, statusMap} from "@/type/group";
-import {SettingOutlined} from "@ant-design/icons";
-import {Typography, Button, App} from "antd";
-import Link from "next/link";
-
-const Page = () => {
- const message = App.useApp().message;
- const table = React.useRef(undefined);
- const columns: ProColumns[] = [
- {
- title: "ID",
- dataIndex: "id",
- sorter: true
- },
- {
- title: "Q群",
- dataIndex: "qq",
- sorter: true,
- search: false,
- render: (_, record) => (
-
- {record.name}
- {record.qq}
-
- )
- },
- {
- title: "创建时间",
- dataIndex: "createAt",
- valueType: "dateTime",
- sorter: true,
- search: false
- },
- {
- title: "状态",
- dataIndex: "status",
- valueType: "select",
- sorter: true,
- valueEnum: statusMap
- },
- {
- title: "操作",
- valueType: "option",
- width: 150,
- render: (_, record) => [
-
- }
- onSubmit={async (values: Record) => {
- try {
- await trpc.group.create.mutate(values as GroupSchema);
- message.success("添加成功")
- table.current?.reload();
- return true;
- } catch {
- message.error("发生错误,请稍后再试")
- return false;
- }
- }}
- />
- ]}
- request={async (params, sort) => {
- const res = await trpc.group.get.query({
- params, sort
- });
- return {
- data: res.items,
- success: true,
- total: res.total
- }
- }}
- />
-
- );
-}
-
-export default Page;
diff --git a/src/app/(ui)/layout.tsx b/src/app/(ui)/layout.tsx
deleted file mode 100644
index d264f9d..0000000
--- a/src/app/(ui)/layout.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from "react";
-import BaseLayout from "@/component/layout";
-
-interface Props {
- children: React.ReactNode;
-}
-
-const Layout = (props: Props) => {
- return (
-
- {props.children}
-
- );
-}
-
-export default Layout;
diff --git a/src/app/(ui)/setting/page.tsx b/src/app/(ui)/setting/page.tsx
deleted file mode 100644
index 052e1ec..0000000
--- a/src/app/(ui)/setting/page.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import SettingContainer from "@/component/container/setting";
-import {parse} from "@/util/data/setting";
-
-export const revalidate = 0;
-
-const Page = async () => {
- const setting = await parse()
-
- return (
-
- );
-}
-
-export default Page;
diff --git a/src/app/(ui)/user/page.tsx b/src/app/(ui)/user/page.tsx
deleted file mode 100644
index 2058723..0000000
--- a/src/app/(ui)/user/page.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-"use client";
-import React from "react";
-import trpc from "@/server/client";
-import UserForm from "@/component/form/user";
-import {Space, Avatar, Typography, Popconfirm, App, Button} from "antd";
-import {ActionType, ProColumns, ProTable} from "@ant-design/pro-table";
-import {DeleteOutlined, EditOutlined} from "@ant-design/icons";
-import {PageContainer} from "@ant-design/pro-layout";
-import {TRPCClientError} from "@trpc/client";
-import {UserSchema} from "@/type/user";
-
-const Page = () => {
- const message = App.useApp().message;
- const table = React.useRef(null);
- const columns: ProColumns[] = [
- {
- title: "ID",
- dataIndex: "id",
- sorter: true
- },
- {
- title: "QQ",
- dataIndex: "qq",
- sorter: true,
- search: false,
- render: (_, record) => (
-
-
-
- {record.name}
- {record.qq}
-
-
- )
- },
- {
- title: "邮箱",
- dataIndex: "email",
- sorter: true,
- search: false
- },
- {
- title: "注册时间",
- dataIndex: "createAt",
- valueType: "dateTime",
- sorter: true,
- search: false
- },
- {
- title: '操作',
- valueType: 'option',
- width: 150,
- render: (_, record, _1, action) => [
- }/>}
- onSubmit={async (values: Record) => {
- try {
- values.id = record.id;
- await trpc.user.update.mutate(values as UserSchema);
- message.success("修改成功");
- action?.reload();
- return true;
- } catch {
- message.error("发生错误,请稍后再试");
- return false;
- }
- }}
- />,
- {
- try {
- await trpc.user.delete.mutate({
- id: record.id
- });
- message.success("删除成功");
- action?.reload();
- return true;
- } catch (e) {
- if (e instanceof TRPCClientError) {
- message.error(e.message);
- } else {
- message.error("发生未知错误");
- }
- return false;
- }
- }}
- >
-
- ],
- }
- ];
-
- return (
-
- [
- 添加