rht
This commit is contained in:
parent
0dad0e18ec
commit
ca079e0096
|
@ -1,3 +1,6 @@
|
||||||
{
|
{
|
||||||
"marscode.chatLanguage": "cn"
|
"marscode.chatLanguage": "cn",
|
||||||
|
"marscode.codeCompletionPro": {
|
||||||
|
"enableCodeCompletionPro": true
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -11,9 +11,9 @@ export class ShareCodeRouter {
|
||||||
|
|
||||||
router = this.trpc.router({
|
router = this.trpc.router({
|
||||||
generateShareCode: this.trpc.procedure
|
generateShareCode: this.trpc.procedure
|
||||||
.input(z.object({ fileId: z.string() }))
|
.input(z.object({ fileId: z.string(), expiresAt: z.date(), canUseTimes: z.number() }))
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return this.shareCodeService.generateShareCode(input.fileId);
|
return this.shareCodeService.generateShareCode(input.fileId, input.expiresAt, input.canUseTimes);
|
||||||
}),
|
}),
|
||||||
validateAndUseCode: this.trpc.procedure
|
validateAndUseCode: this.trpc.procedure
|
||||||
.input(z.object({ code: z.string() }))
|
.input(z.object({ code: z.string() }))
|
||||||
|
@ -41,9 +41,9 @@ export class ShareCodeRouter {
|
||||||
return this.shareCodeService.getFileByShareCode(input.code);
|
return this.shareCodeService.getFileByShareCode(input.code);
|
||||||
}),
|
}),
|
||||||
generateShareCodeByFileId: this.trpc.procedure
|
generateShareCodeByFileId: this.trpc.procedure
|
||||||
.input(z.object({ fileId: z.string() }))
|
.input(z.object({ fileId: z.string(), expiresAt: z.date(), canUseTimes: z.number() }))
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return this.shareCodeService.generateShareCodeByFileId(input.fileId);
|
return this.shareCodeService.generateShareCodeByFileId(input.fileId, input.expiresAt, input.canUseTimes);
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -5,8 +5,11 @@ import { Cron, CronExpression } from '@nestjs/schedule';
|
||||||
import { ResourceService } from '@server/models/resource/resource.service';
|
import { ResourceService } from '@server/models/resource/resource.service';
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import utc from 'dayjs/plugin/utc';
|
||||||
|
import timezone from 'dayjs/plugin/timezone';
|
||||||
|
dayjs.extend(utc);
|
||||||
|
dayjs.extend(timezone);
|
||||||
export interface ShareCode {
|
export interface ShareCode {
|
||||||
id: string;
|
id: string;
|
||||||
code: string;
|
code: string;
|
||||||
|
@ -15,10 +18,12 @@ export interface ShareCode {
|
||||||
expiresAt: Date;
|
expiresAt: Date;
|
||||||
isUsed: boolean;
|
isUsed: boolean;
|
||||||
fileName?: string | null;
|
fileName?: string | null;
|
||||||
|
canUseTimes: number | null;
|
||||||
}
|
}
|
||||||
export interface GenerateShareCodeResponse {
|
export interface GenerateShareCodeResponse {
|
||||||
code: string;
|
code: string;
|
||||||
expiresAt: Date;
|
expiresAt: Date;
|
||||||
|
canUseTimes: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ResourceMeta {
|
interface ResourceMeta {
|
||||||
|
@ -40,6 +45,8 @@ export class ShareCodeService {
|
||||||
|
|
||||||
async generateShareCode(
|
async generateShareCode(
|
||||||
fileId: string,
|
fileId: string,
|
||||||
|
expiresAt: Date,
|
||||||
|
canUseTimes: number,
|
||||||
fileName?: string,
|
fileName?: string,
|
||||||
): Promise<GenerateShareCodeResponse> {
|
): Promise<GenerateShareCodeResponse> {
|
||||||
try {
|
try {
|
||||||
|
@ -55,8 +62,6 @@ export class ShareCodeService {
|
||||||
|
|
||||||
// 生成分享码
|
// 生成分享码
|
||||||
const code = this.generateCode();
|
const code = this.generateCode();
|
||||||
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24小时后过期
|
|
||||||
//const expiresAt = new Date(Date.now() + 10 * 1000); // 24小时后过期
|
|
||||||
// 查找是否已有分享码记录
|
// 查找是否已有分享码记录
|
||||||
const existingShareCode = await db.shareCode.findUnique({
|
const existingShareCode = await db.shareCode.findUnique({
|
||||||
where: { fileId },
|
where: { fileId },
|
||||||
|
@ -69,6 +74,7 @@ export class ShareCodeService {
|
||||||
data: {
|
data: {
|
||||||
code,
|
code,
|
||||||
expiresAt,
|
expiresAt,
|
||||||
|
canUseTimes,
|
||||||
isUsed: false,
|
isUsed: false,
|
||||||
// 只在没有现有文件名且提供了新文件名时才更新文件名
|
// 只在没有现有文件名且提供了新文件名时才更新文件名
|
||||||
...(fileName && !existingShareCode.fileName ? { fileName } : {}),
|
...(fileName && !existingShareCode.fileName ? { fileName } : {}),
|
||||||
|
@ -81,15 +87,17 @@ export class ShareCodeService {
|
||||||
code,
|
code,
|
||||||
fileId,
|
fileId,
|
||||||
expiresAt,
|
expiresAt,
|
||||||
|
canUseTimes,
|
||||||
isUsed: false,
|
isUsed: false,
|
||||||
fileName: fileName || null,
|
fileName: fileName || null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.logger.log(`Generated share code ${code} for file ${fileId}`);
|
this.logger.log(`Generated share code ${code} for file ${fileId} canUseTimes: ${canUseTimes}`);
|
||||||
return {
|
return {
|
||||||
code,
|
code,
|
||||||
expiresAt,
|
expiresAt,
|
||||||
|
canUseTimes,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error('Failed to generate share code', error);
|
this.logger.error('Failed to generate share code', error);
|
||||||
|
@ -106,26 +114,26 @@ export class ShareCodeService {
|
||||||
where: {
|
where: {
|
||||||
code,
|
code,
|
||||||
isUsed: false,
|
isUsed: false,
|
||||||
expiresAt: { gt: new Date() },
|
expiresAt: { gt: dayjs().tz('Asia/Shanghai').toDate() },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
if (shareCode.canUseTimes <= 0) {
|
||||||
|
this.logger.log('分享码已使用次数超过限制');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
//更新已使用次数
|
||||||
|
await db.shareCode.update({
|
||||||
|
where: { id: shareCode.id },
|
||||||
|
data: { canUseTimes: shareCode.canUseTimes - 1 },
|
||||||
|
});
|
||||||
this.logger.log('查询结果:', shareCode);
|
this.logger.log('查询结果:', shareCode);
|
||||||
|
|
||||||
if (!shareCode) {
|
if (!shareCode) {
|
||||||
this.logger.log('分享码无效或已过期');
|
this.logger.log('分享码无效或已过期');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标记分享码为已使用
|
|
||||||
// await db.shareCode.update({
|
|
||||||
// where: { id: shareCode.id },
|
|
||||||
// data: { isUsed: true },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// 记录使用日志
|
// 记录使用日志
|
||||||
this.logger.log(`Share code ${code} used for file ${shareCode.fileId}`);
|
this.logger.log(`Share code ${code} used for file ${shareCode.fileId}`);
|
||||||
|
|
||||||
// 返回完整的分享码信息,包括文件名
|
// 返回完整的分享码信息,包括文件名
|
||||||
return shareCode;
|
return shareCode;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -141,7 +149,11 @@ export class ShareCodeService {
|
||||||
try {
|
try {
|
||||||
const shareCodes = await db.shareCode.findMany({
|
const shareCodes = await db.shareCode.findMany({
|
||||||
where: {
|
where: {
|
||||||
OR: [{ expiresAt: { lt: new Date() } }, { isUsed: true }],
|
OR: [
|
||||||
|
{ expiresAt: { lt: dayjs().tz('Asia/Shanghai').toDate() } },
|
||||||
|
{ isUsed: true },
|
||||||
|
{ canUseTimes: { lte: 0 } }
|
||||||
|
],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.logger.log('需要清理的分享码:', shareCodes);
|
this.logger.log('需要清理的分享码:', shareCodes);
|
||||||
|
@ -222,7 +234,7 @@ export class ShareCodeService {
|
||||||
where: {
|
where: {
|
||||||
fileId,
|
fileId,
|
||||||
isUsed: false,
|
isUsed: false,
|
||||||
expiresAt: { gt: new Date() },
|
expiresAt: { gt: dayjs().tz('Asia/Shanghai').toDate() },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -249,7 +261,6 @@ export class ShareCodeService {
|
||||||
// 根据分享码获取文件
|
// 根据分享码获取文件
|
||||||
async getFileByShareCode(code: string) {
|
async getFileByShareCode(code: string) {
|
||||||
this.logger.log('收到验证分享码请求,code:', code);
|
this.logger.log('收到验证分享码请求,code:', code);
|
||||||
|
|
||||||
const shareCode = await this.validateAndUseCode(code);
|
const shareCode = await this.validateAndUseCode(code);
|
||||||
this.logger.log('验证分享码结果:', shareCode);
|
this.logger.log('验证分享码结果:', shareCode);
|
||||||
|
|
||||||
|
@ -275,6 +286,7 @@ export class ShareCodeService {
|
||||||
code: shareCode.code,
|
code: shareCode.code,
|
||||||
expiresAt: shareCode.expiresAt,
|
expiresAt: shareCode.expiresAt,
|
||||||
url: fileUrl,
|
url: fileUrl,
|
||||||
|
canUseTimes: shareCode.canUseTimes - 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.logger.log('返回给前端的数据:', response); // 添加日志
|
this.logger.log('返回给前端的数据:', response); // 添加日志
|
||||||
|
@ -282,10 +294,10 @@ export class ShareCodeService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据文件ID生成分享码
|
// 根据文件ID生成分享码
|
||||||
async generateShareCodeByFileId(fileId: string) {
|
async generateShareCodeByFileId(fileId: string, expiresAt: Date, canUseTimes: number) {
|
||||||
try {
|
try {
|
||||||
this.logger.log('收到生成分享码请求,fileId:', fileId);
|
this.logger.log('收到生成分享码请求,fileId:', fileId);
|
||||||
const result = await this.generateShareCode(fileId);
|
const result = await this.generateShareCode(fileId, expiresAt, canUseTimes);
|
||||||
this.logger.log('生成分享码结果:', result);
|
this.logger.log('生成分享码结果:', result);
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
"quill": "2.0.3",
|
"quill": "2.0.3",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
|
"react-fast-marquee": "^1.6.5",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
"react-hot-toast": "^2.4.1",
|
"react-hot-toast": "^2.4.1",
|
||||||
"react-player": "^2.16.0",
|
"react-player": "^2.16.0",
|
||||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 828 B After Width: | Height: | Size: 35 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 828 B After Width: | Height: | Size: 35 KiB |
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
import { ShareCodeGenerator } from "../sharecode/sharecodegenerator";
|
import { ShareCodeGenerator } from "../sharecode/sharecodegenerator";
|
||||||
import { ShareCodeValidator } from "../sharecode/sharecodevalidator";
|
import { ShareCodeValidator } from "../sharecode/sharecodevalidator";
|
||||||
import { message, Tabs, Form, Spin } from "antd";
|
import { message, Tabs, Form, Spin, Alert } from "antd";
|
||||||
import { env } from '../../../env'
|
import { env } from '../../../env'
|
||||||
import { TusUploader } from "@web/src/components/common/uploader/TusUploader";
|
import { TusUploader } from "@web/src/components/common/uploader/TusUploader";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
@ -10,30 +10,20 @@ export default function DeptSettingPage() {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const uploadFileId = Form.useWatch(["file"], form)?.[0]
|
const uploadFileId = Form.useWatch(["file"], form)?.[0]
|
||||||
const [isGetingFileId, setIsGetingFileId] = useState(false);
|
const [isGetingFileId, setIsGetingFileId] = useState(false);
|
||||||
// 处理分享码生成成功
|
|
||||||
const handleShareSuccess = (code: string) => {
|
const handleShareSuccess = (code: string) => {
|
||||||
message.success('分享码生成成功:' + code);
|
message.success('分享码生成成功:' + code);
|
||||||
// 可以在这里添加其他逻辑,比如保存到历史记录
|
|
||||||
}
|
}
|
||||||
// 处理分享码验证成功
|
|
||||||
const handleValidSuccess = async (fileUrl: string, fileName: string) => {
|
const handleValidSuccess = async (fileUrl: string, fileName: string) => {
|
||||||
setIsGetingFileId(true);
|
setIsGetingFileId(true);
|
||||||
try {
|
try {
|
||||||
// 构建下载URL(包含文件名参数)
|
|
||||||
console.log('文件url:', fileUrl);
|
|
||||||
const downloadUrl = `http://${env.SERVER_IP}:${env.FILE_PORT}/uploads/${fileUrl}`;
|
const downloadUrl = `http://${env.SERVER_IP}:${env.FILE_PORT}/uploads/${fileUrl}`;
|
||||||
console.log('下载URL:', downloadUrl);
|
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = downloadUrl;
|
link.href = downloadUrl;
|
||||||
|
|
||||||
// 直接使用传入的 fileName
|
|
||||||
link.download = fileName;
|
link.download = fileName;
|
||||||
link.target = '_blank'; // 在新标签页中打开
|
link.target = '_blank';
|
||||||
// 触发下载
|
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
|
|
||||||
message.success('文件下载开始');
|
message.success('文件下载开始');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('下载失败:', error);
|
console.error('下载失败:', error);
|
||||||
|
@ -49,35 +39,30 @@ export default function DeptSettingPage() {
|
||||||
{
|
{
|
||||||
isGetingFileId ?
|
isGetingFileId ?
|
||||||
(<Spin spinning={isGetingFileId} fullscreen />) :
|
(<Spin spinning={isGetingFileId} fullscreen />) :
|
||||||
(<div style={{ maxWidth: '800px', margin: '0 auto', padding: '20px' }}>
|
(<div style={{ maxWidth: '1000px', margin: '0 auto', padding: '20px' }}>
|
||||||
<span className="text-2xl py-4">文件分享中心</span>
|
<div className="flex items-center">
|
||||||
|
<img src="/logo.svg" className="h-20 w-20 rounded-xl" />
|
||||||
|
<span className="text-4xl py-4 mx-4">烽火快传</span>
|
||||||
|
</div>
|
||||||
<Tabs defaultActiveKey="upload">
|
<Tabs defaultActiveKey="upload">
|
||||||
<TabPane tab="上传分享" key="upload">
|
<TabPane tab="上传分享" key="upload">
|
||||||
{/* 文件上传区域 */}
|
<div>
|
||||||
<div style={{ marginBottom: '40px' }}>
|
|
||||||
<span className="text-lg block text-zinc-700 py-2">第一步:上传文件</span>
|
|
||||||
{/* 如果没有已上传文件,显示上传区域 */}
|
|
||||||
<Form form={form}>
|
<Form form={form}>
|
||||||
<Form.Item name="file">
|
<Form.Item name="file">
|
||||||
<TusUploader
|
<TusUploader
|
||||||
multiple={false}
|
multiple={false}
|
||||||
style={"w-full py-4"}
|
style={"w-full py-4 mb-0 h-64"}
|
||||||
></TusUploader>
|
></TusUploader>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 生成分享码区域 */}
|
|
||||||
<div style={{ marginBottom: '40px' }}>
|
<div style={{ marginBottom: '40px' }}>
|
||||||
<span className="text-lg block text-zinc-700 py-4">第二步:生成分享码</span>
|
|
||||||
<ShareCodeGenerator
|
<ShareCodeGenerator
|
||||||
fileId={uploadFileId}
|
fileId={uploadFileId}
|
||||||
onSuccess={handleShareSuccess}
|
onSuccess={handleShareSuccess}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
|
||||||
{/* 使用分享码区域 */}
|
|
||||||
<TabPane tab="下载文件" key="download">
|
<TabPane tab="下载文件" key="download">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-lg block text-zinc-700 py-4">使用分享码下载文件</span>
|
<span className="text-lg block text-zinc-700 py-4">使用分享码下载文件</span>
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
|
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Button, message } from 'antd';
|
import { Button, DatePicker, Form, message, Select } from 'antd';
|
||||||
import { CopyOutlined } from '@ant-design/icons';
|
import { CopyOutlined } from '@ant-design/icons';
|
||||||
import { env } from '../../../env'
|
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { getQueryKey } from '@trpc/react-query';
|
import { getQueryKey } from '@trpc/react-query';
|
||||||
import { api } from '@nice/client';
|
import { api } from '@nice/client';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import utc from 'dayjs/plugin/utc';
|
||||||
|
import timezone from 'dayjs/plugin/timezone';
|
||||||
|
dayjs.extend(utc);
|
||||||
|
dayjs.extend(timezone);
|
||||||
interface ShareCodeGeneratorProps {
|
interface ShareCodeGeneratorProps {
|
||||||
fileId: string;
|
fileId: string;
|
||||||
onSuccess?: (code: string) => void;
|
onSuccess?: (code: string) => void;
|
||||||
|
@ -14,6 +17,7 @@ interface ShareCodeGeneratorProps {
|
||||||
interface ShareCodeResponse {
|
interface ShareCodeResponse {
|
||||||
code?: string;
|
code?: string;
|
||||||
expiresAt?: Date;
|
expiresAt?: Date;
|
||||||
|
canUseTimes?: number;
|
||||||
}
|
}
|
||||||
export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({
|
export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({
|
||||||
fileId,
|
fileId,
|
||||||
|
@ -23,10 +27,12 @@ export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [shareCode, setShareCode] = useState<string>('');
|
const [shareCode, setShareCode] = useState<string>('');
|
||||||
const [expiresAt, setExpiresAt] = useState<Date | null>(null);
|
const [expiresAt, setExpiresAt] = useState<Date | null>(null);
|
||||||
|
const [canUseTimes, setCanUseTimes] = useState<number>(null);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const queryKey = getQueryKey(api.term);
|
const queryKey = getQueryKey(api.term);
|
||||||
const [isGenerate, setIsGenerate] = useState(false);
|
const [isGenerate, setIsGenerate] = useState(false);
|
||||||
const [currentFileId, setCurrentFileId] = useState<string>('');
|
const [currentFileId, setCurrentFileId] = useState<string>('');
|
||||||
|
const [form] = Form.useForm();
|
||||||
const generateShareCode = api.shareCode.generateShareCodeByFileId.useMutation({
|
const generateShareCode = api.shareCode.generateShareCodeByFileId.useMutation({
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
queryClient.invalidateQueries({ queryKey });
|
queryClient.invalidateQueries({ queryKey });
|
||||||
|
@ -46,12 +52,17 @@ export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
console.log('开始生成分享码,fileId:', fileId, 'fileName:', fileName);
|
console.log('开始生成分享码,fileId:', fileId, 'fileName:', fileName);
|
||||||
try {
|
try {
|
||||||
const data: ShareCodeResponse = await generateShareCode.mutateAsync({ fileId });
|
const data: ShareCodeResponse = await generateShareCode.mutateAsync({
|
||||||
console.log('生成分享码结果:', data);
|
fileId,
|
||||||
|
expiresAt: form.getFieldsValue()?.expiresAt ? form.getFieldsValue().expiresAt.toDate() : dayjs().add(1, 'day').tz('Asia/Shanghai').toDate(),
|
||||||
|
canUseTimes: form.getFieldsValue()?.canUseTimes ? form.getFieldsValue().canUseTimes : 10,
|
||||||
|
});
|
||||||
|
console.log('data', data)
|
||||||
setShareCode(data.code);
|
setShareCode(data.code);
|
||||||
setIsGenerate(true);
|
setIsGenerate(true);
|
||||||
setExpiresAt(data.expiresAt ? new Date(data.expiresAt) : null);
|
|
||||||
onSuccess?.(data.code);
|
onSuccess?.(data.code);
|
||||||
|
setExpiresAt(dayjs(data.expiresAt).tz('Asia/Shanghai').toDate());
|
||||||
|
setCanUseTimes(data.canUseTimes);
|
||||||
//message.success('分享码生成成功');
|
//message.success('分享码生成成功');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('生成分享码错误:', error);
|
console.error('生成分享码错误:', error);
|
||||||
|
@ -73,30 +84,61 @@ export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 组件使用
|
|
||||||
const handleCopy = (code) => {
|
const handleCopy = (code) => {
|
||||||
copyToClipboard(code)
|
copyToClipboard(code)
|
||||||
.then(() => console.log('复制成功'))
|
.then(() => console.log('复制成功'))
|
||||||
.catch(() => console.error('复制失败'));
|
.catch(() => console.error('复制失败'));
|
||||||
};
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
const date = dayjs().add(1, 'day').tz('Asia/Shanghai');
|
||||||
|
form.setFieldsValue({
|
||||||
|
expiresAt: date,
|
||||||
|
canUseTimes: 10
|
||||||
|
});
|
||||||
|
}, [form]);
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
if (fileId) {
|
||||||
|
generateCode()
|
||||||
|
}
|
||||||
|
}, [fileId])
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '20px', backgroundColor: '#f8f9fa', borderRadius: '8px' }}>
|
<div style={{ padding: '20px', backgroundColor: '#f8f9fa', borderRadius: '8px' }}>
|
||||||
<div style={{ marginBottom: '10px' }}>
|
<div style={{ marginBottom: '3px' }}>
|
||||||
{/* 添加调试信息 */}
|
<small style={{ color: '#666' }}>文件ID: {fileId ? fileId : '暂未上传文件'}</small>
|
||||||
<small style={{ color: '#666' }}>文件ID: {fileId ? fileId : '未选择文件'}</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isGenerate ? (
|
{!isGenerate ? (
|
||||||
<Button
|
<>
|
||||||
type="primary"
|
<Form form={form}>
|
||||||
onClick={generateCode}
|
<div className='w-4/5 h-16 flex flex-row justify-between items-center'>
|
||||||
loading={loading}
|
<small style={{ color: '#666' }}>
|
||||||
block
|
{"分享码的有效期"}
|
||||||
>
|
</small>
|
||||||
生成分享码
|
<Form.Item name="expiresAt" className='mt-5'>
|
||||||
</Button>
|
<DatePicker
|
||||||
|
showTime
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<small style={{ color: '#666' }}>
|
||||||
|
{"分享码的使用次数"}
|
||||||
|
</small>
|
||||||
|
<Form.Item name="canUseTimes" className='mt-5'>
|
||||||
|
<Select
|
||||||
|
style={{ width: 120 }}
|
||||||
|
//onChange={handleChange}
|
||||||
|
options={[
|
||||||
|
{ value: 10, label: '10' },
|
||||||
|
{ value: 20, label: '20' },
|
||||||
|
{ value: 30, label: '30' },
|
||||||
|
{ value: 40, label: '40' },
|
||||||
|
{ value: 50, label: '50' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</>
|
||||||
|
|
||||||
) : (
|
) : (
|
||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
<div style={{
|
<div style={{
|
||||||
|
@ -126,9 +168,9 @@ export const ShareCodeGenerator: React.FC<ShareCodeGeneratorProps> = ({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{expiresAt ? (
|
{isGenerate && expiresAt ? (
|
||||||
<div style={{ color: '#666' }}>
|
<div style={{ color: '#666' }}>
|
||||||
有效期至: {expiresAt.toLocaleString()}
|
有效期至: {expiresAt.toLocaleString()} 可使用次数: {canUseTimes}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ color: 'red' }}>
|
<div style={{ color: 'red' }}>
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { Input, Button, message } from 'antd';
|
import { Input, Button, message } from 'antd';
|
||||||
import styles from './ShareCodeValidator.module.css';
|
import styles from './ShareCodeValidator.module.css';
|
||||||
import {env} from '../../../env'
|
|
||||||
import { api } from '@nice/client';
|
import { api } from '@nice/client';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
interface ShareCodeValidatorProps {
|
interface ShareCodeValidatorProps {
|
||||||
onValidSuccess: (fileId: string, fileName: string) => void;
|
onValidSuccess: (fileId: string, fileName: string) => void;
|
||||||
}
|
}
|
||||||
|
import utc from 'dayjs/plugin/utc';
|
||||||
|
import timezone from 'dayjs/plugin/timezone';
|
||||||
|
dayjs.extend(utc);
|
||||||
|
dayjs.extend(timezone);
|
||||||
export const ShareCodeValidator: React.FC<ShareCodeValidatorProps> = ({
|
export const ShareCodeValidator: React.FC<ShareCodeValidatorProps> = ({
|
||||||
onValidSuccess,
|
onValidSuccess,
|
||||||
}) => {
|
}) => {
|
||||||
const [code, setCode] = useState('');
|
const [code, setCode] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { data: result, isLoading } = api.shareCode.getFileByShareCode.useQuery(
|
const { data: result, isLoading,refetch } = api.shareCode.getFileByShareCode.useQuery(
|
||||||
{ code: code.trim() },
|
{ code: code.trim() },
|
||||||
{
|
{
|
||||||
enabled: !!code.trim()
|
enabled: false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
const validateCode = useCallback(async () => {
|
||||||
|
|
||||||
|
|
||||||
const validateCode = useCallback(() => {
|
|
||||||
|
|
||||||
if (!code.trim()) {
|
if (!code.trim()) {
|
||||||
message.warning('请输入正确的分享码');
|
message.warning('请输入正确的分享码');
|
||||||
|
@ -29,35 +29,47 @@ export const ShareCodeValidator: React.FC<ShareCodeValidatorProps> = ({
|
||||||
}
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
console.log('验证分享码返回数据:', result);
|
const { data: latestResult } = await refetch();
|
||||||
onValidSuccess(result.url, result.fileName);
|
console.log('验证分享码返回数据:', latestResult);
|
||||||
message.success(`验证成功,文件名:${result.fileName},请等待下载...`);
|
onValidSuccess(latestResult.url, latestResult.fileName);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('验证分享码失败:', error);
|
console.error('验证分享码失败:', error);
|
||||||
message.error('分享码无效或已过期');
|
message.error('分享码无效或已过期');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},[result,code, onValidSuccess])
|
}, [refetch, code, onValidSuccess]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<>
|
||||||
<Input
|
<div className={styles.container}>
|
||||||
className={styles.input}
|
<Input
|
||||||
value={code}
|
className={styles.input}
|
||||||
onChange={(e) => setCode(e.target.value.toUpperCase())}
|
value={code}
|
||||||
placeholder="请输入分享码"
|
onChange={(e) => setCode(e.target.value.toUpperCase())}
|
||||||
maxLength={8}
|
placeholder="请输入分享码"
|
||||||
onPressEnter={validateCode}
|
maxLength={8}
|
||||||
/>
|
onPressEnter={validateCode}
|
||||||
<Button
|
/>
|
||||||
type="primary"
|
<Button
|
||||||
onClick={validateCode}
|
type="primary"
|
||||||
loading={loading}
|
onClick={validateCode}
|
||||||
disabled={!code.trim()}
|
loading={loading}
|
||||||
>
|
disabled={!code.trim()}
|
||||||
验证并下载
|
>
|
||||||
</Button>
|
下载
|
||||||
</div>
|
</Button>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
!loading && result && (
|
||||||
|
<div className='w-full flex justify-between my-2 p-1 antialiased text-secondary-600'>
|
||||||
|
<span >{`分享码:${result?.code ? result.code : ''}`}</span>
|
||||||
|
<span >{`文件名:${result?.fileName ? result.fileName : ''}`}</span>
|
||||||
|
<span >{`过期时间:${result?.expiresAt ? dayjs(result.expiresAt).tz('Asia/Shanghai').toDate().toLocaleString() : ''}`}</span>
|
||||||
|
<span >{`剩余使用次数:${result?.canUseTimes ? result.canUseTimes : ''}`}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
|
@ -4,7 +4,7 @@ import {
|
||||||
CheckCircleOutlined,
|
CheckCircleOutlined,
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { Upload, Progress, Button } from "antd";
|
import { Upload, Progress, Button, message } from "antd";
|
||||||
import { useTusUpload } from "@web/src/hooks/useTusUpload";
|
import { useTusUpload } from "@web/src/hooks/useTusUpload";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
export interface TusUploaderProps {
|
export interface TusUploaderProps {
|
||||||
|
@ -12,9 +12,9 @@ export interface TusUploaderProps {
|
||||||
onChange?: (value: string[]) => void;
|
onChange?: (value: string[]) => void;
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
allowTypes?: string[];
|
allowTypes?: string[];
|
||||||
style?:string
|
style?: string
|
||||||
icon?:ReactNode,
|
icon?: ReactNode,
|
||||||
description?:string
|
description?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UploadingFile {
|
interface UploadingFile {
|
||||||
|
@ -30,7 +30,7 @@ export const TusUploader = ({
|
||||||
onChange,
|
onChange,
|
||||||
multiple = true,
|
multiple = true,
|
||||||
allowTypes = undefined,
|
allowTypes = undefined,
|
||||||
style="",
|
style = "",
|
||||||
icon = <UploadOutlined />,
|
icon = <UploadOutlined />,
|
||||||
description = "点击或拖拽文件到此区域进行上传",
|
description = "点击或拖拽文件到此区域进行上传",
|
||||||
}: TusUploaderProps) => {
|
}: TusUploaderProps) => {
|
||||||
|
@ -67,8 +67,14 @@ export const TusUploader = ({
|
||||||
|
|
||||||
const handleBeforeUpload = useCallback(
|
const handleBeforeUpload = useCallback(
|
||||||
(file: File) => {
|
(file: File) => {
|
||||||
|
console.log('File object:',file)
|
||||||
|
// 判断是否为文件
|
||||||
|
if (!file.type) {
|
||||||
|
message.error('请选择正确的文件');
|
||||||
|
return Upload.LIST_IGNORE;
|
||||||
|
}
|
||||||
if (allowTypes && !allowTypes.includes(file.type)) {
|
if (allowTypes && !allowTypes.includes(file.type)) {
|
||||||
toast.error(`文件类型 ${file.type} 不在允许范围内`);
|
message.error(`文件类型 ${file.type} 不在允许范围内`);
|
||||||
return Upload.LIST_IGNORE; // 使用 antd 的官方阻止方式
|
return Upload.LIST_IGNORE; // 使用 antd 的官方阻止方式
|
||||||
}
|
}
|
||||||
const fileKey = `${file.name}-${Date.now()}`;
|
const fileKey = `${file.name}-${Date.now()}`;
|
||||||
|
@ -150,7 +156,9 @@ export const TusUploader = ({
|
||||||
name="files"
|
name="files"
|
||||||
multiple={multiple}
|
multiple={multiple}
|
||||||
showUploadList={false}
|
showUploadList={false}
|
||||||
beforeUpload={handleBeforeUpload}>
|
beforeUpload={handleBeforeUpload}
|
||||||
|
directory={false}
|
||||||
|
>
|
||||||
<p className="ant-upload-drag-icon">
|
<p className="ant-upload-drag-icon">
|
||||||
{icon}
|
{icon}
|
||||||
</p>
|
</p>
|
||||||
|
@ -180,10 +188,10 @@ export const TusUploader = ({
|
||||||
file.status === "done"
|
file.status === "done"
|
||||||
? 100
|
? 100
|
||||||
: Math.round(
|
: Math.round(
|
||||||
uploadProgress?.[
|
uploadProgress?.[
|
||||||
file.fileKey!
|
file.fileKey!
|
||||||
] || 0
|
] || 0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
status={
|
status={
|
||||||
file.status === "error"
|
file.status === "error"
|
||||||
|
|
|
@ -100,7 +100,7 @@ server {
|
||||||
# 仅供内部使用
|
# 仅供内部使用
|
||||||
internal;
|
internal;
|
||||||
# 代理到认证服务
|
# 代理到认证服务
|
||||||
proxy_pass http://192.168.43.206:3006/auth/file;
|
proxy_pass http://192.168.43.206:3001/auth/file;
|
||||||
|
|
||||||
# 请求优化:不传递请求体
|
# 请求优化:不传递请求体
|
||||||
proxy_pass_request_body off;
|
proxy_pass_request_body off;
|
||||||
|
|
|
@ -89,11 +89,10 @@ model Staff {
|
||||||
officerId String? @map("officer_id")
|
officerId String? @map("officer_id")
|
||||||
|
|
||||||
// watchedPost Post[] @relation("post_watch_staff")
|
// watchedPost Post[] @relation("post_watch_staff")
|
||||||
visits Visit[]
|
visits Visit[]
|
||||||
posts Post[]
|
posts Post[]
|
||||||
|
|
||||||
|
learningPosts Post[] @relation("post_student")
|
||||||
learningPosts Post[] @relation("post_student")
|
|
||||||
sentMsgs Message[] @relation("message_sender")
|
sentMsgs Message[] @relation("message_sender")
|
||||||
receivedMsgs Message[] @relation("message_receiver")
|
receivedMsgs Message[] @relation("message_receiver")
|
||||||
registerToken String?
|
registerToken String?
|
||||||
|
@ -113,7 +112,7 @@ model Department {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
order Float?
|
order Float?
|
||||||
posts Post[] @relation("post_dept")
|
posts Post[] @relation("post_dept")
|
||||||
ancestors DeptAncestry[] @relation("DescendantToAncestor")
|
ancestors DeptAncestry[] @relation("DescendantToAncestor")
|
||||||
descendants DeptAncestry[] @relation("AncestorToDescendant")
|
descendants DeptAncestry[] @relation("AncestorToDescendant")
|
||||||
parentId String? @map("parent_id")
|
parentId String? @map("parent_id")
|
||||||
|
@ -191,45 +190,45 @@ model AppConfig {
|
||||||
|
|
||||||
model Post {
|
model Post {
|
||||||
// 字符串类型字段
|
// 字符串类型字段
|
||||||
id String @id @default(cuid()) // 帖子唯一标识,使用 cuid() 生成默认值
|
id String @id @default(cuid()) // 帖子唯一标识,使用 cuid() 生成默认值
|
||||||
type String? // Post类型,课程、章节、小节、讨论都用Post实现
|
type String? // Post类型,课程、章节、小节、讨论都用Post实现
|
||||||
level String?
|
level String?
|
||||||
state String?
|
state String?
|
||||||
title String? // 帖子标题,可为空
|
title String? // 帖子标题,可为空
|
||||||
subTitle String?
|
subTitle String?
|
||||||
content String? // 帖子内容,可为空
|
content String? // 帖子内容,可为空
|
||||||
important Boolean? //是否重要/精选/突出
|
important Boolean? //是否重要/精选/突出
|
||||||
domainId String? @map("domain_id")
|
domainId String? @map("domain_id")
|
||||||
terms Term[] @relation("post_term")
|
terms Term[] @relation("post_term")
|
||||||
order Float? @default(0) @map("order")
|
order Float? @default(0) @map("order")
|
||||||
duration Int?
|
duration Int?
|
||||||
rating Int? @default(0)
|
rating Int? @default(0)
|
||||||
students Staff[] @relation("post_student")
|
students Staff[] @relation("post_student")
|
||||||
depts Department[] @relation("post_dept")
|
depts Department[] @relation("post_dept")
|
||||||
views Int @default(0) @map("views")
|
views Int @default(0) @map("views")
|
||||||
hates Int @default(0) @map("hates")
|
hates Int @default(0) @map("hates")
|
||||||
likes Int @default(0) @map("likes")
|
likes Int @default(0) @map("likes")
|
||||||
// 索引
|
// 索引
|
||||||
// 日期时间类型字段
|
// 日期时间类型字段
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
publishedAt DateTime? @map("published_at") // 发布时间
|
publishedAt DateTime? @map("published_at") // 发布时间
|
||||||
updatedAt DateTime @map("updated_at")
|
updatedAt DateTime @map("updated_at")
|
||||||
deletedAt DateTime? @map("deleted_at") // 删除时间,可为空
|
deletedAt DateTime? @map("deleted_at") // 删除时间,可为空
|
||||||
instructors PostInstructor[]
|
instructors PostInstructor[]
|
||||||
// 关系类型字段
|
// 关系类型字段
|
||||||
authorId String? @map("author_id")
|
authorId String? @map("author_id")
|
||||||
author Staff? @relation(fields: [authorId], references: [id]) // 帖子作者,关联 Staff 模型
|
author Staff? @relation(fields: [authorId], references: [id]) // 帖子作者,关联 Staff 模型
|
||||||
enrollments Enrollment[] // 学生报名记录
|
enrollments Enrollment[] // 学生报名记录
|
||||||
visits Visit[] // 访问记录,关联 Visit 模型
|
visits Visit[] // 访问记录,关联 Visit 模型
|
||||||
parentId String? @map("parent_id")
|
parentId String? @map("parent_id")
|
||||||
parent Post? @relation("PostChildren", fields: [parentId], references: [id]) // 父级帖子,关联 Post 模型
|
parent Post? @relation("PostChildren", fields: [parentId], references: [id]) // 父级帖子,关联 Post 模型
|
||||||
children Post[] @relation("PostChildren") // 子级帖子列表,关联 Post 模型
|
children Post[] @relation("PostChildren") // 子级帖子列表,关联 Post 模型
|
||||||
hasChildren Boolean? @default(false) @map("has_children")
|
hasChildren Boolean? @default(false) @map("has_children")
|
||||||
// 闭包表关系
|
// 闭包表关系
|
||||||
ancestors PostAncestry[] @relation("DescendantPosts")
|
ancestors PostAncestry[] @relation("DescendantPosts")
|
||||||
descendants PostAncestry[] @relation("AncestorPosts")
|
descendants PostAncestry[] @relation("AncestorPosts")
|
||||||
resources Resource[] // 附件列表
|
resources Resource[] // 附件列表
|
||||||
meta Json? // 封面url 视频url objectives具体的学习目标 rating评分Int
|
meta Json? // 封面url 视频url objectives具体的学习目标 rating评分Int
|
||||||
|
|
||||||
// 索引
|
// 索引
|
||||||
@@index([type, domainId])
|
@@index([type, domainId])
|
||||||
|
@ -247,12 +246,13 @@ model Post {
|
||||||
}
|
}
|
||||||
|
|
||||||
model PostAncestry {
|
model PostAncestry {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
ancestorId String? @map("ancestor_id")
|
ancestorId String? @map("ancestor_id")
|
||||||
descendantId String @map("descendant_id")
|
descendantId String @map("descendant_id")
|
||||||
relDepth Int @map("rel_depth")
|
relDepth Int @map("rel_depth")
|
||||||
ancestor Post? @relation("AncestorPosts", fields: [ancestorId], references: [id])
|
ancestor Post? @relation("AncestorPosts", fields: [ancestorId], references: [id])
|
||||||
descendant Post @relation("DescendantPosts", fields: [descendantId], references: [id])
|
descendant Post @relation("DescendantPosts", fields: [descendantId], references: [id])
|
||||||
|
|
||||||
// 复合索引优化
|
// 复合索引优化
|
||||||
// 索引建议
|
// 索引建议
|
||||||
@@index([ancestorId]) // 针对祖先的查询
|
@@index([ancestorId]) // 针对祖先的查询
|
||||||
|
@ -276,23 +276,24 @@ model Message {
|
||||||
visits Visit[]
|
visits Visit[]
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
updatedAt DateTime? @updatedAt @map("updated_at")
|
updatedAt DateTime? @updatedAt @map("updated_at")
|
||||||
|
|
||||||
@@index([type, createdAt])
|
@@index([type, createdAt])
|
||||||
@@map("message")
|
@@map("message")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Visit {
|
model Visit {
|
||||||
id String @id @default(cuid()) @map("id")
|
id String @id @default(cuid()) @map("id")
|
||||||
type String?
|
type String?
|
||||||
views Int @default(1) @map("views")
|
views Int @default(1) @map("views")
|
||||||
// sourceIP String? @map("source_ip")
|
// sourceIP String? @map("source_ip")
|
||||||
// 关联关系
|
// 关联关系
|
||||||
visitorId String? @map("visitor_id")
|
visitorId String? @map("visitor_id")
|
||||||
visitor Staff? @relation(fields: [visitorId], references: [id])
|
visitor Staff? @relation(fields: [visitorId], references: [id])
|
||||||
postId String? @map("post_id")
|
postId String? @map("post_id")
|
||||||
post Post? @relation(fields: [postId], references: [id])
|
post Post? @relation(fields: [postId], references: [id])
|
||||||
message Message? @relation(fields: [messageId], references: [id])
|
message Message? @relation(fields: [messageId], references: [id])
|
||||||
messageId String? @map("message_id")
|
messageId String? @map("message_id")
|
||||||
lectureId String? @map("lecture_id") // 课时ID
|
lectureId String? @map("lecture_id") // 课时ID
|
||||||
createdAt DateTime @default(now()) @map("created_at") // 创建时间
|
createdAt DateTime @default(now()) @map("created_at") // 创建时间
|
||||||
updatedAt DateTime @updatedAt @map("updated_at") // 更新时间
|
updatedAt DateTime @updatedAt @map("updated_at") // 更新时间
|
||||||
deletedAt DateTime? @map("deleted_at") // 删除时间,可为空
|
deletedAt DateTime? @map("deleted_at") // 删除时间,可为空
|
||||||
|
@ -403,14 +404,16 @@ model NodeEdge {
|
||||||
@@index([targetId])
|
@@index([targetId])
|
||||||
@@map("node_edge")
|
@@map("node_edge")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Animal {
|
model Animal {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
age Int
|
age Int
|
||||||
gender Boolean
|
gender Boolean
|
||||||
personId String?
|
personId String?
|
||||||
person Person? @relation(fields: [personId], references: [id])
|
person Person? @relation(fields: [personId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Person {
|
model Person {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
|
@ -418,15 +421,16 @@ model Person {
|
||||||
gender Boolean
|
gender Boolean
|
||||||
animals Animal[]
|
animals Animal[]
|
||||||
}
|
}
|
||||||
model ShareCode {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
code String? @unique
|
|
||||||
fileId String? @unique
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
expiresAt DateTime? @map("expires_at")
|
|
||||||
isUsed Boolean? @default(false)
|
|
||||||
fileName String? @map("file_name")
|
|
||||||
|
|
||||||
|
model ShareCode {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
code String? @unique
|
||||||
|
fileId String? @unique
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
expiresAt DateTime? @map("expires_at")
|
||||||
|
isUsed Boolean? @default(false)
|
||||||
|
fileName String? @map("file_name")
|
||||||
|
canUseTimes Int?
|
||||||
@@index([code])
|
@@index([code])
|
||||||
@@index([fileId])
|
@@index([fileId])
|
||||||
@@index([expiresAt])
|
@@index([expiresAt])
|
||||||
|
|
|
@ -380,6 +380,9 @@ importers:
|
||||||
react-dom:
|
react-dom:
|
||||||
specifier: 18.2.0
|
specifier: 18.2.0
|
||||||
version: 18.2.0(react@18.2.0)
|
version: 18.2.0(react@18.2.0)
|
||||||
|
react-fast-marquee:
|
||||||
|
specifier: ^1.6.5
|
||||||
|
version: 1.6.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||||
react-hook-form:
|
react-hook-form:
|
||||||
specifier: ^7.54.2
|
specifier: ^7.54.2
|
||||||
version: 7.54.2(react@18.2.0)
|
version: 7.54.2(react@18.2.0)
|
||||||
|
@ -6657,6 +6660,12 @@ packages:
|
||||||
react-fast-compare@3.2.2:
|
react-fast-compare@3.2.2:
|
||||||
resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
|
resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
|
||||||
|
|
||||||
|
react-fast-marquee@1.6.5:
|
||||||
|
resolution: {integrity: sha512-swDnPqrT2XISAih0o74zQVE2wQJFMvkx+9VZXYYNSLb/CUcAzU9pNj637Ar2+hyRw6b4tP6xh4GQZip2ZCpQpg==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>= 16.8.0 || ^18.0.0'
|
||||||
|
react-dom: '>= 16.8.0 || ^18.0.0'
|
||||||
|
|
||||||
react-hook-form@7.54.2:
|
react-hook-form@7.54.2:
|
||||||
resolution: {integrity: sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==}
|
resolution: {integrity: sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
@ -14797,6 +14806,11 @@ snapshots:
|
||||||
|
|
||||||
react-fast-compare@3.2.2: {}
|
react-fast-compare@3.2.2: {}
|
||||||
|
|
||||||
|
react-fast-marquee@1.6.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
|
||||||
react-hook-form@7.54.2(react@18.2.0):
|
react-hook-form@7.54.2(react@18.2.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
|
|
BIN
web-dist.zip
BIN
web-dist.zip
Binary file not shown.
Loading…
Reference in New Issue