文档反馈功能 #73
|
|
@ -0,0 +1,183 @@
|
|||
version: 2
|
||||
name: 流水线zhangyang
|
||||
description: ""
|
||||
global:
|
||||
concurrent: 1
|
||||
trigger:
|
||||
webhook: gitlink@1.0.0
|
||||
event:
|
||||
- ref: push
|
||||
ruleset-operator: AND
|
||||
workflow:
|
||||
- ref: start
|
||||
name: 开始
|
||||
task: start
|
||||
- ref: end
|
||||
name: 结束
|
||||
task: end
|
||||
needs:
|
||||
- shell_0
|
||||
- ref: shell_0
|
||||
name: shell
|
||||
image: docker.jianmuhub.com/library/ubuntu:22.04
|
||||
script:
|
||||
- "#!/bin/bash"
|
||||
- ""
|
||||
- "# 配置信息(已更新为你的服务器信息)"
|
||||
- 'REPO_URL="https://gitlink.org.cn/lirenqiu/gitlink_help_center.git" #
|
||||
替换为你的代码仓库URL'
|
||||
- LOCAL_BUILD_DIR="/tmp/your-project-build"
|
||||
- WEB_ROOT="/var/www/html"
|
||||
- NGINX_CONFIG="/etc/nginx/sites-available/default"
|
||||
- 'REMOTE_SERVER="root@114.55.175.219" # 使用你的服务器IP'
|
||||
- LOG_FILE="/var/log/frontend_deploy.log"
|
||||
- ""
|
||||
- "# 日志函数"
|
||||
- log() {
|
||||
- " local level=$1"
|
||||
- " local message=$2"
|
||||
- " echo \"[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $message\" | tee -a
|
||||
$LOG_FILE"
|
||||
- "}"
|
||||
- ""
|
||||
- "# 错误处理函数"
|
||||
- handle_error() {
|
||||
- ' log "ERROR" "执行命令失败: $BASH_COMMAND"'
|
||||
- ' log "ERROR" "错误代码: $?"'
|
||||
- " exit 1"
|
||||
- "}"
|
||||
- ""
|
||||
- "# 设置错误捕获"
|
||||
- trap 'handle_error' ERR
|
||||
- ""
|
||||
- "# 检查是否以root用户运行"
|
||||
- check_root() {
|
||||
- " if [[ $EUID -ne 0 ]]; then"
|
||||
- ' log "ERROR" "此脚本需要以root用户运行"'
|
||||
- " exit 1"
|
||||
- " fi"
|
||||
- "}"
|
||||
- ""
|
||||
- "# 安装本地依赖"
|
||||
- install_local_dependencies() {
|
||||
- ' log "INFO" "开始安装本地依赖..."'
|
||||
- " "
|
||||
- " # 检测系统类型"
|
||||
- " if [[ -f /etc/debian_version ]]; then"
|
||||
- ' log "INFO" "检测到Debian/Ubuntu系统"'
|
||||
- " apt-get update -y"
|
||||
- " apt-get install -y git curl gnupg sshpass"
|
||||
- " elif [[ -f /etc/redhat-release ]]; then"
|
||||
- ' log "INFO" "检测到CentOS/RHEL系统"'
|
||||
- " yum update -y"
|
||||
- " yum install -y git curl gnupg sshpass"
|
||||
- " else"
|
||||
- ' log "ERROR" "不支持的操作系统"'
|
||||
- " exit 1"
|
||||
- " fi"
|
||||
- " "
|
||||
- " # 安装Node.js和Yarn"
|
||||
- " curl -sL https://deb.nodesource.com/setup_16.x | bash -"
|
||||
- " apt-get install -y nodejs"
|
||||
- " npm install -g yarn"
|
||||
- " "
|
||||
- ' log "INFO" "本地依赖安装完成"'
|
||||
- "}"
|
||||
- ""
|
||||
- "# 克隆代码并构建"
|
||||
- build_project() {
|
||||
- ' log "INFO" "开始克隆代码并构建项目..."'
|
||||
- " "
|
||||
- " # 创建构建目录"
|
||||
- " mkdir -p $LOCAL_BUILD_DIR"
|
||||
- " cd $LOCAL_BUILD_DIR || exit"
|
||||
- " "
|
||||
- " # 克隆代码"
|
||||
- ' if [ -d ".git" ]; then'
|
||||
- ' log "INFO" "目录非空,尝试拉取最新代码"'
|
||||
- " git pull"
|
||||
- " else"
|
||||
- ' log "INFO" "克隆代码仓库"'
|
||||
- " git clone $REPO_URL ."
|
||||
- " fi"
|
||||
- " "
|
||||
- " # 安装项目依赖并构建"
|
||||
- ' log "INFO" "安装项目依赖..."'
|
||||
- " yarn install"
|
||||
- " "
|
||||
- ' log "INFO" "开始项目构建..."'
|
||||
- " yarn build"
|
||||
- " "
|
||||
- ' log "INFO" "项目构建完成"'
|
||||
- "}"
|
||||
- ""
|
||||
- "# 使用密码部署到远程服务器"
|
||||
- deploy_to_remote() {
|
||||
- ' log "INFO" "开始部署到远程服务器..."'
|
||||
- " "
|
||||
- " # 服务器密码(注意:明文存储不安全!)"
|
||||
- " SERVER_PASSWORD='ppuUV7W6UtmVGk01#pd:l' # 你的服务器密码"
|
||||
- " "
|
||||
- " # 复制构建文件到远程服务器"
|
||||
- ' log "INFO" "复制构建文件到远程服务器..."'
|
||||
- ' sshpass -p "$SERVER_PASSWORD" rsync -avz --delete -e ssh
|
||||
"$LOCAL_BUILD_DIR/dist/" "$REMOTE_SERVER:$WEB_ROOT/"'
|
||||
- " "
|
||||
- " # 在远程服务器上配置Nginx"
|
||||
- ' log "INFO" "配置远程服务器Nginx..."'
|
||||
- ' sshpass -p "$SERVER_PASSWORD" ssh -o StrictHostKeyChecking=no
|
||||
$REMOTE_SERVER <<EOF'
|
||||
- sudo bash <<SCRIPT
|
||||
- "# 确保Nginx已安装"
|
||||
- if ! command -v nginx &> /dev/null; then
|
||||
- " if [[ -f /etc/debian_version ]]; then"
|
||||
- " apt-get update -y"
|
||||
- " apt-get install -y nginx"
|
||||
- " elif [[ -f /etc/redhat-release ]]; then"
|
||||
- " yum update -y"
|
||||
- " yum install -y nginx"
|
||||
- " fi"
|
||||
- fi
|
||||
- ""
|
||||
- "# 创建Nginx配置文件"
|
||||
- cat > $NGINX_CONFIG <<NGINXCONF
|
||||
- server {
|
||||
- " listen 80 default_server;"
|
||||
- " listen [::]:80 default_server;"
|
||||
- " "
|
||||
- " root $WEB_ROOT;"
|
||||
- " index index.html index.htm;"
|
||||
- " "
|
||||
- " server_name _;"
|
||||
- " "
|
||||
- " location / {"
|
||||
- " try_files \\$uri \\$uri/ /index.html;"
|
||||
- " }"
|
||||
- "}"
|
||||
- NGINXCONF
|
||||
- ""
|
||||
- "# 测试配置并重启Nginx"
|
||||
- nginx -t
|
||||
- systemctl restart nginx
|
||||
- systemctl enable nginx
|
||||
- SCRIPT
|
||||
- EOF
|
||||
- " "
|
||||
- ' log "INFO" "部署到远程服务器完成"'
|
||||
- "}"
|
||||
- ""
|
||||
- "# 主函数"
|
||||
- main() {
|
||||
- ' log "INFO" "===== 开始前端项目部署脚本 ====="'
|
||||
- " check_root"
|
||||
- " install_local_dependencies"
|
||||
- " build_project"
|
||||
- " deploy_to_remote"
|
||||
- ' log "INFO" "===== 部署完成 ====="'
|
||||
- ' log "INFO" "访问 http://114.55.175.219 查看网站"'
|
||||
- "}"
|
||||
- ""
|
||||
- main
|
||||
needs:
|
||||
- start
|
||||
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
version: 2
|
||||
name: 测试实验一
|
||||
description: ""
|
||||
global:
|
||||
concurrent: 1
|
||||
trigger:
|
||||
webhook: gitlink@1.0.0
|
||||
event:
|
||||
- ref: push
|
||||
ruleset-operator: AND
|
||||
workflow:
|
||||
- ref: start
|
||||
name: 开始
|
||||
task: start
|
||||
- ref: docker_image_build_0
|
||||
name: docker镜像构建
|
||||
task: docker_image_build@1.6.0
|
||||
input:
|
||||
docker_username: ((nudt_devops.nudt_devops))
|
||||
docker_password: ((nudt_devops.nudt_devops))
|
||||
image_name: '"registry.cn-guangzhou.aliyuncs.com/nudt_devops/gitlink_help_center_group5"'
|
||||
image_tag: '"latest"'
|
||||
registry_address: '"registry.cn-guangzhou.aliyuncs.com"'
|
||||
docker_file: '"Dockerfile"'
|
||||
docker_build_path: '"."'
|
||||
workspace: git_clone_0.git_path
|
||||
image_push: true
|
||||
build_args: '""'
|
||||
needs:
|
||||
- git_clone_0
|
||||
- ref: git_clone_0
|
||||
name: git clone
|
||||
task: git_clone@1.2.9
|
||||
input:
|
||||
username: ((ganshihao21.ganshihao21_user))
|
||||
password: ((ganshihao21.ganshihao21))
|
||||
remote_url: '"https://gitlink.org.cn/shuaihao/gitlink_help_center.git"'
|
||||
ref: '"refs/heads/master"'
|
||||
commit_id: '""'
|
||||
depth: 1
|
||||
needs:
|
||||
- start
|
||||
- ref: ssh_cmd_0
|
||||
name: ssh执行命令
|
||||
task: ssh_cmd@1.1.1
|
||||
input:
|
||||
ssh_pass: ((ssh_key.ssh_key))
|
||||
ssh_ip: '"47.96.164.145"'
|
||||
ssh_port: '"22"'
|
||||
ssh_user: '"root"'
|
||||
ssh_cmd: '"docker stop ghc_group5 && docker rm ghc_group5 && docker pull
|
||||
registry.cn-guangzhou.aliyuncs.com/nudt_devops/gitlink_help_center_group5:latest
|
||||
&& docker run -d -p 3000:3000 --name ghc_group5
|
||||
registry.cn-guangzhou.aliyuncs.com/nudt_devops/gitlink_help_center_group5:latest"'
|
||||
needs:
|
||||
- docker_image_build_0
|
||||
- ref: end
|
||||
name: 结束
|
||||
task: end
|
||||
needs:
|
||||
- ssh_cmd_0
|
||||
|
|
@ -62,7 +62,7 @@ GitLink(确实开源)是CCF官方指定的开源创新服务平台,旨在
|
|||
<article class="col col--6 margin-bottom--lg">
|
||||
<a class="card padding--lg cardContainer" href="/Bot市场/bot安装">
|
||||
<h2 class="text--truncate cardTitle" title="Bot市场">Bot市场</h2>
|
||||
<p>Bot市场使用及设置[4个文档]</p>
|
||||
<p>Bot市场使用及设置[3个文档]</p>
|
||||
</a></article>
|
||||
<article class="col col--6 margin-bottom--lg">
|
||||
<a class="card padding--lg cardContainer" href="/第三方服务/跨平台代码同步">
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -24,9 +24,14 @@
|
|||
"clsx": "1.2.1",
|
||||
"docusaurus-gtm-plugin": "0.0.2",
|
||||
"docusaurus-plugin-image-zoom": "1.0.1",
|
||||
"html2pdf": "^0.0.11",
|
||||
"html2pdf.js": "^0.10.3",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-modal": "3.16.1"
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-modal": "3.16.1",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"strip-final-newline": "^4.0.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,164 @@
|
|||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
export default function AIChat({ onClose }) {
|
||||
// 初始化时添加一条AI的欢迎消息
|
||||
const [messages, setMessages] = useState([
|
||||
{
|
||||
role: 'assistant',
|
||||
content: '我是GitLink帮助中心的AI助手,专门解答关于GitLink平台的各种问题,包括代码库管理、合并请求(Merge Request)、疑修(Issue)、维基(Wiki)等功能的使用。如果你有具体问题,可以直接告诉我,我会尽力提供简洁准确的解答,并推荐相关文档链接(如适用)。 例如:**常见问题**:"如何创建合并请求?" **操作指引**:"项目Wiki如何编辑?" 需要帮助时随时提问!'
|
||||
}
|
||||
]);
|
||||
const [input, setInput] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const messagesEndRef = useRef(null);
|
||||
|
||||
// 滚动到最新消息
|
||||
const scrollToBottom = () => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
scrollToBottom();
|
||||
}, [messages]);
|
||||
|
||||
// 发送消息到DeepSeek API
|
||||
const sendMessage = async () => {
|
||||
if (!input.trim()) return;
|
||||
|
||||
const userMessage = { role: 'user', content: input };
|
||||
setMessages([...messages, userMessage]);
|
||||
setInput('');
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
// 添加系统消息提供上下文
|
||||
const systemMessage = {
|
||||
role: 'system',
|
||||
content: '你是GitLink帮助中心的AI助手,可以解答关于GitLink平台使用、代码库管理、合并请求、疑修(Issue)、维基等方面的问题。提供简洁、准确的回答,并尽可能引导用户查看相关文档页面。'
|
||||
};
|
||||
|
||||
const response = await fetch('https://api.deepseek.com/chat/completions', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer sk-94226df2b6f0469e9616b57d3c4ecb12'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "deepseek-chat",
|
||||
messages: [systemMessage, ...messages, userMessage],
|
||||
stream: false
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.choices && data.choices[0]) {
|
||||
setMessages(prevMessages => [
|
||||
...prevMessages,
|
||||
data.choices[0].message
|
||||
]);
|
||||
} else {
|
||||
// 处理错误
|
||||
setMessages(prevMessages => [
|
||||
...prevMessages,
|
||||
{ role: 'assistant', content: '抱歉,我遇到了一些问题,请稍后再试。' }
|
||||
]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
setMessages(prevMessages => [
|
||||
...prevMessages,
|
||||
{ role: 'assistant', content: '抱歉,连接出错,请稍后再试。' }
|
||||
]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 渲染消息内容,用户消息直接显示文本,AI消息使用Markdown渲染
|
||||
const renderMessageContent = (message) => {
|
||||
if (message.role === 'user') {
|
||||
return <div className={styles.messageContent}>{message.content}</div>;
|
||||
} else {
|
||||
return (
|
||||
<div className={styles.messageContent}>
|
||||
<ReactMarkdown
|
||||
components={{
|
||||
code({node, inline, className, children, ...props}) {
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
return !inline && match ? (
|
||||
<SyntaxHighlighter
|
||||
style={vscDarkPlus}
|
||||
language={match[1]}
|
||||
PreTag="div"
|
||||
{...props}
|
||||
>
|
||||
{String(children).replace(/\n$/, '')}
|
||||
</SyntaxHighlighter>
|
||||
) : (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{message.content}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.chatContainer}>
|
||||
<div className={styles.chatHeader}>
|
||||
<h3>DeepSeek AI 助手</h3>
|
||||
<button className={styles.closeButton} onClick={onClose}>×</button>
|
||||
</div>
|
||||
|
||||
<div className={styles.messagesContainer}>
|
||||
{messages.map((msg, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`${styles.message} ${msg.role === 'user' ? styles.userMessage : styles.aiMessage}`}
|
||||
>
|
||||
{renderMessageContent(msg)}
|
||||
</div>
|
||||
))}
|
||||
{loading && (
|
||||
<div className={`${styles.message} ${styles.aiMessage}`}>
|
||||
<div className={styles.messageContent}>
|
||||
<div className={styles.loadingDots}>
|
||||
<span></span><span></span><span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
<div className={styles.inputContainer}>
|
||||
<input
|
||||
type="text"
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
|
||||
placeholder="输入您的问题..."
|
||||
className={styles.chatInput}
|
||||
/>
|
||||
<button
|
||||
onClick={sendMessage}
|
||||
disabled={loading || !input.trim()}
|
||||
className={styles.sendButton}
|
||||
>
|
||||
发送
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,345 @@
|
|||
.chatContainer {
|
||||
position: fixed;
|
||||
bottom: 90px;
|
||||
right: 300px;
|
||||
width: 350px;
|
||||
height: 500px;
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 10001;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chatHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
background-color: var(--ifm-color-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.chatHeader h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.messagesContainer {
|
||||
flex: 1;
|
||||
padding: 15px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.message {
|
||||
max-width: 80%;
|
||||
padding: 10px 15px;
|
||||
border-radius: 18px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.userMessage {
|
||||
align-self: flex-end;
|
||||
background-color: var(--ifm-color-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.aiMessage {
|
||||
align-self: flex-start;
|
||||
background-color: #f3f4f6;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.messageContent {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Markdown样式 */
|
||||
.messageContent p {
|
||||
margin: 0 0 0.8em;
|
||||
}
|
||||
|
||||
.messageContent p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.messageContent h1,
|
||||
.messageContent h2,
|
||||
.messageContent h3,
|
||||
.messageContent h4,
|
||||
.messageContent h5,
|
||||
.messageContent h6 {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 0.5em;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.messageContent h1 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.messageContent h2 {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.messageContent h3 {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.messageContent ul,
|
||||
.messageContent ol {
|
||||
margin: 0.5em 0;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
|
||||
.messageContent li {
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
|
||||
.messageContent code {
|
||||
font-family: monospace;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.messageContent pre {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
padding: 0.5em;
|
||||
border-radius: 5px;
|
||||
overflow-x: auto;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.messageContent pre code {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.messageContent a {
|
||||
color: #2563eb;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.messageContent a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.messageContent blockquote {
|
||||
border-left: 4px solid #e5e7eb;
|
||||
padding-left: 1em;
|
||||
margin: 0.5em 0;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.messageContent img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.messageContent table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.messageContent th,
|
||||
.messageContent td {
|
||||
border: 1px solid #e5e7eb;
|
||||
padding: 0.5em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.messageContent th {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.messageContent strong,
|
||||
.messageContent b {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.messageContent em,
|
||||
.messageContent i {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.userMessage .messageContent code,
|
||||
.userMessage .messageContent pre {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.userMessage .messageContent a {
|
||||
color: #e0e7ff;
|
||||
}
|
||||
|
||||
.emptyState {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: #6b7280;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.inputContainer {
|
||||
display: flex;
|
||||
padding: 10px 15px;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.chatInput {
|
||||
flex: 1;
|
||||
padding: 10px 15px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.chatInput:focus {
|
||||
border-color: var(--ifm-color-primary);
|
||||
}
|
||||
|
||||
.sendButton {
|
||||
margin-left: 10px;
|
||||
padding: 8px 15px;
|
||||
background-color: var(--ifm-color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.sendButton:disabled {
|
||||
background-color: #93c5fd;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.loadingDots {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.loadingDots span {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background-color: #6b7280;
|
||||
animation: bounce 1.4s infinite ease-in-out both;
|
||||
}
|
||||
|
||||
.loadingDots span:nth-child(1) {
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
.loadingDots span:nth-child(2) {
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 80%, 100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
40% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* 暗色主题适配 */
|
||||
html[data-theme='dark'] .chatContainer {
|
||||
background-color: #2b3a56;
|
||||
color: #eee;
|
||||
border: 1px solid #3e4c66;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .chatHeader {
|
||||
background-color: var(--ifm-color-primary);
|
||||
border-bottom: 1px solid #3e4c66;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .chatHeader h3 {
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .closeButton {
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .inputContainer {
|
||||
background-color: #2b3a56;
|
||||
border-top: 1px solid #3e4c66;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .chatInput {
|
||||
background-color: #1b2440;
|
||||
color: #eee;
|
||||
border-color: #3e4c66;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .aiMessage {
|
||||
background-color: #1b2440;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .messageContent code {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .messageContent pre {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .messageContent a {
|
||||
color: #93c5fd;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .messageContent blockquote {
|
||||
border-color: #3e4c66;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .messageContent th,
|
||||
html[data-theme='dark'] .messageContent td {
|
||||
border-color: #3e4c66;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .messageContent th {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media screen and (max-width: 996px) {
|
||||
.chatContainer {
|
||||
width: 300px;
|
||||
bottom: 80px;
|
||||
right: 240px;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import React, { useState } from 'react';
|
||||
import styles from './styles.module.css';
|
||||
import AIChat from '../AIChat';
|
||||
|
||||
export default function AIChatButton() {
|
||||
const [isChatOpen, setIsChatOpen] = useState(false);
|
||||
|
||||
const toggleChat = () => {
|
||||
setIsChatOpen(!isChatOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className={styles.aiChatButton}
|
||||
onClick={toggleChat}
|
||||
aria-label="AI 助手"
|
||||
>
|
||||
<span className={styles.aiIconWrapper}>
|
||||
<svg className={styles.aiIcon} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM12 5C13.66 5 15 6.34 15 8C15 9.66 13.66 11 12 11C10.34 11 9 9.66 9 8C9 6.34 10.34 5 12 5ZM12 19.2C9.5 19.2 7.29 17.92 6 15.98C6.03 13.99 10 12.9 12 12.9C13.99 12.9 17.97 13.99 18 15.98C16.71 17.92 14.5 19.2 12 19.2Z" fill="currentColor"/>
|
||||
</svg>
|
||||
</span>
|
||||
<span className={styles.aiText}>💬 AI 助手</span>
|
||||
</button>
|
||||
{isChatOpen && <AIChat onClose={() => setIsChatOpen(false)} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
.aiChatButton {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 300px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 16px;
|
||||
background-color: #6366f1;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 24px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
z-index: 10000;
|
||||
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4);
|
||||
transition: all 0.2s ease;
|
||||
width: 125px;
|
||||
min-width: 120px;
|
||||
height: 48px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.aiChatButton:hover {
|
||||
background-color: #4f46e5;
|
||||
box-shadow: 0 6px 16px rgba(99, 102, 241, 0.5);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.aiIconWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.aiIcon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.aiText {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 暗色主题适配 */
|
||||
html[data-theme='dark'] .aiChatButton {
|
||||
background-color: #818cf8;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .aiChatButton:hover {
|
||||
background-color: #6366f1;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media screen and (max-width: 996px) {
|
||||
.aiChatButton {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
right: 240px;
|
||||
bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,289 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import styles from './styles.module.css'
|
||||
|
||||
const ExportPDFButton = () => {
|
||||
const [mainContent, setMainContent] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
// 监听路由变化,更新内容
|
||||
const updateContent = () => {
|
||||
const content = document.querySelector('.theme-doc-markdown')
|
||||
if (content) {
|
||||
setMainContent(content)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
updateContent()
|
||||
|
||||
// 创建 MutationObserver 来监听DOM变化
|
||||
const observer = new MutationObserver(mutations => {
|
||||
for (const mutation of mutations) {
|
||||
if (mutation.type === 'childList' || mutation.type === 'subtree') {
|
||||
updateContent()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 监听整个文档的变化
|
||||
observer.observe(document.documentElement, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
})
|
||||
|
||||
return () => observer.disconnect()
|
||||
}, [])
|
||||
|
||||
// 获取当前页面标题并处理成合适的文件名
|
||||
const getFileName = () => {
|
||||
// 获取当前页面标题
|
||||
let title = document.title || 'GitLink帮助文档'
|
||||
|
||||
// 如果标题包含分隔符,只取主要部分
|
||||
if (title.includes('|')) {
|
||||
title = title.split('|')[0].trim()
|
||||
}
|
||||
|
||||
// 移除不适合作为文件名的字符
|
||||
title = title.replace(/[\\/:*?"<>|]/g, '')
|
||||
|
||||
// 如果标题为空,使用默认名称
|
||||
if (!title.trim()) {
|
||||
title = 'GitLink帮助文档'
|
||||
}
|
||||
|
||||
// 添加.pdf后缀
|
||||
return `${title}.pdf`
|
||||
}
|
||||
|
||||
const copyStyles = () => {
|
||||
const styles = []
|
||||
// 获取所有style标签的内容
|
||||
const styleTags = document.getElementsByTagName('style')
|
||||
Array.from(styleTags).forEach(tag => {
|
||||
if (tag.textContent) {
|
||||
styles.push(tag.textContent)
|
||||
}
|
||||
})
|
||||
|
||||
// 获取所有link标签引入的样式
|
||||
const linkTags = document.getElementsByTagName('link')
|
||||
Array.from(linkTags).forEach(link => {
|
||||
if (link.rel === 'stylesheet' && link.href) {
|
||||
try {
|
||||
const xhr = new XMLHttpRequest()
|
||||
xhr.open('GET', link.href, false) // 同步请求
|
||||
xhr.send()
|
||||
if (xhr.status === 200) {
|
||||
styles.push(xhr.responseText)
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('无法加载外部样式表:', link.href)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return styles.join('\n')
|
||||
}
|
||||
|
||||
const processImages = async container => {
|
||||
const images = container.querySelectorAll('img')
|
||||
console.log('找到的图片数量:', images.length)
|
||||
const imagePromises = Array.from(images).map(img => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!img.src) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
|
||||
// 创建新的图片对象以确保加载完成
|
||||
const newImg = new Image()
|
||||
newImg.crossOrigin = 'anonymous'
|
||||
newImg.onload = () => {
|
||||
// 将图片转换为 base64
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = newImg.width
|
||||
canvas.height = newImg.height
|
||||
const ctx = canvas.getContext('2d')
|
||||
ctx.drawImage(newImg, 0, 0)
|
||||
try {
|
||||
const dataUrl = canvas.toDataURL('image/png')
|
||||
img.src = dataUrl
|
||||
} catch (e) {
|
||||
console.error('Image conversion failed:', e)
|
||||
}
|
||||
resolve()
|
||||
}
|
||||
newImg.onerror = () => {
|
||||
console.error('Image load failed:', img.src)
|
||||
resolve()
|
||||
}
|
||||
newImg.src = img.src
|
||||
})
|
||||
})
|
||||
|
||||
await Promise.all(imagePromises)
|
||||
}
|
||||
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
if (!mainContent) {
|
||||
console.error('未找到主要内容容器')
|
||||
alert('未找到可导出的内容,请稍后重试')
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前页面标题
|
||||
const pageTitle = document.title || 'GitLink帮助文档'
|
||||
const fileName = getFileName()
|
||||
|
||||
// 动态导入 html2pdf.js
|
||||
const html2pdf = (await import('html2pdf.js')).default
|
||||
|
||||
// 创建一个临时容器
|
||||
const tempContainer = document.createElement('div')
|
||||
tempContainer.style.cssText = `
|
||||
padding: 2rem;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
background-color: #ffffff;
|
||||
`
|
||||
|
||||
// 添加文档标题
|
||||
const titleDiv = document.createElement('div')
|
||||
titleDiv.style.cssText = `
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid #eee;
|
||||
`
|
||||
const titleH1 = document.createElement('h1')
|
||||
titleH1.textContent = pageTitle
|
||||
titleH1.style.cssText = `
|
||||
font-size: 2rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: var(--ifm-heading-color);
|
||||
`
|
||||
titleDiv.appendChild(titleH1)
|
||||
tempContainer.appendChild(titleDiv)
|
||||
|
||||
// 复制样式
|
||||
const styleElement = document.createElement('style')
|
||||
styleElement.textContent = copyStyles()
|
||||
tempContainer.appendChild(styleElement)
|
||||
|
||||
// 复制内容
|
||||
const contentClone = mainContent.cloneNode(true)
|
||||
|
||||
// 处理图片
|
||||
await processImages(contentClone)
|
||||
|
||||
// 添加主要内容
|
||||
tempContainer.appendChild(contentClone)
|
||||
|
||||
// 添加打印特定的样式
|
||||
const printStyles = document.createElement('style')
|
||||
printStyles.textContent = `
|
||||
@page {
|
||||
margin: 20mm 15mm;
|
||||
size: A4;
|
||||
}
|
||||
@media print {
|
||||
* {
|
||||
-webkit-print-color-adjust: exact !important;
|
||||
color-adjust: exact !important;
|
||||
}
|
||||
img {
|
||||
max-width: 100% !important;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
page-break-after: avoid;
|
||||
break-after: avoid;
|
||||
}
|
||||
table, pre, blockquote, ul, ol {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
p {
|
||||
orphans: 3;
|
||||
widows: 3;
|
||||
}
|
||||
}
|
||||
`
|
||||
tempContainer.appendChild(printStyles)
|
||||
|
||||
// 添加到文档中以确保样式生效
|
||||
document.body.appendChild(tempContainer)
|
||||
|
||||
// 添加加载提示
|
||||
const button = document.querySelector(`.${styles.exportButton}`)
|
||||
const originalText = button.textContent
|
||||
button.textContent = '正在导出...'
|
||||
button.disabled = true
|
||||
|
||||
try {
|
||||
console.log('开始生成PDF...')
|
||||
const worker = html2pdf()
|
||||
.set({
|
||||
margin: 0,
|
||||
filename: fileName,
|
||||
image: { type: 'jpeg', quality: 0.98 },
|
||||
html2canvas: {
|
||||
scale: 2,
|
||||
useCORS: true,
|
||||
allowTaint: true,
|
||||
backgroundColor: '#ffffff',
|
||||
windowWidth: tempContainer.scrollWidth,
|
||||
windowHeight: tempContainer.scrollHeight,
|
||||
logging: true,
|
||||
onclone: function (clonedDoc) {
|
||||
const clonedContainer = clonedDoc.querySelector('div')
|
||||
if (clonedContainer) {
|
||||
// 保持原有的宽度和样式
|
||||
clonedContainer.style.width = '100%'
|
||||
clonedContainer.style.maxWidth = '100%'
|
||||
clonedContainer.style.margin = '0 auto'
|
||||
clonedContainer.style.padding = '2rem'
|
||||
clonedContainer.style.backgroundColor = '#ffffff'
|
||||
}
|
||||
},
|
||||
},
|
||||
jsPDF: {
|
||||
unit: 'mm',
|
||||
format: 'a4',
|
||||
orientation: 'portrait',
|
||||
compress: true,
|
||||
},
|
||||
})
|
||||
.from(tempContainer)
|
||||
.save()
|
||||
|
||||
await worker
|
||||
console.log('PDF生成完成')
|
||||
} finally {
|
||||
// 清理临时容器
|
||||
document.body.removeChild(tempContainer)
|
||||
// 恢复按钮状态
|
||||
button.textContent = originalText
|
||||
button.disabled = false
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('PDF导出失败:', error)
|
||||
alert('PDF导出失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={handleExport}
|
||||
className={styles.exportButton}
|
||||
title='导出为PDF'
|
||||
disabled={!mainContent}
|
||||
>
|
||||
导出PDF
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default ExportPDFButton
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
.exportButton {
|
||||
position: fixed;
|
||||
bottom: 90px;
|
||||
right: 160px;
|
||||
width: auto;
|
||||
height: 48px;
|
||||
border-radius: 24px;
|
||||
padding: 0 20px;
|
||||
background-color: var(--ifm-color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
transition: all 0.3s ease;
|
||||
z-index: 9999;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.exportButton::before {
|
||||
content: "📄";
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.exportButton:hover {
|
||||
background-color: var(--ifm-color-primary-darker);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.exportButton:disabled {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* 暗色主题适配 */
|
||||
html[data-theme='dark'] .exportButton {
|
||||
background-color: var(--ifm-color-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .exportButton:hover {
|
||||
background-color: var(--ifm-color-primary-darker);
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .exportButton:disabled {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 996px) {
|
||||
.exportButton {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
import React, { useState } from 'react';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
const FeedbackWidget = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [feedback, setFeedback] = useState('');
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const [feedbackType, setFeedbackType] = useState('suggestion');
|
||||
|
||||
const toggleWidget = () => {
|
||||
setIsOpen(!isOpen);
|
||||
if (submitted) {
|
||||
setSubmitted(false);
|
||||
setFeedback('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
if (feedback.trim() === '') return;
|
||||
|
||||
// 在真实环境中,这里应该发送反馈到服务器
|
||||
console.log({
|
||||
type: feedbackType,
|
||||
content: feedback,
|
||||
url: window.location.href,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
setSubmitted(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.feedbackContainer}>
|
||||
<button
|
||||
className={styles.feedbackButton}
|
||||
onClick={toggleWidget}
|
||||
title="文档反馈"
|
||||
aria-label="文档反馈"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor">
|
||||
<path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 12h-2v-2h2v2zm0-4h-2V6h2v4z" />
|
||||
</svg>
|
||||
<span className={styles.buttonText}>反馈</span>
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<div className={styles.feedbackWidget}>
|
||||
{!submitted ? (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className={styles.header}>
|
||||
<h3>文档反馈</h3>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.closeButton}
|
||||
onClick={toggleWidget}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={styles.feedbackTypeContainer}>
|
||||
<label className={styles.radioLabel}>
|
||||
<input
|
||||
type="radio"
|
||||
name="feedbackType"
|
||||
value="suggestion"
|
||||
checked={feedbackType === 'suggestion'}
|
||||
onChange={() => setFeedbackType('suggestion')}
|
||||
/>
|
||||
建议
|
||||
</label>
|
||||
<label className={styles.radioLabel}>
|
||||
<input
|
||||
type="radio"
|
||||
name="feedbackType"
|
||||
value="error"
|
||||
checked={feedbackType === 'error'}
|
||||
onChange={() => setFeedbackType('error')}
|
||||
/>
|
||||
文档错误
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
className={styles.feedbackInput}
|
||||
value={feedback}
|
||||
onChange={(e) => setFeedback(e.target.value)}
|
||||
placeholder="请输入您的反馈..."
|
||||
rows="4"
|
||||
required
|
||||
/>
|
||||
|
||||
<button type="submit" className={styles.submitButton}>
|
||||
提交反馈
|
||||
</button>
|
||||
</form>
|
||||
) : (
|
||||
<div className={styles.successMessage}>
|
||||
<div className={styles.header}>
|
||||
<h3>感谢您的反馈</h3>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.closeButton}
|
||||
onClick={toggleWidget}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<p>您的反馈已成功提交,我们会认真考虑您的建议。</p>
|
||||
<button
|
||||
className={styles.submitButton}
|
||||
onClick={toggleWidget}
|
||||
>
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeedbackWidget;
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
.feedbackContainer {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 160px;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.feedbackButton {
|
||||
width: 125px;
|
||||
min-width: 120px;
|
||||
height: 48px;
|
||||
border-radius: 24px;
|
||||
padding: 0 20px;
|
||||
background-color: var(--ifm-color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
transition: background-color 0.3s, transform 0.2s;
|
||||
}
|
||||
|
||||
.buttonText {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.feedbackButton:hover {
|
||||
background-color: var(--ifm-color-primary-dark);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.feedbackWidget {
|
||||
position: absolute;
|
||||
bottom: 60px;
|
||||
right: 0;
|
||||
width: 320px;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2);
|
||||
overflow: hidden;
|
||||
animation: slideIn 0.3s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #eee;
|
||||
background-color: var(--ifm-color-primary-lighter);
|
||||
}
|
||||
|
||||
.header h3 {
|
||||
margin: 0;
|
||||
color: var(--ifm-color-primary-darkest);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
color: var(--ifm-color-primary-darkest);
|
||||
}
|
||||
|
||||
.feedbackTypeContainer {
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.radioLabel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.feedbackInput {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: none;
|
||||
border-top: 1px solid #eee;
|
||||
border-bottom: 1px solid #eee;
|
||||
resize: none;
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.feedbackInput:focus {
|
||||
outline: none;
|
||||
border-color: var(--ifm-color-primary);
|
||||
}
|
||||
|
||||
.submitButton {
|
||||
display: block;
|
||||
width: calc(100% - 32px);
|
||||
margin: 12px 16px;
|
||||
padding: 8px 0;
|
||||
background-color: var(--ifm-color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.submitButton:hover {
|
||||
background-color: var(--ifm-color-primary-dark);
|
||||
}
|
||||
|
||||
.successMessage {
|
||||
padding: 0 0 16px;
|
||||
}
|
||||
|
||||
.successMessage p {
|
||||
padding: 16px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.feedbackWidget {
|
||||
width: 280px;
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .feedbackWidget {
|
||||
background-color: #2b3a56;
|
||||
color: #eee;
|
||||
border: 1px solid #3e4c66;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .header {
|
||||
background-color: #1b2440;
|
||||
border-bottom: 1px solid #3e4c66;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .header h3 {
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .closeButton {
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .feedbackInput {
|
||||
background-color: #2b3a56;
|
||||
color: #eee;
|
||||
border-color: #3e4c66;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] .successMessage p {
|
||||
color: #ddd;
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import styles from './styles.module.css'
|
||||
|
||||
const ScrollButtons = () => {
|
||||
const [showTopButton, setShowTopButton] = useState(false)
|
||||
const [showBottomButton, setShowBottomButton] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop
|
||||
const scrollHeight = document.documentElement.scrollHeight
|
||||
const clientHeight = document.documentElement.clientHeight
|
||||
|
||||
// 当页面滚动超过 300px 时显示回到顶部按钮
|
||||
setShowTopButton(scrollTop > 300)
|
||||
|
||||
// 当距离底部小于 50px 时隐藏跳转到底部按钮
|
||||
const isNearBottom = scrollHeight - (scrollTop + clientHeight) < 50
|
||||
setShowBottomButton(!isNearBottom)
|
||||
}
|
||||
|
||||
// 初始化时检查一次
|
||||
handleScroll()
|
||||
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
return () => window.removeEventListener('scroll', handleScroll)
|
||||
}, [])
|
||||
|
||||
const scrollToTop = () => {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
|
||||
const scrollToBottom = () => {
|
||||
window.scrollTo({
|
||||
top: document.documentElement.scrollHeight,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.scrollButtonsContainer}>
|
||||
{showTopButton && (
|
||||
<button
|
||||
className={`${styles.scrollButton} ${styles.scrollToTop}`}
|
||||
onClick={scrollToTop}
|
||||
title='回到顶部'
|
||||
aria-label='回到顶部'
|
||||
>
|
||||
<svg viewBox='0 0 24 24' width='24' height='24' fill='currentColor'>
|
||||
<path d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z' />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
{showBottomButton && (
|
||||
<button
|
||||
className={`${styles.scrollButton} ${styles.scrollToBottom}`}
|
||||
onClick={scrollToBottom}
|
||||
title='跳转到底部'
|
||||
aria-label='跳转到底部'
|
||||
>
|
||||
<svg viewBox='0 0 24 24' width='24' height='24' fill='currentColor'>
|
||||
<path d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z' />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ScrollButtons
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
.scrollButtonsContainer {
|
||||
position: fixed;
|
||||
right: 110px;
|
||||
bottom: 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.scrollButton {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--ifm-color-primary);
|
||||
border: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.scrollButton:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
background-color: var(--ifm-color-primary-darker);
|
||||
}
|
||||
|
||||
.scrollToTop {
|
||||
opacity: 0;
|
||||
animation: fadeIn 0.3s ease forwards;
|
||||
}
|
||||
|
||||
.scrollToTop svg {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.scrollToTop:hover svg {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.scrollToBottom {
|
||||
opacity: 1;
|
||||
animation: fadeIn 0.3s ease forwards;
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
.scrollToBottom svg {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.scrollToBottom:hover svg {
|
||||
transform: translateY(2px);
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import React, { useEffect } from 'react'
|
||||
import { useLocation } from '@docusaurus/router'
|
||||
|
||||
const ScrollMemory = () => {
|
||||
const location = useLocation()
|
||||
|
||||
useEffect(() => {
|
||||
// 当路由变化时,尝试恢复滚动位置
|
||||
const savedPosition = localStorage.getItem(`scroll_${location.pathname}`)
|
||||
if (savedPosition) {
|
||||
window.scrollTo({
|
||||
top: parseInt(savedPosition, 10),
|
||||
behavior: 'instant',
|
||||
})
|
||||
}
|
||||
|
||||
// 监听滚动事件,保存滚动位置
|
||||
const handleScroll = () => {
|
||||
const currentPosition =
|
||||
window.pageYOffset || document.documentElement.scrollTop
|
||||
localStorage.setItem(
|
||||
`scroll_${location.pathname}`,
|
||||
currentPosition.toString()
|
||||
)
|
||||
}
|
||||
|
||||
// 添加滚动事件监听器
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
|
||||
// 组件卸载时移除事件监听器
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}, [location.pathname])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export default ScrollMemory
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import React from 'react'
|
||||
import styles from './styles.module.css'
|
||||
|
||||
const ScrollToBottom = () => {
|
||||
const scrollToBottom = () => {
|
||||
window.scrollTo({
|
||||
top: document.documentElement.scrollHeight,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className={styles.scrollToBottom}
|
||||
onClick={scrollToBottom}
|
||||
title='跳转到底部'
|
||||
aria-label='跳转到底部'
|
||||
>
|
||||
<svg viewBox='0 0 24 24' width='24' height='24' fill='currentColor'>
|
||||
<path d='M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z' />
|
||||
</svg>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default ScrollToBottom
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
.scrollToBottom {
|
||||
position: fixed;
|
||||
right: 80px;
|
||||
bottom: 30px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--ifm-color-primary);
|
||||
border: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
z-index: 100;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.scrollToBottom:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
background-color: var(--ifm-color-primary-darker);
|
||||
}
|
||||
|
||||
.scrollToBottom svg {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.scrollToBottom:hover svg {
|
||||
transform: translateY(2px);
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react'
|
||||
import OriginalLayout from '@theme-original/Layout'
|
||||
import ExportPDFButton from '@site/src/components/ExportPDFButton'
|
||||
import ScrollButtons from '@site/src/components/ScrollButtons'
|
||||
import FeedbackWidget from '@site/src/components/FeedbackWidget'
|
||||
import AIChatButton from '@site/src/components/AIChatButton'
|
||||
import ScrollMemory from '../../components/ScrollMemory'
|
||||
import { useLocation } from '@docusaurus/router'
|
||||
|
||||
export default function Layout (props) {
|
||||
const location = useLocation()
|
||||
const showExportButton = !location.pathname.includes('search')
|
||||
|
||||
return (
|
||||
<>
|
||||
<OriginalLayout {...props} />
|
||||
<ScrollMemory />
|
||||
{showExportButton && (
|
||||
<>
|
||||
<ExportPDFButton fileName={`${props.title || 'document'}.pdf`} />
|
||||
<ScrollButtons />
|
||||
<FeedbackWidget />
|
||||
<AIChatButton />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -62,13 +62,40 @@ GitLink(确实开源)是CCF官方指定的开源创新服务平台,旨在
|
|||
<article class="col col--6 margin-bottom--lg">
|
||||
<a class="card padding--lg cardContainer" href="/Bot市场/bot安装">
|
||||
<h2 class="text--truncate cardTitle" title="Bot市场">Bot市场</h2>
|
||||
<p>Bot市场使用及设置[4个文档]</p>
|
||||
<p>Bot市场使用及设置[3个文档]</p>
|
||||
</a></article>
|
||||
<article class="col col--6 margin-bottom--lg">
|
||||
<a class="card padding--lg cardContainer" href="/第三方服务/跨平台代码同步">
|
||||
<h2 class="text--truncate cardTitle" title="第三方服务">第三方服务</h2>
|
||||
<p>第三方服务使用及设置[3个文档]</p>
|
||||
</a></article>
|
||||
<article class="col col--6 margin-bottom--lg">
|
||||
<a class="card padding--lg cardContainer" href="/通知/通知简介">
|
||||
<h2 class="text--truncate cardTitle" title="通知">通知</h2>
|
||||
<p>查看GitLink通知信息[2个文档]</p>
|
||||
</a>
|
||||
</article>
|
||||
|
||||
<article class="col col--6 margin-bottom--lg">
|
||||
<a class="card padding--lg cardContainer" href="/个人主页建站/站点创建流程">
|
||||
<h2 class="text--truncate cardTitle" title="个人主页建站">个人主页建站</h2>
|
||||
<p>轻松创建属于您的站点[2个文档]</p>
|
||||
</a>
|
||||
</article>
|
||||
|
||||
<article class="col col--6 margin-bottom--lg">
|
||||
<a class="card padding--lg cardContainer" href="/平台公告">
|
||||
<h2 class="text--truncate cardTitle" title="平台公告">平台公告</h2>
|
||||
<p>获取最新的平台公告[1个文档]</p>
|
||||
</a>
|
||||
</article>
|
||||
|
||||
<article class="col col--6 margin-bottom--lg">
|
||||
<a class="card padding--lg cardContainer" href="/服务协议/GitLink服务协议">
|
||||
<h2 class="text--truncate cardTitle" title="服务协议">服务协议</h2>
|
||||
<p>了解并遵守GitLink的服务条款[1个文档]</p>
|
||||
</a>
|
||||
</article>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,4 +1,11 @@
|
|||
---
|
||||
sidebar_label: '代码提交'
|
||||
sidebar_position: 3
|
||||
---
|
||||
---
|
||||
### **1. 代码提交入口**
|
||||
用户可以通过项目代码库中间的文件下的"上传文件"按键来上传文件。
|
||||
<br/>
|
||||
|
||||
### **2. 上传文件**
|
||||
进入上传文件页面后,如下图所示,拖动文件,选择提交变更的分支的单击"创建项目"按键即可完成创建。
|
||||
<br/>
|
||||
|
|
@ -1,4 +1,13 @@
|
|||
---
|
||||
sidebar_label: '分支管理'
|
||||
sidebar_position: 5
|
||||
---
|
||||
---
|
||||
### **1. 查看分支**
|
||||
在代码库栏下,如下图所示,用户可以点击代码库分支从而查看当前项目的所有分支,并且选择对其进行操作。
|
||||
<br/>
|
||||
|
||||
### **2. 合并分支**
|
||||
在“和并请求(PR)”下点击“新建合并请求”后,
|
||||
<br/>
|
||||
如下图所示,用户可以选择源分支与目标分支以及填写分支信息后提交分支合并请求。
|
||||
<br/>
|
||||
|
|
@ -1,4 +1,19 @@
|
|||
---
|
||||
sidebar_label: '文件管理'
|
||||
sidebar_position: 4
|
||||
---
|
||||
---
|
||||
### **1. 文件管理位置**
|
||||
在代码库界面,可直接对代码库中该分支下的文件进行操作,如下图所示。
|
||||
<br/>
|
||||
|
||||
### **2. 进入目录**
|
||||
点击进入相关目录,找到要编辑的文件,如下图所示。
|
||||
<br/>
|
||||
|
||||
### **3. 进入文件编辑页面**
|
||||
在线编辑文件,单击文件,即可进入编辑文件页面,如下图所示。
|
||||
<br/>
|
||||
|
||||
### **4. 编辑文件**
|
||||
编辑界面右侧有三个按钮,分别是“下载”、“编辑”和“删除”,点击下载即可将文件下载到本地,编辑则可在线编辑文档,删除则将文件从代码库中删除,按钮位置如下图所示。
|
||||
<br/>
|
||||
|
|
@ -3,4 +3,11 @@ sidebar_label: '代码评审'
|
|||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# 代码评审
|
||||
# 代码评审
|
||||
### **1. 进入代码评审**
|
||||
如下图所示,点击“代码评审”按钮可以进入代码评审
|
||||
<br/>
|
||||
|
||||
### **2. 代码评审界面**
|
||||
如下图所示,用户可以在该界面下查看该PR操作要合并的新的代码,并对其进行评审。
|
||||
<br/>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,92 @@
|
|||
---
|
||||
sidebar_label: '合并请求关联疑修'
|
||||
sidebar_position: 5
|
||||
sidebar_label: '合并请求关联疑修'
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# 合并请求关联疑修
|
||||
### **1. 疑修关联概述**
|
||||
|
||||
合并请求(Merge Request)可以与疑修(Issue)关联,这有助于:
|
||||
|
||||
- 追踪解决问题的进度
|
||||
- 自动更新疑修状态
|
||||
- 建立代码变更和问题之间的关系
|
||||
- 方便团队协作和代码审查
|
||||
|
||||
### **2. 关联疑修的方法**
|
||||
|
||||
#### 2.1 创建合并请求时关联
|
||||
|
||||
1. 在创建合并请求时,在描述框中使用关键字引用疑修:
|
||||
- 使用 `#` 后跟疑修编号,如 `#123`
|
||||
- 使用关键字:`fixes`、`closes`、`resolves` 等
|
||||
|
||||
#### 2.2 在已有合并请求中关联
|
||||
|
||||
1. 打开合并请求详情页
|
||||
2. 在评论框中引用疑修编号
|
||||
3. 或使用右侧面板的"关联疑修"功能
|
||||
|
||||
### **3. 关联关键字**
|
||||
|
||||
支持以下关键字自动关联疑修:
|
||||
|
||||
- `fixes #ID` 或 `fixed #ID`
|
||||
- `closes #ID` 或 `closed #ID`
|
||||
- `resolves #ID` 或 `resolved #ID`
|
||||
- `implements #ID` 或 `implemented #ID`
|
||||
|
||||
注:合并请求被合并后,使用这些关键字关联的疑修会自动关闭。
|
||||
|
||||
### **4. 查看关联状态**
|
||||
|
||||
在合并请求页面可以:
|
||||
|
||||
1. 查看已关联的疑修列表
|
||||
2. 查看疑修的当前状态
|
||||
3. 快速跳转到相关疑修
|
||||
4. 查看关联历史记录
|
||||
|
||||
### **5. 取消关联**
|
||||
|
||||
如需取消关联,可以:
|
||||
|
||||
1. 删除描述中的关联引用
|
||||
2. 使用右侧面板的取消关联功能
|
||||
3. 在评论中说明取消关联的原因
|
||||
|
||||
### **6. 批量关联**
|
||||
|
||||
对于需要批量关联的情况:
|
||||
|
||||
1. 在描述中列出多个疑修编号
|
||||
2. 使用逗号分隔多个编号
|
||||
3. 可以使用范围表示法,如 `#123-#128`
|
||||
|
||||
### **7. 跨项目关联**
|
||||
|
||||
支持关联不同项目的疑修:
|
||||
|
||||
1. 使用完整引用格式:`组织/项目#ID`
|
||||
2. 例如:`gitlink/main#123`
|
||||
|
||||
### **8. 最佳实践**
|
||||
|
||||
1. 及时关联相关疑修
|
||||
2. 在描述中清晰说明解决方案
|
||||
3. 使用适当的关键字
|
||||
4. 保持关联关系的准确性
|
||||
5. 定期检查关联状态
|
||||
|
||||
### **9. 注意事项**
|
||||
|
||||
- 关联后的自动化操作不可撤销
|
||||
- 确保有权限访问被关联的疑修
|
||||
- 关联前检查疑修状态
|
||||
- 避免重复关联
|
||||
- 保持疑修描述的更新
|
||||
|
||||
### **10. 权限要求**
|
||||
|
||||
- 需要有合并请求的写入权限
|
||||
- 需要有被关联疑修的读取权限
|
||||
- 管理员可以管理所有关联关系
|
||||
|
|
|
|||
|
|
@ -1,4 +1,69 @@
|
|||
---
|
||||
sidebar_label: '平台公告'
|
||||
sidebar_position: 1
|
||||
---
|
||||
sidebar_label: '平台公告'
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
### **1. 查看平台公告**
|
||||
|
||||
在 GitLink 首页,您可以在顶部导航栏看到"平台公告"入口,点击即可查看所有平台公告。公告按发布时间倒序排列,最新的公告会显示在最前面。
|
||||
|
||||
### **2. 公告分类**
|
||||
|
||||
平台公告分为以下几类:
|
||||
|
||||
- 系统更新公告:介绍平台新功能、性能优化等更新信息
|
||||
- 维护通知:平台维护、升级等相关通知
|
||||
- 活动通知:平台举办的各类活动信息
|
||||
- 重要通知:平台重要政策变更、安全提醒等信息
|
||||
|
||||
### **3. 公告详情**
|
||||
|
||||
点击公告标题可以查看公告详情,包括:
|
||||
|
||||
- 发布时间
|
||||
- 公告类型
|
||||
- 公告内容
|
||||
|
||||
### **4. 公告订阅**
|
||||
|
||||
您可以通过以下方式订阅平台公告:
|
||||
|
||||
1. 邮件订阅:在个人设置中开启邮件通知
|
||||
2. 站内信:平台会自动向所有用户推送重要公告
|
||||
3. RSS 订阅:支持通过 RSS 阅读器订阅平台公告
|
||||
|
||||
### **5. 历史公告查询**
|
||||
|
||||
在平台公告页面:
|
||||
|
||||
1. 使用搜索框搜索特定公告
|
||||
2. 使用筛选器按公告类型、时间范围筛选
|
||||
3. 查看归档的历史公告
|
||||
|
||||
### **6. 管理员功能**
|
||||
|
||||
对于平台管理员:
|
||||
|
||||
1. 发布新公告
|
||||
- 选择公告类型
|
||||
- 编写公告内容
|
||||
- 上传相关附件
|
||||
- 设置发布时间
|
||||
2. 编辑已发布的公告
|
||||
3. 管理公告置顶状态
|
||||
4. 删除过期公告
|
||||
|
||||
### **7. 公告推送设置**
|
||||
|
||||
在个人设置中,您可以自定义公告通知方式:
|
||||
|
||||
1. 选择接收的公告类型
|
||||
2. 设置通知方式(邮件、站内信)
|
||||
3. 设置通知频率
|
||||
|
||||
### **8. 注意事项**
|
||||
|
||||
- 重要公告会在首页显著位置展示
|
||||
- 系统会自动归档超过 3 个月的非重要公告
|
||||
- 建议定期查看平台公告,及时了解平台动态
|
||||
- 可以对公告进行评论和反馈
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ sidebar_position: 2
|
|||
|
||||

|
||||
|
||||
3. 此外,在创建疑修时,可以通过符号 **`#`** 快速添加需要引用的疑修,进而为当前疑修提供辅助的信息;如下图所示,键入 **`#`** 后会弹出可引用的疑修列表,通过鼠标下滑或者键盘输入疑修编号选择需要引用的疑修后,会自动添加引用疑修的链接🔗
|
||||
3. 此外,在创建疑修时,可以通过符号 **`#`** 快速添加需要引用的疑修,进而为当前疑修提供辅助的信息;如下图所示,键入 **`#`** 后会弹出可引用的疑修列表,通过鼠标下滑或者键盘输入疑修编号选择需要引用的疑修后,则会自动添加引用疑修的链接🔗
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
|
|
@ -16,16 +16,16 @@ sidebar_position: 2
|
|||
|
||||

|
||||
|
||||
**注**:只能使用以字母、数字开头,包含字母、数字、下划线、横杠等,长度4到20个字符
|
||||
**注**:只能使用以字母、数字开头,包含字母、数字、下划线、横杠等,长度4到20个字符
|
||||
|
||||
## 组织名称与组织描述
|
||||
|
||||

|
||||
|
||||
**注**:此处为必填项,不得为空
|
||||
**注**:此处为必填项,不得为空
|
||||
|
||||
## 可见性
|
||||
|
||||

|
||||
|
||||
**注**:可见性预设三类组织:公开、受限(仅对登录用户可见)、私有(仅对组织成员可见)。
|
||||
**注**:可见性预设三类组织:公开、受限(仅对登录用户可见)、私有(仅对组织成员可见)。
|
||||
|
|
|
|||
|
|
@ -9,26 +9,26 @@ sidebar_position: 3
|
|||
|
||||
在团队新建页面,输入团队标识、团队名称、团队描述、项目权限以及版本库权限等信息后,点击“新建团队”完成团队的创建。
|
||||
|
||||

|
||||

|
||||
|
||||
## 查看组织团队
|
||||
|
||||
点击组织信息页面中的某个团队名称可以查看该团队的详细信息,该页面包括团队的名称、描述等信息,此外还会列出该团队关联的成员以及项目。
|
||||
|
||||

|
||||

|
||||
|
||||
## 管理组织团队
|
||||
|
||||
点击团队信息页面中的“团队设置”按钮可以对团队进行管理
|
||||
|
||||
- 基本设置:修改项目的基本信息,如名称和描述等。
|
||||

|
||||

|
||||
|
||||
- 团队成员管理:为该团队添加新成员或者移除已有成员。
|
||||

|
||||

|
||||
|
||||
- 团队项目管理:为该团队关联新项目(该组织已经创建的项目)或者移除已关联项目。
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,21 @@
|
|||
---
|
||||
sidebar_label: '组织成员管理'
|
||||
sidebar_position: 4
|
||||
---
|
||||
---
|
||||
# 成员管理(Members Management)
|
||||
|
||||
在 个人所管理的项目当中的**仓库设置**当中的**成员管理**可以进入成员管理界面
|
||||
|
||||

|
||||
|
||||
## 成员查询和添加
|
||||
在成员管理界面当中,可以进行组织成员的查询和添加
|
||||
|
||||

|
||||
|
||||
## 成员邀请权限设置
|
||||
管理员可以对邀请成员的权限进行设置
|
||||

|
||||
此外,也可以复制邀请链接的方式来更方便地邀请成员加入到项目当中来
|
||||
|
||||
**注**:此项功能仅对管理员可见
|
||||
|
|
@ -1,4 +1,23 @@
|
|||
---
|
||||
sidebar_label: '模板导入及导出'
|
||||
sidebar_position: 2
|
||||
---
|
||||
---
|
||||
<<<<<<< HEAD
|
||||
|
||||
# 确实开源帮助中心
|
||||
|
||||
## 模板的导入及导出管理
|
||||
#### 1.模板导入
|
||||
通过点击wiki界面的**导入模板**按钮即可导入本地的txt,markdown等格式文件进行Wiki文档的创建,当前平台此项功能尚在开发测试环节
|
||||

|
||||
#### 2.模板导出
|
||||
当前平台此项功能尚在开发测试环节
|
||||
<br/>
|
||||
|
||||
=======
|
||||
### **1.模板导入**
|
||||
通过点击wiki界面的导入模板按钮即可导入本地的txt,markdown等格式文件进行Wiki文档的创建,当前平台此项功能尚在开发测试环节
|
||||
|
||||
### **2.模板导出**
|
||||
此功能并未实现,没有相关按钮
|
||||
>>>>>>> cd8052206559d3aa0fb8f0f0d6294f8ce3d0db85
|
||||
|
|
|
|||
|
|
@ -1,4 +1,43 @@
|
|||
---
|
||||
sidebar_label: '维基页面管理'
|
||||
sidebar_position: 1
|
||||
---
|
||||
---
|
||||
<<<<<<< HEAD
|
||||
|
||||
|
||||
# 确实开源帮助中心
|
||||
|
||||
## 维基界面管理
|
||||
#### 1.维基界面
|
||||
GitLink项目安排了专门的**维基(Wiki)界面**来展示项目的一些情况
|
||||

|
||||
|
||||
#### 2.创建维基内容
|
||||
在初始化的维基界面当中点击**创建Wiki文档**或者**导入模板**即可进行Wiki文档的编辑
|
||||
<br/>
|
||||
|
||||
#### 3.进行Wiki文档的编辑
|
||||
点击**创建Wiki文档**后即跳转到编辑界面,初始内容为空白的文档
|
||||

|
||||
也可以通过勾选*添加模板*来进行模板的导入,平台提供的模板包括*周报*和*月报*的格式
|
||||

|
||||
完成编辑后点击左下角的的保存即可
|
||||
=======
|
||||
### **1. 维基界面**
|
||||
GitLink项目安排了专门的维基(Wiki)界面来展示项目的一些情况
|
||||
|
||||
### **2.创建维基内容**
|
||||
在初始化的维基界面当中点击创建Wiki文档或者导入模板即可进行Wiki文档的编辑
|
||||
|
||||
### **3.进行Wiki文档的编辑**
|
||||
点击创建Wiki文档后即跳转到编辑界面,初始内容为空白的文档
|
||||
|
||||
也可以通过勾选添加模板来进行模板的导入,平台提供的模板包括周报和月报的格式
|
||||
|
||||
完成编辑后点击左下角的的保存即可
|
||||
|
||||
### **4.进行Wiki文档的导出**
|
||||
点击导出后,有导出为markdown、html、pdf三种选项
|
||||
|
||||
选择对应选项即可完成导出
|
||||
>>>>>>> cd8052206559d3aa0fb8f0f0d6294f8ce3d0db85
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in New Issue