Compare commits
2 Commits
01cdb71903
...
344a0d1439
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
344a0d1439 | ||
|
|
05f6a07d26 |
@@ -3049,11 +3049,9 @@ def ensure_export_dir(task_id: str) -> str:
|
|||||||
|
|
||||||
def generate_export_file_name(task_name: str, export_type: str) -> str:
|
def generate_export_file_name(task_name: str, export_type: str) -> str:
|
||||||
"""生成导出文件名"""
|
"""生成导出文件名"""
|
||||||
from datetime import datetime
|
|
||||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
||||||
# 清理文件名中的非法字符
|
# 清理文件名中的非法字符
|
||||||
safe_name = "".join(c for c in task_name if c.isalnum() or c in (' ', '-', '_')).strip()
|
safe_name = "".join(c for c in task_name if c.isalnum() or c in (' ', '-', '_')).strip()
|
||||||
return f"{safe_name}_{export_type}_{timestamp}"
|
return f"{safe_name}_{export_type}"
|
||||||
|
|
||||||
|
|
||||||
@socketio.on('export')
|
@socketio.on('export')
|
||||||
@@ -3243,7 +3241,7 @@ def handle_get_export_list(data):
|
|||||||
|
|
||||||
# ==================== REST API 接口 ====================
|
# ==================== REST API 接口 ====================
|
||||||
|
|
||||||
@app.route('/api/export/<int:record_id>/download', methods=['GET'])
|
@app.route('/export/<int:record_id>/download', methods=['GET'])
|
||||||
def download_export(record_id: int):
|
def download_export(record_id: int):
|
||||||
"""下载导出文件"""
|
"""下载导出文件"""
|
||||||
try:
|
try:
|
||||||
@@ -3270,7 +3268,7 @@ def download_export(record_id: int):
|
|||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/export/<int:record_id>/preview', methods=['GET'])
|
@app.route('/export/<int:record_id>/preview', methods=['GET'])
|
||||||
def preview_export(record_id: int):
|
def preview_export(record_id: int):
|
||||||
"""预览导出文件"""
|
"""预览导出文件"""
|
||||||
try:
|
try:
|
||||||
@@ -3332,7 +3330,7 @@ def preview_export(record_id: int):
|
|||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/export/<int:record_id>/share', methods=['GET'])
|
@app.route('/export/<int:record_id>/share', methods=['GET'])
|
||||||
def share_export(record_id: int):
|
def share_export(record_id: int):
|
||||||
"""生成分享链接"""
|
"""生成分享链接"""
|
||||||
try:
|
try:
|
||||||
@@ -3355,7 +3353,7 @@ def share_export(record_id: int):
|
|||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/export/<int:record_id>/share/info', methods=['GET'])
|
@app.route('/export/<int:record_id>/share/info', methods=['GET'])
|
||||||
def get_share_info(record_id: int):
|
def get_share_info(record_id: int):
|
||||||
"""获取分享文件信息(无需登录验证)"""
|
"""获取分享文件信息(无需登录验证)"""
|
||||||
try:
|
try:
|
||||||
@@ -3407,7 +3405,7 @@ def get_shared_plan_page(share_token: str):
|
|||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/share/<share_token>/check', methods=['GET'])
|
@app.route('/share/<share_token>/check', methods=['GET'])
|
||||||
def check_share_code(share_token: str):
|
def check_share_code(share_token: str):
|
||||||
"""检查分享链接是否需要提取码"""
|
"""检查分享链接是否需要提取码"""
|
||||||
try:
|
try:
|
||||||
@@ -3431,7 +3429,7 @@ def check_share_code(share_token: str):
|
|||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/share/<share_token>', methods=['GET'])
|
@app.route('/share/<share_token>', methods=['GET'])
|
||||||
def get_shared_plan_info(share_token: str):
|
def get_shared_plan_info(share_token: str):
|
||||||
"""获取分享任务详情(API 接口,无需登录验证)"""
|
"""获取分享任务详情(API 接口,无需登录验证)"""
|
||||||
# 获取URL参数中的提取码
|
# 获取URL参数中的提取码
|
||||||
@@ -3473,7 +3471,7 @@ def get_shared_plan_info(share_token: str):
|
|||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/share/import', methods=['POST'])
|
@app.route('/share/import', methods=['POST'])
|
||||||
def import_shared_plan():
|
def import_shared_plan():
|
||||||
"""导入分享的任务到自己的历史记录(HTTP API,无需 WebSocket)"""
|
"""导入分享的任务到自己的历史记录(HTTP API,无需 WebSocket)"""
|
||||||
try:
|
try:
|
||||||
@@ -3537,7 +3535,7 @@ def import_shared_plan():
|
|||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/export/<int:record_id>', methods=['DELETE'])
|
@app.route('/export/<int:record_id>', methods=['DELETE'])
|
||||||
def delete_export(record_id: int):
|
def delete_export(record_id: int):
|
||||||
"""删除导出记录"""
|
"""删除导出记录"""
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -923,41 +923,8 @@ class Api {
|
|||||||
downloadExport = async (recordId: number): Promise<void> => {
|
downloadExport = async (recordId: number): Promise<void> => {
|
||||||
const configStore = useConfigStoreHook()
|
const configStore = useConfigStoreHook()
|
||||||
const baseURL = configStore.config.apiBaseUrl || ''
|
const baseURL = configStore.config.apiBaseUrl || ''
|
||||||
const url = `${baseURL}/api/export/${recordId}/download`
|
// 直接使用 window.location.href 跳转下载,浏览器会自动处理文件名
|
||||||
|
window.location.href = `${baseURL}/export/${recordId}/download`
|
||||||
try {
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('下载失败')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取文件名从 Content-Disposition 头
|
|
||||||
const contentDisposition = response.headers.get('Content-Disposition')
|
|
||||||
let fileName = 'download'
|
|
||||||
if (contentDisposition) {
|
|
||||||
const match = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)
|
|
||||||
if (match) {
|
|
||||||
fileName = match[1].replace(/['"]/g, '')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建 Blob 并下载
|
|
||||||
const blob = await response.blob()
|
|
||||||
const downloadUrl = window.URL.createObjectURL(blob)
|
|
||||||
const link = document.createElement('a')
|
|
||||||
link.href = downloadUrl
|
|
||||||
link.download = fileName
|
|
||||||
document.body.appendChild(link)
|
|
||||||
link.click()
|
|
||||||
document.body.removeChild(link)
|
|
||||||
window.URL.revokeObjectURL(downloadUrl)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('下载失败:', error)
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -971,7 +938,7 @@ class Api {
|
|||||||
}> => {
|
}> => {
|
||||||
const configStore = useConfigStoreHook()
|
const configStore = useConfigStoreHook()
|
||||||
const baseURL = configStore.config.apiBaseUrl || ''
|
const baseURL = configStore.config.apiBaseUrl || ''
|
||||||
const url = `${baseURL}/api/export/${recordId}/preview`
|
const url = `${baseURL}/export/${recordId}/preview`
|
||||||
|
|
||||||
const response = await request<{
|
const response = await request<{
|
||||||
content?: string
|
content?: string
|
||||||
@@ -996,7 +963,7 @@ class Api {
|
|||||||
}> => {
|
}> => {
|
||||||
const configStore = useConfigStoreHook()
|
const configStore = useConfigStoreHook()
|
||||||
const baseURL = configStore.config.apiBaseUrl || ''
|
const baseURL = configStore.config.apiBaseUrl || ''
|
||||||
const url = `${baseURL}/api/export/${recordId}/share`
|
const url = `${baseURL}/export/${recordId}/share`
|
||||||
|
|
||||||
const response = await request<{
|
const response = await request<{
|
||||||
share_url: string
|
share_url: string
|
||||||
@@ -1012,7 +979,7 @@ class Api {
|
|||||||
deleteExport = async (recordId: number): Promise<boolean> => {
|
deleteExport = async (recordId: number): Promise<boolean> => {
|
||||||
const configStore = useConfigStoreHook()
|
const configStore = useConfigStoreHook()
|
||||||
const baseURL = configStore.config.apiBaseUrl || ''
|
const baseURL = configStore.config.apiBaseUrl || ''
|
||||||
const url = `${baseURL}/api/export/${recordId}`
|
const url = `${baseURL}/export/${recordId}`
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await request({
|
await request({
|
||||||
|
|||||||
@@ -244,7 +244,12 @@ async function handleSearch() {
|
|||||||
currentGenerationId.value = response.generation_id || ''
|
currentGenerationId.value = response.generation_id || ''
|
||||||
// 从 response.data 中获取 task_id(REST API 格式)
|
// 从 response.data 中获取 task_id(REST API 格式)
|
||||||
currentTaskID.value = response.data?.task_id || response.task_id || ''
|
currentTaskID.value = response.data?.task_id || response.task_id || ''
|
||||||
console.log('📋 直接格式: generation_id:', currentGenerationId.value, 'TaskID:', currentTaskID.value)
|
console.log(
|
||||||
|
'📋 直接格式: generation_id:',
|
||||||
|
currentGenerationId.value,
|
||||||
|
'TaskID:',
|
||||||
|
currentTaskID.value
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
outlineData = response
|
outlineData = response
|
||||||
currentGenerationId.value = ''
|
currentGenerationId.value = ''
|
||||||
@@ -602,7 +607,7 @@ defineExpose({
|
|||||||
/>
|
/>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<AssignmentButton v-if="planReady" @click="openAgentAllocationDialog" />
|
<!-- <AssignmentButton v-if="planReady" @click="openAgentAllocationDialog" /> -->
|
||||||
<!-- 设置按钮 -->
|
<!-- 设置按钮 -->
|
||||||
<el-button class="setting-button" circle title="设置" @click="openSettingsPanel">
|
<el-button class="setting-button" circle title="设置" @click="openSettingsPanel">
|
||||||
<el-icon size="18px"><Setting /></el-icon>
|
<el-icon size="18px"><Setting /></el-icon>
|
||||||
|
|||||||
@@ -124,7 +124,9 @@ const agentsStore = useAgentsStore()
|
|||||||
<!-- 数据空间 -->
|
<!-- 数据空间 -->
|
||||||
<div class="text-[12px]">
|
<div class="text-[12px]">
|
||||||
<span class="text-[var(--color-text)] font-bold">数据空间</span>
|
<span class="text-[var(--color-text)] font-bold">数据空间</span>
|
||||||
<div class="text-[var(--color-text-secondary)] mt-1">归属于{{ item.Classification }}数据空间</div>
|
<div class="text-[var(--color-text-secondary)] mt-1">
|
||||||
|
归属于{{ item.Classification }}数据空间
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 分割线 -->
|
<!-- 分割线 -->
|
||||||
<div class="h-[1px] w-full bg-[var(--color-border-default)] my-[8px]"></div>
|
<div class="h-[1px] w-full bg-[var(--color-border-default)] my-[8px]"></div>
|
||||||
@@ -158,8 +160,6 @@ const agentsStore = useAgentsStore()
|
|||||||
v-if="index1 !== taskProcess.filter(i => i.AgentName === item.Name).length - 1"
|
v-if="index1 !== taskProcess.filter(i => i.AgentName === item.Name).length - 1"
|
||||||
class="h-[1px] w-full bg-[var(--color-border-default)] my-[8px]"
|
class="h-[1px] w-full bg-[var(--color-border-default)] my-[8px]"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<AssignmentButton />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -80,8 +80,18 @@
|
|||||||
:title="previewData?.file_name || '文件预览'"
|
:title="previewData?.file_name || '文件预览'"
|
||||||
width="80%"
|
width="80%"
|
||||||
:close-on-click-modal="true"
|
:close-on-click-modal="true"
|
||||||
|
:show-close="false"
|
||||||
class="preview-dialog"
|
class="preview-dialog"
|
||||||
>
|
>
|
||||||
|
<!-- 自定义关闭按钮 -->
|
||||||
|
<template #header>
|
||||||
|
<div class="dialog-header">
|
||||||
|
<span class="dialog-title">{{ previewData?.file_name || '文件预览' }}</span>
|
||||||
|
<button class="dialog-close-btn" @click="previewVisible = false">
|
||||||
|
<SvgIcon icon-class="close" size="18px" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<div class="preview-content">
|
<div class="preview-content">
|
||||||
<div v-if="previewLoading" class="preview-loading">
|
<div v-if="previewLoading" class="preview-loading">
|
||||||
<el-icon class="is-loading"><Loading /></el-icon>
|
<el-icon class="is-loading"><Loading /></el-icon>
|
||||||
@@ -1079,8 +1089,44 @@ onMounted(() => {
|
|||||||
|
|
||||||
// 预览弹窗样式
|
// 预览弹窗样式
|
||||||
.preview-dialog {
|
.preview-dialog {
|
||||||
|
.el-dialog__header {
|
||||||
|
padding: 16px 20px 10px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.el-dialog__body {
|
.el-dialog__body {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-close-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--color-text-regular);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ function handleCancel() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 按钮点击不会冒泡到卡片 -->
|
<!-- 按钮点击不会冒泡到卡片 -->
|
||||||
<BranchButton :step="step" />
|
<!-- <BranchButton :step="step" /> -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1732,6 +1732,7 @@ defineExpose({
|
|||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
transform: translateX(-50%) scale(0.95);
|
transform: translateX(-50%) scale(0.95);
|
||||||
@@ -1744,6 +1745,12 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hover 时显示添加按钮
|
||||||
|
.task-node-wrapper:hover .external-add-btn,
|
||||||
|
.root-task-node-wrapper:hover .external-add-btn {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.agent-node-wrapper {
|
.agent-node-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1904,8 +1911,9 @@ defineExpose({
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.2s ease, opacity 0.2s ease;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
// 左侧位置
|
// 左侧位置
|
||||||
&.left {
|
&.left {
|
||||||
@@ -1930,8 +1938,9 @@ defineExpose({
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.2s ease, opacity 0.2s ease;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
opacity: 0;
|
||||||
top: -12px;
|
top: -12px;
|
||||||
right: -12px;
|
right: -12px;
|
||||||
|
|
||||||
@@ -1940,6 +1949,13 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hover 时显示删除按钮
|
||||||
|
.task-node-wrapper:hover .node-delete-btn,
|
||||||
|
.task-node-wrapper:hover .node-delete-btn-single,
|
||||||
|
.root-task-node-wrapper:hover .node-delete-btn {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
// 边样式 - 与PlanModification.vue保持一致
|
// 边样式 - 与PlanModification.vue保持一致
|
||||||
::deep(.vue-flow__edge.selected .vue-flow__edge-path) {
|
::deep(.vue-flow__edge.selected .vue-flow__edge-path) {
|
||||||
stroke: #409eff;
|
stroke: #409eff;
|
||||||
|
|||||||
@@ -356,11 +356,17 @@ const isDeletable = computed(() => props.isDeletable || false)
|
|||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
transform: translateX(-50%) scale(0.95);
|
transform: translateX(-50%) scale(0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hover 时显示
|
||||||
|
.node-wrapper:hover & {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
// 取消按钮样式
|
// 取消按钮样式
|
||||||
&.cancel-btn {
|
&.cancel-btn {
|
||||||
border-color: #f56c6c;
|
border-color: #f56c6c;
|
||||||
@@ -390,8 +396,9 @@ const isDeletable = computed(() => props.isDeletable || false)
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.2s ease, opacity 0.2s ease;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
// 左侧位置
|
// 左侧位置
|
||||||
&.left {
|
&.left {
|
||||||
@@ -410,6 +417,11 @@ const isDeletable = computed(() => props.isDeletable || false)
|
|||||||
background-color: #0c0c0c;
|
background-color: #0c0c0c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 节点容器 hover 时显示删除按钮
|
||||||
|
.node-wrapper:hover .node-delete-btn {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -366,7 +366,7 @@ defineExpose({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<BranchButton v-if="planReady" @click="openPlanModification" />
|
<!-- <BranchButton v-if="planReady" @click="openPlanModification" /> -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ const downloadFile = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
window.location.href = `${apiBaseUrl}/api/export/${parsed.recordId}/download`
|
window.location.href = `${apiBaseUrl}/export/${parsed.recordId}/download`
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('下载失败:', e)
|
console.error('下载失败:', e)
|
||||||
ElMessage.error('下载失败')
|
ElMessage.error('下载失败')
|
||||||
|
|||||||
@@ -50,10 +50,10 @@ export default defineConfig({
|
|||||||
// target: 'http://82.157.183.212:21092',
|
// target: 'http://82.157.183.212:21092',
|
||||||
// target: 'http://82.157.183.212:21097',
|
// target: 'http://82.157.183.212:21097',
|
||||||
target: 'http://localhost:8000',
|
target: 'http://localhost:8000',
|
||||||
// rewrite: (path: string) => path.replace(/^\/api/, ''),
|
rewrite: (path: string) => path.replace(/^\/api/, ''),
|
||||||
// configure: (proxy, options) => {
|
configure: (proxy, options) => {
|
||||||
// console.log('Proxy configured:', options)
|
console.log('Proxy configured:', options)
|
||||||
// },
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user