v2.0.4 版本更新

This commit is contained in:
肖燊 2017-12-19 23:30:07 +08:00
parent c6742748b9
commit 34c40969e6
76 changed files with 21923 additions and 1526 deletions

View File

@ -1,35 +1,38 @@
# DoraCMS 2.0.3
# DoraCMS 2.0.4
![DoraCMS](http://7xkrk4.com1.z0.glb.clouddn.com/doracms2.jpg "DoraCMS")
## 2.0.3版本更新
## 2.0.4版本更新
1、上传缩略图支持七牛云存储
1、添加系统支持redis缓存通过开关控制并添加通过redis缓存数据中间件。要知道redis在存储性能方面MongoDB要好很多主要存储session数据
2、取消在后台首页显示用户敏感信息,提高安全性
2、重新整理了样式,组件样式全部单独提取,提高可维护性。
3、管理员登录md5加密 ![#87](https://github.com/doramart/DoraCMS/pull/87 "#87")
3、文档详情页添加了“喜欢”功能。
4、修复描述信息不是必填项,但是也验证了 ![#91](https://github.com/doramart/DoraCMS/issues/91 "#91")
4、修复了某些场景下批量删除留言异常的bug。
5、站点地图域名可配置
5、添加了二维码分享功能。
6、统一端口号配置
6、添加了回到顶部按钮。
7、修复管理员在留言管理中 对某个会员 回复信息, 然后再给自己doramart回复信息后进入系统主页浏览器报错的问题![#93](https://github.com/doramart/DoraCMS/issues/93 "#93")
7、优化了包括 最新文档、近期文档、推荐文档等模块的代码结构。
8、修复修改管理员信息没有改手机号却提示手机号格式不正确的问题 ![#92](https://github.com/doramart/DoraCMS/pull/92 "#92")
8、优化了用户中心的界面和交互。
9、前台后台添加404页面
9、修复页面跳转后滚动条不置顶的问题。
10、后台没有权限的菜单不显示
10、修复了某些场景下通过标签查询分页异常的问题。
11、优化了cms在移动端的显示。
12、修复了一些明显bug
11、修复了一些样式问题
## 更新方法:
1、checkout 最新 2.0.3 代码
1、checkout 最新 2.0.4 代码
2、删除 node_modules,重新安装依赖包

View File

@ -26,6 +26,7 @@ const config = {
],
alias: {
'@': path.join(__dirname, '..', 'src'),
'front_public': '@/index/assets/css/public.scss',
'scss_vars': '@/manage/assets/styles/vars.scss',
'~src': path.resolve(__dirname, '../src'),
'~api': path.resolve(__dirname, '../src/api/index-client'),

View File

@ -32,6 +32,8 @@ var config = merge(base, {
},
resolve: {
alias: {
'@': path.join(__dirname, '..', 'src'),
'front_public': '@/index/assets/css/public.scss',
'~api': path.resolve(__dirname, '../src/api/index-server'),
'~server': path.resolve(__dirname, '../server'),
'api-config': path.resolve(__dirname, '../src/api/config-server'),

12334
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "doracms",
"version": "2.0.3",
"version": "2.0.4",
"description": "基于nodejs,express,vue2 内容管理系统.",
"keywords": [
"vue",
@ -28,6 +28,7 @@
"body-parser": "^1.18.2",
"compression": "^1.7.1",
"connect-mongo": "^1.3.2",
"connect-redis": "^3.3.2",
"cookie-parser": "^1.4.3",
"cross-env": "^5.1.1",
"crypto": "0.0.3",
@ -51,6 +52,7 @@
"nodemailer": "^4.4.0",
"nprogress": "^0.2.0",
"qiniu": "^7.1.1",
"qr-image": "^3.2.0",
"serve-favicon": "^2.4.5",
"shelljs": "^0.7.8",
"shortid": "^2.2.8",
@ -114,4 +116,4 @@
"engines": {
"node": "8.x"
}
}
}

View File

@ -9,6 +9,7 @@ const favicon = require('serve-favicon')
const express = require('express')
const session = require('express-session');
const MongoStore = require('connect-mongo')(session);
const RedisStore = require('connect-redis')(session);
const compression = require('compression')
const lurCache = require('lru-cache')
const ueditor = require("ueditor")
@ -96,21 +97,36 @@ app.use(bodyParser.urlencoded({ extended: true }))
// cookie 解析中间件
app.use(cookieParser(settings.session_secret));
// session配置
app.use(session({ //session持久化配置
secret: settings.encrypt_key,
// key: "kvkenskey",
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 1
},
resave: false,
saveUninitialized: true,
store: new MongoStore({
db: "session",
host: "localhost",
port: 27017,
url: !isProd ? settings.URL : 'mongodb://' + settings.USERNAME + ':' + settings.PASSWORD + '@' + settings.HOST + ':' + settings.PORT + '/' + settings.DB + ''
})
}));
let sessionConfig = {};
if (settings.openRedis) {
sessionConfig = {
secret: settings.session_secret,
store: new RedisStore({
port: settings.redis_port,
host: settings.redis_host,
pass: settings.redis_psd,
ttl: 1800 // 过期时间
}),
resave: true,
saveUninitialized: true
}
} else {
sessionConfig = {
secret: settings.encrypt_key,
cookie: {
maxAge: 1000 * 60 * 10
},
resave: false,
saveUninitialized: true,
store: new MongoStore({
db: "session",
host: "localhost",
port: 27017,
url: !isProd ? settings.URL : 'mongodb://' + settings.USERNAME + ':' + settings.PASSWORD + '@' + settings.HOST + ':' + settings.PORT + '/' + settings.DB + ''
})
}
}
app.use(session(sessionConfig));
// 鉴权用户
app.use(authUser.auth);
// 初始化日志目录

View File

@ -6,7 +6,7 @@ const formidable = require('formidable');
const { service, settings, validatorUtil, logUtil, siteFunc } = require('../../../utils');
const shortid = require('shortid');
const validator = require('validator')
const _ = require('lodash')
function checkFormData(req, res, fields) {
let errMsg = '';
if (fields._id && !siteFunc.checkCurrentId(fields._id)) {
@ -63,9 +63,12 @@ class Content {
}
if (sortby) {
for (const item of sortby) {
sortObj[item] = -1
}
// for (const item of sortby) {
// sortObj[item] = -1
// }
delete sortObj.date;
sortObj[sortby] = -1;
}
if (state) {
@ -169,7 +172,7 @@ class Content {
content && (content.commentNum = commentNum);
// 推荐文章查询
const totalContents = await ContentModel.count({});
const randomArticles = await ContentModel.find({}, 'stitle sImg').skip(Math.floor(totalContents * Math.random())).limit(4);
const randomArticles = await ContentModel.find({}, 'stitle sImg').skip(Math.floor(totalContents * Math.random())).limit(6);
res.send({
state: 'success',
doc: content || {},
@ -187,6 +190,33 @@ class Content {
}
}
async updateLikeNum(req, res, next) {
let targetId = req.query.contentId;
let userId = req.session.user._id;
try {
let oldContent = await ContentModel.findOne({ _id: targetId });
if (!_.isEmpty(oldContent) && (oldContent.likeUserIds).indexOf(userId) > -1) {
res.send({
state: 'error',
type: 'ERROR_IN_UPDATE_DATA',
message: '不可重复提交',
})
} else {
let newContent = await ContentModel.findOneAndUpdate({ _id: targetId }, { '$inc': { 'likeNum': 1 }, '$push': { 'likeUserIds': userId } });
res.send({
state: 'success',
likeNum: newContent.likeNum + 1
});
}
} catch (error) {
res.send({
state: 'error',
type: 'ERROR_IN_SAVE_DATA',
message: '更新数据失败:' + error,
})
}
}
async addContent(req, res, next) {
const form = new formidable.IncomingForm();
form.parse(req, async (err, fields, files) => {
@ -216,7 +246,8 @@ class Content {
isTop: fields.isTop,
from: fields.from,
discription: fields.discription,
comments: fields.comments
comments: fields.comments,
likeUserIds: []
}
const newContent = new ContentModel(groupObj);

View File

@ -183,12 +183,10 @@ class Message {
message: errMsg,
})
}
let contentIdArr = [];
for (let i = 0; i < targetIds.length; i++) {
let msgObj = await MessageModel.findOne({ _id: targetIds[i] });
if (msgObj && contentIdArr.indexOf(msgObj.contentId) == -1) {
// 避免重复删除
contentIdArr.push(msgObj.contentId);
if (msgObj) {
await ContentModel.findOneAndUpdate({ _id: msgObj.contentId }, { '$inc': { 'commentNum': -1 } })
}
}

View File

@ -66,7 +66,7 @@ class User {
queryObj.userName = { $regex: reKey }
}
const Users = await UserModel.find(queryObj, { password: 0}).sort({ date: -1 }).skip(Number(pageSize) * (Number(current) - 1)).limit(Number(pageSize));
const Users = await UserModel.find(queryObj, { password: 0 }).sort({ date: -1 }).skip(Number(pageSize) * (Number(current) - 1)).limit(Number(pageSize));
const totalItems = await UserModel.count(queryObj);
res.send({
state: 'success',
@ -113,10 +113,12 @@ class User {
email: fields.email,
logo: fields.logo,
phoneNum: fields.phoneNum || '',
password: service.encrypt(fields.password, settings.encrypt_key),
confirm: fields.confirm,
group: fields.group
}
if (fields.password) {
userObj.password = service.encrypt(fields.password, settings.encrypt_key);
}
const item_id = fields._id;
try {

View File

@ -14,7 +14,7 @@ var AdminUser = require('./AdminUser');
var ContentSchema = new Schema({
_id: {
type: String,
'default': shortid.generate
},
title: String,
@ -35,7 +35,7 @@ var ContentSchema = new Schema({
comments: String,
commentNum: { type: Number, default: 0 }, // 评论数
likeNum: { type: Number, default: 0 }, // 喜欢数
likeUserIds: String, // 喜欢该文章的用户ID集合
likeUserIds: [{ type: String, default: [] }], // 喜欢该文章的用户ID集合
from: { type: String, default: '1' } // 来源 1为原创 2为转载
});
@ -48,7 +48,7 @@ ContentSchema.path('date').get(function (v) {
return moment(v).startOf('hour').fromNow();
});
ContentSchema.path('updateDate').get(function (v) {
return moment(v).format("YYYY-MM-DD HH:mm");
return moment(v).format("YYYY-MM-DD");
});
var Content = mongoose.model("Content", ContentSchema);

View File

@ -15,6 +15,7 @@ const authUser = require('../../utils/middleware/authUser');
const { AdminUser, ContentCategory, Content, ContentTag, User, Message, SystemConfig, UserNotify, Ads } = require('../lib/controller');
const _ = require('lodash');
const qr = require('qr-image')
function checkUserSession(req, res, next) {
if (!_.isEmpty(req.session.user)) {
@ -55,6 +56,22 @@ router.get('/content/getSimpleListByParams', (req, res, next) => { req.query.sta
// 查询文档详情
router.get('/content/getContent', Content.getOneContent)
// 更新喜欢文档
router.get('/content/updateLikeNum', checkUserSession, Content.updateLikeNum)
//文章二维码生成
router.get('/qrImg', (req, res, next) => {
let detailLink = req.query.detailLink;
try {
let img = qr.image(detailLink, { size: 10 });
res.writeHead(200, { 'Content-Type': 'image/png' });
img.pipe(res);
} catch (e) {
res.writeHead(414, { 'Content-Type': 'text/html' });
res.end('<h1>414 Request-URI Too Large</h1>');
}
});
// 用户登录
router.post('/users/doLogin', User.loginAction);

View File

@ -2,7 +2,6 @@ import Vue from 'vue'
import { createApp } from './app'
import ProgressBar from './index/components/ProgressBar.vue'
import "./index/assets/base.css"
import '../node_modules/element-ui/lib/theme-chalk/index.css'
import '../node_modules/element-ui/lib/theme-chalk/display.css';
import '../node_modules/font-awesome/css/font-awesome.min.css'
@ -22,9 +21,6 @@ router.beforeResolve((to, from, next) => {
const matched = router.getMatchedComponents(to)
const prevMatched = router.getMatchedComponents(from)
// [a, b]
// [a, b, c, d]
// => [c, d]
let diffed = false
const activated = matched.filter((c, i) => diffed || (diffed = prevMatched[i] !== c))

View File

@ -1,5 +1,5 @@
<style lang="scss">
@import "~front_public";
</style>
<template>
<div id="app">

View File

@ -1,171 +0,0 @@
html,
body {
padding: 0;
margin: 0;
background: #ffffff;
-webkit-font-smoothing: antialiased;
color: rgb(51, 51, 51);
font-family: -apple-system, SF UI Text, Arial, PingFang SC, Hiragino Sans GB, Microsoft YaHei, WenQuanYi Micro Hei, sans-serif;
}
.fade-enter-active,
.fade-leave-active {
transition: all .2s ease;
}
.fade-enter,
.fade-leave-active {
opacity: 0;
}
a:link,
a:active,
a:visited {
text-decoration: none;
color: #333333;
}
a:hover {
color: #409EFF
}
.content-main a:link,
.content-main a:active,
.content-main a:visited {
color: #409EFF;
}
ul {
margin: 0;
padding: 0;
}
li {
list-style-type: none;
}
.h1,
.h2,
.h3,
.h4,
.h5,
.h6,
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
}
h2 {
font-size: 18px;
}
h3 {
font-size: 16px;
}
pre {
display: block;
padding: 15px;
margin: 0 0 10px;
font-size: 13px;
line-height: 1.42857143;
color: #657b83;
word-break: break-all;
word-wrap: break-word;
background-color: #f5f5f5;
border: 1px solid #ccc;
border-radius: 3px;
overflow: auto;
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
}
.contentContainer {
margin-top: 30px;
}
.content-item:last-child {
border: none !important;
}
.normaltitle,
.catetitle {
font-size: 14px;
color: #fff;
text-align: left;
font-weight: normal;
margin: 0;
margin-bottom: 10px;
}
.normaltitle span {
background-color: #409EFF;
height: 30px;
line-height: 30px;
padding: 2px 12px;
display: inline-block;
}
.catetitle {
color: #333333;
}
.login-form {
margin: 0 auto;
padding-top: 6%;
padding-bottom: 100px;
}
.login-form .submit-btn {
text-align: left;
}
.login-form .login-container {
background-clip: padding-box;
padding: 25px 35px 10px 35px;
background: #fff;
box-shadow: 0 0 8px rgba(0, 0, 0, .1);
border-radius: 4px;
}
.login-form .login-container .title {
margin: 0px auto 40px auto;
text-align: center;
color: #505458;
}
.login-form .login-container.remember {
margin: 0px 0px 35px 0px;
}
@media screen and (max-width:768px) {
.header .header-main {
padding: 5px 0 !important;
}
.header .header-main .header-logo {
width: 150px !important;
margin: 0 auto;
}
.header .header-main .header-nav {
border-left: none !important;
margin-left: 0 !important;
}
.footer .sitemap {
display: none !important;
}
.content-item {
padding-bottom: 15px !important;
}
}
@media screen and (max-width:1200px) {
.search-pannel {
display: none !important;
}
}

View File

@ -0,0 +1,69 @@
.admin-logo-title {
h3 {
color: #99a9bf;
font-size: 35px;
text-align: center;
font-weight: normal;
}
}
.admin-login-form {
margin: 0 auto;
margin-top: 70px;
margin-bottom: 100px;
input {
border-top-right-radius: 4px !important;
border-bottom-right-radius: 4px !important;
}
.el-input-group__prepend {
padding: 0 10px;
}
.submit-btn {
text-align: center;
.el-button {
width: 100%;
}
}
.imageCode {
width: 8rem;
height: 2rem;
float: right;
}
.login-container {
-webkit-border-radius: 5px;
border-radius: 5px;
-moz-border-radius: 5px;
background-clip: padding-box;
background: transparent;
border: 1px solid #eaeaea;
box-shadow: 0 0 20px #cac6c6;
h3 {
margin: 0;
border-bottom: 1px solid #e9eaec;
padding: 14px 16px;
line-height: 1;
font-weight: 500;
i {
display: inline-block;
margin-right: 10px;
}
}
.loginForm {
padding: 14px 16px;
}
.el-form-item {
margin-bottom: 15px;
.el-form-item__error {
left: 2rem;
}
}
.title {
margin: 0px auto 40px auto;
text-align: center;
color: #505458;
}
.remember {
margin: 0px 0px 35px 0px;
}
}
}

View File

@ -0,0 +1,20 @@
// 回到顶部组件
.page-component-up {
background-color: #fff;
position: fixed;
right: 50px;
bottom: 150px;
width: 40px;
height: 40px;
border-radius: 20px;
cursor: pointer;
transition: 0.3s;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.12);
i {
color: $color-primary;
display: block;
line-height: 40px;
text-align: center;
font-size: 18px;
}
}

View File

@ -0,0 +1,23 @@
// 右侧分类
.catesMenu {
font-size: 14px;
.parent-name {
font-weight: 700;
height: 30px;
line-height: 30px;
padding-left: 30px;
margin-top: 15px;
}
.cate-list {
padding-left: 40px;
.active a:link,
.active a:visited {
color: #3ca5f6;
}
}
.cate-list li {
font-weight: normal;
height: 30px;
line-height: 30px;
}
}

View File

@ -0,0 +1,32 @@
// 轮播图
.content-ads {
.el-carousel__item h3 {
color: #475669;
font-size: 18px;
opacity: 0.75;
margin: 0;
}
.el-carousel__item:nth-child(2n) {
background-color: #99a9bf;
}
.el-carousel__item:nth-child(2n + 1) {
background-color: #d3dce6;
}
.img-pannel {
img {
width: 100%;
}
}
.text-pannel ul li {
display: inline-block;
margin-right: 10px;
}
.case-title {
color: #b4bccc;
margin: 15px auto;
font-size: 13px;
}
.case-item {
margin-bottom: 20px;
}
}

View File

@ -0,0 +1,17 @@
.footer {
font-size: 14px;
padding: 35px 0;
color: #5f676f;
background: #2d3237;
ul {
li {
text-align: center;
line-height: 35px;
padding: 0 10px;
a:link,
a:visited {
color: #76818c;
}
}
}
}

View File

@ -0,0 +1,159 @@
// header组件
.drop-menu {
width: 96%;
}
.header {
position: fixed;
left: 0;
top: 0;
z-index: 999;
width: 100%;
border: 0;
background: #fff;
-webkit-box-shadow: 0 0 3px 1px rgba(0, 0, 0, 0.1);
box-shadow: 0 0 3px 1px rgba(0, 0, 0, 0.1);
.header-main {
margin: 0 auto;
overflow: hidden;
.header-logo {
a {
text-decoration: none;
color: #333333;
height: 62px;
display: table-cell;
vertical-align: middle;
}
img {
max-height: 38px;
width: auto;
margin-top: 8px;
}
}
.header-nav {
font-size: 15px;
float: left;
width: 100%;
.el-dropdown {
font-size: 15px;
color: #333333;
}
ul {
li {
display: inline-block;
a {
padding: 0 20px;
line-height: 62px;
color: #333333;
}
}
li.active {
a:link,
a:visited {
color: $color-primary;
}
}
}
}
.right-pannel {
margin-top: 10px;
}
}
.toggle-menu,
.toggle-search {
color: #aaaaaa;
font-size: 18px;
padding-left: 0.6rem;
padding-right: 0.6rem;
border: none;
margin-top: 0.4rem;
}
.toggle-search {
float: right;
}
} // loginPannle
.login-pannel {
float: right;
text-align: right;
ul {
li {
cursor: pointer;
color: $color-primary;
height: 40px;
line-height: 40px;
display: inline-block;
font-size: 14px;
i {
font-size: 12px;
}
.logo {
width: 1.8rem;
height: 1.8rem;
display: inline-block;
vertical-align: middle;
margin-right: 5px;
img {
width: 100%;
border-radius: 50%;
}
}
}
.login-txt {
a:first-child {
margin-right: 10px;
}
}
}
} // searchPannel
.search-pannel {
width: 100%;
display: inline-block;
margin-top: 5px;
text-align: right;
.input-area {
display: inline-block;
position: relative;
i {
display: inline-block;
cursor: pointer;
font-weight: 700px;
color: #cccccc;
position: absolute;
top: 5px;
right: 14px;
font-size: 20px;
}
width: 50px;
height: 30px;
background: #fff;
border-left: 1px solid #eee;
border-right: 1px solid #eee;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
vertical-align: top;
-webkit-transition: all 0.3s ease-out 0s;
-o-transition: all 0.3s ease-out 0s;
transition: all 0.3s ease-out 0s;
input {
border: none;
width: 0;
}
}
.input-area.active {
width: 184px;
-webkit-transition: all 0.3s ease-out 0s;
-o-transition: all 0.3s ease-out 0s;
transition: all 0.3s ease-out 0s;
input {
width: 155px;
padding: 0 10px;
-webkit-transition: all 0.3s ease-out 0s;
-o-transition: all 0.3s ease-out 0s;
transition: all 0.3s ease-out 0s;
}
}
.el-form-item {
margin-bottom: 0;
}
}

View File

@ -0,0 +1,46 @@
// 热门话题
// 近期文档
.hot-content-list {
.content-list {
text-align: left;
ul {
li {
font-size: 14px;
padding: 0 0 0.75rem 1rem;
position: relative;
.triangle {
position: absolute;
top: 0.5rem;
left: 0;
width: 0;
height: 0;
border-style: solid;
border-color: #fff #fff #fff #cccccc;
-webkit-transform-origin: 25% center;
transform-origin: 25% center;
border-width: 4px;
}
.con {
-webkit-transition: opacity 0.5s ease-in;
transition: opacity 0.5s ease-in;
.title {
display: block;
}
.time {
padding-top: 3px;
display: inline-block;
color: #a4abb1;
}
}
.con a:link,
.con a:visited {
color: #666666;
}
.con a:hover {
color: $color-primary;
}
}
}
}
}

View File

@ -0,0 +1,121 @@
// 留言组件
.content-message,
.content-message-list {
padding: 0 !important;
h3 {
padding: 0;
border: none;
font-size: 18px;
margin: 30px 0 20px;
font-weight: 500;
}
}
.content-message {
.give-message {
.el-form-item {
margin-bottom: 0;
}
.el-form-item__content {
margin-left: 0 !important;
}
.el-textarea__inner {
border-color: #efefef !important;
border-bottom-color: #f5f5f5 !important;
border-width: 1px !important;
}
.el-form-item__error {
padding-top: 20px;
text-align: right;
right: 100px;
}
.user-notice {
float: left;
font-size: 13px;
a:link,
a:visited {
color: $color-primary;
}
}
.send-content {
// margin-bottom: 10px;
}
.send-button {
padding: 5px 15px;
text-align: right;
background: #fbfbfb;
border: 1px solid #efefef;
border-top: 0;
-webkit-border-bottom-left-radius: 2px;
border-bottom-left-radius: 2px;
-webkit-border-bottom-right-radius: 2px;
border-bottom-right-radius: 2px;
overflow: hidden;
}
}
}
.content-message-list {
ul {
li {
border-top: 1px solid #efefef;
padding: 15px 0 24px;
font-size: 14px;
.user-logo {
img {
width: 82%;
min-height: 2rem;
border-radius: 50%;
}
}
.user-content {
position: relative;
color: #666666;
padding-left: 15px;
word-break: break-all;
.reply {
position: absolute;
display: none;
bottom: -20px;
right: 0;
}
}
.reply-message {
margin-top: 30px;
padding-left: 25px;
.el-form-item {
margin-bottom: 0px;
margin-top: 5px;
text-align: right;
.el-form-item__error {
right: 100px;
padding-top: 20px;
}
}
}
.user-name {
margin: 0px 0 0px 15px;
color: $color-primary;
.name {
display: inline-block;
}
.time {
font-size: 11px;
display: inline-block;
color: #777;
}
}
}
li:hover {
margin: 0 -30px;
padding: 15px 30px 24px;
background: #fafafa;
-webkit-transition: all 0.3s ease-out 0s;
-o-transition: all 0.3s ease-out 0s;
transition: all 0.3s ease-out 0s;
.reply {
display: block;
}
}
}
}

View File

@ -0,0 +1,27 @@
$divide:10;
$pswWidth:375;
$ppr:375/$divide/1; //定义单位以iphone6为标准
@mixin font-dpr($font-size) {
font-size: #{$font-size / $ppr}rem;
}
@mixin renderRem($property, $values...) {
$max: length($values);
$remValues: '';
@for $i from 1 through $max {
$newValue: nth($values, $i);
$value: '';
@if $newValue==auto {
$value: $newValue;
$remValues: #{$remValues + $value};
}
@else {
$value: $newValue * $divide / $pswWidth;
$remValues: #{$remValues + $value}rem;
}
@if $i < $max {
$remValues:#{$remValues + " "}
}
}
#{$property}:$remValues;
}

View File

@ -0,0 +1,5 @@
// 分页组件
.content-pagination {
margin: 30px 0;
text-align: center;
}

View File

@ -0,0 +1,18 @@
// 公共模块-pannelbox
.pannel-box {
padding: 15px;
margin-bottom: 20px;
background: #fff;
}
.pannel-title {
margin: 0 0 10px;
padding: 1px 0 1px 7px;
font-size: 16px;
font-weight: normal;
border-left: 3px solid #3ca5f6;
}
.pannel-footer {
clear: both;
}

View File

@ -0,0 +1,13 @@
// 进度条
.progress {
position: fixed;
top: 0px;
left: 0px;
right: 0px;
height: 2px;
width: 0%;
transition: width 0.2s, opacity 0.4s;
opacity: 1;
background-color: #efc14e;
z-index: 999999;
}

View File

@ -0,0 +1,18 @@
// 随机文档
.random-articls {
margin-top: 10px;
margin-bottom: 25px;
.contentImg {
height: 8rem;
margin-bottom: 8px;
}
.title {
font-size: 14px;
display: block;
text-align: left;
padding: 0px;
min-height: 52px;
line-height: 20px;
word-wrap: break-word;
}
}

View File

@ -0,0 +1,29 @@
// 推荐文档
.rec-content-list {
margin-bottom: 30px;
.content-list {
text-align: left;
padding-top: 5px;
.hot-li {
margin: 0 0 10px;
display: inline-block;
.contentImg {
height: 4.7rem;
display: block;
img {
width: 100%;
height: 100%;
border-radius: 2px;
}
}
.title {
font-size: 12px;
word-break: break-all;
line-height: 16px;
display: inline-block;
height: 45px;
vertical-align: text-top;
}
}
}
}

View File

@ -0,0 +1,48 @@
// 近期文档
.recent-content-list {
margin-bottom: 30px;
.content-list {
text-align: left;
ul {
.hot-li:last-child {
border: none;
}
li {
position: relative;
margin: 0;
padding: 7px 0 8px;
overflow: hidden;
font-size: 14px;
display: block;
img {
width: 100%;
vertical-align: top;
border-radius: 2px;
}
.right-text {
position: relative;
line-height: 1.4;
.title {
font-size: 13px;
height: 2.3rem;
display: block;
margin-bottom: 10px;
word-break: break-all;
}
span {
display: block;
margin: 0;
font-size: 12px;
color: #999;
line-height: 14px; // margin-top: 10px;
}
}
}
}
}
}
.recent-content-list.fixed {
position: fixed;
top: 62px;
}

View File

@ -0,0 +1,56 @@
.user-menu-options {
margin-top: 2rem;
text-align: left;
li {
position: relative;
margin-bottom: 1px;
background: #eee;
cursor: pointer;
a {
color: #999;
padding: 4px 0;
font-size: 14px;
height: 30px;
line-height: 30px;
display: block;
i {
padding: 0 15px;
font-size: 20px;
line-height: 30px;
}
span.label {
font-size: 14px;
color: #555;
padding-left: 15px;
}
span.fa {
font-size: 20px;
padding: 0 15px;
line-height: 30px;
border-right: 1px solid #ccc;
}
span.fa-asterisk {
font-size: 15px;
}
.fa-angle-right {
right: 0;
top: 6px;
font-size: 26px;
position: absolute;
opacity: .6;
}
}
a:hover {
color: #444;
background: #ddd;
}
}
li.active {
span.label {
font-weight: 700;
}
span.fa {
color: $color-primary;
}
}
}

View File

@ -0,0 +1,47 @@
// 公共模块-userbar
.user-bar {
background-color: #444444;
width: 100%;
z-index: 100;
padding: 10px 0;
text-align: left; // box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .05);
transition: all 0.2s;
-webkit-transform: translateZ(0);
transform: translateZ(0);
.bar-items {
ul {
li {
position: relative;
border-radius: 4px;
padding: 6px 10px 6px 28px;
margin-left: 10px;
display: inline-block;
font-size: 14px;
font-weight: 600;
color: #ffffff;
i {
position: absolute;
left: 11px;
top: 11px;
}
a:link,
a:visited {
color: #ffffff;
}
}
li.active {
a:link,
a:visited {
color: #ffffff;
}
background-color: $color-primary;
}
li.active:hover {
background-color: $color-primary;
}
li:hover {
background-color: #555;
}
}
}
}

View File

@ -0,0 +1,613 @@
@import "mixin.scss";
$color-primary: #409EFF; //主色调
html,
body {
padding: 0;
margin: 0;
-webkit-font-smoothing: antialiased;
color: rgb(51, 51, 51);
line-height: 1.8;
background: #f5f5f5;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Ubuntu, Helvetica Neue, Helvetica, Arial, PingFang SC, Hiragino Sans GB, Microsoft YaHei UI, Microsoft YaHei, Source Han Sans CN, sans-serif;
}
.fade-enter-active,
.fade-leave-active {
transition: all .2s ease;
}
.fade-enter,
.fade-leave-active {
opacity: 0;
}
a:link,
a:active,
a:visited {
text-decoration: none;
color: #333333;
}
a:hover {
color: $color-primary
}
ul {
margin: 0;
padding: 0;
}
li {
list-style-type: none;
}
.h1,
.h2,
.h3,
.h4,
.h5,
.h6,
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
}
h2 {
font-size: 18px;
}
h3 {
font-size: 16px;
}
pre {
display: block;
padding: 10px;
margin: 0 0 10px;
font-size: 13px;
line-height: 1.42857143;
color: #657b83;
word-break: break-all;
word-wrap: break-word;
background-color: #f5f5f5;
border: 1px solid #ccc;
border-radius: 3px;
overflow: auto;
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
}
input,
textarea {
border-radius: 0 !important;
}
.el-dropdown-menu {
li {
font-size: 14px;
}
}
.prop-wechat {
text-align: center;
h5 {
line-height: 25px;
font-weight: 700;
margin-bottom: 0;
}
img {
width: 10rem;
height: 10rem;
}
}
@import 'pannelbox.scss';
@import 'header.scss';
@import 'footer.scss';
@import 'backToTop.scss';
@import 'catesMenu.scss';
@import 'contentAds.scss';
@import 'hotContent.scss';
@import 'recContent.scss';
@import 'messageBox.scss';
@import 'pagonation.scss';
@import 'progress.scss';
@import 'randomArticls.scss';
@import 'userBar.scss';
@import 'recentContent.scss';
@import 'ucLeftMenu.scss';
@import 'adminLogin.scss';
.contentContainer {
padding-top: 62px;
.mainbody {
padding: 20px 0px 20px;
.login-form,
.user-info,
.login-main {
.el-form-item {
.el-input .el-input__inner,
.el-textarea .el-textarea__inner {
border: 2px solid #dddddd;
}
.el-form-item__label {
line-height: 22px;
padding-bottom: 8px !important;
font-weight: 600;
font-size: 15px;
}
}
} // 文档列表
.content-list {
.main-list {
padding-bottom: 10px;
background-color: #fff;
.column-wrap {
position: relative;
height: 30px;
line-height: 30px;
font-size: 20px;
color: #303030;
padding: 0 15px;
margin-bottom: 15px;
h1 {
margin: 0;
padding: 0;
font-size: 18px;
line-height: 48px;
font-weight: 500;
color: #333;
border-bottom: 1px solid #EFEFEF;
}
}
.cate-pannle-menu {
position: relative;
padding: 0 15px;
.el-menu--horizontal {
border-bottom: solid 1px #efefef;
.el-menu-item {
height: 48px;
line-height: 48px;
padding: 0;
margin-right: 31px;
font-size: 16px;
}
.el-menu-item.is-active {
color: #3ca5f6;
font-weight: 500;
}
}
}
.article-list {
padding: 0 15px;
margin: 0 0 20px;
list-style: none;
.post-b {
border-bottom: 1px solid #efefef; // margin-bottom: 25px;
padding: 20px 0px;
.content-item {
.post-angle {
position: absolute;
left: -10px;
height: 21px;
color: #fff;
text-align: center;
background-color: #f63756;
line-height: 24px;
padding: 0 10px;
z-index: 101;
top: 0px;
font-size: 13px;
}
.post-angle:after {
content: " ";
position: absolute;
left: 0;
top: 21px;
width: 0;
height: 0;
border-top: 6px solid #cd213d;
border-left: 10px solid transparent;
}
.contentImg {
width: 100%;
overflow: hidden;
img {
width: 100%;
min-height: 6rem;
vertical-align: top;
}
height: auto;
display: block;
position: relative;
.content-cate {
position: absolute;
top: 0.4rem;
left: 0.4rem;
display: block;
padding: 0 0.5rem;
color: #fff;
background: rgba(0, 0, 0, 0.5);
font-size: 0.6rem;
text-align: center;
border-radius: 1rem;
z-index: 11;
}
}
.discription {
text-align: left; // min-height: 152px;
h2 {
margin: 0;
font-size: 20px;
word-break: break-all;
font-weight: 400;
line-height: 1.4;
}
.dis {
margin: 10px 0;
font-size: 13px;
color: #666666;
}
.post-meta {
a:link,
a:visited {
color: #3ca5f6;
}
position: absolute;
bottom: 0;
li {
display: inline-block;
font-size: 13px;
color: #999999;
margin: 0px 10px 0px 0;
}
}
}
}
.content-item:last-child {
border: none !important;
}
}
.post-b:last-child {
border: none;
}
.post-b:hover {
margin: 0 -15px;
padding: 20px 15px;
background: #fafafa;
-webkit-transition: all 0.3s ease 0s;
-o-transition: all 0.3s ease 0s;
transition: all 0.3s ease 0s;
}
}
}
.main-right {}
} // 缩略图效果
.contentImg {
overflow: hidden;
-webkit-transition: -webkit-box-shadow .3s ease 0s;
transition: -webkit-box-shadow .3s ease 0s;
-o-transition: box-shadow .3s ease 0s;
transition: box-shadow .3s ease 0s;
transition: box-shadow .3s ease 0s, -webkit-box-shadow .3s ease 0s;
img {
-webkit-transition: all 0.3s ease-out 0s;
-o-transition: all 0.3s ease-out 0s;
transition: all 0.3s ease-out 0s;
}
img:hover {
-webkit-transform: scale(1.03);
-ms-transform: scale(1.03);
transform: scale(1.03);
-webkit-transition: all .3s ease-out 0s;
-o-transition: all .3s ease-out 0s;
transition: all .3s ease-out 0s;
}
}
.contentImg:hover {
-webkit-box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.3);
box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.3);
-webkit-transition: -webkit-box-shadow .3s ease 0s;
transition: -webkit-box-shadow .3s ease 0s;
-o-transition: box-shadow .3s ease 0s;
transition: box-shadow .3s ease 0s;
transition: box-shadow .3s ease 0s, -webkit-box-shadow .3s ease 0s;
}
} // 文档详情
.content-detail {
color: #3f3f3f;
.hentry {
background: #fff;
padding: 30px;
margin-bottom: 20px;
}
.from {
color: #fa5555;
font-size: 13px;
font-weight: normal;
}
img {
max-width: 100% !important;
height: auto;
}
.content-title {
margin-top: 0;
font-weight: 700;
font-size: 20px;
}
.content-author {
color: #969696;
ul {
li.author-name {
color: $color-primary;
}
li {
display: inline-block;
margin-bottom: 10px;
font-size: 13px;
}
}
}
.content-main {
font-size: 15px;
}
.meta-bottom {
margin-top: 40px;
margin-bottom: 40px;
}
.share-group {
text-align: right;
ul {
li {
padding: 0 10px;
vertical-align: middle;
display: inline-block;
cursor: pointer;
i {
font-size: 18px;
}
.fa-qq {
color: #4296d3;
}
.fa-wechat {
color: #00bb29;
}
.fa-weibo {
color: #e05244;
}
.fa-heart {
color: #cccccc;
}
.fa-heart:hover {
color: #e05244;
}
}
li.like {
height: 18px;
line-height: 18px;
color: #cccccc;
border-right: 1px solid #ccc;
padding-right: 20px;
span {
font-size: 12px;
}
}
li.more {
border: none;
}
}
}
} // 案例展示
.case-box {
.case-list {
background-color: #ffffff;
padding: 20px;
h3 {
margin: 5px 10px 20px;
font-size: 15px;
color: #878d99;
.el-button--mini {
padding: 5px 8px;
font-weight: 700;
}
}
img {
width: 100%;
height: 2rem;
}
}
min-height: 400px;
} // 用户中心
.user-center {
.user-message {
background-color: #ffffff;
}
.user-info {
background-color: #ffffff;
.basic-info {
padding: 30px;
.left-pannel {
padding: 0 30px;
.user-left-menu {
.user-logo {
margin: 0 auto;
width: 100px;
text-align: center;
margin-top: 4rem;
.el-form-item {
margin-bottom: 5px;
}
span {
font-weight: 700;
color: #555;
text-decoration: none !important;
font-size: 18px;
line-height: 1.4em;
}
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 50%;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: $color-primary;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 100px;
height: 100px;
line-height: 100px;
text-align: center;
}
.avatar {
width: 100px;
height: 100px;
display: block;
}
}
}
}
.right-pannel {
.top-bar {
font-size: 18px;
line-height: 18px;
font-weight: 700;
color: #555;
margin-bottom: 15px;
i {
margin-right: 10px;
}
}
}
}
.el-button {
padding: 12px 50px;
font-size: 15px;
}
}
} // 登录注册
.login-main {
background-color: #fff;
.login-box {
width: 50%;
margin: 0 auto;
margin-top: 20px;
padding: 20px 0px 100px;
.title {
text-align: center;
font-size: 24px;
position: relative;
}
.title::after {
position: absolute;
left: 50%;
bottom: -15px;
width: 20px;
height: 2px;
margin-left: -10px;
background: #999;
content: '';
}
.el-button {
width: 100%;
padding: 16px 20px;
}
}
}
}
@media screen and (max-width:768px) {
.header {
.header-main {
padding: 5px 0 !important;
.header-logo {
/* width: 150px !important; */
margin-top: .5rem;
a {
height: auto !important;
img {
width: 100% !important;
margin-top: 0;
}
}
}
.header-nav {
border-left: none !important;
margin-left: 0 !important;
}
}
}
.content-item {
.row-list {
margin: 0 !important;
.el-col {
padding: 0 !important;
}
.contentImg {
max-height: 6rem;
}
}
.discription {
margin-top: 10px !important;
line-height: 1.5;
h2 {
font-size: 16px !important;
}
.post-meta {
position: relative !important;
}
}
}
.main-list {
padding: 0 !important;
}
.footer {
display: none !important;
}
.page-component-up {
display: none !important;
}
.contentContainer {
.mainbody {
padding: 10px 0px 10px;
.main-list {
.cate-pannle-menu {
display: none !important;
}
}
}
.content-detail {
.hentry {
padding: 10px !important;
}
.meta-bottom {
margin-top: 20px;
margin-bottom: 25px;
.meta-tags {
margin-bottom: 20px;
}
}
.share-group {
text-align: center !important;
}
.random-articls {
.contentImg {
height: 5rem !important;
}
}
}
.login-main {
.login-box {
width: 80%;
}
}
}
}

View File

@ -2,7 +2,7 @@
<div class="content-ads" v-if="ads.data">
<div class="img-pannel" v-if="ads.data.type == '1'">
<div v-if="ads.data.items.length == 1">
<a :href="ads.data.items[0].link" target="_blank"><img style="border-radius:4px;border: 1px solid #f0f0f0;" :src="ads.data.items[0].sImg" :alt="ads.data.items[0].alt" /></a>
<a :href="ads.data.items[0].link" target="_blank"><img :src="ads.data.items[0].sImg" :alt="ads.data.items[0].alt" /></a>
</div>
<div v-else>
<!-- 轮播展示 -->
@ -17,7 +17,8 @@
</div>
<!-- 橱窗展示 -->
<div v-else>
<el-col class="case-item" :xs="12" :sm="8" :md="6" :lg="6" :xl="6" v-for="(item,index) in ads.data.items" :key="item._id">
<el-row :gutter="20">
<el-col class="case-item" :xs="12" :sm="8" :md="6" :lg="6" :xl="6" v-for="(item,index) in ads.data.items" :key="item._id">
<el-card :body-style="{ padding: '0px' }">
<div style="padding:14px 14px 5px;text-align:center;cursor:point">
<a :href="item.link" target="_blank"><img :src="item.sImg" class="image" :alt="item.alt"></a>
@ -25,6 +26,7 @@
</div>
</el-card>
</el-col>
</el-row>
</div>
</div>
</div>
@ -69,38 +71,5 @@ export default {
</script>
<style lang="scss">
.el-carousel__item h3 {
color: #475669;
font-size: 18px;
opacity: 0.75;
margin: 0;
}
.el-carousel__item:nth-child(2n) {
background-color: #99a9bf;
}
.el-carousel__item:nth-child(2n + 1) {
background-color: #d3dce6;
}
.img-pannel {
img {
width: 100%;
}
}
.text-pannel ul li {
display: inline-block;
margin-right: 10px;
}
.case-title {
color: #b4bccc;
margin: 15px auto;
font-size: 13px;
}
.case-item {
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,46 @@
<template>
<div class="page-component-up" :style="{display:showScroll?'block':'none'}" @click="scrollToTop(300)"><i class="el-icon-caret-top"></i></div>
</template>
<script>
export default {
name: "BackTop",
data() {
return {
showScroll: false
};
},
methods: {
scrollToTop(scrollDuration) {
const scrollHeight = window.scrollY,
scrollStep = Math.PI / (scrollDuration / 15),
cosParameter = scrollHeight / 2;
let scrollCount = 0,
scrollMargin,
scrollInterval = setInterval(function() {
if (window.scrollY != 0) {
scrollCount = scrollCount + 1;
scrollMargin =
cosParameter - cosParameter * Math.cos(scrollCount * scrollStep);
window.scrollTo(0, scrollHeight - scrollMargin);
} else {
clearInterval(scrollInterval);
}
}, 15);
}
},
mounted() {
window.onscroll = () => {
let t = document.documentElement.scrollTop || document.body.scrollTop;
if (t >= 400) {
this.showScroll = true;
} else {
this.showScroll = false;
}
};
}
};
</script>
<style lang="scss">
</style>

View File

@ -52,25 +52,5 @@
}
</script>
<style lang="scss">
.catesMenu {
font-size: 14px;
.parent-name {
font-weight: 700;
height: 30px;
line-height: 30px;
padding-left: 30px;
margin-top: 15px;
}
.cate-list {
padding-left: 40px;
.active a:link,.active a:visited{
color: #3ca5f6;
}
}
.cate-list li {
font-weight: normal;
height: 30px;
line-height: 30px;
}
}
</style>

View File

@ -19,51 +19,28 @@
<script>
let packageJson = require("../../../package.json");
import {
mapGetters,
mapActions
} from 'vuex'
import { mapGetters, mapActions } from "vuex";
export default {
name: 'Footer',
async asyncData({
store,
route
}, config = {
}) {
const {
params: path
} = route
const base = {
...config,
path,
}
await store.dispatch('global/footerConfigs/getSystemConfig')
},
serverCacheKey: props => 'footer',
computed: {
...mapGetters({
systemConfig: 'global/footerConfigs/getSystemConfig'
}),
codeVersion() {
return "DoraCMS " + packageJson.version
}
name: "Footer",
async asyncData({ store, route }, config = {}) {
const { params: path } = route;
const base = {
...config,
path
};
await store.dispatch("global/footerConfigs/getSystemConfig");
},
serverCacheKey: props => "footer",
computed: {
...mapGetters({
systemConfig: "global/footerConfigs/getSystemConfig"
}),
codeVersion() {
return "DoraCMS " + packageJson.version;
}
}
}
};
</script>
<style lang="scss">
.footer {
font-size: 14px;
background: #ffffff;
padding: 20px 0;
ul {
li {
text-align: center;
line-height: 35px;
padding: 0 10px;
}
}
}
</style>

View File

@ -1,18 +1,16 @@
<template>
<div>
<PannelBox title="推荐文章" className="hot-content-list">
<PannelBox title="热门文章" className="hot-content-list">
<div class="content-list">
<ul>
<li class="hot-li" v-for="(item,index) in hotItems" :key="item._id">
<router-link :to="'/details/'+item._id+'.html'" class="continue-reading">
<img :src="item.sImg" :alt="item.title" />
</router-link>
<div class="left-area">
<router-link class="title" :to="'/details/'+item._id+'.html'">{{item.title}}</router-link>
</div>
</li>
</ul>
</div>
<ul>
<li :key='index' v-for="(item,index) in hotItems">
<span class="triangle"></span>
<div class="con">
<router-link class="title" :to="'/details/'+item._id+'.html'">{{item.title}}</router-link>
</div>
</li>
</ul>
</div>
</PannelBox>
</div>
</template>
@ -36,33 +34,5 @@ export default {
</script>
<style lang="scss">
.hot-content-list {
margin-bottom: 30px;
.content-list {
text-align: left;
ul {
.hot-li:last-child {
border: none;
}
li {
position: relative;
padding: 10px 0;
overflow: hidden;
font-size: 14px;
display: block;
height: 2rem;
img {
width: 3rem;
height: 2rem;
position: absolute;
border-radius: 4px;
border: 1px solid #f0f0f0;
}
.left-area {
padding-left: 4rem;
}
}
}
}
}
</style>

View File

@ -1,275 +1,236 @@
<template>
<PannelBox title="评论" className="content-message">
<div>
<el-row :gutter="10">
<el-col :xs="24" :sm="24" :md="24" :lg="24">
<div class="give-message">
<el-form :model="msgFormState.formData" :rules="rules" ref="ruleForm" label-width="0px" class="demo-ruleForm">
<el-form-item class="send-content" prop="content">
<el-input @focus="changeReplyState(false)" type="textarea" :autosize="{ minRows: 2, maxRows: 4}" placeholder="请输入内容" v-model="msgFormState.formData.content">
</el-input>
</el-form-item>
<el-form-item class="send-button">
<div class="user-notice">
<div v-if="loginState.logined">
你好,
<span style="color: #409EFF">{{loginState.userInfo.userName}} !</span>
</div>
<div v-else>
<router-link to="/users/login">登录</router-link>&nbsp;
</div>
</div>
<el-button type="primary" round @click="submitForm('ruleForm')">提交评论</el-button>
</el-form-item>
</el-form>
</div>
</el-col>
</el-row>
</div>
<ul>
<li v-for="(item,index) in userMessageList" :key="index">
<el-row>
<el-col :xs="3" :sm="3" :md="2" :lg="1">
<div class="user-logo">
<div v-if="item.utype == '1'">
<img :src="item.adminAuthor.logo" />
</div>
<div v-else>
<img :src="item.author.logo" />
</div>
</div>
</el-col>
<el-col :xs="21" :sm="21" :md="22" :lg="23">
<div class="user-name">
<div class="name" v-if="item.utype == '1'">
{{item.adminAuthor.userName}}
<span title="管理员" style="color: #409EFF;font-size: 12px;">[
<i class="el-icon-star-on"></i>&nbsp;管理员]</span>
</div>
<div class="name" v-else>{{item.author.userName}}</div>
<span class="time">{{item.date}}</span>
<i @click="replyMsg(item)" class="fa fa-reply"></i>
</div>
<div class="user-content">
<div v-if="item.replyAuthor">
<span style="color: #409EFF">{{'@'+item.replyAuthor.userName}}</span>&nbsp; {{item.content}}
</div>
<div v-else-if="item.adminReplyAuthor">
<span style="color: #409EFF">{{'@'+item.adminReplyAuthor.userName}}</span>&nbsp; {{item.content}}
</div>
<div v-else>
{{item.content}}
</div>
<el-collapse-transition>
<div class="reply-message" v-if="msgFormState.reply && replyObj._id == item._id">
<el-form :model="msgFormState.formData" :rules="replyRules" ref="replyRuleForm" label-width="0px" class="demo-ruleForm">
<el-form-item class="send-content" prop="replyContent">
<el-input @focus="changeReplyState(true)" :autofocus="true" type="textarea" :autosize="{ minRows: 2, maxRows: 4}" placeholder="请输入内容" v-model="msgFormState.formData.replyContent">
</el-input>
</el-form-item>
<el-form-item class="send-button">
<el-button type="primary" round @click="submitForm('replyRuleForm')" size="small">回复</el-button>
</el-form-item>
</el-form>
</div>
</el-collapse-transition>
</div>
</el-col>
</el-row>
</li>
</ul>
</PannelBox>
<div>
<PannelBox title="发表评论" className="content-message">
<div>
<el-row :gutter="10">
<el-col :xs="24" :sm="24" :md="24" :lg="24">
<div class="give-message">
<el-form :model="msgFormState.formData" :rules="rules" ref="ruleForm" label-width="0px" class="demo-ruleForm">
<el-form-item class="send-content" prop="content">
<el-input @focus="changeReplyState(false)" type="textarea" :autosize="{ minRows: 4, maxRows: 8}" placeholder="请输入内容" v-model="msgFormState.formData.content">
</el-input>
</el-form-item>
<el-form-item class="send-button">
<div class="user-notice">
<div v-if="loginState.logined">
你好,
<span style="color: #409EFF">{{loginState.userInfo.userName}} !</span>
</div>
<div v-else>
<router-link to="/users/login">登录</router-link>&nbsp;
</div>
</div>
<el-button type="primary" size="small" @click="submitForm('ruleForm')">发表</el-button>
</el-form-item>
</el-form>
</div>
</el-col>
</el-row>
</div>
</PannelBox>
<PannelBox :title="messagelistTitle" className="content-message-list" v-if="userMessageList.length > 0">
<ul>
<li v-for="(item,index) in userMessageList" :key="index">
<el-row>
<el-col :xs="3" :sm="3" :md="2" :lg="2" :xl="2">
<div class="user-logo">
<div v-if="item.utype == '1'">
<img :src="item.adminAuthor.logo" />
</div>
<div v-else>
<img :src="item.author.logo" />
</div>
</div>
</el-col>
<el-col :xs="21" :sm="21" :md="22" :lg="22" :xl="22">
<div class="user-name">
<div class="name" v-if="item.utype == '1'">
{{item.adminAuthor.userName}}
<span title="管理员" style="color: #409EFF;font-size: 12px;">[
<i class="el-icon-star-on"></i>&nbsp;管理员]</span>
</div>
<div class="name" v-else>{{item.author.userName}}</div>
<span class="time">{{item.date}}</span>
</div>
<div class="user-content">
<div v-if="item.replyAuthor">
<span style="color: #409EFF">{{'@'+item.replyAuthor.userName}}</span>&nbsp; {{item.content}}
</div>
<div v-else-if="item.adminReplyAuthor">
<span style="color: #409EFF">{{'@'+item.adminReplyAuthor.userName}}</span>&nbsp; {{item.content}}
</div>
<div v-else>
{{item.content}}
</div>
<el-button size="mini" class="reply" type="text" @click="replyMsg(item)">回复</el-button>
</div>
<el-collapse-transition>
<div class="reply-message" v-if="msgFormState.reply && replyObj._id == item._id">
<el-form :model="msgFormState.formData" :rules="replyRules" ref="replyRuleForm" label-width="0px" class="demo-ruleForm">
<el-form-item class="send-content" prop="replyContent">
<el-input @focus="changeReplyState(true)" :autofocus="true" type="textarea" :autosize="{ minRows: 4, maxRows: 8}" :placeholder="replyPlaceholder" v-model="msgFormState.formData.replyContent">
</el-input>
</el-form-item>
<el-form-item class="send-button">
<el-button type="primary" @click="submitForm('replyRuleForm')" size="small">回复</el-button>
</el-form-item>
</el-form>
</div>
</el-collapse-transition>
</el-col>
</el-row>
</li>
</ul>
</PannelBox>
</div>
</template>
<script>
import {
mapGetters
} from 'vuex'
import api from '~api'
import _ from 'lodash'
import PannelBox from './PannelBox.vue'
import { mapGetters } from "vuex";
import api from "~api";
import _ from "lodash";
import PannelBox from "./PannelBox.vue";
export default {
name: 'Message',
data() {
return {
replyObj: {},
rules: {
content: [{
required: true,
message: '请填写评论',
trigger: 'blur'
}, {
min: 5,
max: 200,
message: '请输入5-200个字符',
trigger: 'blur'
}]
},
replyRules: {
replyContent: [{
required: true,
message: '请填写回复',
trigger: 'blur'
}, {
min: 5,
max: 200,
message: '请输入5-200个字符',
trigger: 'blur'
}]
}
}
},
props: {
userMessageList: Array,
contentId: String
},
computed: {
...mapGetters({
msgFormState: 'global/message/getMessageForm',
loginState: 'frontend/user/getSessionState'
})
},
components: {
PannelBox
},
methods: {
changeReplyState(state) {
if (!state) this.replyObj = {};
this.$store.dispatch('global/message/messageform', { reply: state })
},
replyMsg(item) {
this.replyObj = item;
let currentMsgAuthor = !_.isEmpty(item.author) ? item.author : item.adminAuthor;
let formParams = { replyAuthor: '', adminReplyAuthor: '', relationMsgId: item._id, replyContent: "@" + currentMsgAuthor.userName + " " };
if(!_.isEmpty(item.author)){
formParams.replyAuthor = currentMsgAuthor._id;
}else{
formParams.adminReplyAuthor = currentMsgAuthor._id
}
this.$store.dispatch('global/message/messageform', { reply: true, formData: formParams})
},
submitForm(formName) {
if (!this.loginState.logined) {
this.$router.push('/users/login');
} else {
let targetForm = this.msgFormState.reply ? this.$refs[formName][0] : this.$refs[formName];
targetForm.validate((valid) => {
if (valid) {
let params = this.msgFormState.formData;
if (this.msgFormState.formData.replyContent) {
let currentMsgAuthor = !_.isEmpty(this.replyObj.author) ? this.replyObj.author : this.replyObj.adminAuthor;
let oldContent = this.msgFormState.formData.replyContent;
params.content = oldContent.replace("@" + currentMsgAuthor.userName + " ", "");
} else {
params['replyAuthor'] = '';
params['relationMsgId'] = '';
params['replyContent'] = '';
}
params.contentId = this.contentId;
api.post('message/post', params).then((result) => {
if (result.data.state === 'success') {
this.$store.dispatch('global/message/getUserMessageList', {
contentId: this.contentId
})
this.$message({
message: '发布成功',
type: 'success'
});
this.$store.dispatch('global/message/messageform', {
reply: false,
formData: {
content: '',
replyContent: ''
}
})
} else {
this.$message({
message: result.data.message,
type: 'error'
});
}
}).catch((err) => {
this.$message.error(err.response.data.error)
})
} else {
console.log('error submit!!');
return false;
}
});
}
}
name: "Message",
data() {
return {
replyObj: {},
rules: {
content: [
{
required: true,
message: "请填写评论",
trigger: "blur"
},
{
min: 5,
max: 200,
message: "请输入5-200个字符",
trigger: "blur"
}
]
},
replyRules: {
replyContent: [
{
required: true,
message: "请填写回复",
trigger: "blur"
},
{
min: 5,
max: 200,
message: "请输入5-200个字符",
trigger: "blur"
}
]
},
replyPlaceholder: "请输入回复内容"
};
},
props: {
userMessageList: Array,
contentId: String
},
computed: {
...mapGetters({
msgFormState: "global/message/getMessageForm",
loginState: "frontend/user/getSessionState"
}),
messagelistTitle() {
return "评论列表 (" + this.userMessageList.length + "条)";
}
}
},
components: {
PannelBox
},
methods: {
changeReplyState(state) {
if (!state) this.replyObj = {};
this.$store.dispatch("global/message/messageform", { reply: state });
},
replyMsg(item) {
this.replyObj = item;
let currentMsgAuthor = !_.isEmpty(item.author)
? item.author
: item.adminAuthor;
this.replyPlaceholder = "回复 " + currentMsgAuthor.userName;
let formParams = {
replyAuthor: "",
adminReplyAuthor: "",
relationMsgId: item._id
// replyContent: "@" + currentMsgAuthor.userName + " "
};
if (!_.isEmpty(item.author)) {
formParams.replyAuthor = currentMsgAuthor._id;
} else {
formParams.adminReplyAuthor = currentMsgAuthor._id;
}
this.$store.dispatch("global/message/messageform", {
reply: true,
formData: formParams
});
},
submitForm(formName) {
if (!this.loginState.logined) {
this.$router.push("/users/login");
} else {
let targetForm = this.msgFormState.reply
? this.$refs[formName][0]
: this.$refs[formName];
targetForm.validate(valid => {
if (valid) {
let params = this.msgFormState.formData;
if (this.msgFormState.formData.replyContent) {
// let currentMsgAuthor = !_.isEmpty(this.replyObj.author)
// ? this.replyObj.author
// : this.replyObj.adminAuthor;
// let oldContent = this.msgFormState.formData.replyContent;
params.content = this.msgFormState.formData.replyContent;
} else {
params["replyAuthor"] = "";
params["relationMsgId"] = "";
params["replyContent"] = "";
}
params.contentId = this.contentId;
api
.post("message/post", params)
.then(result => {
if (result.data.state === "success") {
this.$store.dispatch("global/message/getUserMessageList", {
contentId: this.contentId
});
this.$message({
message: "发布成功",
type: "success"
});
this.$store.dispatch("global/message/messageform", {
reply: false,
formData: {
content: "",
replyContent: ""
}
});
} else {
this.$message({
message: result.data.message,
type: "error"
});
}
})
.catch(err => {
this.$message.error(err.response.data.error);
});
} else {
console.log("error submit!!");
return false;
}
});
}
}
}
};
</script>
<style lang="scss">
.content-message {
ul {
li {
border-top: 1px solid #f0f0f0;
padding: 24px 0;
font-size: 15px;
.user-logo {
img {
width: 100%;
border-radius: 50%
}
}
.user-content {
color: #666666;
padding-left: 15px;
word-break: break-all;
}
.user-name {
margin: 5px 0 15px 15px;
color: #409EFF;
.name {
display: inline-block;
}
.time {
font-size: 11px;
display: inline-block;
color: #777;
}
.fa-reply {
float: right;
color: #D3DCE6;
display: block;
}
}
}
}
.give-message {
padding-top: 15px;
.el-form-item__content {
margin-left: 0 !important;
}
.user-notice {
float: left;
a:link,
a:visited {
color: #409EFF
}
}
.send-content {
margin-bottom: 10px;
}
.send-button {
margin-top: 5px;
text-align: right;
}
}
.reply-message {
margin-top: 15px;
padding-left: 25px;
.el-form-item {
margin-bottom: 20px;
}
}
}
</style>

View File

@ -37,8 +37,5 @@ export default {
</script>
<style lang="scss">
.content-pagination {
margin: 30px 0;
text-align: center;
}
</style>

View File

@ -6,24 +6,12 @@
</div>
</template>
<script>
export default {
name: 'PannelBox',
props: ['title', 'className']
}
export default {
name: "PannelBox",
props: ["title", "className"]
};
</script>
<style lang="scss">
.pannel-box {
padding-bottom: 24px;
}
.pannel-title {
font-size: 14px;
color: #969696;
font-weight: 400;
}
.pannel-footer {
clear: both;
}
</style>

View File

@ -88,16 +88,5 @@ export default {
</script>
<style scoped>
.progress {
position: fixed;
top: 0px;
left: 0px;
right: 0px;
height: 2px;
width: 0%;
transition: width 0.2s, opacity 0.4s;
opacity: 1;
background-color: #efc14e;
z-index: 999999;
}
</style>

View File

@ -1,38 +1,33 @@
<template>
<div class="random-articls" v-if="articles && articles.length > 0">
<el-row class="grid-content bg-purple-light" :gutter="15">
<el-col :xs="12" :sm="12" :md="6" :lg="6" v-for="(item,index) in articles" :key="index">
<router-link :to="'/details/'+item._id+'.html'" class="continue-reading"><img :src="item.sImg" :alt="item.title" /></router-link>
<span class="title">{{item.stitle}}</span>
</el-col>
</el-row>
</div>
<PannelBox title="相关推荐" className="content-message">
<div class="random-articls" v-if="articles && articles.length > 0">
<el-row class="grid-content bg-purple-light" :gutter="15">
<el-col :xs="12" :sm="12" :md="8" :lg="8" :xl="8" v-for="(item,index) in articles" :key="index">
<div class="contentImg">
<router-link :to="'/details/'+item._id+'.html'" class="continue-reading"><img :src="item.sImg" :alt="item.title" /></router-link>
</div>
<span class="title">
<router-link class="title" :to="'/details/'+item._id+'.html'">{{item.stitle?item.stitle:item.title}}</router-link>
</span>
</el-col>
</el-row>
</div>
</PannelBox>
</template>
<script>
let packageJson = require("../../../package.json");
import PannelBox from "./PannelBox.vue";
import { mapGetters, mapActions } from "vuex";
export default {
props: {
articles: Array
},
components: {
PannelBox
},
computed: {}
};
</script>
<style lang="scss">
.random-articls {
margin-top: 40px;
margin-bottom: 25px;
img {
border-radius: 4px;
border: 1px solid #f0f0f0;
}
.title {
display: block;
text-align: center;
padding: 10px;
}
}
</style>

View File

@ -1,70 +1,45 @@
<template>
<PannelBox title="近期文章" className="recent-content-list">
<PannelBox title="最新文章" :className="recentClassName">
<div class="content-list">
<ul>
<li :key='index' v-for="(item,index) in recentItems">
<span class="triangle"></span>
<div class="con">
<router-link class="title" :to="'/details/'+item._id+'.html'">{{item.title}}</router-link>
<span class="time">{{item.updateDate}}</span>
</div>
<li class="hot-li" v-for="item in recentItems" :key="item._id">
<el-row :gutter="10">
<el-col :xs="9" :sm="9" :md="9" :lg="9" :xl="9">
<div class="contentImg">
<router-link :to="'/details/'+item._id+'.html'" class="continue-reading">
<img :src="item.sImg" :alt="item.title" />
</router-link>
</div>
</el-col>
<el-col :xs="15" :sm="15" :md="15" :lg="15" :xl="15">
<div class="right-text">
<router-link class="title" :to="'/details/'+item._id+'.html'">{{item.title}}</router-link>
<span>{{item.updateDate}}</span>
</div>
</el-col>
</el-row>
</li>
</ul>
</div>
</PannelBox>
</template>
<script>
import PannelBox from './PannelBox.vue'
export default {
name: 'recentlyContents',
data() {
return {
loadingState: true
}
},
components: {
PannelBox
},
props: ['recentItems']
}
import PannelBox from "./PannelBox.vue";
export default {
name: "recentlyContents",
data() {
return {
loadingState: true,
recentClassName: "recent-content-list"
};
},
components: {
PannelBox
},
props: ["recentItems"]
};
</script>
<style lang="scss">
.recent-content-list {
.content-list {
text-align: left;
ul {
li {
font-size: 14px;
padding: 0 0 .75rem 1rem;
position: relative;
color: #333;
.triangle {
position: absolute;
top: .3rem;
left: .3rem;
width: 0;
height: 0;
border-style: solid;
border-color: #fff #fff #fff #4285f4;
-webkit-transform-origin: 25% center;
transform-origin: 25% center;
border-width: 4px;
}
.con {
-webkit-transition: opacity .5s ease-in;
transition: opacity .5s ease-in;
.title{
display: block;
}
.time {
padding-top: 3px;
display: inline-block;
color: #a4abb1;
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,40 @@
<template>
<div>
<PannelBox title="推荐文章" className="rec-content-list">
<div class="content-list">
<el-row :gutter="10">
<el-col :xs="12" :sm="12" :md="12" :lg="12" :xl="12" class="hot-li" v-for="(item,index) in reclist" :key="item._id">
<div class="contentImg">
<router-link :to="'/details/'+item._id+'.html'" class="continue-reading">
<img :src="item.sImg" :alt="item.title" />
</router-link>
</div>
<router-link class="title" :to="'/details/'+item._id+'.html'">{{item.title | cutWords(25)}}</router-link>
</el-col>
</el-row>
</div>
</PannelBox>
</div>
</template>
<script>
import PannelBox from "./PannelBox.vue";
export default {
name: "recommendContents",
data() {
return {
loadingState: true
};
},
props: ["reclist", "typeId"],
components: {
PannelBox
},
serverCacheKey: props => {
return `recItem-${props.typeId}`;
}
};
</script>
<style lang="scss">
</style>

View File

@ -1,60 +1,27 @@
<template>
<div class="user-bar">
<el-row :gutter="0" class="header-main">
<el-col :xs="1" :sm="1" :md="3" :lg="3" :xl="6">
<div class="grid-content bg-purple">&nbsp;</div>
</el-col>
<el-col :xs="22" :sm="22" :md="18" :lg="18" :xl="12">
<div class="bar-items">
<ul>
<li :class="{active : $route.fullPath == '/users/center'}">
<router-link to="/users/center">个人资料</router-link>
</li>
<li :class="{active : $route.fullPath == '/users/messages'}">
<router-link to="/users/messages">消息管理</router-link>
</li>
<li :class="{active : $route.fullPath == '/users/replies'}">
<router-link to="/users/replies">参与话题</router-link>
</li>
</ul>
</div>
</el-col>
<el-col :xs="1" :sm="1" :md="3" :lg="3" :xl="6">
<div class="grid-content bg-purple">
&nbsp;
</div>
</el-col>
</el-row>
<div class="bar-items">
<ul>
<!-- <li :class="{active : $route.fullPath == '/users/center'}">
<router-link to="/users/center">个人资料</router-link>
</li> -->
<li :class="{active : $route.fullPath == '/users/messages'}">
<router-link to="/users/messages"><i aria-hidden="true" class="fa fa-comment"></i>&nbsp;消息管理</router-link>
</li>
<li :class="{active : $route.fullPath == '/users/replies'}">
<router-link to="/users/replies"><i aria-hidden="true" class="fa fa-file-text"></i>&nbsp;参与话题</router-link>
</li>
</ul>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import { mapGetters } from "vuex";
export default {
name: 'UserBar'
}
name: "UserBar"
};
</script>
<style lang="scss">
.user-bar {
background-color: #ffffff;
width: 100%;
z-index: 100;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .05);
transition: all .2s;
-webkit-transform: translateZ(0);
transform: translateZ(0);
.bar-items {
ul {
li {
padding: 12px 20px 12px 0;
display: inline-block;
font-size: 13px;
}
li.active a {
color: #409EFF
}
}
}
}
</style>

View File

@ -0,0 +1,47 @@
<template>
<div class="user-left-menu">
<div class="user-logo" v-if="userInfo">
<el-upload class="avatar-uploader" action="/system/upload?type=images" :show-file-list="false" :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload">
<img v-if="userInfo.logo" :src="userInfo.logo" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
<span>{{userInfo.userName}}</span>
</div>
<ul class="user-menu-options">
<li :class="{active : $route.fullPath == '/users/center'}" @click="getToUserPage('center')"><a><span class="fa fa-user"></span><span class="label">基本资料</span><i class="fa fa-angle-right"></i></a></li>
<li :class="{active : $route.fullPath == '/users/password'}" @click="getToUserPage('password')"><a><span class="fa fa-asterisk"></span><span class="label">修改密码</span><i class="fa fa-angle-right"></i></a></li>
</ul>
</div>
</template>
<script>
export default {
name: "user-left-menu",
props: ["userInfo"],
methods: {
handleAvatarSuccess(res, file) {
this.$emit("setNewlogo", res);
},
beforeAvatarUpload(file) {
const isJPG = file.type === "image/jpeg";
const isPNG = file.type === "image/png";
const isGIF = file.type === "image/gif";
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG && !isPNG && !isGIF) {
this.$message.error("上传头像图片只能是 JPG,PNG,GIF 格式!");
}
if (!isLt2M) {
this.$message.error("上传头像图片大小不能超过 2MB!");
}
return (isJPG || isPNG || isGIF) && isLt2M;
},
getToUserPage(page) {
this.$router.push("/users/" + page);
}
}
};
</script>
<style lang="scss">
</style>

View File

@ -17,7 +17,7 @@
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" type="danger" plain round icon="el-icon-delete" @click="deleteMessage(scope.$index, dataList)"></el-button>
<el-button size="mini" type="danger" icon="el-icon-delete" @click="deleteMessage(scope.$index, dataList)"></el-button>
</template>
</el-table-column>
</el-table>

View File

@ -12,41 +12,39 @@
</template>
<script>
import api from '~api'
import api from "~api";
export default {
props: {
dataList: Array,
userInfo: Object
},
data() {
return {
loading: false,
multipleSelection: [],
}
},
props: {
dataList: Array,
userInfo: Object
},
data() {
return {
loading: false,
multipleSelection: []
};
},
methods: {
}
}
methods: {}
};
</script>
<style lang="scss">
.replyList {
li {
font-size: 14px;
position: relative;
padding-left: 15px;
border-bottom: 1px dashed #ededed;
blockquote {
color: #475669;
}
time {
float: right;
color: #8492A6;
font-size: 12px;
}
li {
font-size: 14px;
position: relative;
padding-left: 15px;
border-bottom: 1px dashed #ededed;
blockquote {
color: #475669;
}
time {
float: right;
color: #8492a6;
font-size: 12px;
margin-right: 30px;
}
}
}
</style>

View File

@ -1,40 +1,89 @@
<template>
<header class="header">
<el-row :gutter="0" class="header-main">
<el-col :xs="1" :sm="1" :md="3" :lg="3" :xl="6">
<el-col :xs="1" :sm="1" :md="1" :lg="2" :xl="5">
<div class="grid-content bg-purple">&nbsp;</div>
</el-col>
<el-col :xs="22" :sm="22" :md="18" :lg="18" :xl="12">
<el-row :gutter="10" class="grid-content bg-purple-light">
<el-col :xs="22" :sm="22" :md="22" :lg="20" :xl="14">
<el-row :gutter="15" class="grid-content bg-purple-light">
<el-col :xs="24" :sm="4" :md="4" :lg="4">
<div class="header-logo">
<router-link :to="{path: '/'}">
<img src="../../assets/logo.png" />
</router-link>
</div>
<el-row>
<el-col :xs="7" :sm="0" :md="0" :lg="0" :xl="0">
<el-dropdown trigger="click">
<el-button class="toggle-menu" size="mini" plain><i class="fa fa-align-justify"></i></el-button>
<el-dropdown-menu class="drop-menu" slot="dropdown">
<el-dropdown-item v-for="(nav,index) in headerNav" :key="index" v-once>
<router-link :to="{path: '/'+nav.defaultUrl+ '___'+nav._id}">{{nav.name}}</router-link>
</el-dropdown-item>
<el-dropdown-item divided v-if="loginState.logined && loginState.userInfo">
<div>{{loginState.userInfo.userName}}
&nbsp;<el-button type="text" @click="logOut">退出</el-button>
</div>
</el-dropdown-item>
<el-dropdown-item divided v-else>
<div>
<el-button type="text" @click="login">登录</el-button>
<el-button type="text" @click="regUser">注册</el-button>
</div>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-col>
<el-col :xs="10" :sm="24" :md="24" :lg="24" :xl="24">
<div class="header-logo">
<router-link :to="{path: '/'}">
<img src="../../assets/logo.png" />
</router-link>
</div>
</el-col>
<el-col :xs="7" :sm="0" :md="0" :lg="0" :xl="0">
<el-popover
ref="popoverSearch"
placement="bottom"
width="100%"
v-model="visibleSearchPannel">
<div>
<el-input size="small" placeholder="请输入关键字" v-model="searchkey" suffix-icon="el-icon-search" @keyup.enter.native="searchResult">
</el-input>
</div>
</el-popover>
<el-button v-popover:popoverSearch class="toggle-search" size="mini" plain><i class="fa fa-search"></i></el-button>
</el-col>
</el-row>
</el-col>
<el-col :xs="24" :sm="13" :md="13" :lg="13">
<el-col :xs="0" :sm="12" :md="12" :lg="12">
<nav class="header-nav">
<el-row type="flex">
<el-col v-for="(nav,index) in headerNav" :key="index" v-once>
<router-link :to="{path: '/'+nav.defaultUrl+ '___'+nav._id}">{{nav.name}}</router-link>
</el-col>
</el-row>
<ul>
<li :class="{active : $route.fullPath == '/'}"><router-link :to="{path: '/'}">首页</router-link></li>
<li>
<el-dropdown size="medium">
<span class="el-dropdown-link">
文章分类<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="(nav,index) in headerNav" :key="index" v-once>
<router-link :to="{path: '/'+nav.defaultUrl+ '___'+nav._id}">{{nav.name}}</router-link>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</li>
<li :class="{active : $route.fullPath == '/cmscase___SkCL09aCb'}"><router-link :to="{path: '/cmscase___SkCL09aCb'}">应用案例</router-link></li>
</ul>
</nav>
</el-col>
<el-col :xs="0" :sm="7" :md="7" :lg="7">
<el-col :xs="0" :sm="8" :md="8" :lg="8" class="right-pannel">
<el-row>
<el-col :xs="0" :sm="14" :md="14" :lg="14" hidden-xs-only>
<SearchBox />
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="10">
<LoginPannel/>
<el-col :xs="24" :sm="10" :md="10" :lg="10">
<LoginPannel ref="loginPannel"/>
</el-col>
</el-row>
</el-col>
</el-row>
</el-col>
<el-col :xs="1" :sm="1" :md="3" :lg="3" :xl="6">
<el-col :xs="1" :sm="1" :md="1" :lg="2" :xl="5">
<div class="grid-content bg-purple">
&nbsp;
</div>
@ -65,9 +114,15 @@ export default {
navs: Array
},
data() {
return {};
return {
visibleSearchPannel: false,
searchkey: ""
};
},
computed: {
...mapGetters({
loginState: "frontend/user/getSessionState"
}),
headerNav() {
let fullNav = this.$store.getters["global/category/getHeaderNavList"];
let navs = fullNav.data;
@ -79,42 +134,25 @@ export default {
return [];
}
}
},
methods: {
searchResult() {
this.visibleSearchPannel = false;
this.$router.replace("/search/" + this.searchkey);
this.searchkey = "";
},
login() {
this.$router.push("/users/login");
},
regUser() {
this.$router.push("/users/reg");
},
logOut() {
this.$refs.loginPannel.logout();
}
}
};
</script>
<style lang="scss">
.header {
overflow: hidden;
border-bottom: 1px solid #f1f1f1;
.header-main {
margin: 0 auto;
padding: 5px 0px;
overflow: hidden;
.header-logo {
img {
max-height: 40px;
}
}
.header-nav {
height: 40px;
line-height: 40px;
float: left;
width: 100%;
.el-row {
margin: 0;
padding: 0;
.el-col {
list-style-type: none;
display: inline-block;
text-align: center;
a.router-link-active {
color: #409eff;
}
}
}
}
}
}
</style>

View File

@ -2,20 +2,22 @@
<div class="login-pannel">
<ul>
<li v-if="loginState.logined && loginState.userInfo">
<div class="logo"><img :src="loginState.userInfo.logo" alt=""></div>
<el-dropdown>
<span class="el-dropdown-link">
{{loginState.userInfo.userName}}
<i class="el-icon-caret-bottom el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="userCenter">用户中心</el-dropdown-item>
<el-dropdown-item @click.native="userCenter('messages')">用户中心</el-dropdown-item>
<el-dropdown-item @click.native="userCenter('center')">帐号设置</el-dropdown-item>
<el-dropdown-item @click.native="logout">退出</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</li>
<li class="login-txt" v-else>
<el-button type="text" style="color:#878D99;fontSize:13px;" @click="login">登录</el-button>
<el-button type="primary" plain round size="mini" @click="regUser">注册</el-button>
<el-button type="text" style="color:#878D99;fontSize:15px;" @click="login">登录</el-button>
<el-button type="primary" size="small" @click="regUser">注册</el-button>
</li>
</ul>
</div>
@ -42,8 +44,8 @@ export default {
regUser() {
this.$router.push("/users/reg");
},
userCenter() {
this.$router.push("/users/center");
userCenter(page) {
this.$router.push("/users/" + page);
},
logout() {
api.get("users/logOut").then(result => {
@ -68,31 +70,7 @@ export default {
</script>
<style lang="scss">
.login-pannel {
float: right;
text-align: right;
ul {
li {
color: #409eff;
height: 40px;
line-height: 40px;
display: inline-block;
font-size: 14px;
i {
font-size: 12px;
}
}
.login-txt {
a:first-child {
margin-right: 10px;
}
}
}
}
.el-dropdown-menu {
li {
font-size: 14px;
}
}
</style>

View File

@ -1,7 +1,8 @@
<template>
<div class="search-pannel" v-once>
<div class="input-area">
<el-input @keyup.enter.native="handleIconClick" suffix-icon="el-icon-search" size="small" placeholder="请输入关键字" v-model="searchkey"></el-input>
<div class="search-pannel">
<div class="input-area" v-bind:class="{ active: activeSearch }">
<el-input @keyup.enter.native="handleIconClick" size="small" placeholder="请输入关键字" v-model="searchkey"></el-input>
<i class="fa fa-search" @click="showSearchInput"></i>
</div>
</div>
</template>
@ -11,25 +12,21 @@ export default {
name: "searchbox",
data() {
return {
searchkey: ""
// readySearch: false
searchkey: "",
activeSearch: false
};
},
methods: {
handleIconClick(ev) {
this.$router.replace("/search/" + this.searchkey);
},
showSearchInput() {
this.activeSearch = !this.activeSearch;
},
search(e) {}
}
};
</script>
<style lang="scss">
.search-pannel {
width: 100%;
display: inline-block;
margin-top: 5px;
.el-form-item {
margin-bottom: 0;
}
}
</style>

View File

@ -6,6 +6,7 @@ export const AdminLogin = () => import('../views/AdminLogin.vue')
export const UserLoginForm = () => import('../views/UserLoginForm.vue')
export const UserRegForm = () => import('../views/UserRegForm.vue')
export const UserCenter = () => import('../views/UserCenter.vue')
export const UserPwd = () => import('../views/UserPwd.vue')
export const UserMessage = () => import('../views/UserMessage.vue')
export const UserReplies = () => import('../views/UserReplies.vue')
export const SiteMap = () => import('../views/SiteMap.vue')

View File

@ -5,6 +5,7 @@ export const AdminLogin = require('../views/AdminLogin.vue')
export const UserLoginForm = require('../views/UserLoginForm.vue')
export const UserRegForm = require('../views/UserRegForm.vue')
export const UserCenter = require('../views/UserCenter.vue')
export const UserPwd = require('../views/UserPwd.vue')
export const UserMessage = require('../views/UserMessage.vue')
export const UserReplies = require('../views/UserReplies.vue')
export const SiteMap = require('../views/SiteMap.vue')
@ -16,6 +17,7 @@ AdminLogin.chunkName = 'AdminLogin';
UserLoginForm.chunkName = 'UserLoginForm';
UserRegForm.chunkName = 'UserRegForm';
UserCenter.chunkName = 'UserCenter';
UserPwd.chunkName = 'UserPwd';
UserMessage.chunkName = 'UserMessage';
UserReplies.chunkName = 'UserReplies';
SiteMap.chunkName = 'SiteMap';

View File

@ -2,44 +2,56 @@ import Vue from 'vue'
import VueRouter from 'vue-router'
import Meta from 'vue-meta'
import { ArticleList, CmsCase, Article, AdminLogin, UserLoginForm, UserRegForm, UserCenter, UserMessage, UserReplies, SiteMap } from 'create-route'
import { ArticleList, CmsCase, Article, AdminLogin, UserLoginForm, UserRegForm, UserCenter, UserPwd, UserMessage, UserReplies, SiteMap } from 'create-route'
Vue.use(VueRouter)
Vue.use(Meta)
const scrollBehavior = to => {
const position = {}
if (to.hash) {
position.selector = to.hash
const scrollBehavior = (to, from, savedPosition) => {
if (savedPosition) {
// savedPosition is only available for popstate navigations.
return savedPosition
} else {
const position = {}
// new navigation.
// scroll to anchor by returning the selector
if (to.hash) {
position.selector = to.hash
}
// check if any matched route config has meta that requires scrolling to top
if (to.matched.some(m => m.meta.scrollToTop)) {
// cords will be used if no selector is provided,
// or if the selector didn't match any element.
position.x = 0
position.y = 0
}
// if the returned position is falsy or an empty object,
// will retain current scroll position.
return position
}
if (to.matched.some(mm => mm.meta.scrollToTop)) {
position.x = 0
position.y = 0
}
return position
}
export function createRouter() {
const router = new VueRouter({
mode: 'history',
//base: __dirname,
// scrollBehavior,
scrollBehavior,
routes: [
{ name: 'index', path: '/', component: ArticleList, meta: { typeId: 'indexPage' } },
{ name: 'index', path: '/page/:current(\\d+)?', component: ArticleList, meta: { typeId: 'indexPage' } },
{ name: 'index', path: '/', component: ArticleList, meta: { typeId: 'indexPage', scrollToTop: true } },
{ name: 'index', path: '/page/:current(\\d+)?', component: ArticleList, meta: { typeId: 'indexPage', scrollToTop: true } },
{ name: 'cmscase', path: '/cmscase___:typeId?/:current(\\d+)?', component: CmsCase },
{ name: 'category', path: '/:cate1?___:typeId?/:current(\\d+)?', component: ArticleList },
{ name: 'category', path: '/:cate0/:cate1?___:typeId?/:current(\\d+)?', component: ArticleList },
{ name: 'search', path: '/search/:searchkey/:current(\\d+)?', component: ArticleList, meta: { typeId: 'search' } },
{ name: 'article', path: '/details/:id', component: Article, meta: { notKeepAlive: true } },
{ name: 'category', path: '/:cate1?___:typeId?/:current(\\d+)?', component: ArticleList, meta: { scrollToTop: true } },
{ name: 'category', path: '/:cate0/:cate1?___:typeId?/:current(\\d+)?', component: ArticleList, meta: { scrollToTop: true } },
{ name: 'search', path: '/search/:searchkey/:current(\\d+)?', component: ArticleList, meta: { typeId: 'search', scrollToTop: true } },
{ name: 'article', path: '/details/:id', component: Article, meta: { notKeepAlive: true, scrollToTop: true } },
{ name: 'login', path: '/users/login', component: UserLoginForm },
{ name: 'reg', path: '/users/reg', component: UserRegForm },
{ name: 'ucenter', path: '/users/center', component: UserCenter },
{ name: 'upassword', path: '/users/password', component: UserPwd },
{ name: 'umessage', path: '/users/messages', component: UserMessage },
{ name: 'uReplies', path: '/users/replies', component: UserReplies },
{ name: 'adminlogin', path: '/dr-admin', component: AdminLogin, meta: { typeId: 'adminlogin' } },
{ name: 'sitemap', path: '/sitemap.html', component: SiteMap },
{ name: 'tagPage', path: '/tag/:tagName/:page(\\d+)?', component: ArticleList, meta: { typeId: 'tags' } }
{ name: 'tagPage', path: '/tag/:tagName/:current(\\d+)?', component: ArticleList, meta: { typeId: 'tags', scrollToTop: true } }
]
})
return router;

View File

@ -13,6 +13,7 @@ const state = () => ({
isLoad: false
},
hotContentList: [],
recContentList: [],
recentContentList: []
})
@ -65,7 +66,6 @@ const actions = {
data
} = await api.get('content/getSimpleListByParams', {
...config,
isTop: 1,
sortby: 'clickNum',
model: 'simple',
cache: true
@ -74,6 +74,23 @@ const actions = {
commit('receiveHotList', data)
}
},
async ['getRecContentList']({
commit,
state
}, config) {
if (state.recContentList.length && config.path === state.lists.path) return
const {
data
} = await api.get('content/getSimpleListByParams', {
...config,
isTop: 1,
model: 'simple',
cache: true
})
if (data.docs && data.state === 'success') {
commit('receiveRecList', data)
}
},
async ['getRecentContentList']({
commit,
state
@ -122,6 +139,9 @@ const mutations = {
['receiveHotList'](state, data) {
state.hotContentList = data.docs
},
['receiveRecList'](state, data) {
state.recContentList = data.docs
},
['receiveRecentList'](state, data) {
state.recentContentList = data.docs
}
@ -142,6 +162,9 @@ const getters = {
['getHotContentList'](state) {
return state.hotContentList
},
['getRecContentList'](state) {
return state.recContentList
},
['getRecentContentList'](state) {
return state.recentContentList
}

View File

@ -2,35 +2,42 @@
<div class="dr-admin-login">
<div class="admin-login-form">
<el-row :gutter="10">
<el-col :xs="2" :sm="6" :md="8" :lg="8" :xl="10">
<el-col :xs="2" :sm="7" :md="8" :lg="8" :xl="9">
<div class="grid-content bg-purple">&nbsp;</div>
</el-col>
<el-col :xs="20" :sm="12" :md="8" :lg="8" :xl="4">
<el-col :xs="20" :sm="10" :md="8" :lg="8" :xl="6">
<div class="admin-logo-title">
<h3>
<b style="color:#1D8CE0">Dora</b>CMS</h3>
</div>
<el-form :model="adminLoginFormData" :rules="rules" ref="ruleForm" label-width="80px" class="demo-ruleForm login-container">
<h3 class="pannel-title">
<span>管理员登录</span>
<div class="login-container">
<h3>
<span><i class="fa fa-sign-in" aria-hidden="true"></i>欢迎登录</span>
</h3>
<el-form-item label="用户名" prop="userName">
<el-input size="small" v-model="adminLoginFormData.userName"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input size="small" type="password" v-model="adminLoginFormData.password"></el-input>
</el-form-item>
<el-form-item label="验证码" prop="imageCode">
<el-input size="small" style="width: 30%" type="imageCode" @keyup.enter.native="submitForm('ruleForm')" v-model="adminLoginFormData.imageCode"></el-input>
<img :src="imgCodeUrl" class="imageCode" @click="reSetImgCode"/>
</el-form-item>
<el-form-item class="submit-btn">
<el-button size="small" type="primary" @click="submitForm('ruleForm')">登录</el-button>
<el-button size="small" @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
<el-form :model="adminLoginFormData" :rules="rules" ref="ruleForm" label-width="0px" class="loginForm">
<el-form-item prop="userName">
<el-input size="small" v-model="adminLoginFormData.userName" placeholder="请输入用户名">
<template slot="prepend"><i class="fa fa-user"></i></template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input size="small" type="password" v-model="adminLoginFormData.password" placeholder="请输入密码">
<template slot="prepend"><i class="fa fa-lock"></i></template>
</el-input>
</el-form-item>
<el-form-item prop="imageCode">
<el-input size="small" style="width: 40%" type="imageCode" placeholder="图形码" @keyup.enter.native="submitForm('ruleForm')" v-model="adminLoginFormData.imageCode">
<template slot="prepend"><i class="fa fa-random"></i></template>
</el-input>
<img :src="imgCodeUrl" class="imageCode" @click="reSetImgCode"/>
</el-form-item>
<el-form-item class="submit-btn">
<el-button size="small" type="primary" @click="submitForm('ruleForm')">登录</el-button>
</el-form-item>
</el-form>
</div>
</el-col>
<el-col :xs="2" :sm="6" :md="8" :lg="8" :xl="10">
<el-col :xs="2" :sm="7" :md="8" :lg="8" :xl="9">
<div class="grid-content bg-purple">&nbsp;</div>
</el-col>
</el-row>
@ -133,9 +140,6 @@ export default {
this.$refs[formName].resetFields();
}
},
beforeMount() {
// this.$store.dispatch('simplePage');
},
mounted() {},
computed: {
...mapGetters({
@ -146,50 +150,5 @@ export default {
</script>
<style lang="scss">
.admin-logo-title {
h3 {
color: #99a9bf;
font-size: 35px;
text-align: center;
font-weight: normal;
}
}
.admin-login-form {
margin: 0 auto;
margin-top: 70px;
margin-bottom: 100px;
.submit-btn {
text-align: left;
}
.imageCode {
width: 10rem;
height: 2rem;
float: right;
}
.login-container {
-webkit-border-radius: 5px;
border-radius: 5px;
-moz-border-radius: 5px;
background-clip: padding-box; // margin: 180px auto;
padding: 25px 35px 10px 35px;
background: #fff;
border: 1px solid #eaeaea;
box-shadow: 0 0 25px #cac6c6;
.el-form-item {
margin-bottom: 15px;
}
.title {
margin: 0px auto 40px auto;
text-align: center;
color: #505458;
}
.remember {
margin: 0px 0px 35px 0px;
}
}
}
</style>

View File

@ -1,20 +1,19 @@
<template>
<div>
<div class="content-detail">
<div class="readme">
<el-row :gutter="0" class="header-main">
<el-col :xs="1" :sm="1" :md="3" :lg="3" :xl="6">
<div class="contentContainer">
<div class="mainbody content-detail">
<el-row :gutter="0">
<el-col :xs="1" :sm="1" :md="1" :lg="2" :xl="5">
<div class="grid-content bg-purple">&nbsp;</div>
</el-col>
<el-col :xs="22" :sm="22" :md="18" :lg="18" :xl="12">
<el-row :gutter="24">
<el-col :xs="24" :sm="17" :md="17" :lg="17">
<div>
<el-col :xs="22" :sm="22" :md="22" :lg="20" :xl="14" class="main-details">
<el-row :gutter="15">
<el-col :xs="24" :sm="17" :md="17" :lg="17" >
<div class="hentry">
<h2 class="content-title">{{article.doc.title}}&nbsp;<span v-show="article.doc.from == '2'" class="from">[]</span></h2>
<div class="content-author">
<ul>
<li class="author-name">
<el-tag size="mini">{{article.doc.author ? article.doc.author.name:''}}</el-tag>
{{article.doc.author ? article.doc.author.name:''}}
</li>
<li>
<span class="dot">&nbsp;&nbsp;</span>{{cateName}}
@ -29,6 +28,53 @@
</div>
<div v-html="article.doc.comments" class="content-main">
</div>
<!-- <div class="meta-tags" v-if="article.doc.tags && article.doc.tags.length>0">
<el-button @click="searchByTag(tag.name)" plain v-for="tag in article.doc.tags" size="mini" :key="tag._id">{{tag.name}}</el-button>
</div> -->
<div class="meta-bottom">
<el-row :gutter="10">
<el-col :xs="24" :sm="14" :md="14" :lg="14" :xl="14">
<!-- <div class="like"> <el-button type="primary" plain @click="likeIt(article.doc._id)"><i class="fa fa-heart-o"></i>&nbsp;喜欢 ({{article.doc.likeNum}})</el-button></div> -->
<div class="meta-tags" v-if="article.doc.tags && article.doc.tags.length>0">
<el-button type="primary" @click="searchByTag(tag.name)" plain v-for="tag in article.doc.tags" size="mini" :key="tag._id">{{tag.name}}</el-button>
</div>
</el-col>
<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10">
<div class="share-group" v-if="systemConfig.data">
<ul>
<li class="like">
<i class="fa fa-heart" @click="likeIt(article.doc._id)"></i>&nbsp;<span>{{article.doc.likeNum}}</span>
</li>
<li class="qq" @click="shareToQq(article.doc)">
<a><i class="fa fa-qq"></i></a>
</li>
<el-popover
ref="popover1"
placement="top-start"
width="200"
trigger="click"
popper-class="prop-wechat"
content="这是一段内容,这是一段内容,这是一段内容,这是一段内容。">
<template slot-scope="scope">
<h5>打开微信扫一扫打开网页后点击屏幕右上角分享按钮</h5>
<img :src="'/api/qrImg?detailLink='+systemConfig.data[0].siteDomain+'/details/'+article.doc._id+'.html'" :alt="article.doc.title">
</template>
</el-popover>
<li class="wechat">
<a v-popover:popover1>
<i class="fa fa-wechat"></i>
</a>
</li>
<li class="weibo">
<a :href="'http://service.weibo.com/share/share.php?url='+systemConfig.data[0].siteDomain+'/details/'+article.doc._id+'.html&amp;title='+article.doc.discription+'&amp;pic='+((article.doc.sImg).indexOf('cdn') > -1 ?'':systemConfig.data[0].siteDomain)+article.doc.sImg+'&amp;appkey=902932546 target=_blank'">
<i class="fa fa-weibo"></i>
</a>
</li>
</ul>
</div>
</el-col>
</el-row>
</div>
<RandomArticle :articles="article.randomArticles" />
<Messages :userMessageList="messages.data" :contentId="article.doc._id" />
</div>
@ -37,17 +83,16 @@
<div class="grid-content bg-purple-light title">
<CatesMenu :typeId="typeId" />
<RecentContents :recentItems="recentArticle" />
<HotContents :hotItems="hotlist" :typeId="$route.params.typeId" v-if="hotlist.length > 0" />
<!-- <RecommendContents :reclist="reclist" :typeId="$route.params.typeId" v-if="reclist.length > 0" /> -->
<AdsPannel id="Sk_n90ucb" />
</div>
</el-col>
</el-row>
</el-col>
<el-col :xs="1" :sm="1" :md="3" :lg="3" :xl="6">
<div class="grid-content bg-purple">&nbsp;</div>
<el-col :xs="1" :sm="1" :md="1" :lg="2" :xl="5">
<BackTop/>
</el-col>
</el-row>
</div>
</div>
</div>
</template>
@ -61,10 +106,11 @@
import RandomArticle from '../components/RandomArticle.vue'
import RecentContents from '../components/RecentContents.vue'
import SearchBox from '../components/SearchBox.vue'
import HotContents from '../components/HotContents.vue'
import RecommendContents from '../components/RecommendContents.vue'
import CatesMenu from '../components/CatesMenu.vue'
import AdsPannel from '../components/AdsPannel.vue'
import BackTop from '../components/BackTop.vue'
import api from "~api";
export default {
name: 'frontend-article',
async asyncData({ store, route }) {
@ -77,7 +123,7 @@
currentId = id;
}
}
store.dispatch('frontend/article/getHotContentList', { typeId: 'indexPage', pageSize: 10})
store.dispatch('frontend/article/getRecContentList', { typeId: 'indexPage', pageSize: 10})
store.dispatch('global/message/getUserMessageList',{ contentId: currentId, pageSize: 999})
store.dispatch('frontend/article/getRecentContentList')
await store.dispatch(`frontend/article/getArticleItem`, { id: currentId, path })
@ -93,9 +139,11 @@
computed: {
...mapGetters({
article: 'frontend/article/getArticleItem',
hotlist: 'frontend/article/getHotContentList',
reclist: 'frontend/article/getRecContentList',
messages: 'global/message/getUserMessageList',
recentArticle: 'frontend/article/getRecentContentList'
recentArticle: 'frontend/article/getRecentContentList',
loginState: 'frontend/user/getSessionState',
systemConfig: 'global/footerConfigs/getSystemConfig'
}),
cateName() {
let catesArr = this.article.doc.categories;
@ -119,14 +167,44 @@
RandomArticle,
RecentContents,
SearchBox,
HotContents,
RecommendContents,
CatesMenu,
AdsPannel
AdsPannel,
BackTop
},
methods: {
addTarget(content) {
if (!content) return ''
return content.replace(/<a(.*?)href="http/g, '<a$1target="_blank" href="http')
},
likeIt(itemId){
if (!this.loginState.logined) {
this.$router.push('/users/login');
}else{
api.get("content/updateLikeNum", { contentId: itemId }).then(result => {
let data = result.data;
if (data.state == "success") {
this.article.doc.likeNum = data.likeNum;
} else {
this.$message({
message: data.message,
type: 'error'
});
}
}).catch((err) => {
this.$message.error(err.response.data.error)
});
}
},
searchByTag(name){
this.$router.push("/tag/" + name);
},
shareToQq(content){
let { title, discription, _id, sImg } = content;
let url = this.systemConfig.data[0].siteDomain+'/details/' + _id;
let picurl = (sImg.indexOf('cdn') > -1 ? '' : this.systemConfig.data[0].siteDomain) + sImg;
let shareqqzonestring='http://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?summary='+title+'.'+discription+'&url='+url+'&pics='+picurl;
window.open(shareqqzonestring,'newwindow','height=400,width=400,top=100,left=100');
}
},
mounted() {
@ -153,37 +231,5 @@
</script>
<style lang="scss">
.content-detail {
color: #3f3f3f;
margin-top: 20px;
.from {
color: #fa5555;
font-size: 13px;
font-weight: normal;
}
img {
max-width: 100% !important;
}
.content-title {
margin-top: 0;
font-weight: 700;
font-size: 20px;
}
.content-author {
color: #969696;
ul {
li.author-name {
color: #409eff;
}
li {
display: inline-block;
margin-bottom: 10px;
font-size: 13px;
}
}
}
.content-main {
font-size: 15px;
}
}
</style>

View File

@ -1,42 +1,51 @@
<template>
<div>
<div class="contentContainer">
<div>
<div class="mainbody">
<el-row :gutter="0">
<el-col :xs="1" :sm="1" :md="3" :lg="3" :xl="6">
<el-col :xs="1" :sm="1" :md="1" :lg="2" :xl="5">
<div class="grid-content bg-purple">&nbsp;</div>
</el-col>
<el-col :xs="22" :sm="22" :md="18" :lg="18" :xl="12" class="content-mainbody-left">
<el-row :gutter="24">
<el-col :xs="22" :sm="22" :md="22" :lg="20" :xl="14" class="content-list">
<el-row :gutter="15">
<el-col :xs="24" :sm="17" :md="17" :lg="17" v-if="topics.data.length > 0">
<div class="column-wrap" v-show="typeId != 'indexPage'">
<span v-if="$route.params.tagName">{{'标签' + $route.params.tagName}}</span>
<span v-else>{{typeId == 'search' ? '搜索' + $route.params.searchkey : currentCate.name}}</span>
</div>
<div>
<ItemList v-for="item in topics.data" :item="item" :key="item._id" />
</div>
<div class="content-pagination">
<Pagination :pageInfo="topics.pageInfo" :typeId="typeId" />
<div class="main-list">
<div class="column-wrap" v-show="typeId != 'indexPage'">
<h1 v-if="$route.params.tagName">{{'标签' + $route.params.tagName}}</h1>
<h1 v-else>{{typeId == 'search' ? '搜索' + $route.params.searchkey : currentCate.name}}</h1>
</div>
<div>
<div class="cate-pannle-menu" v-show="typeId == 'indexPage'">
<el-menu :default-active="activeIndex" class="el-menu-demo" mode="horizontal" @select="handleSelect">
<el-menu-item index="1">最新文档</el-menu-item>
</el-menu>
</div>
<div class="article-list">
<ItemList v-for="item in topics.data" :item="item" :key="item._id" />
</div>
</div>
<div class="content-pagination">
<Pagination :pageInfo="topics.pageInfo" :typeId="typeId" />
</div>
</div>
</el-col>
<el-col :xs="24" :sm="17" :md="17" :lg="17" v-else>
<div style="height:300px;" v-loading="loadingState">&nbsp;</div>
</el-col>
<el-col :xs="0" :sm="7" :md="7" :lg="7" class="content-mainbody-right">
<div class="grid-content bg-purple-light title">
<el-col :xs="0" :sm="7" :md="7" :lg="7" >
<div class="main-right">
<AdsPannel id="SJllJUAdcZ" />
<div v-if="checkCateList">
<CatesMenu :typeId="$route.params.typeId" />
</div>
<Tag :tags="tags.data" />
<HotContents :hotItems="hotlist" :typeId="$route.params.typeId" v-if="hotlist.length > 0" />
<RecommendContents :reclist="reclist" :typeId="$route.params.typeId" v-if="reclist.length > 0" />
</div>
</el-col>
</el-row>
</el-col>
<el-col :xs="1" :sm="1" :md="3" :lg="3" :xl="6">
<div class="grid-content bg-purple">&nbsp;</div>
<el-col :xs="1" :sm="1" :md="1" :lg="2" :xl="5">
<BackTop/>
</el-col>
</el-row>
</div>
@ -50,6 +59,7 @@
mapGetters
} from 'vuex'
import metaMixin from '~mixins'
import RecommendContents from '../components/RecommendContents.vue'
import HotContents from '../components/HotContents.vue'
import SearchBox from '../components/SearchBox.vue'
import ItemList from '../views/ItemList.vue'
@ -57,64 +67,40 @@
import Tag from '../components/Tag.vue'
import CatesMenu from '../components/CatesMenu.vue'
import AdsPannel from '../components/AdsPannel.vue'
import BackTop from '../components/BackTop.vue'
export default {
name: 'frontend-index',
async asyncData({
store,
route
}, config = {
current: 1,
model: 'normal'
}) {
const {
params: {
id,
key,
tagName,
current,
typeId,
searchkey
},
path
} = route
const base = { ...config,
pageSize: 10,
id,
path,
searchkey,
tagName,
current,
typeId
}
store.dispatch('frontend/article/getHotContentList', {
pageSize: 10,
typeId
})
store.dispatch('global/tags/getTagList', {
pageSize: 30
})
await store.dispatch('frontend/article/getArticleList', base)
async asyncData({ store, route }, config = { current: 1, model: 'normal'}) {
const { params: { id, key, tagName, current, typeId, searchkey },path} = route
const base = { ...config, pageSize: 10, id, path, searchkey, tagName, current, typeId}
store.dispatch('frontend/article/getRecContentList', { pageSize: 10,typeId })
store.dispatch('frontend/article/getArticleList', base)
await store.dispatch('frontend/article/getHotContentList', base)
},
data(){
return{
loadingState: false
loadingState: false,
activeIndex: '1',
}
},
mixins: [metaMixin],
components: {
ItemList,
Pagination,
RecommendContents,
HotContents,
SearchBox,
Tag,
CatesMenu,
AdsPannel
AdsPannel,
BackTop
},
computed: {
...mapGetters({
reclist: 'frontend/article/getRecContentList',
hotlist: 'frontend/article/getHotContentList',
tags: 'global/tags/getTagList',
// tags: 'global/tags/getTagList',
systemConfig: 'global/footerConfigs/getSystemConfig'
}),
topics(){
@ -136,29 +122,18 @@
}
},
methods: {
handleSelect(key, keyPath) {
console.log(key, keyPath);
}
},
activated() {
this.$options.asyncData({
store: this.$store,
route: this.$route
}, {
current: 1
})
this.$options.asyncData({ store: this.$store, route: this.$route}, { current: 1})
},
metaInfo() {
const systemData = this.systemConfig.data[0];
const {
siteName,
siteDiscription,
siteKeywords
} = systemData;
const { siteName, siteDiscription, siteKeywords } = systemData;
let title = '首页';
const {
tagName,
typeId,
searchkey
} = this.$route.params
const { tagName, typeId, searchkey } = this.$route.params
if (typeId) {
const obj = this.currentCate;
if (obj) {
@ -189,23 +164,23 @@
</script>
<style lang="scss">
.column-wrap {
position: relative;
height: 30px;
line-height: 30px;
font-size: 20px;
color: #303030;
padding-left: 18px;
margin-bottom: 15px;
}
// .column-wrap {
// position: relative;
// height: 30px;
// line-height: 30px;
// font-size: 20px;
// color: #303030;
// padding-left: 18px;
// margin-bottom: 15px;
// }
.column-wrap:before {
content: "";
position: absolute;
width: 4px;
height: 21px;
background: #f63756;
left: 0;
top: 4px;
}
// .column-wrap:before {
// content: "";
// position: absolute;
// width: 4px;
// height: 21px;
// background: #f63756;
// left: 0;
// top: 4px;
// }
</style>

View File

@ -1,13 +1,12 @@
<template>
<article class="case-box">
<div class="contentContainer">
<article class="contentContainer">
<div class="mainbody case-box">
<el-row :gutter="0">
<el-col :xs="1" :sm="1" :md="3" :lg="3" :xl="6">
<el-col :xs="1" :sm="1" :md="1" :lg="2" :xl="5">
&nbsp;
</el-col>
<el-col :xs="22" :sm="22" :md="18" :lg="18" :xl="12">
<el-row :gutter="20">
<el-col :xs="22" :sm="22" :md="22" :lg="20" :xl="14" >
<div class="case-list">
<h3>
这些站点在用DoraCMS
<el-button-group>
@ -16,9 +15,10 @@
</el-button-group>
</h3>
<AdsPannel id="BkxSmqcaAZ" />
</el-row>
<div style="clear:both"></div>
</div>
</el-col>
<el-col :xs="1" :sm="1" :md="3" :lg="3" :xl="6">
<el-col :xs="1" :sm="1" :md="1" :lg="2" :xl="5">
&nbsp;
</el-col>
</el-row>
@ -27,22 +27,7 @@
</article>
</template>
<style lang="scss">
.case-box {
min-height: 400px;
h3 {
margin: 5px 10px 20px;
font-size: 15px;
color: #878d99;
.el-button--mini {
padding: 5px 8px;
font-weight: 700;
}
}
img {
width: 100%;
height: 2rem;
}
}
</style>
<script>

View File

@ -1,13 +1,5 @@
<style lang='scss'>
.post-b {
border-bottom: 1px solid #f0f0f0;
margin-bottom: 25px;
padding: 0px 0px 15px;
}
.post-b:last-child {
border: none;
}
</style>
<template>
<div class="post-b">

View File

@ -1,7 +1,5 @@
<style lang='scss'>
.contentContainer {
margin-top: 30px;
}
</style>
<template>
<div>

View File

@ -10,10 +10,10 @@
<template>
<div class="container min-hight" style="margin-bottom: 20px;background: #FFFFFF">
<el-row :gutter="10">
<el-col :xs="2" :sm="2" :md="4" :lg="4">
<el-col :xs="1" :sm="1" :md="2" :lg="2" :xl="5">
<div class="grid-content bg-purple">&nbsp;</div>
</el-col>
<el-col :xs="20" :sm="20" :md="16" :lg="16">
<el-col :xs="22" :sm="22" :md="20" :lg="20" :xl="14">
<div class="col-md-12 siteMap">
<ul>
<li v-for="(item,index) in siteMapList.data" :key="item._id" v-once>
@ -22,7 +22,7 @@
</ul>
</div>
</el-col>
<el-col :xs="2" :sm="2" :md="4" :lg="4">
<el-col :xs="1" :sm="1" :md="2" :lg="2" :xl="5">
<div class="grid-content bg-purple">&nbsp;</div>
</el-col>
</el-row>

View File

@ -1,118 +1,42 @@
<template>
<article class="content-item">
<el-row :gutter="30">
<el-col :xs="0" :sm="7" :md="7" :lg="7" hidden-xs-only>
<div class="grid-content bg-purple contentImg">
<router-link :to="'/details/'+item._id+'.html'" class="continue-reading">
<img :src="item.sImg" :alt="item.title" />
</router-link>
</div>
</el-col>
<el-col :xs="24" :sm="17" :md="17" :lg="17" class='discription'>
<div class="grid-content bg-purple-light title">
<h2>
<router-link :to="'/details/'+item._id+'.html'" class="continue-reading">{{item.title}}</router-link>
</h2>
<div class="dis">{{item.discription | cutWords(90)}}</div>
<ul class="post-meta">
<li>
<div v-if="item.categories && item.categories.length>1">
<router-link :to="{path: '/'+(item.categories)[item.categories.length-1].defaultUrl+ '___'+(item.categories)[item.categories.length-1]._id}">{{(item.categories)[item.categories.length-1].name}}</router-link>
</div>
</li>
<li>
<i class="fa fa-clock-o" aria-hidden="true"></i>&nbsp;&nbsp;{{item.date}}</li>
<li>
<i class="fa fa-eye" aria-hidden="true"></i>&nbsp;&nbsp;{{item.clickNum}}</li>
<li>
<i class="fa fa-comment" aria-hidden="true"></i>&nbsp;&nbsp;{{item.commentNum}}</li>
</ul>
</div>
</el-col>
<el-row :gutter="30" class="row-list">
<el-col :xs="24" :sm="8" :md="8" :lg="8" class='image'>
<div class="grid-content bg-purple contentImg">
<router-link :to="'/details/'+item._id+'.html'" class="continue-reading">
<img :src="item.sImg" :alt="item.title" />
</router-link>
</div>
</el-col>
<el-col :xs="24" :sm="16" :md="16" :lg="16">
<div class="discription">
<h2>
<router-link :to="'/details/'+item._id+'.html'" class="continue-reading">{{item.title}}</router-link>
</h2>
<div class="dis">{{item.discription | cutWords(90)}}</div>
<ul class="post-meta">
<li v-if="item.categories">
<div v-if="item.categories.length>1">
<router-link :to="{path: '/'+(item.categories)[item.categories.length-1].defaultUrl+ '___'+(item.categories)[item.categories.length-1]._id}">{{(item.categories)[item.categories.length-1].name}}</router-link>
</div>
<div v-else-if="item.categories.length == 1">
<router-link :to="{path: '/'+(item.categories)[0].defaultUrl+ '___'+(item.categories)[0]._id}">{{(item.categories)[0].name}}</router-link>
</div>
</li>
<li>
<i class="fa fa-clock-o" aria-hidden="true"></i>&nbsp;&nbsp;{{item.date}}</li>
<li>
<i class="fa fa-eye" aria-hidden="true"></i>&nbsp;&nbsp;{{item.clickNum}}</li>
<li>
<i class="fa fa-comment" aria-hidden="true"></i>&nbsp;&nbsp;{{item.commentNum}}</li>
</ul>
</div>
</el-col>
</el-row>
</article>
</template>
<style lang="scss">
.content-item {
.post-angle {
position: absolute;
left: -10px;
height: 21px;
color: #fff;
text-align: center;
background-color: #f63756;
line-height: 24px;
padding: 0 10px;
z-index: 101;
top: 0px;
font-size: 13px;
}
.post-angle:after {
content: " ";
position: absolute;
left: 0;
top: 21px;
width: 0;
height: 0;
border-top: 6px solid #cd213d;
border-left: 10px solid transparent;
}
.contentImg {
img {
width: 100%;
min-height: 6rem;
border-radius: 4px;
border: 1px solid #f0f0f0;
}
// margin-right: 30px;
height: auto;
display: block;
position: relative;
.content-cate {
position: absolute;
top: 0.4rem;
left: 0.4rem;
display: block;
padding: 0 0.5rem;
color: #fff;
background: rgba(0, 0, 0, 0.5);
font-size: 0.6rem;
text-align: center;
border-radius: 1rem;
z-index: 11;
}
}
.discription {
text-align: left;
.post-meta {
a:link,
a:visited {
color: #3ca5f6;
}
li {
display: inline-block;
font-size: 13px;
color: #b4b4b4;
margin: 10px 10px 10px 0;
}
}
.title {
h2 {
margin: 0;
font-size: 18px;
word-break: break-all;
font-weight: 700;
}
}
.dis {
margin: 10px 0;
font-size: 13px;
color: #333333;
}
}
}
</style>
<script>

View File

@ -1,51 +1,43 @@
<template>
<div class="user-center">
<div>
<UserBar />
<el-row :gutter="0" class="header-main">
<el-col :xs="1" :sm="1" :md="3" :lg="3" :xl="6">
<div class="contentContainer">
<div class="mainbody user-center">
<el-row :gutter="0">
<el-col :xs="1" :sm="1" :md="1" :lg="2" :xl="5">
<div class="grid-content bg-purple">&nbsp;</div>
</el-col>
<el-col :xs="22" :sm="22" :md="18" :lg="18" :xl="12">
<el-col :xs="22" :sm="22" :md="22" :lg="20" :xl="14">
<div class="user-info">
<el-form :model="loginState.userInfo" :rules="rules" ref="ruleForm" label-width="150px" class="demo-ruleForm">
<div class="user-logo">
<el-form-item prop="sImg">
<el-upload class="avatar-uploader" action="/system/upload?type=images" :show-file-list="false" :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload">
<img v-if="loginState.userInfo.logo" :src="loginState.userInfo.logo" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
</div>
<el-form-item label="用户名" prop="userName">
<el-input size="small" v-model="loginState.userInfo.userName"></el-input>
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input size="small" v-model="loginState.userInfo.name"></el-input>
</el-form-item>
<el-form-item label="电话" prop="phoneNum">
<el-input size="small" v-model="loginState.userInfo.phoneNum"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input size="small" v-model="loginState.userInfo.email"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input size="small" placeholder="请输入密码" type="password" v-model="loginState.userInfo.password"></el-input>
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input size="small" placeholder="请确认密码" type="password" v-model="loginState.userInfo.confirmPassword"></el-input>
</el-form-item>
<el-form-item label="备注" prop="comments">
<el-input size="small" type="textarea" v-model="loginState.userInfo.comments"></el-input>
</el-form-item>
<el-form-item>
<el-button size="small" type="primary" @click="submitForm('ruleForm')">保存</el-button>
<el-button size="small" @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="30" class="basic-info">
<el-col :xs="8" :sm="8" :md="8" :lg="8" :xl="8" class="left-pannel">
<UserCenterLeftMenu @setNewlogo="setFormLogo" :userInfo="loginState.userInfo"/>
</el-col>
<el-col :xs="16" :sm="16" :md="16" :lg="16" :xl="16" class="right-pannel">
<h3 class="top-bar"><i class="fa fa-user"></i><span>基本资料</span></h3>
<el-form label-position="top" :model="loginState.userInfo" :rules="rules" ref="ruleForm" label-width="150px" class="info-form">
<el-form-item label="用户名" prop="userName">
<el-input v-model="loginState.userInfo.userName"></el-input>
</el-form-item>
<el-form-item label="姓名" prop="name">
<el-input v-model="loginState.userInfo.name"></el-input>
</el-form-item>
<el-form-item label="电话" prop="phoneNum">
<el-input v-model="loginState.userInfo.phoneNum"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="loginState.userInfo.email"></el-input>
</el-form-item>
<el-form-item label="备注" prop="comments">
<el-input type="textarea" v-model="loginState.userInfo.comments"></el-input>
</el-form-item>
<el-form-item>
<el-button size="small" type="primary" @click="submitForm('ruleForm')">更新信息</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</div>
</el-col>
<el-col :xs="1" :sm="1" :md="3" :lg="3" :xl="6">
<el-col :xs="1" :sm="1" :md="1" :lg="2" :xl="5">
<div class="grid-content bg-purple">
&nbsp;
</div>
@ -57,6 +49,7 @@
<script>
import api from "~api";
import UserBar from "../components/UserBar";
import UserCenterLeftMenu from "../components/UserCenterLeftMenu";
const validatorUtil = require("../../../utils/validatorUtil.js");
import { mapGetters, mapActions } from "vuex";
export default {
@ -67,7 +60,8 @@ export default {
};
},
components: {
UserBar
UserBar,
UserCenterLeftMenu
},
data() {
return {
@ -146,40 +140,6 @@ export default {
trigger: "blur"
}
],
password: [
{
required: true,
message: "请输入密码",
trigger: "blur"
},
{
validator: (rule, value, callback) => {
if (!validatorUtil.checkPwd(value)) {
callback(new Error("6-12位只能包含字母、数字和下划线!"));
} else {
callback();
}
},
trigger: "blur"
}
],
confirmPassword: [
{
required: true,
message: "请确认密码",
trigger: "blur"
},
{
validator: (rule, value, callback) => {
if (value !== this.loginState.userInfo.password) {
callback(new Error("两次输入密码不一致!"));
} else {
callback();
}
},
trigger: "blur"
}
],
comments: [
{
message: "请填写备注",
@ -196,21 +156,8 @@ export default {
};
},
methods: {
handleAvatarSuccess(res, file) {
this.loginState.userInfo.logo = res;
},
beforeAvatarUpload(file) {
const isJPG = file.type === "image/jpeg";
const isPNG = file.type === "image/png";
const isGIF = file.type === "image/gif";
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG && !isPNG && !isGIF) {
this.$message.error("上传头像图片只能是 JPG,PNG,GIF 格式!");
}
if (!isLt2M) {
this.$message.error("上传头像图片大小不能超过 2MB!");
}
return (isJPG || isPNG || isGIF) && isLt2M;
setFormLogo(res) {
this.loginState && (this.loginState.userInfo.logo = res);
},
submitForm(formName) {
this.$refs[formName].validate(valid => {
@ -239,14 +186,8 @@ export default {
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
},
beforeMount() {
// this.$store.dispatch('simplePage');
},
computed: {
...mapGetters({
loginState: "frontend/user/getSessionState"
@ -256,45 +197,5 @@ export default {
</script>
<style lang="scss">
.user-center {
background-color: #f4f5f5;
.user-info {
form {
width: 50%;
}
margin: 15px 0;
padding: 15px;
background-color: #ffffff;
position: relative;
.user-logo {
position: absolute;
top: 30px;
right: 100px;
}
}
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 100px;
height: 100px;
line-height: 100px;
text-align: center;
}
.avatar {
width: 100px;
height: 100px;
display: block;
}
}
</style>

View File

@ -1,32 +1,39 @@
<template>
<div class="dr-user-login">
<div class="login-form">
<div class="contentContainer">
<div class="mainbody login-form">
<el-row :gutter="10">
<el-col :xs="2" :sm="6" :md="8" :lg="8" :xl="10">
<el-col :xs="1" :sm="1" :md="1" :lg="2" :xl="5">
<div class="grid-content bg-purple">&nbsp;</div>
</el-col>
<el-col :xs="20" :sm="12" :md="8" :lg="8" :xl="4">
<el-form :model="userLoginFormData" :rules="rules" ref="ruleForm" label-width="0px" class="demo-ruleForm login-container">
<h3 class="pannel-title">
<span>用户登录</span>
<el-col :xs="22" :sm="22" :md="22" :lg="20" :xl="14" class="login-main">
<div class="login-box">
<el-form label-position="top" :model="userLoginFormData" :rules="rules" ref="ruleForm" label-width="80px" class="demo-ruleForm">
<h3 class="title">
<span>登录</span>
</h3>
<el-form-item prop="email">
<el-input size="small" placeholder="请填写邮箱" v-model="userLoginFormData.email"></el-input>
<el-form-item prop="email" label="邮箱">
<el-input placeholder="请填写邮箱" v-model="userLoginFormData.email"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input size="small" placeholder="请输入密码" type="password" @keyup.enter.native="submitForm('ruleForm')" v-model="userLoginFormData.password"></el-input>
<el-form-item prop="password" label="密码">
<el-input placeholder="请输入密码" type="password" @keyup.enter.native="submitForm('ruleForm')" v-model="userLoginFormData.password"></el-input>
</el-form-item>
<el-form-item class="submit-btn">
<el-button size="small" round type="primary" @click="submitForm('ruleForm')">登录</el-button>
<el-button size="small" round @click="resetForm('ruleForm')">重置</el-button>
<el-form-item>
<el-row :gutter="10">
<el-col :xs="12" :sm="12" :md="12" :lg="12" :xl="12">
<el-button type="primary" @click="submitForm('ruleForm')">登录</el-button>
</el-col>
<el-col :xs="12" :sm="12" :md="12" :lg="12" :xl="12">
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-col>
</el-row>
</el-form-item>
</el-form>
</div>
</el-col>
<el-col :xs="2" :sm="6" :md="8" :lg="8" :xl="10">
<el-col :xs="1" :sm="1" :md="1" :lg="2" :xl="5">
<div class="grid-content bg-purple">&nbsp;</div>
</el-col>
</el-row>
</div>
</div>
</template>
@ -34,6 +41,7 @@
import api from "~api";
const validatorUtil = require("../../../utils/validatorUtil.js");
import { mapGetters, mapActions } from "vuex";
export default {
name: "userLogin",
metaInfo() {
@ -43,6 +51,7 @@ export default {
},
data() {
return {
referPath: "/",
rules: {
email: [
{
@ -81,6 +90,11 @@ export default {
}
};
},
beforeRouteEnter(to, from, next) {
next(vm => {
from.path && (vm.referPath = from.path);
});
},
methods: {
submitForm(formName) {
this.$refs[formName].validate(valid => {
@ -90,7 +104,7 @@ export default {
.post("users/doLogin", params)
.then(result => {
if (result.data.state == "success") {
window.location = "/";
window.location = this.referPath;
} else {
this.$message({
message: result.data.message,
@ -111,9 +125,6 @@ export default {
this.$refs[formName].resetFields();
}
},
beforeMount() {
// this.$store.dispatch('simplePage');
},
computed: {
...mapGetters({
userLoginFormData: "frontend/user/loginForm"

View File

@ -1,13 +1,13 @@
<template>
<div class="user-center">
<div>
<UserBar />
<el-row :gutter="0" class="header-main">
<el-col :xs="1" :sm="1" :md="3" :lg="3" :xl="6">
<div class="contentContainer">
<div class="mainbody user-center">
<el-row :gutter="0">
<el-col :xs="1" :sm="1" :md="1" :lg="2" :xl="5">
<div class="grid-content bg-purple">&nbsp;</div>
</el-col>
<el-col :xs="22" :sm="22" :md="18" :lg="18" :xl="12">
<el-col :xs="22" :sm="22" :md="22" :lg="20" :xl="14">
<div class="user-message">
<UserBar />
<div v-if="noticelist.docs.length>0">
<UserNoticeDataTable :dataList="noticelist.docs"></UserNoticeDataTable>
<div class="content-pagination">
@ -19,7 +19,7 @@
</div>
</div>
</el-col>
<el-col :xs="1" :sm="1" :md="3" :lg="3" :xl="6">
<el-col :xs="1" :sm="1" :md="1" :lg="2" :xl="5">
<div class="grid-content bg-purple">
&nbsp;
</div>
@ -64,12 +64,5 @@ export default {
</script>
<style lang="scss">
.user-center {
background-color: #f4f5f5;
.user-message {
margin: 15px 0;
padding: 15px;
background-color: #ffffff;
}
}
</style>

139
src/index/views/UserPwd.vue Normal file
View File

@ -0,0 +1,139 @@
<template>
<div class="contentContainer">
<div class="mainbody user-center">
<el-row :gutter="0">
<el-col :xs="1" :sm="1" :md="1" :lg="2" :xl="5">
<div class="grid-content bg-purple">&nbsp;</div>
</el-col>
<el-col :xs="22" :sm="22" :md="22" :lg="20" :xl="14">
<div class="user-info">
<el-row :gutter="30" class="basic-info">
<el-col :xs="8" :sm="8" :md="8" :lg="8" :xl="8" class="left-pannel">
<UserCenterLeftMenu @setNewlogo="setFormLogo" :userInfo="loginState.userInfo"/>
</el-col>
<el-col :xs="16" :sm="16" :md="16" :lg="16" :xl="16" class="right-pannel">
<h3 class="top-bar"><i class="fa fa-asterisk"></i><span>修改密码</span></h3>
<el-form label-position="top" :model="loginState.userInfo" :rules="rules" ref="ruleForm" label-width="150px" class="info-form">
<el-form-item label="密码" prop="password">
<el-input placeholder="请输入密码" type="password" v-model="loginState.userInfo.password"></el-input>
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input placeholder="请确认密码" type="password" v-model="loginState.userInfo.confirmPassword"></el-input>
</el-form-item>
<el-form-item>
<el-button size="small" type="primary" @click="submitForm('ruleForm')">更新信息</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</div>
</el-col>
<el-col :xs="1" :sm="1" :md="1" :lg="2" :xl="5">
<div class="grid-content bg-purple">
&nbsp;
</div>
</el-col>
</el-row>
</div>
</div>
</template>
<script>
import api from "~api";
import UserCenterLeftMenu from "../components/UserCenterLeftMenu";
const validatorUtil = require("../../../utils/validatorUtil.js");
import { mapGetters, mapActions } from "vuex";
export default {
name: "userLogin",
metaInfo() {
return {
title: "用户中心"
};
},
components: {
UserCenterLeftMenu
},
data() {
return {
rules: {
password: [
{
required: true,
message: "请输入密码",
trigger: "blur"
},
{
validator: (rule, value, callback) => {
if (!validatorUtil.checkPwd(value)) {
callback(new Error("6-12位只能包含字母、数字和下划线!"));
} else {
callback();
}
},
trigger: "blur"
}
],
confirmPassword: [
{
required: true,
message: "请确认密码",
trigger: "blur"
},
{
validator: (rule, value, callback) => {
if (value !== this.loginState.userInfo.password) {
callback(new Error("两次输入密码不一致!"));
} else {
callback();
}
},
trigger: "blur"
}
]
}
};
},
methods: {
setFormLogo(res) {
this.loginState && (this.loginState.userInfo.logo = res);
},
submitForm(formName) {
this.$refs[formName].validate(valid => {
if (valid) {
let params = this.loginState.userInfo;
api
.post("users/updateInfo", params)
.then(result => {
if (result.data.state == "success") {
this.$message({
message: "信息更新成功!",
type: "success"
});
} else {
this.$message({
message: result.data.message,
type: "error"
});
}
})
.catch(err => {
this.$message.error(err.response.data.error);
});
} else {
console.log("error submit!!");
return false;
}
});
}
},
computed: {
...mapGetters({
loginState: "frontend/user/getSessionState"
})
}
};
</script>
<style lang="scss">
</style>

View File

@ -1,33 +1,35 @@
<template>
<div class="dr-user-login">
<div class="login-form">
<div class="contentContainer">
<div class="mainbody">
<el-row :gutter="10">
<el-col :xs="2" :sm="6" :md="8" :lg="8" :xl="10">
<el-col :xs="1" :sm="1" :md="1" :lg="2" :xl="5">
<div class="grid-content bg-purple">&nbsp;</div>
</el-col>
<el-col :xs="20" :sm="12" :md="8" :lg="8" :xl="4">
<el-form :model="userRegFormData" :rules="regRules" ref="regRuleForm" label-width="0px" class="demo-ruleForm login-container">
<h3 class="pannel-title">
<span>用户注册</span>
<el-col :xs="22" :sm="22" :md="22" :lg="20" :xl="14" class="login-main">
<div class="login-box">
<el-form label-position="top" :model="userRegFormData" :rules="regRules" ref="regRuleForm" label-width="0px" class="demo-ruleForm login-container">
<h3 class="title">
<span>注册</span>
</h3>
<el-form-item prop="userName">
<el-input size="small" placeholder="请填写用户名" v-model="userRegFormData.userName"></el-input>
<el-form-item prop="userName" label="用户名">
<el-input placeholder="请填写用户名" v-model="userRegFormData.userName"></el-input>
</el-form-item>
<el-form-item prop="email">
<el-input size="small" placeholder="请填写邮箱" v-model="userRegFormData.email"></el-input>
<el-form-item prop="email" label="邮箱">
<el-input placeholder="请填写邮箱" v-model="userRegFormData.email"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input size="small" placeholder="请输入密码" type="password" v-model="userRegFormData.password"></el-input>
<el-form-item prop="password" label="密码">
<el-input placeholder="请输入密码" type="password" v-model="userRegFormData.password"></el-input>
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input size="small" placeholder="请确认密码" type="password" v-model="userRegFormData.confirmPassword"></el-input>
<el-form-item prop="confirmPassword" label="重复密码">
<el-input placeholder="请确认密码" type="password" v-model="userRegFormData.confirmPassword"></el-input>
</el-form-item>
<el-form-item class="submit-btn">
<el-button size="small" round type="primary" @click="submitRegForm('regRuleForm')">提交</el-button>
<el-button type="primary" @click="submitRegForm('regRuleForm')">注册</el-button>
</el-form-item>
</el-form>
</div>
</el-col>
<el-col :xs="2" :sm="6" :md="8" :lg="8":xl="10" >
<el-col :xs="1" :sm="1" :md="1" :lg="2" :xl="5">
<div class="grid-content bg-purple">&nbsp;</div>
</el-col>
</el-row>

View File

@ -1,13 +1,13 @@
<template>
<div class="user-center">
<div>
<UserBar />
<div class="contentContainer">
<div class="mainbody user-center">
<el-row :gutter="0" class="header-main">
<el-col :xs="1" :sm="1" :md="3" :lg="3" :xl="6">
<el-col :xs="1" :sm="1" :md="1" :lg="2" :xl="5">
<div class="grid-content bg-purple">&nbsp;</div>
</el-col>
<el-col :xs="22" :sm="22" :md="18" :lg="18" :xl="12">
<el-col :xs="22" :sm="22" :md="22" :lg="20" :xl="14">
<div class="user-message">
<UserBar />
<div v-if="replylist">
<UserReplieDataTable :dataList="replylist.docs" :userInfo="loginState.userInfo"></UserReplieDataTable>
<div class="content-pagination">
@ -19,7 +19,7 @@
</div>
</div>
</el-col>
<el-col :xs="1" :sm="1" :md="3" :lg="3" :xl="6">
<el-col :xs="1" :sm="1" :md="1" :lg="2" :xl="5">
<div class="grid-content bg-purple">
&nbsp;
</div>
@ -29,22 +29,19 @@
</div>
</template>
<script>
import api from '~api'
import UserBar from '../components/UserBar'
import UserReplieDataTable from '../components/UserReplieDataTable';
import Pagination from '../components/Pagination.vue'
import api from "~api";
import UserBar from "../components/UserBar";
import UserReplieDataTable from "../components/UserReplieDataTable";
import Pagination from "../components/Pagination.vue";
const validatorUtil = require('../../../utils/validatorUtil.js')
import {
mapGetters,
mapActions
} from 'vuex';
const validatorUtil = require("../../../utils/validatorUtil.js");
import { mapGetters, mapActions } from "vuex";
export default {
name: 'userMessage',
name: "userMessage",
metaInfo() {
return {
title: '用户中心'
}
title: "用户中心"
};
},
components: {
UserBar,
@ -52,32 +49,20 @@ export default {
Pagination
},
data() {
return {
}
},
methods: {
return {};
},
methods: {},
mounted() {
this.$store.dispatch('frontend/user/userReplies');
this.$store.dispatch("frontend/user/userReplies");
},
computed: {
...mapGetters({
replylist: 'frontend/user/replylist',
loginState: 'frontend/user/getSessionState'
replylist: "frontend/user/replylist",
loginState: "frontend/user/getSessionState"
})
}
}
};
</script>
<style lang="scss">
.user-center {
background-color: #f4f5f5;
.user-message {
margin: 15px 0;
padding: 15px;
background-color: #ffffff;
}
}
</style>

View File

@ -75,6 +75,10 @@
<el-button size="small" type="warning" plain round @click="getToPage('systemConfig')">
<i class="fa fa-fw fa-cog"></i> 系统配置</el-button>
</li>
<li>
<el-button size="small" type="danger" plain round @click="getToPage('backUpData')">
<i class="fa fa-fw fa-database"></i> 数据备份</el-button>
</li>
</ul>
</div>
</el-card>

View File

@ -3,7 +3,6 @@ const {
} = require('../server/lib/controller');
const _ = require('lodash');
module.exports = (req, res, next) => {
// console.log('---req.originalUrl---', req.originalUrl);
req.query.resourcefiles = "_id api";
AdminResource.getAllResource(req, res, {
type: '1'

View File

@ -1,11 +1,9 @@
/**
* Created by Administrator on 2015/9/9.
*/
// let mongoose = require('mongoose');
// let UserModel = mongoose.model('User');
let settings = require('../settings');
let siteFunc = require('../siteFunc');
// let UserNotify = require('../models/UserNotify');
//用户实体类
const { User, UserNotify } = require('../../server/lib/controller');

49
utils/middleware/cache.js Normal file
View File

@ -0,0 +1,49 @@
var redis = require('./redis');
var _ = require('lodash');
/**
* 从cache中取出缓存
* @param key
* @param callback 回调函数
*/
var get = function (key, callback) {
redis.get(key, function (err, data) {
if (err) {
return callback(err);
}
if (!data) {
return callback();
}
data = JSON.parse(data);
callback(data);
});
};
exports.get = get;
/**
* 将键值对数据缓存起来
*
* @param key
* @param value
* @param time 参数可选毫秒为单位,切换为redis以秒为单位除以1000
* @param callback 回调函数
*/
var set = function (key, value, time, callback) {
if (typeof time === 'function') {
callback = time;
time = null;
}
callback = callback || _.noop;
value = JSON.stringify(value);
if (!time) {
redis.set(key, value, callback);
} else {
//将毫秒单位转为秒
redis.setex(key, parseInt(time / 1000), value, callback);
}
};
exports.set = set;

10
utils/middleware/redis.js Normal file
View File

@ -0,0 +1,10 @@
/*!
* redis client
*/
'use strict';
var settings = require('../settings');
var redis = require('redis');
var client = redis.createClient(settings.redis_port, settings.redis_host);
client.auth(settings.redis_psd);
module.exports = client;

View File

@ -31,6 +31,12 @@ module.exports = {
origin: 'http://cdn.html-js.cn', // cdn域名
fsizeLimit: 1024 * 1024 * 5, // 上传文件大小限制默认为5M
// redis配置
openRedis: false, //是否开启,若为true 则下面的信息必须配置正确完整
redis_host: '10.0.0.1',
redis_port: 6379,
redis_psd: 'your redis password',
redis_db: 0,
// 站点基础信息配置
DORACMSAPI: 'http://api.html-js.cn', // 系统服务提供商
SYSTEMLOGPATH: '/home/doraData/logsdir/doracms', // 服务器日志保存目录

6879
yarn.lock Normal file

File diff suppressed because it is too large Load Diff