diff --git a/module/doc/css/view.ui.css b/module/doc/css/view.ui.css
new file mode 100644
index 0000000000..d93df9d96d
--- /dev/null
+++ b/module/doc/css/view.ui.css
@@ -0,0 +1,12 @@
+.panel-heading .title {font-size: 18px; font-weight: 600}
+.panel-heading { justify-content: space-between; }
+.panel-actions { margin-left: auto; }
+#content .user-time {margin-right: 4px; color: #838a9d}
+#content .user-time .icon {margin-right: 4px}
+
+.scrollbar-hover { overflow-y: scroll; }
+#mainContent .panel+.panel {flex: none}
+#history{ position: relative;}
+#history > #closeBtn { position: absolute; right: 10px; top: 10px;}
+
+#content {height: calc(100vh - 300px); overflow-y: auto}
diff --git a/module/doc/js/view.ui.js b/module/doc/js/view.ui.js
new file mode 100644
index 0000000000..e6f7888334
--- /dev/null
+++ b/module/doc/js/view.ui.js
@@ -0,0 +1,102 @@
+/**
+ * Display the document in full screen.
+ *
+ * @access public
+ * @return void
+ */
+window.fullScreen = function()
+{
+ var element = document.getElementById('content');
+ var requestMethod = element.requestFullScreen || element.webkitRequestFullScreen || element.mozRequestFullScreen || element.msRequestFullscreen;
+ if(requestMethod)
+ {
+ var afterEnterFullscreen = function()
+ {
+ $('#content').addClass('scrollbar-hover');
+ $('#content').css('background', '#fff');
+ $.cookie.set('isFullScreen', 1);
+ };
+
+ var whenFailEnterFullscreen = function(error)
+ {
+ $.cookie.set('isFullScreen', 0);
+ };
+
+ try
+ {
+ var result = requestMethod.call(element);
+ if(result && (typeof result.then === 'function' || result instanceof window.Promise))
+ {
+ result.then(afterEnterFullscreen).catch(whenFailEnterFullscreen);
+ }
+ else
+ {
+ afterEnterFullscreen();
+ }
+ }
+ catch (error)
+ {
+ whenFailEnterFullscreen(error);
+ }
+ }
+}
+
+/**
+ * Exit full screen.
+ *
+ * @access public
+ * @return void
+ */
+function exitFullScreen()
+{
+ $('#content').removeClass('scrollbar-hover');
+ $.cookie.set('isFullScreen', 0);
+}
+
+document.addEventListener('fullscreenchange', function (e)
+{
+ if(!document.fullscreenElement) exitFullScreen();
+});
+
+document.addEventListener('webkitfullscreenchange', function (e)
+{
+ if(!document.webkitFullscreenElement) exitFullScreen();
+});
+
+document.addEventListener('mozfullscreenchange', function (e)
+{
+ if(!document.mozFullScreenElement) exitFullScreen();
+});
+
+document.addEventListener('msfullscreenChange', function (e)
+{
+ if(!document.msfullscreenElement) exitFullScreen();
+});
+
+$(function()
+{
+ if($.cookie.get('isFullScreen') == 1) fullScreen();
+
+ /* Update doc content silently on switch doc version, story #40503 */
+ $('.panel').on('click', '#hisTrigger', function()
+ {
+ var $history = $('#history');
+ var $icon = $(this);
+ if($history.hasClass('hidden'))
+ {
+ $history.removeClass('hidden');
+ $icon.addClass('text-primary');
+ }
+ else
+ {
+ $history.addClass('hidden');
+ $icon.removeClass('text-primary');
+ }
+ }).on('click', '#closeBtn', function()
+ {
+ $('#history').addClass('hidden');
+ $('#hisTrigger').removeClass('text-primary');
+ });
+
+ $('#history').append('');
+});
diff --git a/module/doc/ui/view.html.php b/module/doc/ui/view.html.php
new file mode 100644
index 0000000000..adf2d1cb18
--- /dev/null
+++ b/module/doc/ui/view.html.php
@@ -0,0 +1,226 @@
+
+ * @package doc
+ * @link https://www.zentao.net
+ */
+namespace zin;
+
+featureBar
+(
+ li(backBtn(setClass('ghost'), set::icon('back'), $lang->goback)),
+);
+
+if($libID && common::hasPriv('doc', 'create')) include 'createbutton.html.php';
+include './left.html.php';
+
+toolbar
+(
+ $canExport ? item(set(array
+ (
+ 'id' => $exportMethod,
+ 'icon' => 'export',
+ 'class' => 'ghost export',
+ 'text' => $lang->export,
+ 'url' => $createLink('doc', $exportMethod, "libID={$libID}&moduleID={$moduleID}"),
+ 'data-toggle' => 'modal'
+ ))) : null,
+ common::hasPriv('doc', 'createLib') ? item(set(array
+ (
+ 'icon' => 'plus',
+ 'class' => 'btn secondary',
+ 'text' => $lang->doc->createLib,
+ 'url' => createLink('doc', 'createLib', "type={$type}&objectID={$objectID}"),
+ 'data-toggle' => 'modal'
+ ))) : null,
+ $libID && common::hasPriv('doc', 'create') ? $createButton : null
+);
+
+$versionList = array();
+for($itemVersion = $doc->version; $itemVersion > 0; $itemVersion--)
+{
+ $versionList[] = array('text' => "V$itemVersion", 'url' => createLink('doc', 'view', "docID={$docID}&version={$itemVersion}"));
+}
+
+$star = strpos($doc->collector, ',' . $app->user->account . ',') !== false ? 'star' : 'star-empty';
+$collectLink = $this->createLink('doc', 'collect', "objectID=$doc->id&objectType=doc");
+$starBtn = "" . html::image("static/svg/{$star}.svg", "class='$star'") . '';
+
+/* 导入资产库的按钮. */
+if($config->vision == 'rnd' and $config->edition == 'max' and $app->tab == 'project')
+{
+ $canImportToPracticeLib = (common::hasPriv('doc', 'importToPracticeLib') and helper::hasFeature('practicelib'));
+ $canImportToComponentLib = (common::hasPriv('doc', 'importToComponentLib') and helper::hasFeature('componentlib'));
+
+ if($canImportToPracticeLib) $items[] = array('text' => $lang->doc->importToPracticeLib, 'url' => '#importToPracticeLib', 'data-toggle' => 'modal');
+ if($canImportToComponentLib) $items[] = array('text' => $lang->doc->importToComponentLib, 'url' => '#importToComponentLib', 'data-toggle' => 'modal');
+
+ $importLibBtn = $items ? dropdown
+ (
+ btn
+ (
+ setClass('ghost btn square btn-default'),
+ icon('diamond')
+ ),
+ set::items($items)
+ ) : null;
+}
+
+$createInfo = $doc->status == 'draft' ? zget($users, $doc->addedBy) . " {$lang->colon} " . substr($doc->addedDate, 0, 10) . (common::checkNotCN() ? ' ' : '') . $lang->doc->createAB : zget($users, $doc->releasedBy) . " {$lang->colon} " . substr($doc->releasedDate, 0, 10) . (common::checkNotCN() ? ' ' : '') . $lang->doc->release;
+
+$keywordsLabel = array();
+if($doc->keywords)
+{
+ foreach($doc->keywords as $keywords)
+ {
+ if($keywords)
+ {
+ $keywordsLabel[] = span
+ (
+ setClass('label secondary-outline'),
+ $keywords
+ );
+ }
+ }
+}
+
+/* Build editor group. */
+$editorGroup = '';
+if(!empty($editors))
+{
+ $space = common::checkNotCN() ? ' ' : '';
+ $firstEditor = current($editors);
+ $editorInfo = zget($users, $firstEditor->account) . ' ' . substr($firstEditor->date, 0, 10) . $space . $lang->doc->update;
+
+ array_shift($editors);
+
+ $items = array();
+ foreach($editors as $editor)
+ {
+ $info = zget($users, $editor->account) . ' ' . substr($editor->date, 0, 10) . $space . $lang->doc->update;
+ $items[] = array('text' => $info);
+ }
+
+ $editorGroup = dropdown
+ (
+ btn
+ (
+ setClass('ghost btn square btn-default'),
+ $editorInfo
+ ),
+ set::items($items)
+ );
+}
+
+panel
+(
+ div
+ (
+ setClass('panel-heading'),
+ div
+ (
+ setClass('title'),
+ $doc->title
+ ),
+ dropdown
+ (
+ btn
+ (
+ setClass('ghost btn square btn-default'),
+ 'V' . ($version ? $version : $doc->version)
+ ),
+ set::items($versionList)
+ ),
+ div
+ (
+ setClass('panel-actions flex'),
+ div
+ (
+ setClass('toolbar'),
+ btn
+ (
+ set::url('javascript:fullScreen()'),
+ setClass('btn ghost'),
+ icon('fullscreen'),
+ ),
+ common::hasPriv('doc', 'collect') ? html($starBtn) : null,
+ ($config->vision == 'rnd' and $config->edition == 'max' and $app->tab == 'project') ? $importLibBtn : null,
+ (!$isRelease && common::hasPriv('doc', 'edit')) ? btn
+ (
+ set::url(createLink('doc', 'edit', "docID=$doc->id")),
+ setClass('btn ghost'),
+ icon('edit'),
+ ) : null,
+ (!$isRelease && common::hasPriv('doc', 'delete')) ? btn
+ (
+ set::url(createLink('doc', 'delete', "docID=$doc->id")),
+ setClass('btn ghost ajax-submit'),
+ set('data-confirm', $lang->doc->confirmDelete),
+ icon('trash'),
+ ) : null,
+ btn
+ (
+ set::id('hisTrigger'),
+ set::url('###)'),
+ setClass('btn ghost'),
+ icon('clock'),
+ ),
+ ),
+ div
+ (
+ set::id('editorBox'),
+ setClass('flex'),
+ $editorGroup
+ )
+ )
+ ),
+ div
+ (
+ set::Class('panel-body'),
+ set::id('content'),
+ div
+ (
+ setClass('info'),
+ span
+ (
+ setClass('user-time'),
+ icon('contacts'),
+ $createInfo,
+ ),
+ span
+ (
+ setClass('user-time'),
+ icon('star'),
+ $doc->collects ? $doc->collects : 0,
+ ),
+ span
+ (
+ setClass('user-time'),
+ icon('eye'),
+ $doc->views,
+ ),
+ $keywordsLabel ? span
+ (
+ setClass('keywords'),
+ $keywordsLabel
+ ) : null,
+ ),
+ html($doc->content)
+ ),
+ h::hr(),
+ fileList
+ (
+ set::files($doc->files)
+ )
+);
+
+panel
+(
+ set::id('history'),
+ setClass('hidden'),
+ history()
+);