01270029
This commit is contained in:
parent
406fa8014f
commit
4d4f8f2cda
11
Dockerfile
11
Dockerfile
|
@ -85,7 +85,10 @@ EXPOSE 80
|
||||||
CMD ["/usr/bin/entrypoint.sh"]
|
CMD ["/usr/bin/entrypoint.sh"]
|
||||||
|
|
||||||
|
|
||||||
|
# 使用 Nginx 的 Alpine 版本作为基础镜像
|
||||||
FROM nginx:stable-alpine as nginx
|
FROM nginx:stable-alpine as nginx
|
||||||
|
|
||||||
|
# 替换 Alpine 的软件源为阿里云镜像
|
||||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
||||||
|
|
||||||
# 设置工作目录
|
# 设置工作目录
|
||||||
|
@ -94,9 +97,11 @@ WORKDIR /usr/share/nginx/html
|
||||||
# 设置环境变量
|
# 设置环境变量
|
||||||
ENV NODE_ENV production
|
ENV NODE_ENV production
|
||||||
|
|
||||||
# 安装 envsubst 以支持环境变量替换
|
# 安装 envsubst 和 inotify-tools
|
||||||
RUN apk add --no-cache gettext
|
RUN apk add --no-cache gettext inotify-tools
|
||||||
|
|
||||||
|
# 创建 /data/uploads 目录
|
||||||
|
RUN mkdir -p /data/uploads
|
||||||
|
|
||||||
# 暴露 80 端口
|
# 暴露 80 端口
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ export class AuthService {
|
||||||
return { isValid: false, error: FileValidationErrorType.INVALID_URI };
|
return { isValid: false, error: FileValidationErrorType.INVALID_URI };
|
||||||
}
|
}
|
||||||
const fileId = extractFileIdFromNginxUrl(params.originalUri);
|
const fileId = extractFileIdFromNginxUrl(params.originalUri);
|
||||||
console.log(params.originalUri, fileId);
|
console.log('auth', params.originalUri, fileId);
|
||||||
const resource = await db.resource.findFirst({ where: { fileId } });
|
const resource = await db.resource.findFirst({ where: { fileId } });
|
||||||
|
|
||||||
// 资源验证
|
// 资源验证
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { db, PostState, PostType, VisitType } from '@nice/common';
|
import { db, PostState, PostType, VisitType } from '@nice/common';
|
||||||
export async function updatePostViewCount(id: string, type: VisitType) {
|
export async function updatePostViewCount(id: string, type: VisitType) {
|
||||||
console.log('updatePostViewCount', type);
|
|
||||||
const totalViews = await db.visit.aggregate({
|
const totalViews = await db.visit.aggregate({
|
||||||
_sum: {
|
_sum: {
|
||||||
views: true,
|
views: true,
|
||||||
|
|
|
@ -42,18 +42,19 @@ export function SendCard({ staff, termId }: SendCardProps) {
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 mb-4">
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 mb-4">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-3 mb-2">
|
<div className="flex items-center gap-3 mb-2">
|
||||||
<h3 className="text-2xl font-semibold text-gray-700">
|
{/* 修改后的职级显示部分 */}
|
||||||
|
{staff.meta?.rank && (
|
||||||
|
<span className="text-2xl font-bold text-primary tracking-wide mr-2">
|
||||||
|
{staff.meta.rank}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<h3 className="text-2xl font-semibold text-primary">
|
||||||
{staff?.showname || staff?.username}
|
{staff?.showname || staff?.username}
|
||||||
</h3>
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<Tooltip title="职级">
|
|
||||||
<span className="inline-flex items-center px-4 py-1.5 text-sm font-medium bg-gradient-to-r from-blue-50 to-blue-100 text-primary rounded-full hover:from-blue-100 hover:to-blue-200 transition-colors shadow-sm">
|
|
||||||
{staff.meta?.rank || '未设置职级'}
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Contact Information */}
|
{/* Contact Information */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm text-gray-600 mb-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm text-gray-600 mb-6">
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { useState, useCallback, useEffect, useMemo } from "react";
|
import { useState, useCallback, useEffect} from "react";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
import { SendCard } from "./SendCard";
|
import { SendCard } from "./SendCard";
|
||||||
import { Spin, Empty, Input, Alert, Pagination } from "antd";
|
import { Spin, Empty, Input, Alert, Pagination } from "antd";
|
||||||
import { api, useTerm } from "@nice/client";
|
import { api, useTerm } from "@nice/client";
|
||||||
|
@ -9,7 +8,7 @@ import DepartmentSelect from "@web/src/components/models/department/department-s
|
||||||
import debounce from "lodash/debounce";
|
import debounce from "lodash/debounce";
|
||||||
import { SearchOutlined } from "@ant-design/icons";
|
import { SearchOutlined } from "@ant-design/icons";
|
||||||
import WriteHeader from "./WriteHeader";
|
import WriteHeader from "./WriteHeader";
|
||||||
import { ObjectType, RoleName } from "@nice/common";
|
import { RoleName, staffDetailSelect } from "@nice/common";
|
||||||
|
|
||||||
export default function WriteLetterPage() {
|
export default function WriteLetterPage() {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
@ -23,13 +22,12 @@ export default function WriteLetterPage() {
|
||||||
api.rolemap.getStaffIdsByRoleNames.useQuery({
|
api.rolemap.getStaffIdsByRoleNames.useQuery({
|
||||||
roleNames: [RoleName.Leader, RoleName.Organization],
|
roleNames: [RoleName.Leader, RoleName.Organization],
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
|
|
||||||
const { data, isLoading, error } =
|
const { data, isLoading, error } =
|
||||||
api.staff.findManyWithPagination.useQuery(
|
api.staff.findManyWithPagination.useQuery(
|
||||||
{
|
{
|
||||||
page: currentPage,
|
page: currentPage,
|
||||||
pageSize,
|
pageSize,
|
||||||
|
select: staffDetailSelect,
|
||||||
where: {
|
where: {
|
||||||
id: enabledStaffIds
|
id: enabledStaffIds
|
||||||
? {
|
? {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { message, Progress, Spin, theme } from "antd";
|
||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import { useTusUpload } from "@web/src/hooks/useTusUpload";
|
import { useTusUpload } from "@web/src/hooks/useTusUpload";
|
||||||
import { Avatar } from "antd/lib";
|
import { Avatar } from "antd/lib";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
export interface AvatarUploaderProps {
|
export interface AvatarUploaderProps {
|
||||||
value?: string;
|
value?: string;
|
||||||
|
@ -67,6 +68,7 @@ const AvatarUploader: React.FC<AvatarUploaderProps> = ({
|
||||||
url: result.url,
|
url: result.url,
|
||||||
compressedUrl: result.compressedUrl,
|
compressedUrl: result.compressedUrl,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
setUrl(result.url);
|
setUrl(result.url);
|
||||||
setPreviewUrl(result.compressedUrl);
|
setPreviewUrl(result.compressedUrl);
|
||||||
// 直接使用 result 中的最新值
|
// 直接使用 result 中的最新值
|
||||||
|
@ -78,15 +80,13 @@ const AvatarUploader: React.FC<AvatarUploaderProps> = ({
|
||||||
file?.fileKey
|
file?.fileKey
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
// await new Promise((resolve) => setTimeout(resolve, 5000)); // 方法1:使用 await 暂停执行
|
|
||||||
// 使用 resolved 的最新值调用 onChange
|
|
||||||
// 强制刷新 Avatar 组件
|
|
||||||
setAvatarKey((prev) => prev + 1); // 修改 key 强制重新挂载
|
setAvatarKey((prev) => prev + 1); // 修改 key 强制重新挂载
|
||||||
onChange?.(uploadedUrl);
|
onChange?.(uploadedUrl);
|
||||||
message.success("头像上传成功");
|
console.log(uploadedUrl)
|
||||||
|
toast.success("头像上传成功");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("上传错误:", error);
|
console.error("上传错误:", error);
|
||||||
message.error("头像上传失败");
|
toast.error("头像上传失败");
|
||||||
setFile((prev) => ({ ...prev!, status: "error" }));
|
setFile((prev) => ({ ...prev!, status: "error" }));
|
||||||
} finally {
|
} finally {
|
||||||
setUploading(false);
|
setUploading(false);
|
||||||
|
|
|
@ -9,7 +9,7 @@ export default function Content() {
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
const contentWrapperRef = useRef(null);
|
const contentWrapperRef = useRef(null);
|
||||||
const [shouldCollapse, setShouldCollapse] = useState(false);
|
const [shouldCollapse, setShouldCollapse] = useState(false);
|
||||||
|
console.log(post)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (contentWrapperRef.current) {
|
if (contentWrapperRef.current) {
|
||||||
const shouldCollapse = contentWrapperRef.current.scrollHeight > 150;
|
const shouldCollapse = contentWrapperRef.current.scrollHeight > 150;
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { PostDto } from "@nice/common";
|
||||||
import { env } from "@web/src/env";
|
import { env } from "@web/src/env";
|
||||||
import { getFileIcon } from "./utils";
|
import { getFileIcon } from "./utils";
|
||||||
import { formatFileSize, getCompressedImageUrl } from '@nice/utils';
|
import { formatFileSize, getCompressedImageUrl } from '@nice/utils';
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
|
||||||
export default function PostResources({ post }: { post: PostDto }) {
|
export default function PostResources({ post }: { post: PostDto }) {
|
||||||
|
@ -29,7 +30,6 @@ export default function PostResources({ post }: { post: PostDto }) {
|
||||||
|
|
||||||
const imageResources = resources.filter((res) => res.isImage);
|
const imageResources = resources.filter((res) => res.isImage);
|
||||||
const fileResources = resources.filter((res) => !res.isImage);
|
const fileResources = resources.filter((res) => !res.isImage);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{imageResources.length > 0 && (
|
{imageResources.length > 0 && (
|
||||||
|
@ -44,12 +44,9 @@ export default function PostResources({ post }: { post: PostDto }) {
|
||||||
lg={6}
|
lg={6}
|
||||||
xl={4}
|
xl={4}
|
||||||
className="relative">
|
className="relative">
|
||||||
<div className="relative aspect-square rounded-lg overflow-hidden shadow-md hover:shadow-lg transition-shadow duration-300 bg-gray-100">
|
<div className="relative aspect-square rounded-lg overflow-hidden bg-gray-100">
|
||||||
<div className="w-full h-full">
|
<div className="w-full h-full">
|
||||||
<Image
|
<Image
|
||||||
onError={(e) => {
|
|
||||||
console.log(e)
|
|
||||||
}}
|
|
||||||
src={resource.url}
|
src={resource.url}
|
||||||
alt={resource.title}
|
alt={resource.title}
|
||||||
preview={{
|
preview={{
|
||||||
|
|
|
@ -45,7 +45,6 @@ export function LetterFormProvider({
|
||||||
|
|
||||||
const onSubmit = async (data: LetterFormData) => {
|
const onSubmit = async (data: LetterFormData) => {
|
||||||
try {
|
try {
|
||||||
console.log("data", data);
|
|
||||||
const receivers = data?.receivers;
|
const receivers = data?.receivers;
|
||||||
const terms = data?.terms;
|
const terms = data?.terms;
|
||||||
delete data.receivers;
|
delete data.receivers;
|
||||||
|
|
|
@ -74,7 +74,8 @@ export function useTusUpload() {
|
||||||
[fileKey]: progress,
|
[fileKey]: progress,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async (payload) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (upload.url) {
|
if (upload.url) {
|
||||||
const fileId = getFileId(upload.url);
|
const fileId = getFileId(upload.url);
|
||||||
|
|
|
@ -65,10 +65,9 @@ server {
|
||||||
sendfile on;
|
sendfile on;
|
||||||
tcp_nopush on;
|
tcp_nopush on;
|
||||||
# 异步IO
|
# 异步IO
|
||||||
aio on;
|
aio off;
|
||||||
# 直接IO,提高大文件传输效率
|
# 直接IO,提高大文件传输效率
|
||||||
directio 512;
|
directio off;
|
||||||
|
|
||||||
# 文件访问认证
|
# 文件访问认证
|
||||||
# 通过内部认证服务验证
|
# 通过内部认证服务验证
|
||||||
auth_request /auth-file;
|
auth_request /auth-file;
|
||||||
|
@ -77,9 +76,12 @@ server {
|
||||||
auth_request_set $auth_user_id $upstream_http_x_user_id;
|
auth_request_set $auth_user_id $upstream_http_x_user_id;
|
||||||
auth_request_set $auth_resource_type $upstream_http_x_resource_type;
|
auth_request_set $auth_resource_type $upstream_http_x_resource_type;
|
||||||
# 不缓存
|
# 不缓存
|
||||||
expires 0;
|
# expires 0;
|
||||||
# 私有缓存,禁止转换
|
# 私有缓存,禁止转换
|
||||||
add_header Cache-Control "private, no-transform";
|
# add_header Cache-Control "private, no-transform";
|
||||||
|
open_file_cache off; # 禁用文件缓存
|
||||||
|
add_header Cache-Control "no-cache, no-store, must-revalidate"; # 禁用浏览器缓存
|
||||||
|
expires -1; # 立即过期
|
||||||
# 添加用户和资源类型头
|
# 添加用户和资源类型头
|
||||||
add_header X-User-Id $auth_user_id;
|
add_header X-User-Id $auth_user_id;
|
||||||
add_header X-Resource-Type $auth_resource_type;
|
add_header X-Resource-Type $auth_resource_type;
|
||||||
|
|
|
@ -68,7 +68,6 @@ server {
|
||||||
aio on;
|
aio on;
|
||||||
# 直接IO,提高大文件传输效率
|
# 直接IO,提高大文件传输效率
|
||||||
directio 512;
|
directio 512;
|
||||||
|
|
||||||
# 文件访问认证
|
# 文件访问认证
|
||||||
# 通过内部认证服务验证
|
# 通过内部认证服务验证
|
||||||
auth_request /auth-file;
|
auth_request /auth-file;
|
||||||
|
@ -80,6 +79,8 @@ server {
|
||||||
expires 0;
|
expires 0;
|
||||||
# 私有缓存,禁止转换
|
# 私有缓存,禁止转换
|
||||||
add_header Cache-Control "private, no-transform";
|
add_header Cache-Control "private, no-transform";
|
||||||
|
|
||||||
|
expires -1; # 立即过期
|
||||||
# 添加用户和资源类型头
|
# 添加用户和资源类型头
|
||||||
add_header X-User-Id $auth_user_id;
|
add_header X-User-Id $auth_user_id;
|
||||||
add_header X-Resource-Type $auth_resource_type;
|
add_header X-Resource-Type $auth_resource_type;
|
||||||
|
|
|
@ -1,5 +1,25 @@
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
|
export const staffDetailSelect: Prisma.StaffSelect = {
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
department: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showname: true,
|
||||||
|
phoneNumber: true,
|
||||||
|
deptId: true,
|
||||||
|
domain: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
domainId: true,
|
||||||
|
meta: true
|
||||||
|
}
|
||||||
export const postDetailSelect: Prisma.PostSelect = {
|
export const postDetailSelect: Prisma.PostSelect = {
|
||||||
id: true,
|
id: true,
|
||||||
type: true,
|
type: true,
|
||||||
|
|
Loading…
Reference in New Issue