Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
keyescgm
jadx
提交
9c88f707
J
jadx
项目概览
keyescgm
/
jadx
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
J
jadx
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
9c88f707
编写于
12月 22, 2019
作者:
S
Skylot
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
fix(gui): load file in background thread and show progress indicator
上级
9ab003df
变更
12
隐藏空白更改
内联
并排
Showing
12 changed file
with
318 addition
and
40 deletion
+318
-40
jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java
jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java
+182
-0
jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java
jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java
+6
-1
jadx-gui/src/main/java/jadx/gui/jobs/DecompileJob.java
jadx-gui/src/main/java/jadx/gui/jobs/DecompileJob.java
+3
-1
jadx-gui/src/main/java/jadx/gui/jobs/IBackgroundTask.java
jadx-gui/src/main/java/jadx/gui/jobs/IBackgroundTask.java
+14
-0
jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java
jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java
+3
-1
jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
+26
-15
jadx-gui/src/main/java/jadx/gui/ui/ProgressPanel.java
jadx-gui/src/main/java/jadx/gui/ui/ProgressPanel.java
+58
-20
jadx-gui/src/main/java/jadx/gui/utils/NLS.java
jadx-gui/src/main/java/jadx/gui/utils/NLS.java
+8
-0
jadx-gui/src/main/resources/i18n/Messages_de_DE.properties
jadx-gui/src/main/resources/i18n/Messages_de_DE.properties
+5
-1
jadx-gui/src/main/resources/i18n/Messages_en_US.properties
jadx-gui/src/main/resources/i18n/Messages_en_US.properties
+5
-1
jadx-gui/src/main/resources/i18n/Messages_es_ES.properties
jadx-gui/src/main/resources/i18n/Messages_es_ES.properties
+4
-0
jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties
jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties
+4
-0
未找到文件。
jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java
0 → 100644
浏览文件 @
9c88f707
package
jadx.gui.jobs
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.concurrent.Executors
;
import
java.util.concurrent.Future
;
import
java.util.concurrent.ThreadPoolExecutor
;
import
java.util.concurrent.TimeUnit
;
import
javax.swing.SwingWorker
;
import
org.jetbrains.annotations.Nullable
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
jadx.gui.ui.MainWindow
;
import
jadx.gui.ui.ProgressPanel
;
/**
* Class for run tasks in background with progress bar indication.
* Use instance created in {@link MainWindow}.
*/
public
class
BackgroundExecutor
{
private
static
final
Logger
LOG
=
LoggerFactory
.
getLogger
(
BackgroundExecutor
.
class
);
private
final
MainWindow
mainWindow
;
private
final
ProgressPanel
progressPane
;
private
ThreadPoolExecutor
taskQueueExecutor
;
public
BackgroundExecutor
(
MainWindow
mainWindow
)
{
this
.
mainWindow
=
mainWindow
;
this
.
progressPane
=
mainWindow
.
getProgressPane
();
this
.
taskQueueExecutor
=
makeTaskQueueExecutor
();
}
public
Future
<
Boolean
>
execute
(
IBackgroundTask
task
)
{
TaskWorker
taskWorker
=
new
TaskWorker
(
task
);
taskWorker
.
init
();
taskQueueExecutor
.
execute
(
taskWorker
);
return
taskWorker
;
}
public
void
cancelAll
()
{
try
{
taskQueueExecutor
.
shutdownNow
();
taskQueueExecutor
.
awaitTermination
(
1
,
TimeUnit
.
SECONDS
);
}
catch
(
Exception
e
)
{
LOG
.
error
(
"Error terminating task executor"
,
e
);
}
finally
{
taskQueueExecutor
=
makeTaskQueueExecutor
();
}
}
public
void
execute
(
String
title
,
Runnable
backgroundRunnable
,
Runnable
onFinishUiRunnable
)
{
execute
(
new
SimpleTask
(
title
,
backgroundRunnable
,
onFinishUiRunnable
));
}
public
void
execute
(
String
title
,
Runnable
backgroundRunnable
)
{
execute
(
new
SimpleTask
(
title
,
backgroundRunnable
,
null
));
}
private
ThreadPoolExecutor
makeTaskQueueExecutor
()
{
return
(
ThreadPoolExecutor
)
Executors
.
newFixedThreadPool
(
1
);
}
private
final
class
TaskWorker
extends
SwingWorker
<
Boolean
,
Void
>
{
private
final
IBackgroundTask
task
;
private
long
jobsCount
;
public
TaskWorker
(
IBackgroundTask
task
)
{
this
.
task
=
task
;
}
public
void
init
()
{
addPropertyChangeListener
(
progressPane
);
progressPane
.
reset
();
}
@Override
protected
Boolean
doInBackground
()
throws
Exception
{
progressPane
.
changeLabel
(
this
,
task
.
getTitle
()
+
':'
);
progressPane
.
changeCancelBtnVisible
(
this
,
task
.
canBeCanceled
());
progressPane
.
changeVisibility
(
this
,
true
);
List
<
Runnable
>
jobs
=
task
.
scheduleJobs
();
jobsCount
=
jobs
.
size
();
if
(
jobsCount
==
1
)
{
jobs
.
get
(
0
).
run
();
return
true
;
}
int
threadsCount
=
mainWindow
.
getSettings
().
getThreadsCount
();
if
(
threadsCount
==
1
)
{
return
runInCurrentThread
(
jobs
);
}
return
runInExecutor
(
jobs
,
threadsCount
);
}
private
boolean
runInCurrentThread
(
List
<
Runnable
>
jobs
)
{
int
k
=
0
;
for
(
Runnable
job
:
jobs
)
{
job
.
run
();
k
++;
setProgress
(
calcProgress
(
k
));
if
(
isCancelled
())
{
return
false
;
}
}
return
true
;
}
private
boolean
runInExecutor
(
List
<
Runnable
>
jobs
,
int
threadsCount
)
throws
InterruptedException
{
ThreadPoolExecutor
executor
=
(
ThreadPoolExecutor
)
Executors
.
newFixedThreadPool
(
threadsCount
);
for
(
Runnable
job
:
jobs
)
{
executor
.
execute
(
job
);
}
executor
.
shutdown
();
return
waitTermination
(
executor
);
}
private
boolean
waitTermination
(
ThreadPoolExecutor
executor
)
throws
InterruptedException
{
while
(
true
)
{
if
(
executor
.
isTerminated
())
{
return
true
;
}
if
(
isCancelled
())
{
executor
.
shutdownNow
();
progressPane
.
changeLabel
(
this
,
task
.
getTitle
()
+
" (Canceling):"
);
// force termination
executor
.
awaitTermination
(
5
,
TimeUnit
.
SECONDS
);
return
false
;
}
setProgress
(
calcProgress
(
executor
.
getCompletedTaskCount
()));
Thread
.
sleep
(
500
);
}
}
private
int
calcProgress
(
long
done
)
{
return
Math
.
round
(
done
*
100
/
(
float
)
jobsCount
);
}
@Override
protected
void
done
()
{
progressPane
.
setVisible
(
false
);
task
.
onFinish
();
}
}
private
static
final
class
SimpleTask
implements
IBackgroundTask
{
private
final
String
title
;
private
final
Runnable
runnable
;
private
final
Runnable
onFinish
;
public
SimpleTask
(
String
title
,
Runnable
runnable
,
@Nullable
Runnable
onFinish
)
{
this
.
title
=
title
;
this
.
runnable
=
runnable
;
this
.
onFinish
=
onFinish
;
}
@Override
public
String
getTitle
()
{
return
title
;
}
@Override
public
List
<
Runnable
>
scheduleJobs
()
{
return
Collections
.
singletonList
(
runnable
);
}
@Override
public
boolean
canBeCanceled
()
{
return
false
;
}
@Override
public
void
onFinish
()
{
if
(
onFinish
!=
null
)
{
onFinish
.
run
();
}
}
}
}
jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java
浏览文件 @
9c88f707
...
...
@@ -2,7 +2,9 @@ package jadx.gui.jobs;
import
java.util.concurrent.Future
;
import
javax.swing.*
;
import
javax.swing.JOptionPane
;
import
javax.swing.SwingUtilities
;
import
javax.swing.SwingWorker
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
...
...
@@ -13,6 +15,9 @@ import jadx.gui.utils.NLS;
import
jadx.gui.utils.UiUtils
;
import
jadx.gui.utils.search.TextSearchIndex
;
/**
* Deprecated. Use {@link BackgroundExecutor} instead.
*/
public
class
BackgroundWorker
extends
SwingWorker
<
Void
,
Void
>
{
private
static
final
Logger
LOG
=
LoggerFactory
.
getLogger
(
BackgroundWorker
.
class
);
...
...
jadx-gui/src/main/java/jadx/gui/jobs/DecompileJob.java
浏览文件 @
9c88f707
...
...
@@ -2,6 +2,7 @@ package jadx.gui.jobs;
import
jadx.api.JavaClass
;
import
jadx.gui.JadxWrapper
;
import
jadx.gui.utils.NLS
;
public
class
DecompileJob
extends
BackgroundJob
{
...
...
@@ -9,6 +10,7 @@ public class DecompileJob extends BackgroundJob {
super
(
wrapper
,
threadsCount
);
}
@Override
protected
void
runJob
()
{
for
(
final
JavaClass
cls
:
wrapper
.
getIncludedClasses
())
{
addTask
(
cls:
:
decompile
);
...
...
@@ -17,6 +19,6 @@ public class DecompileJob extends BackgroundJob {
@Override
public
String
getInfoString
()
{
return
"Decompiling: "
;
return
NLS
.
str
(
"progress.decompile"
)
;
}
}
jadx-gui/src/main/java/jadx/gui/jobs/IBackgroundTask.java
0 → 100644
浏览文件 @
9c88f707
package
jadx.gui.jobs
;
import
java.util.List
;
public
interface
IBackgroundTask
{
String
getTitle
();
List
<
Runnable
>
scheduleJobs
();
void
onFinish
();
boolean
canBeCanceled
();
}
jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java
浏览文件 @
9c88f707
...
...
@@ -13,6 +13,7 @@ import jadx.gui.utils.CacheObject;
import
jadx.gui.utils.CodeLinesInfo
;
import
jadx.gui.utils.CodeUsageInfo
;
import
jadx.gui.utils.JNodeCache
;
import
jadx.gui.utils.NLS
;
import
jadx.gui.utils.UiUtils
;
import
jadx.gui.utils.search.StringRef
;
import
jadx.gui.utils.search.TextSearchIndex
;
...
...
@@ -27,6 +28,7 @@ public class IndexJob extends BackgroundJob {
this
.
cache
=
cache
;
}
@Override
protected
void
runJob
()
{
JNodeCache
nodeCache
=
cache
.
getNodeCache
();
final
TextSearchIndex
index
=
new
TextSearchIndex
(
nodeCache
);
...
...
@@ -66,6 +68,6 @@ public class IndexJob extends BackgroundJob {
@Override
public
String
getInfoString
()
{
return
"Indexing: "
;
return
NLS
.
str
(
"progress.index"
)
;
}
}
jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
浏览文件 @
9c88f707
...
...
@@ -78,6 +78,7 @@ import jadx.api.JavaClass;
import
jadx.api.JavaNode
;
import
jadx.api.ResourceFile
;
import
jadx.gui.JadxWrapper
;
import
jadx.gui.jobs.BackgroundExecutor
;
import
jadx.gui.jobs.BackgroundWorker
;
import
jadx.gui.jobs.DecompileJob
;
import
jadx.gui.jobs.IndexJob
;
...
...
@@ -156,6 +157,7 @@ public class MainWindow extends JFrame {
private
transient
Link
updateLink
;
private
transient
ProgressPanel
progressPane
;
private
transient
BackgroundWorker
backgroundWorker
;
private
transient
BackgroundExecutor
backgroundExecutor
;
private
transient
Theme
editorTheme
;
public
MainWindow
(
JadxSettings
settings
)
{
...
...
@@ -172,6 +174,8 @@ public class MainWindow extends JFrame {
loadSettings
();
checkForUpdate
();
newProject
();
this
.
backgroundExecutor
=
new
BackgroundExecutor
(
this
);
}
public
void
init
()
{
...
...
@@ -252,14 +256,6 @@ public class MainWindow extends JFrame {
clearTree
();
}
private
void
clearTree
()
{
tabbedPane
.
closeAllTabs
();
resetCache
();
treeRoot
=
null
;
treeModel
.
setRoot
(
treeRoot
);
treeModel
.
reload
();
}
private
void
saveProject
()
{
if
(
project
.
getProjectPath
()
==
null
)
{
saveProjectAs
();
...
...
@@ -310,13 +306,15 @@ public class MainWindow extends JFrame {
openProject
(
path
);
}
else
{
project
.
setFilePath
(
path
);
tabbedPane
.
closeAllTabs
();
resetCache
();
wrapper
.
openFile
(
path
.
toFile
());
deobfToggleBtn
.
setSelected
(
settings
.
isDeobfuscationOn
());
initTree
();
update
();
runBackgroundJobs
();
clearTree
();
backgroundExecutor
.
execute
(
NLS
.
str
(
"progress.load"
),
()
->
wrapper
.
openFile
(
path
.
toFile
()),
()
->
{
deobfToggleBtn
.
setSelected
(
settings
.
isDeobfuscationOn
());
initTree
();
update
();
runBackgroundJobs
();
});
}
}
...
...
@@ -398,6 +396,7 @@ public class MainWindow extends JFrame {
}
public
synchronized
void
cancelBackgroundJobs
()
{
backgroundExecutor
.
cancelAll
();
if
(
backgroundWorker
!=
null
)
{
backgroundWorker
.
stop
();
backgroundWorker
=
new
BackgroundWorker
(
cacheObject
,
progressPane
);
...
...
@@ -492,6 +491,14 @@ public class MainWindow extends JFrame {
reloadTree
();
}
private
void
clearTree
()
{
tabbedPane
.
closeAllTabs
();
resetCache
();
treeRoot
=
null
;
treeModel
.
setRoot
(
treeRoot
);
treeModel
.
reload
();
}
private
void
reloadTree
()
{
treeReloading
=
true
;
...
...
@@ -1101,6 +1108,10 @@ public class MainWindow extends JFrame {
return
backgroundWorker
;
}
public
ProgressPanel
getProgressPane
()
{
return
progressPane
;
}
private
class
RecentProjectsMenuListener
implements
MenuListener
{
private
final
JMenu
recentProjects
;
...
...
jadx-gui/src/main/java/jadx/gui/ui/ProgressPanel.java
浏览文件 @
9c88f707
package
jadx.gui.ui
;
import
java.awt.
*
;
import
java.awt.
Dimension
;
import
java.beans.PropertyChangeEvent
;
import
java.beans.PropertyChangeListener
;
import
javax.swing.*
;
import
javax.swing.BorderFactory
;
import
javax.swing.BoxLayout
;
import
javax.swing.Icon
;
import
javax.swing.JButton
;
import
javax.swing.JLabel
;
import
javax.swing.JPanel
;
import
javax.swing.JProgressBar
;
import
javax.swing.SwingWorker
;
import
jadx.gui.utils.UiUtils
;
...
...
@@ -16,8 +23,12 @@ public class ProgressPanel extends JPanel implements PropertyChangeListener {
private
final
JProgressBar
progressBar
;
private
final
JLabel
progressLabel
;
private
final
JButton
cancelButton
;
private
final
boolean
showCancelButton
;
public
ProgressPanel
(
final
MainWindow
mainWindow
,
boolean
showCancelButton
)
{
this
.
showCancelButton
=
showCancelButton
;
progressLabel
=
new
JLabel
();
progressBar
=
new
JProgressBar
(
0
,
100
);
progressBar
.
setIndeterminate
(
true
);
...
...
@@ -30,28 +41,47 @@ public class ProgressPanel extends JPanel implements PropertyChangeListener {
add
(
progressLabel
);
add
(
progressBar
);
if
(
showCancelButton
)
{
JButton
cancelButton
=
new
JButton
(
ICON_CANCEL
);
cancelButton
.
setPreferredSize
(
new
Dimension
(
ICON_CANCEL
.
getIconWidth
(),
ICON_CANCEL
.
getIconHeight
()));
cancelButton
.
setToolTipText
(
"Cancel background jobs"
);
cancelButton
.
setBorderPainted
(
false
);
cancelButton
.
setFocusPainted
(
false
);
cancelButton
.
setContentAreaFilled
(
false
);
cancelButton
.
addActionListener
(
e
->
mainWindow
.
cancelBackgroundJobs
());
add
(
cancelButton
);
}
cancelButton
=
new
JButton
(
ICON_CANCEL
);
cancelButton
.
setPreferredSize
(
new
Dimension
(
ICON_CANCEL
.
getIconWidth
(),
ICON_CANCEL
.
getIconHeight
()));
cancelButton
.
setToolTipText
(
"Cancel background jobs"
);
cancelButton
.
setBorderPainted
(
false
);
cancelButton
.
setFocusPainted
(
false
);
cancelButton
.
setContentAreaFilled
(
false
);
cancelButton
.
addActionListener
(
e
->
mainWindow
.
cancelBackgroundJobs
());
cancelButton
.
setVisible
(
showCancelButton
);
add
(
cancelButton
);
}
public
void
reset
()
{
cancelButton
.
setVisible
(
showCancelButton
);
progressBar
.
setIndeterminate
(
true
);
progressBar
.
setValue
(
0
);
progressBar
.
setString
(
""
);
progressBar
.
setStringPainted
(
true
);
}
@Override
public
void
propertyChange
(
PropertyChangeEvent
evt
)
{
if
(
"progress"
.
equals
(
evt
.
getPropertyName
()))
{
int
progress
=
(
Integer
)
evt
.
getNewValue
();
progressBar
.
setIndeterminate
(
false
);
progressBar
.
setValue
(
progress
);
progressBar
.
setString
(
progress
+
"%"
);
progressBar
.
setStringPainted
(
true
);
}
else
if
(
"label"
.
equals
(
evt
.
getPropertyName
()))
{
setLabel
((
String
)
evt
.
getNewValue
());
switch
(
evt
.
getPropertyName
())
{
case
"progress"
:
int
progress
=
(
Integer
)
evt
.
getNewValue
();
progressBar
.
setIndeterminate
(
false
);
progressBar
.
setValue
(
progress
);
progressBar
.
setString
(
progress
+
"%"
);
progressBar
.
setStringPainted
(
true
);
break
;
case
"label"
:
setLabel
((
String
)
evt
.
getNewValue
());
break
;
case
"visible"
:
setVisible
(((
Boolean
)
evt
.
getNewValue
()));
break
;
case
"cancel-visible"
:
cancelButton
.
setVisible
(((
Boolean
)
evt
.
getNewValue
()));
break
;
}
}
...
...
@@ -66,4 +96,12 @@ public class ProgressPanel extends JPanel implements PropertyChangeListener {
public
void
changeLabel
(
SwingWorker
<?,
?>
task
,
String
label
)
{
task
.
firePropertyChange
(
"label"
,
null
,
label
);
}
public
void
changeVisibility
(
SwingWorker
<?,
?>
task
,
boolean
visible
)
{
task
.
firePropertyChange
(
"visible"
,
null
,
visible
);
}
public
void
changeCancelBtnVisible
(
SwingWorker
<?,
?>
task
,
boolean
visible
)
{
task
.
firePropertyChange
(
"cancel-visible"
,
null
,
visible
);
}
}
jadx-gui/src/main/java/jadx/gui/utils/NLS.java
浏览文件 @
9c88f707
...
...
@@ -62,6 +62,14 @@ public class NLS {
LANG_LOCALES_MAP
.
put
(
locale
,
bundle
);
}
public
static
String
str
(
String
key
)
{
try
{
return
localizedMessagesMap
.
getString
(
key
);
}
catch
(
Exception
e
)
{
return
FALLBACK_MESSAGES_MAP
.
getString
(
key
);
}
}
public
static
String
str
(
String
key
,
Object
...
parameters
)
{
String
value
;
try
{
...
...
jadx-gui/src/main/resources/i18n/Messages_de_DE.properties
浏览文件 @
9c88f707
...
...
@@ -32,6 +32,10 @@ tree.sources_title=Quellcode
tree.resources_title
=
Ressourcen
tree.loading
=
Laden…
progress.load
=
Laden
progress.decompile
=
Decompiling
progress.index
=
Indexing
error_dialog.title
=
Fehler
search.previous
=
Zurück
...
...
@@ -133,7 +137,7 @@ msg.project_error_title=Fehler
msg.project_error
=
Projekt konnte nicht geladen werden
msg.rename_disabled_title
=
Umbenennen deaktiviert
msg.rename_disabled
=
Einige der Umbenennungseinstellungen sind deaktiviert, bitte beachten Sie dies.
msg.cmd_select_class_error
=
Klasse
\n
%s auswählen nicht möglich
\n
Sie existiert nicht.
msg.cmd_select_class_error
=
Klasse
\n
%s auswählen nicht möglich
\n
Sie existiert nicht.
popup.undo
=
Rückgängig
popup.redo
=
Wiederholen
...
...
jadx-gui/src/main/resources/i18n/Messages_en_US.properties
浏览文件 @
9c88f707
...
...
@@ -32,6 +32,10 @@ tree.sources_title=Source code
tree.resources_title
=
Resources
tree.loading
=
Loading...
progress.load
=
Loading
progress.decompile
=
Decompiling
progress.index
=
Indexing
error_dialog.title
=
Error
search.previous
=
Previous
...
...
@@ -133,7 +137,7 @@ msg.project_error_title=Error
msg.project_error
=
Project could not be loaded
msg.rename_disabled_title
=
Rename disabled
msg.rename_disabled
=
Some of rename settings are disabled, please take this into consideration
msg.cmd_select_class_error
=
Failed to select the class
\n
%s
\n
The class does not exist.
msg.cmd_select_class_error
=
Failed to select the class
\n
%s
\n
The class does not exist.
popup.undo
=
Undo
popup.redo
=
Redo
...
...
jadx-gui/src/main/resources/i18n/Messages_es_ES.properties
浏览文件 @
9c88f707
...
...
@@ -32,6 +32,10 @@ tree.sources_title=Código fuente
tree.resources_title
=
Recursos
tree.loading
=
Cargando...
progress.load
=
Cargando
progress.decompile
=
Decompiling
progress.index
=
Indexing
#error_dialog.title=
search.previous
=
Anterior
...
...
jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties
浏览文件 @
9c88f707
...
...
@@ -32,6 +32,10 @@ tree.sources_title=源代码
tree.resources_title
=
资源文件
tree.loading
=
稍等...
progress.load
=
稍等
progress.decompile
=
Decompiling
progress.index
=
Indexing
error_dialog.title
=
错误
search.previous
=
上一个
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录