forked from mirror/MrDoc
新增文档全文搜索功能
This commit is contained in:
parent
945f25d63c
commit
14dae401d9
@ -53,10 +53,11 @@ INSTALLED_APPS = [
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'app_admin',
|
||||
'app_doc',
|
||||
'app_api',
|
||||
'django.contrib.sitemaps',
|
||||
'haystack', # 全文搜索
|
||||
'app_admin', # 管理APP
|
||||
'app_doc', # 文档APP
|
||||
'app_api', # API APP
|
||||
'django.contrib.sitemaps', # 站点地图
|
||||
'rest_framework',
|
||||
]
|
||||
|
||||
@ -200,4 +201,20 @@ except:
|
||||
try:
|
||||
CHROMIUM_ARGS = CONFIG['chromium']['args'].split(',')
|
||||
except:
|
||||
CHROMIUM_ARGS = []
|
||||
CHROMIUM_ARGS = []
|
||||
|
||||
|
||||
# 全文检索配置
|
||||
HAYSTACK_CONNECTIONS = {
|
||||
'default': {
|
||||
# 使用whoosh引擎
|
||||
'ENGINE': 'app_doc.search.whoosh_cn_backend.WhooshEngine',
|
||||
# 索引文件路径
|
||||
'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
|
||||
}
|
||||
}
|
||||
|
||||
# 当添加、修改、删除数据时,自动生成索引
|
||||
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
|
||||
# 自定义高亮
|
||||
HAYSTACK_CUSTOM_HIGHLIGHTER = "app_doc.search.highlight.MyHighLighter"
|
29
app_doc/search/chinese_analyzer.py
Normal file
29
app_doc/search/chinese_analyzer.py
Normal file
@ -0,0 +1,29 @@
|
||||
# coding:utf-8
|
||||
# @文件: chinese_analyzer.py
|
||||
# @创建者:州的先生
|
||||
# #日期:2020/11/22
|
||||
# 博客地址:zmister.com
|
||||
|
||||
import jieba
|
||||
from whoosh.analysis import Tokenizer, Token
|
||||
|
||||
class ChineseTokenizer(Tokenizer):
|
||||
def __call__(self, value, positions=False, chars=False,
|
||||
keeporiginal=False, removestops=True,
|
||||
start_pos=0, start_char=0, mode='', **kwargs):
|
||||
t = Token(positions, chars, removestops=removestops, mode=mode,
|
||||
**kwargs)
|
||||
seglist = jieba.cut(value, cut_all=True)
|
||||
for w in seglist:
|
||||
t.original = t.text = w
|
||||
t.boost = 1.0
|
||||
if positions:
|
||||
t.pos = start_pos + value.find(w)
|
||||
if chars:
|
||||
t.startchar = start_char + value.find(w)
|
||||
t.endchar = start_char + value.find(w) + len(w)
|
||||
yield t
|
||||
|
||||
|
||||
def ChineseAnalyzer():
|
||||
return ChineseTokenizer()
|
172
app_doc/search/highlight.py
Normal file
172
app_doc/search/highlight.py
Normal file
@ -0,0 +1,172 @@
|
||||
# coding:utf-8
|
||||
# @文件: highlight.py
|
||||
# @创建者:州的先生
|
||||
# #日期:2020/11/24
|
||||
# 博客地址:zmister.com
|
||||
from haystack.utils import Highlighter
|
||||
from django.utils.html import strip_tags
|
||||
from app_doc.search.chinese_analyzer import ChineseAnalyzer as StemmingAnalyzer
|
||||
|
||||
|
||||
class MyHighLighter(Highlighter):
|
||||
|
||||
def __init__(self, query, **kwargs):
|
||||
|
||||
# self.query = [token.text for token in sa(query)]
|
||||
self.query = query
|
||||
|
||||
if "max_length" in kwargs:
|
||||
self.max_length = int(kwargs["max_length"])
|
||||
|
||||
if "html_tag" in kwargs:
|
||||
self.html_tag = kwargs["html_tag"]
|
||||
|
||||
if "css_class" in kwargs:
|
||||
self.css_class = kwargs["css_class"]
|
||||
|
||||
# self.query_words = set(
|
||||
# [word.lower() for word in self.query.split() if not word.startswith("-")]
|
||||
# )
|
||||
sa = StemmingAnalyzer()
|
||||
self.query_words = set(
|
||||
[token.text.lower() for token in sa(query) if token.text.replace(" ",'') != '']
|
||||
)
|
||||
# print(self.query_words)
|
||||
|
||||
def highlight(self, text_block):
|
||||
self.text_block = strip_tags(text_block)
|
||||
highlight_locations = self.find_highlightable_words()
|
||||
start_offset, end_offset = self.find_window(highlight_locations)
|
||||
if len(text_block) < self.max_length:
|
||||
start_offset = 0
|
||||
return self.render_html(highlight_locations, start_offset, end_offset)
|
||||
|
||||
def find_window(self, highlight_locations):
|
||||
best_start = 0
|
||||
best_end = self.max_length
|
||||
|
||||
# First, make sure we have words.
|
||||
if not len(highlight_locations):
|
||||
return (best_start, best_end)
|
||||
|
||||
words_found = []
|
||||
|
||||
# Next, make sure we found any words at all.
|
||||
for word, offset_list in highlight_locations.items():
|
||||
if len(offset_list):
|
||||
# Add all of the locations to the list.
|
||||
words_found.extend(offset_list)
|
||||
|
||||
if not len(words_found):
|
||||
return (best_start, best_end)
|
||||
|
||||
# 只有一个搜索词被发现
|
||||
if len(words_found) == 1:
|
||||
# return (words_found[0], words_found[0] + self.max_length) #
|
||||
# new added
|
||||
best_start = words_found[0]
|
||||
best_end = words_found[0] + self.max_length
|
||||
|
||||
if best_end > len(self.text_block):
|
||||
current_length = len(self.text_block) - best_start
|
||||
move_forward_steps = self.max_length - current_length
|
||||
best_start = best_start - move_forward_steps
|
||||
if best_start < 0:
|
||||
best_start = 0
|
||||
return (best_start, best_end)
|
||||
|
||||
# Sort the list so it's in ascending order.
|
||||
words_found = sorted(words_found)
|
||||
|
||||
# We now have a denormalized list of all positions were a word was
|
||||
# found. We'll iterate through and find the densest window we can by
|
||||
# counting the number of found offsets (-1 to fit in the window).
|
||||
highest_density = 0
|
||||
|
||||
if words_found[:-1][0] > self.max_length:
|
||||
best_start = words_found[:-1][0]
|
||||
best_end = best_start + self.max_length
|
||||
|
||||
for count, start in enumerate(words_found[:-1]):
|
||||
current_density = 1
|
||||
|
||||
for end in words_found[count + 1:]:
|
||||
if end - start < self.max_length:
|
||||
current_density += 1
|
||||
else:
|
||||
current_density = 0
|
||||
|
||||
# Only replace if we have a bigger (not equal density) so we
|
||||
# give deference to windows earlier in the document.
|
||||
if current_density > highest_density:
|
||||
# print(start)
|
||||
if start == 0:
|
||||
best_start = start
|
||||
else:
|
||||
best_start = start
|
||||
best_end = start + self.max_length
|
||||
highest_density = current_density
|
||||
if best_start < 10:
|
||||
best_start = 0
|
||||
best_end = 200
|
||||
else:
|
||||
best_start -= 10
|
||||
best_end -= 10
|
||||
|
||||
return (best_start, best_end)
|
||||
|
||||
def render_html(self, highlight_locations=None, start_offset=None, end_offset=None):
|
||||
# Start by chopping the block down to the proper window.
|
||||
text = self.text_block[start_offset:end_offset]
|
||||
|
||||
# Invert highlight_locations to a location -> term list
|
||||
term_list = []
|
||||
|
||||
for term, locations in highlight_locations.items():
|
||||
term_list += [(loc - start_offset, term) for loc in locations]
|
||||
|
||||
loc_to_term = sorted(term_list)
|
||||
|
||||
# Prepare the highlight template
|
||||
if self.css_class:
|
||||
hl_start = '<%s class="%s">' % (self.html_tag, self.css_class)
|
||||
else:
|
||||
hl_start = "<%s>" % (self.html_tag)
|
||||
|
||||
hl_end = "</%s>" % self.html_tag
|
||||
|
||||
# Copy the part from the start of the string to the first match,
|
||||
# and there replace the match with a highlighted version.
|
||||
highlighted_chunk = ""
|
||||
matched_so_far = 0
|
||||
prev = 0
|
||||
prev_str = ""
|
||||
|
||||
for cur, cur_str in loc_to_term:
|
||||
# This can be in a different case than cur_str
|
||||
actual_term = text[cur : cur + len(cur_str)]
|
||||
|
||||
# Handle incorrect highlight_locations by first checking for the term
|
||||
if actual_term.lower() == cur_str:
|
||||
if cur < prev + len(prev_str):
|
||||
continue
|
||||
|
||||
highlighted_chunk += (
|
||||
text[prev + len(prev_str) : cur] + hl_start + actual_term + hl_end
|
||||
)
|
||||
prev = cur
|
||||
prev_str = cur_str
|
||||
|
||||
# Keep track of how far we've copied so far, for the last step
|
||||
matched_so_far = cur + len(actual_term)
|
||||
|
||||
# Don't forget the chunk after the last term
|
||||
highlighted_chunk += text[matched_so_far:]
|
||||
|
||||
if start_offset > 0:
|
||||
highlighted_chunk = "%s" % highlighted_chunk
|
||||
|
||||
if end_offset < len(self.text_block):
|
||||
highlighted_chunk = "%s..." % highlighted_chunk
|
||||
|
||||
return highlighted_chunk
|
1073
app_doc/search/whoosh_cn_backend.py
Normal file
1073
app_doc/search/whoosh_cn_backend.py
Normal file
File diff suppressed because it is too large
Load Diff
20
app_doc/search_indexes.py
Normal file
20
app_doc/search_indexes.py
Normal file
@ -0,0 +1,20 @@
|
||||
# coding:utf-8
|
||||
# @文件: search_indexes.py
|
||||
# @创建者:州的先生
|
||||
# #日期:2020/11/22
|
||||
# 博客地址:zmister.com
|
||||
|
||||
from haystack import indexes
|
||||
from app_doc.models import *
|
||||
|
||||
# 文档索引
|
||||
class DocIndex(indexes.SearchIndex,indexes.Indexable):
|
||||
text = indexes.CharField(document=True, use_template=True)
|
||||
top_doc = indexes.IntegerField(model_attr='top_doc')
|
||||
modify_time = indexes.DateTimeField(model_attr='modify_time')
|
||||
|
||||
def get_model(self):
|
||||
return Doc
|
||||
|
||||
def index_queryset(self, using=None):
|
||||
return self.get_model().objects.filter(status=1)
|
@ -1,5 +1,5 @@
|
||||
from django.urls import path,re_path
|
||||
from app_doc import views,views_user,util_upload_img,import_views
|
||||
from django.urls import path,re_path,include
|
||||
from app_doc import views,views_user,views_search,util_upload_img,import_views
|
||||
|
||||
urlpatterns = [
|
||||
path('',views.project_list,name='pro_list'),# 文档首页
|
||||
@ -22,6 +22,7 @@ urlpatterns = [
|
||||
path('manage_project_import/',import_views.import_project,name="import_project"), # 导入文集
|
||||
path('manage_project_doc_sort/',import_views.project_doc_sort,name='project_doc_sort'), # 导入文集文档排序
|
||||
path('manage_project_transfer/<int:pro_id>/',views.manage_project_transfer,name='manage_pro_transfer'), # 文集转让
|
||||
path('manage_pro_doc_sort/<int:pro_id>/',views.manage_project_doc_sort,name='manage_pro_doc_sort'), # 文集排序
|
||||
#################文档相关
|
||||
path('project-<int:pro_id>/doc-<int:doc_id>/', views.doc, name='doc'), # 文档浏览页
|
||||
path('create_doc/', views.create_doc, name="create_doc"), # 新建文档
|
||||
@ -57,6 +58,8 @@ urlpatterns = [
|
||||
path('user/center_menu/',views_user.user_center_menu,name="user_center_menu"), # 个人中心菜单数据
|
||||
path('upload_doc_img/',util_upload_img.upload_img,name="upload_doc_img"), # 上传图片
|
||||
path('search/',views.search,name="search"), # 搜索功能
|
||||
# path('doc_search/', include('haystack.urls')), # 全文检索框架
|
||||
path('doc_search/', views_search.DocSearchView(),name="doc_search"), # 全文检索框架
|
||||
path('manage_overview/',views.manage_overview,name="manage_overview"), # 个人中心概览
|
||||
path('manage_self/',views.manage_self,name="manage_self"), # 个人设置
|
||||
]
|
@ -6,6 +6,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="Cache-Control" content="no-siteapp">
|
||||
<link href="{% static 'mrdoc/mrdoc-search.css' %}?version={{mrdoc_version}}" rel="stylesheet">
|
||||
<link rel="icon" href="{% static 'search/mrdoc_logo_300.png' %}" sizes="192x192" />
|
||||
<link rel="apple-touch-icon-precomposed" href="{% static 'search/mrdoc_logo_300.png' %}" />
|
||||
<meta name="msapplication-TileImage" content="{% static 'search/mrdoc_logo_300.png' %}" />
|
||||
@ -23,320 +24,6 @@
|
||||
<!--QQ应用模式-->
|
||||
<title>觅道搜索{% if site_name != None and site_name != '' %} - {{site_name}}{% endif %}</title>
|
||||
<style>
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
outline: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: 'Noto Sans SC Sliced', PingFangSC-Light, Microsoft YaHei UI, Microsoft YaHei, helvetica, sans-serif;
|
||||
font-weight: 300;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
form,
|
||||
input,
|
||||
button {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
input::-webkit-input-placeholder {
|
||||
color: #ccc;
|
||||
letter-spacing: 2px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
ul,
|
||||
li {
|
||||
display: block;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
#content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.con {
|
||||
width: 100%;
|
||||
transition: 1s all;
|
||||
margin: auto;
|
||||
min-width: 320px;
|
||||
height: 380px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: -100px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.con .shlogo {
|
||||
position: relative;
|
||||
width: 480px;
|
||||
height: 120px;
|
||||
margin: 20px auto;
|
||||
background: url(/static/search/mrdoc_search_logo.svg) no-repeat center/cover;
|
||||
}
|
||||
|
||||
.con .shlogo a {
|
||||
width: 100%;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.con .sou {
|
||||
max-width: 680px;
|
||||
position: relative;
|
||||
width: calc(100% - 60px);
|
||||
min-width: 320px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.con .sou form {
|
||||
width: 100%;
|
||||
/*border: 1px solid #ddd;*/
|
||||
height: 50px;
|
||||
display: block;
|
||||
margin: 10px auto 30px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.con .sou form .wd {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 25px;
|
||||
line-height: 100%;
|
||||
text-indent: 20px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.con .sou form .wd:focus {
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 6px 0 rgba(32, 33, 36, 0.28);
|
||||
border-color: #fff
|
||||
}
|
||||
|
||||
.con .sou form button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
right: 6px;
|
||||
top: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 22px;
|
||||
line-height: 40px;
|
||||
border-radius: 50%;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.con .sou ul {
|
||||
height: 40px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.con .sou ul li {
|
||||
width: 120px;
|
||||
margin: 0 10px;
|
||||
float: left;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
background: #eee;
|
||||
font-size: 16px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
text-indent: 30px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.con .sou ul li:active {
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 20px 0 rgba(0, 0, 0, .1);
|
||||
}
|
||||
|
||||
.con .sou ul li i {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
left: 0;
|
||||
/* border-radius: 50%; */
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
/* background-color: #fff; */
|
||||
transform: scale(0.7);
|
||||
}
|
||||
|
||||
.foot {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
z-index: 1000;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
color: #999;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.home {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
position: absolute;
|
||||
right: 70px;
|
||||
top: 10px;
|
||||
z-index: 200;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.home a {
|
||||
font-size: 20px;
|
||||
color: #999;
|
||||
line-height: 50px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
svg.icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin: 0 5px 0 8px;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.con {
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.con .shlogo {
|
||||
width: 320px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.con .sou form .wd:focus {
|
||||
background: #f1f1f1;
|
||||
box-shadow: none;
|
||||
border-color: #ccc
|
||||
}
|
||||
|
||||
.con .sou form button {
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.con .sou ul li {
|
||||
width: 100px;
|
||||
font-size: 12px;
|
||||
text-indent: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 420px) {
|
||||
.con {
|
||||
margin: 0;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.con .sou form .wd {
|
||||
text-indent: 50px;
|
||||
}
|
||||
|
||||
.con .sou form:after {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 10px;
|
||||
background: url(/static/search/search.svg) no-repeat center/cover;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.con .shlogo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.foot {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #162035;
|
||||
}
|
||||
|
||||
.con .sou ul li {
|
||||
background: #293550;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.con .sou form .wd:focus {
|
||||
background: #293550;
|
||||
border: 1px solid #162035;
|
||||
}
|
||||
|
||||
.con .sou form .wd {
|
||||
border: 1px solid #293550;
|
||||
color: #bbb;
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #222d46;
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #293550;
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #293550;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: #222d46;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@ -345,9 +32,9 @@
|
||||
<div class="con">
|
||||
<div class="shlogo"></div>
|
||||
<div class="sou">
|
||||
<form action="{% url 'search' %}" method="get" target="_self">
|
||||
<input class="wd" type="text" placeholder="请输入搜索内容" name="kw" x-webkit-speech lang="zh-CN">
|
||||
<input name="type" value="doc" hidden>
|
||||
<form action="{% url 'doc_search' %}" method="get" target="_self">
|
||||
<input class="wd" type="text" placeholder="请输入搜索内容" name="q" x-webkit-speech lang="zh-CN">
|
||||
<!-- <input name="type" value="doc" hidden> -->
|
||||
<input name="d_range" value="all" hidden>
|
||||
<button type="submit" style="background-image: url(/static/search/search_btn.svg);"></button>
|
||||
</form>
|
||||
|
@ -13,109 +13,9 @@
|
||||
<title>{{ kw }} - 觅道搜索 - {% if site_name != None and site_name != '' %}{{ site_name }}{% endif %}</title>
|
||||
<link href="{% static 'layui/css/layui.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'mrdoc/mrdoc.css' %}?version={{mrdoc_version}}" rel="stylesheet">
|
||||
<link href="{% static 'mrdoc/mrdoc-search-result.css' %}?version={{mrdoc_version}}" rel="stylesheet">
|
||||
<link rel="icon" href="{% static 'search/mrdoc_logo_300.png' %}" sizes="192x192" />
|
||||
<style>
|
||||
.layui-nav .layui-this:after, .layui-nav-bar, .layui-nav-tree .layui-nav-itemed:after{
|
||||
background-color: #0885ff !important;
|
||||
}
|
||||
.layui-nav .layui-nav-child dd.layui-this a, .layui-nav-child dd.layui-this{
|
||||
background-color: #0885ff !important;
|
||||
}
|
||||
/* layui分页组件样式 */
|
||||
.layui-laypage .layui-laypage-curr .layui-laypage-em{
|
||||
background-color: #0885ff !important;
|
||||
}
|
||||
/* 控制栏表单下拉框样式 */
|
||||
.index-control .layui-input-inline{
|
||||
/* width: 100px; */
|
||||
}
|
||||
.index-control .layui-input{
|
||||
height: 25px;
|
||||
border: none;
|
||||
}
|
||||
.index-control .layui-form-select dl {
|
||||
top: 30px;
|
||||
}
|
||||
.index-control .layui-form-item{
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
/* 文集列表样式 */
|
||||
.project-item-list{
|
||||
/* float: left; */
|
||||
min-width: 0;
|
||||
width: 100vw;
|
||||
height: 120px;
|
||||
/* margin-top: 20px; */
|
||||
/* margin-left: 20px; */
|
||||
margin: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,.055);
|
||||
}
|
||||
/* 搜索类型 */
|
||||
a.search_type{
|
||||
color: #999;
|
||||
margin-left: 15px;
|
||||
font-size: 16px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
a.search_type:hover{
|
||||
color: #333;
|
||||
}
|
||||
/* 当前搜索类型链接样式 */
|
||||
.current_search_type{
|
||||
color: #333 !important;
|
||||
border-bottom: 2px solid#007fff !important;
|
||||
}
|
||||
/* 搜索结果标题 */
|
||||
.search_result_title{
|
||||
color: #00c;
|
||||
font-weight: 400;
|
||||
font-size: medium;
|
||||
line-height:26px;
|
||||
}
|
||||
.search_result_title:hover{
|
||||
color: #00c;
|
||||
}
|
||||
/* 搜索结果简介 */
|
||||
.search_result_pre{
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
line-height: 20px;
|
||||
word-break: break-word;
|
||||
}
|
||||
/* 搜索结果归属 */
|
||||
.search_result_info a{
|
||||
color: green;
|
||||
}
|
||||
/* 时间筛选下拉框 */
|
||||
.layui-form-select dl dd.layui-this{
|
||||
background-color:#0885ff;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1200px){
|
||||
.layui-container {
|
||||
width: 970px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 移动端筛选控制栏样式 */
|
||||
@media screen and (max-width: 768px){
|
||||
/* 控制栏样式 */
|
||||
.index-control .layui-form-item .layui-inline{
|
||||
display: -webkit-inline-box;
|
||||
}
|
||||
.index-control .layui-form-item .layui-input-inline{
|
||||
display: -webkit-inline-box;
|
||||
float: none;
|
||||
left: -3px;
|
||||
/* width: auto; */
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body class="layui-container">
|
||||
@ -239,7 +139,7 @@
|
||||
<div class="layui-inline">
|
||||
<!-- 文档搜索 -->
|
||||
<div class="layui-input-inline" style="width: inherit;">
|
||||
<a href="{% url 'search' %}?kw={{kw}}&type=doc&d_range={{d_range}}" class="search_type" id="search_doc"><i class="layui-icon layui-icon-search"></i>文档</a>
|
||||
<a href="{% url 'doc_search' %}?q={{kw}}&d_range={{d_range}}" class="search_type" id="search_doc"><i class="layui-icon layui-icon-search"></i>文档</a>
|
||||
<a href="{% url 'search' %}?kw={{kw}}&type=pro&d_range={{d_range}}" class="search_type" id="search_project"><i class="layui-icon layui-icon-list"></i>文集</a>
|
||||
<a href="{% url 'search' %}?kw={{kw}}&type=tag&d_range={{d_range}}" class="search_type" id="search_tag"><i class="layui-icon layui-icon-note"></i>标签</a>
|
||||
</div>
|
||||
@ -312,7 +212,7 @@
|
||||
<div class="search_result_pre">{{ result.intro|get_key_context:kw }}</div>
|
||||
<!-- 所属文集 -->
|
||||
<p class="search_result_info">
|
||||
<a>{{ result.create_user }}</a> - <span style="font-size: 14px;color: #999;">{{result.modify_time}}</span></p>
|
||||
<a>{% if result.create_user.first_name != '' %}{{ result.create_user.first_name }}{% else %}{{result.create_user}}{% endif %}</a> - <span style="font-size: 14px;color: #999;">{{result.modify_time}}</span></p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<!-- 标签搜索结果 -->
|
||||
|
2
template/search/indexes/app_doc/doc_text.txt
Normal file
2
template/search/indexes/app_doc/doc_text.txt
Normal file
@ -0,0 +1,2 @@
|
||||
{{object.name}}
|
||||
{{object.pre_content}}
|
327
template/search/search.html
Normal file
327
template/search/search.html
Normal file
@ -0,0 +1,327 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-cn">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
|
||||
<meta http-equiv="Cache-Control" content="no-transform" />
|
||||
<meta http-equiv="Cache-Control" content="no-siteapp" />
|
||||
<meta http-equiv="Cache-Control" content="max-age=7200" />
|
||||
<meta name="keywords" content="{% if site_keywords != None %}{{site_keywords}}{% endif %}"/>
|
||||
<meta name="description" content="{% if site_desc != None %}{{site_desc}}{% endif %}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>{{ query }} - 觅道搜索 - {% if site_name != None and site_name != '' %}{{ site_name }}{% endif %}</title>
|
||||
<link href="{% static 'layui/css/layui.css' %}" rel="stylesheet">
|
||||
<link href="{% static 'mrdoc/mrdoc.css' %}?version={{mrdoc_version}}" rel="stylesheet">
|
||||
<link href="{% static 'mrdoc/mrdoc-search-result.css' %}?version={{mrdoc_version}}" rel="stylesheet">
|
||||
<link rel="icon" href="{% static 'search/mrdoc_logo_300.png' %}" sizes="192x192" />
|
||||
<style>
|
||||
</style>
|
||||
</head>
|
||||
<body class="layui-container">
|
||||
<!-- 页头 -->
|
||||
<div class="layui-header layui-fluid">
|
||||
<div class="" style="display:flex;flex-direction:row;justify-content:space-between;">
|
||||
<!-- LOGO -->
|
||||
<div class="">
|
||||
<a class="logo" href="{% url 'search' %}">
|
||||
<img src="{% static 'search/mrdoc_search_logo_2.svg' %}" style="height: 32px;width: auto;">
|
||||
</a>
|
||||
</div>
|
||||
<!-- 搜索框 -->
|
||||
<div style="margin:12px;" class="layui-hide-xs">
|
||||
<form method="get" action="">
|
||||
<div class="layui-input-inline">
|
||||
<input class="layui-input mrdoc-search-input"
|
||||
placeholder="搜索文集或文档" name="q" style="width: 500px;border-radius:5px" value="{{query}}"/>
|
||||
<input name="type" value="{{search_type}}" hidden>
|
||||
<button type="submit"
|
||||
style="position: absolute;top:12px;right: 8px;border: none;background-color: white;">
|
||||
<i class="layui-icon layui-icon-search" ></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- 用户菜单 -->
|
||||
<div class="">
|
||||
<ul class="layui-nav layui-layout-right">
|
||||
{% if request.user.is_authenticated %}
|
||||
<li class="layui-nav-item">
|
||||
<a href="javascript:void(0);">
|
||||
<i class="layui-icon layui-icon-friends"></i>
|
||||
<span class="layui-hide-xs">{% if request.user.first_name != '' %}{{request.user.first_name}}{% else %}{{request.user.username}}{% endif %}</span>
|
||||
</a>
|
||||
<dl class="layui-nav-child">
|
||||
<!-- <dd><a href="">基本资料</a></dd> -->
|
||||
{% if request.user.is_superuser %}
|
||||
<dd>
|
||||
<a href="{% url 'pro_list' %}">
|
||||
<i class="layui-icon layui-icon-console layui-hide-md"></i>
|
||||
<span class="layui-hide-xs">返回首页</span>
|
||||
</a>
|
||||
</dd>
|
||||
{% endif %}
|
||||
<dd class="layui-hide-md">
|
||||
<a href="{% url 'create_doc' %}">
|
||||
<i class="layui-icon layui-icon-add-1 layui-hide-md"></i>
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="{% url 'manage_doc' %}">
|
||||
<i class="layui-icon layui-icon-app layui-hide-md"></i>
|
||||
<span class="layui-hide-xs">个人中心</span>
|
||||
</a>
|
||||
</dd>
|
||||
<dd>
|
||||
<a href="{% url 'logout' %}">
|
||||
<i class="layui-icon layui-icon-release layui-hide-md"></i>
|
||||
<span class="layui-hide-xs">退出登录</span>
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="layui-nav-item">
|
||||
<a href="javascript:void(0);">
|
||||
<i class="layui-icon layui-icon-username"></i> 游客
|
||||
</a>
|
||||
<dl class="layui-nav-child">
|
||||
<!-- <dd><a href="">基本资料</a></dd> -->
|
||||
{% if close_register == 'on' %}
|
||||
<dd><a href="{% url 'login' %}">登录</a></dd>
|
||||
{% else %}
|
||||
<dd><a href="{% url 'register' %}">注册</a></dd>
|
||||
<dd><a href="{% url 'login' %}">登录</a></dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 页头结束 -->
|
||||
|
||||
<!-- 小屏下的搜索框 -->
|
||||
<div class="" style="margin-top:10px;padding-left:15px;">
|
||||
<!-- 搜索框 -->
|
||||
<div style="margin:12px 0 12px 0;" class="layui-hide-md layui-hide-lg layui-hide-sm">
|
||||
<form method="get" action="">
|
||||
<div class="layui-inline">
|
||||
<div class="layui-input-inline">
|
||||
<input class="layui-input mrdoc-search-input"
|
||||
placeholder="搜索文集或文档" name="q" style="border-radius:5px" value="{{kw}}"/>
|
||||
<input name="type" value="{{search_type}}" hidden>
|
||||
</div>
|
||||
<div class="layui-input-inline">
|
||||
<button type="submit" class="layui-btn layui-btn-sm layui-btn-normal">
|
||||
<i class="layui-icon layui-icon-search" ></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<div class="layui-container" style="margin-top: 10px;">
|
||||
<!-- 表单风格开始 -->
|
||||
<div class="layui-row">
|
||||
<form class="index-control layui-form" lay-filter="filter-time-form">
|
||||
<div class="layui-form-item">
|
||||
<!-- 筛选开始 -->
|
||||
<div class="layui-inline">
|
||||
<!-- 文档搜索 -->
|
||||
<div class="layui-input-inline" style="width: inherit;">
|
||||
<a href="?q={{query}}&type=doc&d_range={{d_range}}" class="search_type current_search_type" id="search_doc"><i class="layui-icon layui-icon-search"></i>文档</a>
|
||||
<a href="{% url 'search' %}?kw={{query}}&type=pro&d_range={{d_range}}" class="search_type" id="search_project"><i class="layui-icon layui-icon-list"></i>文集</a>
|
||||
<a href="{% url 'search' %}?kw={{query}}&type=tag&d_range={{d_range}}" class="search_type" id="search_tag"><i class="layui-icon layui-icon-note"></i>标签</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 筛选结束 -->
|
||||
<div class="layui-input-inline" style="float: right;">
|
||||
<select name="d_range" lay-verify="sel_recent" id="sel_recent">
|
||||
<option value="">时间筛选</option>
|
||||
<option value="all">全部时间</option>
|
||||
<option value="recent1">近1天</option>
|
||||
<option value="recent7">近7天</option>
|
||||
<option value="recent30">近30天</option>
|
||||
<option value="recent365">近1年</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- 表单风格结束 -->
|
||||
</div>
|
||||
|
||||
<!-- 搜索结果提示 -->
|
||||
{% if query != '' %}
|
||||
<div style="padding: 0 15px;margin-top: 10px;margin-bottom: 10px;color: #999;">
|
||||
觅道文档在
|
||||
{% if date_range == 'recent1' %}
|
||||
近1天内
|
||||
{% elif date_range == 'recent7' %}
|
||||
近7天内
|
||||
{% elif date_range == 'recent30' %}
|
||||
近30天内
|
||||
{% elif date_range == 'recent365' %}
|
||||
近一年内
|
||||
{% else %}
|
||||
全部时间内
|
||||
{% endif %}
|
||||
搜索到 {{ page.paginator.count }} 条结果
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- 主体 -->
|
||||
<div class="layui-container search_result" style="padding: 0 20px;">
|
||||
<!-- 遍历搜索结果列表 -->
|
||||
{% if query %}
|
||||
{% load doc_filter %}
|
||||
{% load highlight %}
|
||||
{% for result in page.object_list %}
|
||||
<div style="margin-bottom: 18px;">
|
||||
<!-- 标题 -->
|
||||
<h3>
|
||||
<a href="{{ result.object.get_absolute_url }}" target="_blank" class="search_result_title">{% highlight result.object.name with query %}</a>
|
||||
</h3>
|
||||
<!-- 简介 -->
|
||||
{% highlight result.object.pre_content with query %}
|
||||
<!-- 所属文集 -->
|
||||
<p class="search_result_info">
|
||||
<a href="{% url 'pro_index' pro_id=result.object.top_doc %}" target="_blank">{{ result.object.top_doc | get_doc_top }}</a> - <span style="font-size: 14px;color: #999;">{{result.object.modify_time}}</span></p>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p>没有搜索结果。</p>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{# Show some example queries to run, maybe query syntax, something else? #}
|
||||
{% endif %}
|
||||
<!-- 文档搜索结果 -->
|
||||
</div>
|
||||
|
||||
<!-- 主体结束 -->
|
||||
<!-- 分页 -->
|
||||
<div class="layui-row project-list-page" style="text-align: center;">
|
||||
<div class="layui-box layui-laypage layui-laypage-default">
|
||||
<!-- 上一页 -->
|
||||
{% if page.has_previous %}
|
||||
<a href="?page={{ page.previous_page_number }}&q={{query}}&d_range={{date_range}}" class="layui-btn layui-btn-xs layui-btn-normal">上一页</a>
|
||||
{% else %}
|
||||
<a href="javascript:;" class="layui-btn layui-btn-xs layui-btn-disabled">上一页</a>
|
||||
{% endif %}
|
||||
<!-- 当前页 -->
|
||||
<span class="layui-laypage-curr">
|
||||
<em class="layui-laypage-em"></em>
|
||||
<em>{{ page.number }}/{{ page.paginator.num_pages }}</em>
|
||||
</span>
|
||||
<!-- 下一页 -->
|
||||
{% if page.has_next %}
|
||||
<a href="?page={{ page.next_page_number }}&q={{query}}&d_range={{date_range}}" class="layui-btn layui-btn-xs layui-btn-normal">下一页</a>
|
||||
{% else %}
|
||||
<a class="layui-btn layui-btn-xs layui-btn-disabled">下一页</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分页结束 -->
|
||||
|
||||
<!-- 页脚 -->
|
||||
{% include 'app_doc/foot_base.html' %}
|
||||
<!-- 页脚结束 -->
|
||||
|
||||
<script src="{% static 'jquery/3.1.1/jquery.min.js' %}"></script>
|
||||
<script src="{% static 'layui/layui.all.js' %}"></script>
|
||||
{% block custom_script %}
|
||||
<script>
|
||||
$.ajaxSetup({
|
||||
data: {csrfmiddlewaretoken: '{{ csrf_token }}' },
|
||||
});
|
||||
var layer = layui.layer;
|
||||
var form = layui.form;
|
||||
function positionFooter() {
|
||||
// 获取页脚的高度
|
||||
footerHeight = $(".layui-footer").height();
|
||||
// 获取页脚的高度
|
||||
// scrollTop() 设置或获取位于对象最顶端和窗口中可见内容的最顶端之间的距离
|
||||
footerTop = ($(window).scrollTop() + $(window).height() - footerHeight - 35)+"px";
|
||||
//如果页面内容高度小于屏幕高度,div#footer将绝对定位到屏幕底部,否则div#footer保留它的正常静态定位
|
||||
if(($(".layui-header").height() + $(".index-control").height() + $(".project-list-content").height() + $(".project-list-page").height() + 16) < $(window).height()) {
|
||||
console.log("页脚置底")
|
||||
$(".layui-footer").css({ position: "absolute",left:"0" }).stop().css({top:footerTop});
|
||||
}else{
|
||||
$(".layui-footer").css({ position: ""})
|
||||
}
|
||||
};
|
||||
$(window).bind("load", function() {
|
||||
// 设置页脚位置
|
||||
var footerHeight = 0;
|
||||
var footerTop = 0;
|
||||
positionFooter();
|
||||
//$(window).scroll(positionFooter).resize(positionFooter);
|
||||
//设置条件栏选中值
|
||||
var url = layui.url();
|
||||
// console.log(url)
|
||||
$("#sel-role").val(url.search.role);
|
||||
$("#sel-sort").val(url.search.sort);
|
||||
layui.form.render('select');
|
||||
});
|
||||
|
||||
// 搜索词高亮
|
||||
function keyLight(id, key, bgColor){
|
||||
var oDiv = document.getElementById(id),
|
||||
sText = oDiv.innerHTML,
|
||||
bgColor = bgColor || "#c00",
|
||||
sKey = "<span name='addSpan' style='color: "+bgColor+";'>"+key+"</span>",
|
||||
num = -1,
|
||||
rStr = new RegExp(key, "ig"),
|
||||
rHtml = new RegExp("\<.*?\>","ig"), //匹配html元素
|
||||
aHtml = sText.match(rHtml); //存放html元素的数组
|
||||
sText = sText.replace(rHtml, '{~}'); //替换html标签
|
||||
// sText = sText.replace(rStr,sKey); //替换key
|
||||
sText = sText.replace(rStr,function(text){
|
||||
return "<span name='addSpan' style='color: "+bgColor+";'>"+text+"</span>"
|
||||
}); //替换key
|
||||
sText = sText.replace(/{~}/g,function(){ //恢复html标签
|
||||
num++;
|
||||
return aHtml[num];
|
||||
});
|
||||
oDiv.innerHTML = sText;
|
||||
};
|
||||
// keyLight('search_result',"{{query}}")
|
||||
|
||||
// 侦听Select下拉框的选择事件
|
||||
form.on('select()', function(data){
|
||||
var filter_data = form.val("filter-time-form");
|
||||
console.log(filter_data)
|
||||
window.location.href = '?q={{query}}&d_range=' + filter_data['d_range']
|
||||
});
|
||||
|
||||
// 当前搜索类型动态设置
|
||||
tagCurrentSearchType = function(){
|
||||
if('{{ search_type }}' == 'doc'){
|
||||
$('#search_doc').addClass('current_search_type')
|
||||
$('#search_project').removeClass('current_search_type')
|
||||
$('#search_tag').removeClass('current_search_type')
|
||||
}else if('{{ search_type }}' == 'pro'){
|
||||
$('#search_project').addClass('current_search_type')
|
||||
$('#search_doc').removeClass('current_search_type')
|
||||
$('#search_tag').removeClass('current_search_type')
|
||||
}else if('{{ search_type }}' == 'tag'){
|
||||
$('#search_tag').addClass('current_search_type')
|
||||
$('#search_doc').removeClass('current_search_type')
|
||||
$('#search_project').removeClass('current_search_type')
|
||||
}
|
||||
}
|
||||
tagCurrentSearchType();
|
||||
|
||||
</script>
|
||||
<!-- 统计代码开始 -->
|
||||
{% if debug %}
|
||||
{% else %}
|
||||
{{ static_code | safe }}
|
||||
{% endif %}
|
||||
<!-- 统计代码结束 -->
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user