修复提示框的弹出位置移动的问题
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import axios, { AxiosRequestConfig, AxiosResponse, isAxiosError } from 'axios';
|
import axios, { AxiosRequestConfig, AxiosResponse, isAxiosError } from 'axios';
|
||||||
import { mockData, type MockApiResponse } from './mock';
|
import { mockData, type MockApiResponse } from './mock';
|
||||||
|
import { API_BASE_URL, DOCUMENT_URL } from '../config/api-config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API响应类型
|
* API响应类型
|
||||||
@@ -13,14 +14,15 @@ export type ApiResponse<T> = {
|
|||||||
|
|
||||||
export type QueryParams = Record<string, string | number | boolean | undefined>;
|
export type QueryParams = Record<string, string | number | boolean | undefined>;
|
||||||
|
|
||||||
// 获取 API 基础 URL
|
// 获取 API 基础 URL (从配置文件导入)
|
||||||
// const API_BASE_URL = 'http://172.16.0.58:8008';
|
// const API_BASE_URL = 'http://172.16.0.58:8008';
|
||||||
const API_BASE_URL = 'http://nas.7bm.co:3000';
|
// const API_BASE_URL = 'http://nas.7bm.co:3000';
|
||||||
// const API_BASE_URL = 'http://172.18.0.100:3000';
|
// const API_BASE_URL = 'http://172.18.0.100:3000';
|
||||||
// const API_BASE_URL = 'http://172.16.0.119:9000/admin';
|
// const API_BASE_URL = 'http://172.16.0.119:9000/admin';
|
||||||
|
|
||||||
// 文档URL前缀
|
// 文档URL前缀 (从配置文件导入)
|
||||||
export const DOCUMENT_URL = 'http://nas.7bm.co:9000/docauditai/';
|
// export const DOCUMENT_URL = 'http://nas.7bm.co:9000/docauditai/';
|
||||||
|
export { DOCUMENT_URL };
|
||||||
|
|
||||||
// 是否使用模拟数据(开发环境使用)
|
// 是否使用模拟数据(开发环境使用)
|
||||||
const USE_MOCK_DATA = false; // 设置为true使用模拟数据,避免API连接问题
|
const USE_MOCK_DATA = false; // 设置为true使用模拟数据,避免API连接问题
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { postgrestGet, type PostgrestParams } from '../postgrest-client';
|
import { postgrestGet, type PostgrestParams } from '../postgrest-client';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { UPLOAD_URL } from '../../config/api-config';
|
||||||
// import { API_BASE_URL } from '../client';
|
// import { API_BASE_URL } from '../client';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -153,13 +154,13 @@ export async function uploadDocumentToServer(
|
|||||||
formData.append('upload_info', JSON.stringify(uploadInfo));
|
formData.append('upload_info', JSON.stringify(uploadInfo));
|
||||||
// console.log('【调试】FormData准备完成:', JSON.stringify(uploadInfo));
|
// console.log('【调试】FormData准备完成:', JSON.stringify(uploadInfo));
|
||||||
|
|
||||||
// console.log('【调试】准备发送请求到服务器:', 'http://172.16.0.58:8008/admin/documents/upload');
|
// console.log('【调试】准备发送请求到服务器:', UPLOAD_URL);
|
||||||
|
|
||||||
// 发送请求
|
// 发送请求
|
||||||
// const response = await fetch(`${API_BASE_URL}/admin/documents/upload`, {
|
// const response = await fetch(`${API_BASE_URL}/admin/documents/upload`, {
|
||||||
try {
|
try {
|
||||||
// console.log('【调试】开始fetch请求...');
|
// console.log('【调试】开始fetch请求...');
|
||||||
const response = await fetch('http://172.16.0.58:8008/admin/documents/upload', {
|
const response = await fetch(UPLOAD_URL, {
|
||||||
// const response = await fetch('http://172.16.0.55:8000/admin/documents/upload', {
|
// const response = await fetch('http://172.16.0.55:8000/admin/documents/upload', {
|
||||||
// const response = await fetch('http://172.16.0.119:8000/admin/documents/upload', {
|
// const response = await fetch('http://172.16.0.119:8000/admin/documents/upload', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -147,7 +147,8 @@ interface ReviewPointsListProps {
|
|||||||
let activeTooltip = {
|
let activeTooltip = {
|
||||||
show: false, // 控制提示框是否显示
|
show: false, // 控制提示框是否显示
|
||||||
content: null as React.ReactNode, // 提示框内容(React节点)
|
content: null as React.ReactNode, // 提示框内容(React节点)
|
||||||
position: { top: 0, left: 0 } // 提示框在屏幕上的位置
|
position: { top: 0, left: 0 }, // 提示框在屏幕上的位置
|
||||||
|
ready: false // 新增:控制是否已准备好显示
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -186,7 +187,10 @@ function TooltipPortal() {
|
|||||||
style={{
|
style={{
|
||||||
top: `${tooltip.position.top}px`,
|
top: `${tooltip.position.top}px`,
|
||||||
left: `${tooltip.position.left}px`,
|
left: `${tooltip.position.left}px`,
|
||||||
transform: 'translate(-100%, -50%)' // 调整位置,使提示框在指针左侧居中显示
|
transform: 'translate(-100%, -50%)', // 调整位置,使提示框在指针左侧居中显示
|
||||||
|
opacity: tooltip.ready ? 1 : 0, // 根据ready状态控制透明度
|
||||||
|
visibility: tooltip.ready ? 'visible' : 'hidden', // 使用visibility确保在位置计算时元素存在但不可见
|
||||||
|
transition: 'opacity 0.15s ease-out' // 添加平滑过渡效果
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tooltip.content}
|
{tooltip.content}
|
||||||
@@ -203,22 +207,66 @@ function TooltipPortal() {
|
|||||||
* @param position 显示位置坐标
|
* @param position 显示位置坐标
|
||||||
*/
|
*/
|
||||||
function showTooltip(content: React.ReactNode, position: { top: number; left: number }): void {
|
function showTooltip(content: React.ReactNode, position: { top: number; left: number }): void {
|
||||||
// 更新全局状态对象
|
// 先设置内容和位置,但不立即显示
|
||||||
activeTooltip = {
|
activeTooltip = {
|
||||||
show: true,
|
show: true,
|
||||||
content,
|
content,
|
||||||
position
|
position,
|
||||||
|
ready: false // 初始设为未准备好
|
||||||
};
|
};
|
||||||
// 触发自定义事件,通知TooltipPortal组件更新状态
|
|
||||||
|
// 触发事件,让TooltipPortal渲染tooltip(但不可见)
|
||||||
window.dispatchEvent(new Event('tooltip-update'));
|
window.dispatchEvent(new Event('tooltip-update'));
|
||||||
|
|
||||||
|
// 使用RAF确保tooltip已渲染到DOM后再计算最终位置
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
// 查找刚创建的tooltip元素
|
||||||
|
const tooltipElement = document.querySelector('.fixed.bg-white.shadow-lg.rounded-md') as HTMLElement;
|
||||||
|
|
||||||
|
if (tooltipElement) {
|
||||||
|
// 获取tooltip的实际尺寸
|
||||||
|
const tooltipRect = tooltipElement.getBoundingClientRect();
|
||||||
|
|
||||||
|
// 重新计算位置,确保tooltip不会超出视口
|
||||||
|
let adjustedTop = position.top;
|
||||||
|
let adjustedLeft = position.left;
|
||||||
|
|
||||||
|
// 检查是否超出右边界
|
||||||
|
if (adjustedLeft - tooltipRect.width < 0) {
|
||||||
|
adjustedLeft = tooltipRect.width + 10; // 留一些边距
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否超出上边界
|
||||||
|
if (adjustedTop - tooltipRect.height / 2 < 0) {
|
||||||
|
adjustedTop = tooltipRect.height / 2 + 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否超出下边界
|
||||||
|
if (adjustedTop + tooltipRect.height / 2 > window.innerHeight) {
|
||||||
|
adjustedTop = window.innerHeight - tooltipRect.height / 2 - 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新位置并设为准备好显示
|
||||||
|
activeTooltip.position = { top: adjustedTop, left: adjustedLeft };
|
||||||
|
activeTooltip.ready = true;
|
||||||
|
|
||||||
|
// 再次触发事件更新显示状态
|
||||||
|
window.dispatchEvent(new Event('tooltip-update'));
|
||||||
|
} else {
|
||||||
|
// 如果找不到tooltip元素,直接显示
|
||||||
|
activeTooltip.ready = true;
|
||||||
|
window.dispatchEvent(new Event('tooltip-update'));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 隐藏提示框的辅助函数
|
* 隐藏提示框的辅助函数
|
||||||
*/
|
*/
|
||||||
function hideTooltip(): void {
|
function hideTooltip(): void {
|
||||||
// 设置为不显示状态
|
// 设置为不显示状态并重置ready状态
|
||||||
activeTooltip.show = false;
|
activeTooltip.show = false;
|
||||||
|
activeTooltip.ready = false;
|
||||||
// 触发自定义事件,通知TooltipPortal组件更新状态
|
// 触发自定义事件,通知TooltipPortal组件更新状态
|
||||||
window.dispatchEvent(new Event('tooltip-update'));
|
window.dispatchEvent(new Event('tooltip-update'));
|
||||||
}
|
}
|
||||||
@@ -232,6 +280,7 @@ function hideTooltip(): void {
|
|||||||
*/
|
*/
|
||||||
const ReactTableTooltip = ({ content }: { content: string }) => {
|
const ReactTableTooltip = ({ content }: { content: string }) => {
|
||||||
const [showTooltip, setShowTooltip] = useState(false);
|
const [showTooltip, setShowTooltip] = useState(false);
|
||||||
|
const [renderedContent, setRenderedContent] = useState<React.ReactNode>(null);
|
||||||
const textRef = useRef<HTMLDivElement>(null);
|
const textRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const isTableLike = content.includes('\t') && content.includes('\n');
|
const isTableLike = content.includes('\t') && content.includes('\n');
|
||||||
@@ -245,7 +294,14 @@ const ReactTableTooltip = ({ content }: { content: string }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
setTimeout(checkTextOverflow, 0);
|
// 预渲染内容并缓存
|
||||||
|
if (isTableLike) {
|
||||||
|
setRenderedContent(renderReactTable(content));
|
||||||
|
} else {
|
||||||
|
setRenderedContent(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(checkTextOverflow);
|
||||||
window.addEventListener('resize', checkTextOverflow);
|
window.addEventListener('resize', checkTextOverflow);
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('resize', checkTextOverflow);
|
window.removeEventListener('resize', checkTextOverflow);
|
||||||
@@ -310,14 +366,14 @@ const ReactTableTooltip = ({ content }: { content: string }) => {
|
|||||||
<div className="text-xs p-1 rounded cursor-text w-full text-left">
|
<div className="text-xs p-1 rounded cursor-text w-full text-left">
|
||||||
{showTooltip ? (
|
{showTooltip ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={isTableLike ? renderReactTable(content) : content}
|
content={renderedContent}
|
||||||
placement="top"
|
placement="top"
|
||||||
theme="light"
|
theme="light"
|
||||||
trigger="hover"
|
trigger="hover"
|
||||||
showArrow={true}
|
showArrow={true}
|
||||||
className="tooltip-custom-offset"
|
className="tooltip-custom-offset"
|
||||||
// fixedPlacement={true}
|
// fixedPlacement={true}
|
||||||
// scrollable={true}
|
scrollable={true}
|
||||||
maxHeight={400}
|
maxHeight={400}
|
||||||
>
|
>
|
||||||
<div className="text-gray-800 break-all overflow-hidden line-clamp-2" ref={textRef}>
|
<div className="text-gray-800 break-all overflow-hidden line-clamp-2" ref={textRef}>
|
||||||
@@ -1561,8 +1617,8 @@ export function ReviewPointsList({
|
|||||||
fieldElements.push(
|
fieldElements.push(
|
||||||
<div key="message" className="p-2 bg-blue-50 rounded border border-blue-200 text-xs mb-3 select-text">
|
<div key="message" className="p-2 bg-blue-50 rounded border border-blue-200 text-xs mb-3 select-text">
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
<i className="ri-robot-2-line text-blue-500 mr-2 "></i>
|
<i className="ri-robot-2-line text-blue-500 mr-2"></i>
|
||||||
<p className="text-xs text-gray-600 select-text">{messageContent}</p>
|
<p className="text-xs text-gray-600 select-text mb-0">{messageContent}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -1980,10 +2036,10 @@ export function ReviewPointsList({
|
|||||||
|
|
||||||
{/* 建议内容显示区域 */}
|
{/* 建议内容显示区域 */}
|
||||||
{reviewPoint.suggestion && (
|
{reviewPoint.suggestion && (
|
||||||
<div className="p-2 bg-blue-50 rounded border border-blue-200 text-xs mb-3 select-text">
|
<div className="p-2 bg-blue-50 rounded border border-blue-200 mb-3 text-xs select-text">
|
||||||
<div className="flex items-start">
|
<div className="flex items-center flex-row">
|
||||||
<i className="ri-information-line text-blue-500 mr-2 mt-0.5"></i>
|
<i className="ri-information-line text-blue-500 mr-2 mt-0.5"></i>
|
||||||
<p className="text-xs text-gray-600 select-text text-left">{reviewPoint.suggestion}</p>
|
<p className="text-xs text-gray-600 select-text text-left mb-0">{reviewPoint.suggestion}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ export function Tooltip({
|
|||||||
// 获取实际显示状态
|
// 获取实际显示状态
|
||||||
const isVisible = isControlled ? controlledVisible : visible;
|
const isVisible = isControlled ? controlledVisible : visible;
|
||||||
|
|
||||||
|
// 添加一个状态来控制实际渲染,解决位置跳跃问题
|
||||||
|
const [readyToShow, setReadyToShow] = useState(false);
|
||||||
|
|
||||||
// 引用DOM元素
|
// 引用DOM元素
|
||||||
const triggerRef = useRef<HTMLDivElement>(null);
|
const triggerRef = useRef<HTMLDivElement>(null);
|
||||||
const tooltipRef = useRef<HTMLDivElement>(null);
|
const tooltipRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -73,11 +76,17 @@ export function Tooltip({
|
|||||||
if (!isControlled) {
|
if (!isControlled) {
|
||||||
setVisible(newVisible);
|
setVisible(newVisible);
|
||||||
}
|
}
|
||||||
onVisibleChange?.(newVisible);
|
|
||||||
|
|
||||||
// 当显示状态变为false时,重置actualPlacement为初始placement
|
// 当显示状态变为false时,立即隐藏
|
||||||
if (!newVisible) {
|
if (!newVisible) {
|
||||||
|
setReadyToShow(false);
|
||||||
|
onVisibleChange?.(newVisible);
|
||||||
|
// 当显示状态变为false时,重置actualPlacement为初始placement
|
||||||
setActualPlacement(placement);
|
setActualPlacement(placement);
|
||||||
|
} else {
|
||||||
|
// 当显示状态变为true时,先延迟设置readyToShow
|
||||||
|
// 这样允许位置预计算完成后再显示tooltip
|
||||||
|
onVisibleChange?.(newVisible);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,7 +106,6 @@ export function Tooltip({
|
|||||||
|
|
||||||
// 获取触发元素位置
|
// 获取触发元素位置
|
||||||
const triggerRect = triggerRef.current.getBoundingClientRect();
|
const triggerRect = triggerRef.current.getBoundingClientRect();
|
||||||
const tooltipRect = tooltipRef.current.getBoundingClientRect();
|
|
||||||
|
|
||||||
// 设置最大宽度
|
// 设置最大宽度
|
||||||
tooltipRef.current.style.maxWidth = typeof maxWidth === 'number'
|
tooltipRef.current.style.maxWidth = typeof maxWidth === 'number'
|
||||||
@@ -115,6 +123,16 @@ export function Tooltip({
|
|||||||
tooltipRef.current.style.overflow = 'visible';
|
tooltipRef.current.style.overflow = 'visible';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 强制重新计算布局,确保maxHeight生效后再获取尺寸
|
||||||
|
tooltipRef.current.style.visibility = 'hidden';
|
||||||
|
tooltipRef.current.style.display = 'block';
|
||||||
|
|
||||||
|
// 触发重排,确保maxHeight限制已应用
|
||||||
|
void tooltipRef.current.offsetHeight;
|
||||||
|
|
||||||
|
// 获取应用maxHeight后的实际tooltip尺寸
|
||||||
|
const tooltipRect = tooltipRef.current.getBoundingClientRect();
|
||||||
|
|
||||||
// 计算各个方向的位置
|
// 计算各个方向的位置
|
||||||
let top = 0, left = 0;
|
let top = 0, left = 0;
|
||||||
let arrowTop = 0, arrowLeft = 0;
|
let arrowTop = 0, arrowLeft = 0;
|
||||||
@@ -311,6 +329,17 @@ export function Tooltip({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 恢复可见性(位置计算完成后)
|
||||||
|
tooltipRef.current.style.visibility = 'visible';
|
||||||
|
|
||||||
|
// 如果位置已更新且还没有显示,现在可以显示了
|
||||||
|
if (!readyToShow) {
|
||||||
|
// 使用requestAnimationFrame确保DOM更新后再显示
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
setReadyToShow(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 当组件首次挂载或placement改变时,重置actualPlacement
|
// 当组件首次挂载或placement改变时,重置actualPlacement
|
||||||
@@ -318,12 +347,27 @@ export function Tooltip({
|
|||||||
setActualPlacement(placement);
|
setActualPlacement(placement);
|
||||||
}, [placement]);
|
}, [placement]);
|
||||||
|
|
||||||
// 计算提示框位置
|
// 当isVisible状态变化时,处理初始化和清理工作
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isVisible) return;
|
if (!isVisible) {
|
||||||
|
// 当tooltip隐藏时,重置readyToShow状态
|
||||||
|
setReadyToShow(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化位置
|
// 创建一个隐藏的tooltip用于预计算位置
|
||||||
updateTooltipPosition();
|
// 使用一个隐藏的div来渲染tooltip,并获取其尺寸
|
||||||
|
const preCalculatePosition = () => {
|
||||||
|
// 初始时先不显示,让updateTooltipPosition完成位置计算
|
||||||
|
setReadyToShow(false);
|
||||||
|
|
||||||
|
// 初始化位置计算 - 添加短暂延迟确保内容已渲染
|
||||||
|
setTimeout(() => {
|
||||||
|
updateTooltipPosition();
|
||||||
|
}, 10);
|
||||||
|
};
|
||||||
|
|
||||||
|
preCalculatePosition();
|
||||||
|
|
||||||
// 添加滚动和调整大小事件监听器
|
// 添加滚动和调整大小事件监听器
|
||||||
window.addEventListener('scroll', updateTooltipPosition, true);
|
window.addEventListener('scroll', updateTooltipPosition, true);
|
||||||
@@ -338,7 +382,7 @@ export function Tooltip({
|
|||||||
window.removeEventListener('resize', updateTooltipPosition);
|
window.removeEventListener('resize', updateTooltipPosition);
|
||||||
clearInterval(intervalId);
|
clearInterval(intervalId);
|
||||||
};
|
};
|
||||||
}, [isVisible, placement, maxWidth, showArrow, fixedPlacement, actualPlacement]);
|
}, [isVisible, placement, maxWidth, showArrow, fixedPlacement]);
|
||||||
|
|
||||||
// 处理点击外部关闭
|
// 处理点击外部关闭
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -389,18 +433,23 @@ export function Tooltip({
|
|||||||
`tooltip-${actualPlacement}`, // 使用actualPlacement而不是placement
|
`tooltip-${actualPlacement}`, // 使用actualPlacement而不是placement
|
||||||
`tooltip-${theme}`,
|
`tooltip-${theme}`,
|
||||||
rich ? 'tooltip-rich' : '',
|
rich ? 'tooltip-rich' : '',
|
||||||
isVisible ? 'tooltip-visible' : '',
|
readyToShow ? 'tooltip-visible' : 'tooltip-hidden', // 使用readyToShow控制可见性
|
||||||
fixedPlacement ? 'tooltip-fixed-placement' : '',
|
fixedPlacement ? 'tooltip-fixed-placement' : '',
|
||||||
className
|
className
|
||||||
].filter(Boolean).join(' ');
|
].filter(Boolean).join(' ');
|
||||||
|
|
||||||
// 使用Portal渲染提示框
|
// 使用Portal渲染提示框,但根据readyToShow来控制可见性样式
|
||||||
const tooltipPortal = isVisible && typeof document !== 'undefined'
|
const tooltipPortal = isVisible && typeof document !== 'undefined'
|
||||||
? createPortal(
|
? createPortal(
|
||||||
<div
|
<div
|
||||||
ref={tooltipRef}
|
ref={tooltipRef}
|
||||||
className={tooltipClassNames}
|
className={tooltipClassNames}
|
||||||
style={{ maxWidth }}
|
style={{
|
||||||
|
maxWidth,
|
||||||
|
opacity: readyToShow ? 1 : 0, // 根据readyToShow控制透明度
|
||||||
|
visibility: readyToShow ? 'visible' : 'hidden', // 使用visibility确保在位置计算时元素存在但不可见
|
||||||
|
transition: 'opacity 0.1s ease-out' // 添加平滑过渡效果
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{renderTooltipContent()}
|
{renderTooltipContent()}
|
||||||
{showArrow && <div className="tooltip-arrow"></div>}
|
{showArrow && <div className="tooltip-arrow"></div>}
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
/**
|
||||||
|
* API配置文件
|
||||||
|
* 统一管理所有API地址,方便部署时修改
|
||||||
|
* 支持环境变量覆盖配置
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 环境配置类型
|
||||||
|
interface ApiConfig {
|
||||||
|
// 主API基础URL
|
||||||
|
baseUrl: string;
|
||||||
|
// 文档服务URL
|
||||||
|
documentUrl: string;
|
||||||
|
// 文档上传API URL
|
||||||
|
uploadUrl: string;
|
||||||
|
// PostgREST URL (如果使用)
|
||||||
|
postgrestUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不同环境的默认配置
|
||||||
|
const configs: Record<string, ApiConfig> = {
|
||||||
|
// 开发环境
|
||||||
|
development: {
|
||||||
|
baseUrl: 'http://nas.7bm.co:3000',
|
||||||
|
documentUrl: 'http://nas.7bm.co:9000/docauditai/',
|
||||||
|
uploadUrl: 'http://172.16.0.58:8008/admin/documents/upload',
|
||||||
|
},
|
||||||
|
|
||||||
|
// 测试环境
|
||||||
|
testing: {
|
||||||
|
baseUrl: 'http://172.18.0.100:3000',
|
||||||
|
documentUrl: 'http://nas.7bm.co:9000/docauditai/',
|
||||||
|
uploadUrl: 'http://172.16.0.58:8008/admin/documents/upload',
|
||||||
|
},
|
||||||
|
|
||||||
|
// 生产环境
|
||||||
|
production: {
|
||||||
|
baseUrl: 'http://nas.7bm.co:3000',
|
||||||
|
documentUrl: 'http://nas.7bm.co:9000/docauditai/',
|
||||||
|
uploadUrl: 'http://172.16.0.58:8008/admin/documents/upload',
|
||||||
|
},
|
||||||
|
|
||||||
|
// 备用配置 (可以根据需要添加更多环境)
|
||||||
|
staging: {
|
||||||
|
baseUrl: 'http://172.16.0.119:9000/admin',
|
||||||
|
documentUrl: 'http://nas.7bm.co:9000/docauditai/',
|
||||||
|
uploadUrl: 'http://172.16.0.119:8000/admin/documents/upload',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取当前环境,默认为development
|
||||||
|
const getCurrentEnvironment = (): string => {
|
||||||
|
// 优先使用环境变量,然后使用 NODE_ENV
|
||||||
|
return process.env.NEXT_PUBLIC_API_ENV || process.env.NODE_ENV || 'development';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 从环境变量获取配置,如果环境变量不存在则使用默认配置
|
||||||
|
const getConfigFromEnv = (defaultConfig: ApiConfig): ApiConfig => {
|
||||||
|
return {
|
||||||
|
baseUrl: process.env.NEXT_PUBLIC_API_BASE_URL || defaultConfig.baseUrl,
|
||||||
|
documentUrl: process.env.NEXT_PUBLIC_DOCUMENT_URL || defaultConfig.documentUrl,
|
||||||
|
uploadUrl: process.env.NEXT_PUBLIC_UPLOAD_URL || defaultConfig.uploadUrl,
|
||||||
|
postgrestUrl: process.env.NEXT_PUBLIC_POSTGREST_URL || defaultConfig.postgrestUrl,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取当前配置
|
||||||
|
const getCurrentConfig = (): ApiConfig => {
|
||||||
|
const env = getCurrentEnvironment();
|
||||||
|
const defaultConfig = configs[env] || configs.development;
|
||||||
|
|
||||||
|
// 如果是浏览器环境,尝试从环境变量覆盖配置
|
||||||
|
if (typeof window !== 'undefined' || process.env.NEXT_PUBLIC_API_BASE_URL) {
|
||||||
|
return getConfigFromEnv(defaultConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 导出当前环境的配置
|
||||||
|
export const apiConfig = getCurrentConfig();
|
||||||
|
|
||||||
|
// 导出具体的配置项,方便使用
|
||||||
|
export const {
|
||||||
|
baseUrl: API_BASE_URL,
|
||||||
|
documentUrl: DOCUMENT_URL,
|
||||||
|
uploadUrl: UPLOAD_URL,
|
||||||
|
postgrestUrl: POSTGREST_URL
|
||||||
|
} = apiConfig;
|
||||||
|
|
||||||
|
// 导出所有配置,供调试使用
|
||||||
|
export { configs };
|
||||||
|
|
||||||
|
// 工具函数:设置环境(主要用于测试)
|
||||||
|
export const setEnvironment = (env: string): ApiConfig => {
|
||||||
|
return configs[env] || configs.development;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 调试信息(仅在开发环境显示)
|
||||||
|
// if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('📦 API配置信息:', {
|
||||||
|
environment: getCurrentEnvironment(),
|
||||||
|
config: apiConfig
|
||||||
|
});
|
||||||
|
// }
|
||||||
+1
-1
@@ -197,7 +197,7 @@ export function links() {
|
|||||||
{ rel: "stylesheet", href: messageModalStyles },
|
{ rel: "stylesheet", href: messageModalStyles },
|
||||||
{ rel: "stylesheet", href: toastStyles },
|
{ rel: "stylesheet", href: toastStyles },
|
||||||
// 添加 Antd 样式
|
// 添加 Antd 样式
|
||||||
{ rel: "stylesheet", href: "https://cdn.jsdelivr.net/npm/antd@5/dist/reset.css" },
|
// { rel: "stylesheet", href: "https://cdn.jsdelivr.net/npm/antd@5/dist/reset.css" },
|
||||||
{ rel: "icon", type: "image/svg+xml", href: "/logo.svg" },
|
{ rel: "icon", type: "image/svg+xml", href: "/logo.svg" },
|
||||||
// { rel: "preconnect", href: "https://fonts.googleapis.com" },
|
// { rel: "preconnect", href: "https://fonts.googleapis.com" },
|
||||||
// { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" },
|
// { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" },
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
# 部署配置说明
|
||||||
|
|
||||||
|
## API 地址配置
|
||||||
|
|
||||||
|
本项目已将所有 API 地址统一配置在 `app/config/api-config.ts` 文件中,支持多种配置方式:
|
||||||
|
|
||||||
|
### 1. 默认配置
|
||||||
|
|
||||||
|
在 `api-config.ts` 中定义了不同环境的默认配置:
|
||||||
|
|
||||||
|
- **development** (开发环境)
|
||||||
|
- **testing** (测试环境)
|
||||||
|
- **production** (生产环境)
|
||||||
|
- **staging** (预发布环境)
|
||||||
|
|
||||||
|
### 2. 环境变量配置 (推荐)
|
||||||
|
|
||||||
|
可以通过环境变量覆盖默认配置,支持以下环境变量:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 指定环境类型
|
||||||
|
NEXT_PUBLIC_API_ENV=production
|
||||||
|
|
||||||
|
# API基础URL
|
||||||
|
NEXT_PUBLIC_API_BASE_URL=http://your-api-server.com:3000
|
||||||
|
|
||||||
|
# 文档服务URL
|
||||||
|
NEXT_PUBLIC_DOCUMENT_URL=http://your-document-server.com:9000/docauditai/
|
||||||
|
|
||||||
|
# 文档上传API URL
|
||||||
|
NEXT_PUBLIC_UPLOAD_URL=http://your-upload-server.com:8008/admin/documents/upload
|
||||||
|
|
||||||
|
# PostgREST URL (可选)
|
||||||
|
NEXT_PUBLIC_POSTGREST_URL=http://your-postgrest-server.com:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 部署方式
|
||||||
|
|
||||||
|
#### 方式一:环境变量文件 (推荐)
|
||||||
|
|
||||||
|
1. 在项目根目录创建 `.env.local` 文件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .env.local
|
||||||
|
NODE_ENV=production
|
||||||
|
NEXT_PUBLIC_API_BASE_URL=http://production-api.company.com:3000
|
||||||
|
NEXT_PUBLIC_DOCUMENT_URL=http://production-docs.company.com:9000/docauditai/
|
||||||
|
NEXT_PUBLIC_UPLOAD_URL=http://production-upload.company.com:8008/admin/documents/upload
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 构建和启动应用:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 方式二:直接修改配置文件
|
||||||
|
|
||||||
|
直接编辑 `app/config/api-config.ts` 中对应环境的配置:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 修改生产环境配置
|
||||||
|
production: {
|
||||||
|
baseUrl: 'http://your-production-api.com:3000',
|
||||||
|
documentUrl: 'http://your-production-docs.com:9000/docauditai/',
|
||||||
|
uploadUrl: 'http://your-production-upload.com:8008/admin/documents/upload',
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 方式三:Docker 环境变量
|
||||||
|
|
||||||
|
在 Docker 部署时传入环境变量:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
-e NODE_ENV=production \
|
||||||
|
-e NEXT_PUBLIC_API_BASE_URL=http://api.company.com:3000 \
|
||||||
|
-e NEXT_PUBLIC_DOCUMENT_URL=http://docs.company.com:9000/docauditai/ \
|
||||||
|
-e NEXT_PUBLIC_UPLOAD_URL=http://upload.company.com:8008/admin/documents/upload \
|
||||||
|
your-app-image
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 配置项说明
|
||||||
|
|
||||||
|
| 配置项 | 用途 | 示例 |
|
||||||
|
|--------|------|------|
|
||||||
|
| `API_BASE_URL` | 主要API服务地址 | `http://nas.7bm.co:3000` |
|
||||||
|
| `DOCUMENT_URL` | 文档下载服务地址 | `http://nas.7bm.co:9000/docauditai/` |
|
||||||
|
| `UPLOAD_URL` | 文档上传API地址 | `http://172.16.0.58:8008/admin/documents/upload` |
|
||||||
|
|
||||||
|
### 5. 文件影响范围
|
||||||
|
|
||||||
|
以下文件已更新使用统一配置:
|
||||||
|
|
||||||
|
- `app/api/axios-client.ts` - 使用 `API_BASE_URL` 和 `DOCUMENT_URL`
|
||||||
|
- `app/api/files/files-upload.ts` - 使用 `UPLOAD_URL`
|
||||||
|
|
||||||
|
### 6. 验证配置
|
||||||
|
|
||||||
|
在开发环境下,控制台会输出当前使用的配置信息,便于调试:
|
||||||
|
|
||||||
|
```
|
||||||
|
📦 API配置信息: {
|
||||||
|
environment: "development",
|
||||||
|
config: {
|
||||||
|
baseUrl: "http://nas.7bm.co:3000",
|
||||||
|
documentUrl: "http://nas.7bm.co:9000/docauditai/",
|
||||||
|
uploadUrl: "http://172.16.0.58:8008/admin/documents/upload"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. 注意事项
|
||||||
|
|
||||||
|
1. 环境变量必须以 `NEXT_PUBLIC_` 开头才能在客户端使用
|
||||||
|
2. 修改环境变量后需要重新构建应用
|
||||||
|
3. URL 地址末尾的斜杠要保持一致
|
||||||
|
4. 确保所有服务地址在目标环境中可访问
|
||||||
@@ -0,0 +1,272 @@
|
|||||||
|
# Docker 部署指南
|
||||||
|
|
||||||
|
## 环境变量工作原理
|
||||||
|
|
||||||
|
### 在 Remix 项目中如何工作
|
||||||
|
|
||||||
|
1. **构建时环境变量**: 在 Docker 构建时,环境变量会被嵌入到构建产物中
|
||||||
|
2. **运行时环境变量**: 通过 `dotenv` 包在应用启动时加载环境变量
|
||||||
|
3. **客户端环境变量**: 以 `NEXT_PUBLIC_` 开头的变量可以在客户端访问
|
||||||
|
|
||||||
|
### API 配置文件的识别机制
|
||||||
|
|
||||||
|
我们的 `app/config/api-config.ts` 配置文件通过以下方式识别环境变量:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 从环境变量获取配置,如果环境变量不存在则使用默认配置
|
||||||
|
const getConfigFromEnv = (defaultConfig: ApiConfig): ApiConfig => {
|
||||||
|
return {
|
||||||
|
baseUrl: process.env.NEXT_PUBLIC_API_BASE_URL || defaultConfig.baseUrl,
|
||||||
|
documentUrl: process.env.NEXT_PUBLIC_DOCUMENT_URL || defaultConfig.documentUrl,
|
||||||
|
uploadUrl: process.env.NEXT_PUBLIC_UPLOAD_URL || defaultConfig.uploadUrl,
|
||||||
|
postgrestUrl: process.env.NEXT_PUBLIC_POSTGREST_URL || defaultConfig.postgrestUrl,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件使用配置的映射关系
|
||||||
|
|
||||||
|
1. **app/api/axios-client.ts** 使用:
|
||||||
|
- `API_BASE_URL` (来自 `NEXT_PUBLIC_API_BASE_URL`)
|
||||||
|
- `DOCUMENT_URL` (来自 `NEXT_PUBLIC_DOCUMENT_URL`)
|
||||||
|
|
||||||
|
2. **app/api/files/files-upload.ts** 使用:
|
||||||
|
- `UPLOAD_URL` (来自 `NEXT_PUBLIC_UPLOAD_URL`)
|
||||||
|
|
||||||
|
## Docker 部署方式
|
||||||
|
|
||||||
|
### 方式一:直接使用 docker run 命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 构建镜像
|
||||||
|
docker build -t docreview-app .
|
||||||
|
|
||||||
|
# 运行容器,通过 -e 参数传入环境变量
|
||||||
|
docker run -d \
|
||||||
|
--name docreview \
|
||||||
|
-p 3000:3000 \
|
||||||
|
-e NODE_ENV=production \
|
||||||
|
-e NEXT_PUBLIC_API_BASE_URL=http://your-api-server.com:3000 \
|
||||||
|
-e NEXT_PUBLIC_DOCUMENT_URL=http://your-docs-server.com:9000/docauditai/ \
|
||||||
|
-e NEXT_PUBLIC_UPLOAD_URL=http://your-upload-server.com:8008/admin/documents/upload \
|
||||||
|
docreview-app
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方式二:使用 docker-compose
|
||||||
|
|
||||||
|
1. **使用内联环境变量**:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yml
|
||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
docreview-app:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- NEXT_PUBLIC_API_BASE_URL=http://api.company.com:3000
|
||||||
|
- NEXT_PUBLIC_DOCUMENT_URL=http://docs.company.com:9000/docauditai/
|
||||||
|
- NEXT_PUBLIC_UPLOAD_URL=http://upload.company.com:8008/admin/documents/upload
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **使用环境变量文件**:
|
||||||
|
|
||||||
|
创建 `.env.production` 文件:
|
||||||
|
```bash
|
||||||
|
NODE_ENV=production
|
||||||
|
NEXT_PUBLIC_API_BASE_URL=http://api.company.com:3000
|
||||||
|
NEXT_PUBLIC_DOCUMENT_URL=http://docs.company.com:9000/docauditai/
|
||||||
|
NEXT_PUBLIC_UPLOAD_URL=http://upload.company.com:8008/admin/documents/upload
|
||||||
|
```
|
||||||
|
|
||||||
|
然后在 docker-compose.yml 中引用:
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
docreview-app:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
env_file:
|
||||||
|
- .env.production
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方式三:Kubernetes 部署
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: docreview-app
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: docreview-app
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: docreview-app
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: docreview-app
|
||||||
|
image: docreview-app:latest
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
env:
|
||||||
|
- name: NODE_ENV
|
||||||
|
value: "production"
|
||||||
|
- name: NEXT_PUBLIC_API_BASE_URL
|
||||||
|
value: "http://api.company.com:3000"
|
||||||
|
- name: NEXT_PUBLIC_DOCUMENT_URL
|
||||||
|
value: "http://docs.company.com:9000/docauditai/"
|
||||||
|
- name: NEXT_PUBLIC_UPLOAD_URL
|
||||||
|
value: "http://upload.company.com:8008/admin/documents/upload"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 环境变量验证
|
||||||
|
|
||||||
|
### 在容器内检查环境变量
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 进入运行中的容器
|
||||||
|
docker exec -it docreview /bin/sh
|
||||||
|
|
||||||
|
# 查看环境变量
|
||||||
|
env | grep NEXT_PUBLIC
|
||||||
|
|
||||||
|
# 或者查看具体变量
|
||||||
|
echo $NEXT_PUBLIC_API_BASE_URL
|
||||||
|
echo $NEXT_PUBLIC_DOCUMENT_URL
|
||||||
|
echo $NEXT_PUBLIC_UPLOAD_URL
|
||||||
|
```
|
||||||
|
|
||||||
|
### 应用内验证
|
||||||
|
|
||||||
|
在开发模式下,配置文件会输出当前使用的配置:
|
||||||
|
```
|
||||||
|
📦 API配置信息: {
|
||||||
|
environment: "production",
|
||||||
|
config: {
|
||||||
|
baseUrl: "http://api.company.com:3000",
|
||||||
|
documentUrl: "http://docs.company.com:9000/docauditai/",
|
||||||
|
uploadUrl: "http://upload.company.com:8008/admin/documents/upload"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 构建时的环境变量注入
|
||||||
|
|
||||||
|
### 方式一:Docker 构建时传入
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build \
|
||||||
|
--build-arg NEXT_PUBLIC_API_BASE_URL=http://api.company.com:3000 \
|
||||||
|
--build-arg NEXT_PUBLIC_DOCUMENT_URL=http://docs.company.com:9000/docauditai/ \
|
||||||
|
--build-arg NEXT_PUBLIC_UPLOAD_URL=http://upload.company.com:8008/admin/documents/upload \
|
||||||
|
-t docreview-app .
|
||||||
|
```
|
||||||
|
|
||||||
|
然后在 Dockerfile 中添加:
|
||||||
|
```dockerfile
|
||||||
|
# 在构建阶段添加
|
||||||
|
ARG NEXT_PUBLIC_API_BASE_URL
|
||||||
|
ARG NEXT_PUBLIC_DOCUMENT_URL
|
||||||
|
ARG NEXT_PUBLIC_UPLOAD_URL
|
||||||
|
|
||||||
|
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
|
||||||
|
ENV NEXT_PUBLIC_DOCUMENT_URL=$NEXT_PUBLIC_DOCUMENT_URL
|
||||||
|
ENV NEXT_PUBLIC_UPLOAD_URL=$NEXT_PUBLIC_UPLOAD_URL
|
||||||
|
```
|
||||||
|
|
||||||
|
## 多环境配置示例
|
||||||
|
|
||||||
|
### 开发环境
|
||||||
|
```bash
|
||||||
|
# .env.development
|
||||||
|
NODE_ENV=development
|
||||||
|
NEXT_PUBLIC_API_BASE_URL=http://localhost:8008
|
||||||
|
NEXT_PUBLIC_DOCUMENT_URL=http://localhost:9000/docauditai/
|
||||||
|
NEXT_PUBLIC_UPLOAD_URL=http://localhost:8008/admin/documents/upload
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试环境
|
||||||
|
```bash
|
||||||
|
# .env.testing
|
||||||
|
NODE_ENV=testing
|
||||||
|
NEXT_PUBLIC_API_BASE_URL=http://test-api.company.com:3000
|
||||||
|
NEXT_PUBLIC_DOCUMENT_URL=http://test-docs.company.com:9000/docauditai/
|
||||||
|
NEXT_PUBLIC_UPLOAD_URL=http://test-upload.company.com:8008/admin/documents/upload
|
||||||
|
```
|
||||||
|
|
||||||
|
### 生产环境
|
||||||
|
```bash
|
||||||
|
# .env.production
|
||||||
|
NODE_ENV=production
|
||||||
|
NEXT_PUBLIC_API_BASE_URL=http://api.company.com:3000
|
||||||
|
NEXT_PUBLIC_DOCUMENT_URL=http://docs.company.com:9000/docauditai/
|
||||||
|
NEXT_PUBLIC_UPLOAD_URL=http://upload.company.com:8008/admin/documents/upload
|
||||||
|
```
|
||||||
|
|
||||||
|
## 部署脚本示例
|
||||||
|
|
||||||
|
创建 `deploy.sh` 脚本:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 部署脚本
|
||||||
|
ENV=${1:-production}
|
||||||
|
|
||||||
|
echo "部署环境: $ENV"
|
||||||
|
|
||||||
|
# 构建镜像
|
||||||
|
docker build -t docreview-app:$ENV .
|
||||||
|
|
||||||
|
# 停止旧容器
|
||||||
|
docker stop docreview-$ENV 2>/dev/null || true
|
||||||
|
docker rm docreview-$ENV 2>/dev/null || true
|
||||||
|
|
||||||
|
# 根据环境启动容器
|
||||||
|
case $ENV in
|
||||||
|
"development")
|
||||||
|
docker run -d \
|
||||||
|
--name docreview-$ENV \
|
||||||
|
-p 3001:3000 \
|
||||||
|
--env-file .env.development \
|
||||||
|
docreview-app:$ENV
|
||||||
|
;;
|
||||||
|
"testing")
|
||||||
|
docker run -d \
|
||||||
|
--name docreview-$ENV \
|
||||||
|
-p 3002:3000 \
|
||||||
|
--env-file .env.testing \
|
||||||
|
docreview-app:$ENV
|
||||||
|
;;
|
||||||
|
"production")
|
||||||
|
docker run -d \
|
||||||
|
--name docreview-$ENV \
|
||||||
|
-p 3000:3000 \
|
||||||
|
--env-file .env.production \
|
||||||
|
docreview-app:$ENV
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "部署完成,容器名: docreview-$ENV"
|
||||||
|
```
|
||||||
|
|
||||||
|
使用方法:
|
||||||
|
```bash
|
||||||
|
chmod +x deploy.sh
|
||||||
|
./deploy.sh production # 部署生产环境
|
||||||
|
./deploy.sh testing # 部署测试环境
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **环境变量优先级**:Docker 环境变量 > 环境变量文件 > 默认配置
|
||||||
|
2. **客户端变量**:必须以 `NEXT_PUBLIC_` 开头才能在客户端使用
|
||||||
|
3. **构建时注入**:客户端环境变量在构建时被注入,运行时修改不会生效
|
||||||
|
4. **安全性**:不要在环境变量中存储敏感信息,如API密钥等
|
||||||
|
5. **网络访问**:确保容器能够访问配置的API地址
|
||||||
Reference in New Issue
Block a user