新增文档全文搜索功能

This commit is contained in:
yangjian 2020-11-26 20:55:44 +08:00
parent 945f25d63c
commit 14dae401d9
10 changed files with 1657 additions and 427 deletions

View File

@ -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"

View 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
View 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

File diff suppressed because it is too large Load Diff

20
app_doc/search_indexes.py Normal file
View 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)

View File

@ -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"), # 个人设置
]

View File

@ -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>

View File

@ -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 %}
<!-- 标签搜索结果 -->

View File

@ -0,0 +1,2 @@
{{object.name}}
{{object.pre_content}}

327
template/search/search.html Normal file
View 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=Edgechrome=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>