Dev

Co-authored-by: John Niang <johnniang@riseup.net>
pull/286/head
Ryan Wang 2019-06-19 14:53:36 +08:00 committed by GitHub
commit d4f3850cb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 420 additions and 76 deletions

View File

@ -41,7 +41,7 @@ wget https://github.com/halo-dev/halo/releases/download/v1.0.2/halo-1.0.2.jar -O
nohup java -jar halo-latest.jar >/dev/null 2>&1&
```
详细文档请移步:<https://halo.run/docs>
详细文档请移步:<https://halo.run/guide>
## 博客示例

View File

@ -196,6 +196,8 @@ public class InstallController {
installParam.update(user);
// Set password manually
userService.setPassword(user, installParam.getPassword());
// Set default avatar
userService.setDefaultAvatar(user);
// Update user
return userService.update(user);
}).orElseGet(() -> userService.createBy(installParam));

View File

@ -0,0 +1,34 @@
package run.halo.app.controller.admin.api;
import io.swagger.annotations.ApiOperation;
import org.springframework.boot.actuate.trace.http.HttpTrace;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.service.TraceService;
import java.util.List;
/**
* Trace controller.
*
* @author johnniang
* @date 19-6-18
*/
@RestController
@RequestMapping("/api/admin/traces")
public class TraceController {
private final TraceService traceService;
public TraceController(TraceService traceService) {
this.traceService = traceService;
}
@GetMapping
@ApiOperation("Lists http traces")
public List<HttpTrace> listHttpTraces() {
return traceService.listHttpTraces();
}
}

View File

@ -55,7 +55,7 @@ public class OptionController {
@ApiOperation("Options for comment")
public Map<String, Object> comment() {
List<String> keys = new ArrayList<>();
keys.add("comment_gavatar_default");
keys.add("comment_gravatar_default");
keys.add("comment_content_placeholder");
return optionService.listOptions(keys);
}

View File

@ -1,14 +1,28 @@
package run.halo.app.controller.core;
import cn.hutool.core.text.StrBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.util.NestedServletException;
import run.halo.app.exception.HaloException;
import run.halo.app.exception.NotFoundException;
import run.halo.app.service.ThemeService;
import run.halo.app.utils.FilenameUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.util.Map;
/**
* Error page Controller
@ -18,18 +32,30 @@ import javax.servlet.http.HttpServletRequest;
*/
@Slf4j
@Controller
public class CommonController implements ErrorController {
@RequestMapping("${server.error.path:${error.path:/error}}")
public class CommonController extends AbstractErrorController {
private static final String ERROR_PATH = "/error";
private static final String NOT_FROUND_TEMPLATE = "404.ftl";
private static final String NOT_FOUND_TEMPLATE = "404.ftl";
private static final String INTERNAL_ERROR_TEMPLATE = "500.ftl";
private static final String ERROR_TEMPLATE = "error.ftl";
private static final String DEFAULT_ERROR_PATH = "common/error/error";
private final ThemeService themeService;
public CommonController(ThemeService themeService) {
private final ErrorProperties errorProperties;
private final ErrorAttributes errorAttributes;
public CommonController(ThemeService themeService,
ErrorAttributes errorAttributes,
ServerProperties serverProperties) {
super(errorAttributes);
this.themeService = themeService;
this.errorAttributes = errorAttributes;
this.errorProperties = serverProperties.getError();
}
/**
@ -38,26 +64,26 @@ public class CommonController implements ErrorController {
* @param request request
* @return String
*/
@GetMapping(value = ERROR_PATH)
public String handleError(HttpServletRequest request) {
final Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
@GetMapping
public String handleError(HttpServletRequest request, HttpServletResponse response, Model model) {
HttpStatus status = getStatus(request);
log.error("Error path: [{}], status: [{}]", getErrorPath(), statusCode);
log.error("Error path: [{}], status: [{}]", getErrorPath(), status);
// Get the exception
Throwable throwable = (Throwable) request.getAttribute("javax.servlet.error.exception");
handleCustomException(request);
if (throwable != null) {
log.error("Captured an exception", throwable);
Map<String, Object> errorDetail = Collections.unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request)));
model.addAttribute("error", errorDetail);
if (StringUtils.startsWithIgnoreCase(throwable.getMessage(), "Could not resolve view with name '")) {
// TODO May cause unknown-reason problem
// if Ftl was not found then redirect to /404
return contentNotFround();
}
log.debug("Error detail: [{}]", errorDetail);
if (status.equals(HttpStatus.INTERNAL_SERVER_ERROR)) {
return contentInternalError();
} else if (status.equals(HttpStatus.NOT_FOUND)) {
return contentNotFound();
} else {
return defaultErrorHandler();
}
return statusCode == 500 ? contentInternalError() : contentNotFround();
}
/**
@ -66,14 +92,16 @@ public class CommonController implements ErrorController {
* @return String
*/
@GetMapping(value = "/404")
public String contentNotFround() {
if (!themeService.templateExists(NOT_FROUND_TEMPLATE)) {
return "common/error/404";
public String contentNotFound() {
if (themeService.templateExists(ERROR_TEMPLATE)) {
return getActualTemplatePath(ERROR_TEMPLATE);
}
StrBuilder path = new StrBuilder("themes/");
path.append(themeService.getActivatedTheme().getFolderName());
path.append("/404");
return path.toString();
if (themeService.templateExists(NOT_FOUND_TEMPLATE)) {
return getActualTemplatePath(NOT_FOUND_TEMPLATE);
}
return defaultErrorHandler();
}
/**
@ -83,15 +111,67 @@ public class CommonController implements ErrorController {
*/
@GetMapping(value = "/500")
public String contentInternalError() {
if (!themeService.templateExists(INTERNAL_ERROR_TEMPLATE)) {
return "common/error/500";
if (themeService.templateExists(ERROR_TEMPLATE)) {
return getActualTemplatePath(ERROR_TEMPLATE);
}
StrBuilder path = new StrBuilder("themes/");
path.append(themeService.getActivatedTheme().getFolderName());
path.append("/500");
if (themeService.templateExists(INTERNAL_ERROR_TEMPLATE)) {
return getActualTemplatePath(INTERNAL_ERROR_TEMPLATE);
}
return defaultErrorHandler();
}
private String defaultErrorHandler() {
return DEFAULT_ERROR_PATH;
}
private String getActualTemplatePath(@NonNull String template) {
Assert.hasText(template, "FTL template must not be blank");
StringBuilder path = new StringBuilder();
path.append("themes/")
.append(themeService.getActivatedTheme().getFolderName())
.append('/')
.append(FilenameUtils.getBasename(template));
return path.toString();
}
/**
* Handles custom exception, like HaloException.
*
* @param request http servlet request must not be null
*/
private void handleCustomException(@NonNull HttpServletRequest request) {
Assert.notNull(request, "Http servlet request must not be null");
Object throwableObject = request.getAttribute("javax.servlet.error.exception");
if (throwableObject == null) {
return;
}
Throwable throwable = (Throwable) throwableObject;
log.error("Captured an exception", throwable);
if (throwable instanceof NestedServletException) {
Throwable rootCause = ((NestedServletException) throwable).getRootCause();
if (rootCause instanceof HaloException) {
HaloException haloException = (HaloException) rootCause;
request.setAttribute("javax.servlet.error.status_code", haloException.getStatus().value());
request.setAttribute("javax.servlet.error.exception", rootCause);
request.setAttribute("javax.servlet.error.message", haloException.getMessage());
}
} else if (StringUtils.startsWithIgnoreCase(throwable.getMessage(), "Could not resolve view with name '")) {
request.setAttribute("javax.servlet.error.status_code", HttpStatus.NOT_FOUND.value());
NotFoundException viewNotFound = new NotFoundException("该路径没有对应的模板");
request.setAttribute("javax.servlet.error.exception", viewNotFound);
request.setAttribute("javax.servlet.error.message", viewNotFound.getMessage());
}
}
/**
* Returns the path of the error page.
*
@ -99,6 +179,23 @@ public class CommonController implements ErrorController {
*/
@Override
public String getErrorPath() {
return ERROR_PATH;
return this.errorProperties.getPath();
}
/**
* Determine if the stacktrace attribute should be included.
*
* @param request the source request
* @return if the stacktrace attribute should be included
*/
protected boolean isIncludeStackTrace(HttpServletRequest request) {
ErrorProperties.IncludeStacktrace include = errorProperties.getIncludeStacktrace();
if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
return true;
}
if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM) {
return getTraceParameter(request);
}
return false;
}
}

View File

@ -29,7 +29,7 @@ public class BaseCommentDTO implements OutputConverter<BaseCommentDTO, BaseComme
private String authorUrl;
private String gavatarMd5;
private String gravatarMd5;
private String content;

View File

@ -0,0 +1,20 @@
package run.halo.app.model.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;
/**
* Http trace dto.
*
* @author johnniang
* @date 19-6-18
*/
@Data
@ToString
@EqualsAndHashCode
public class HttpTraceDTO {
}

View File

@ -50,10 +50,10 @@ public class BaseComment extends BaseEntity {
private String authorUrl;
/**
* Gavatar md5
* Gravatar md5
*/
@Column(name = "gavatar_md5", columnDefinition = "varchar(128) default ''")
private String gavatarMd5;
@Column(name = "gravatar_md5", columnDefinition = "varchar(128) default ''")
private String gravatarMd5;
/**
* Comment content.
@ -117,8 +117,8 @@ public class BaseComment extends BaseEntity {
authorUrl = "";
}
if (gavatarMd5 == null) {
gavatarMd5 = "";
if (gravatarMd5 == null) {
gravatarMd5 = "";
}
if (status == null) {

View File

@ -62,13 +62,9 @@ public class PostParam implements InputConverter<Post> {
@Override
public Post convertTo() {
if (StringUtils.isBlank(url)) {
url = HaloUtils.normalizeUrl(title);
} else {
url = HaloUtils.normalizeUrl(url);
url = title;
}
url = HaloUtils.initializeUrlIfBlank(url);
Post post = InputConverter.super.convertTo();
// Crypt password
if (StringUtils.isNotBlank(password)) {
@ -81,13 +77,9 @@ public class PostParam implements InputConverter<Post> {
@Override
public void update(Post post) {
if (StringUtils.isBlank(url)) {
url = HaloUtils.normalizeUrl(title);
} else {
url = HaloUtils.normalizeUrl(url);
url = title;
}
url = HaloUtils.initializeUrlIfBlank(url);
InputConverter.super.update(post);
// Crypt password

View File

@ -9,7 +9,7 @@ package run.halo.app.model.properties;
*/
public enum CommentProperties implements PropertyEnum {
GAVATAR_DEFAULT("comment_gavatar_default", String.class, "mm"),
GRAVATAR_DEFAULT("comment_gravatar_default", String.class, "mm"),
NEW_NEED_CHECK("comment_new_need_check", Boolean.class, "true"),

View File

@ -0,0 +1,24 @@
package run.halo.app.service;
import org.springframework.boot.actuate.trace.http.HttpTrace;
import org.springframework.lang.NonNull;
import java.util.List;
/**
* Trace service interface.
*
* @author johnniang
* @date 19-6-18
*/
public interface TraceService {
/**
* Gets all http traces.
*
* @return
*/
@NonNull
List<HttpTrace> listHttpTraces();
}

View File

@ -122,4 +122,11 @@ public interface UserService extends CrudService<User, Integer> {
* @param plainPassword plain password must not be blank
*/
void setPassword(@NonNull User user, @NonNull String plainPassword);
/**
* Set user default avataruse Gravatar(http://cn.gravatar.com)
*
* @param user user must not be null
*/
void setDefaultAvatar(@NonNull User user);
}

View File

@ -241,8 +241,8 @@ public abstract class BaseCommentServiceImpl<COMMENT extends BaseComment> extend
comment.setUserAgent(ServletUtils.getHeaderIgnoreCase(HttpHeaders.USER_AGENT));
}
if (comment.getGavatarMd5() == null) {
comment.setGavatarMd5(DigestUtils.md5Hex(comment.getEmail()));
if (comment.getGravatarMd5() == null) {
comment.setGravatarMd5(DigestUtils.md5Hex(comment.getEmail()));
}
if (StringUtils.isNotEmpty(comment.getAuthorUrl())) {

View File

@ -346,7 +346,7 @@ public class RecoveryServiceImpl implements RecoveryService {
baseComment.setEmail(commentMap.getOrDefault("commentAuthorEmail", "").toString());
baseComment.setIpAddress(commentMap.getOrDefault("commentAuthorIp", "").toString());
baseComment.setAuthorUrl(commentMap.getOrDefault("commentAuthorUrl", "").toString());
baseComment.setGavatarMd5(commentMap.getOrDefault("commentAuthorAvatarMd5", "").toString());
baseComment.setGravatarMd5(commentMap.getOrDefault("commentAuthorAvatarMd5", "").toString());
baseComment.setContent(commentMap.getOrDefault("commentContent", "").toString());
baseComment.setUserAgent(commentMap.getOrDefault("commentAgent", "").toString());
baseComment.setIsAdmin(getBooleanOrDefault(commentMap.getOrDefault("isAdmin", "").toString(), false));

View File

@ -0,0 +1,31 @@
package run.halo.app.service.impl;
import org.springframework.boot.actuate.trace.http.HttpTrace;
import org.springframework.boot.actuate.trace.http.HttpTraceRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import run.halo.app.service.TraceService;
import java.util.List;
/**
* @author johnniang
* @date 19-6-18
*/
@Service
public class TraceServiceImpl implements TraceService {
private final HttpTraceRepository httpTraceRepository;
public TraceServiceImpl(HttpTraceRepository httpTraceRepository) {
this.httpTraceRepository = httpTraceRepository;
}
@Override
public List<HttpTrace> listHttpTraces() {
return httpTraceRepository.findAll();
}
}

View File

@ -1,5 +1,7 @@
package run.halo.app.service.impl;
import cn.hutool.core.text.StrBuilder;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.BCrypt;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationEventPublisher;
@ -193,4 +195,12 @@ public class UserServiceImpl extends AbstractCrudService<User, Integer> implemen
user.setPassword(BCrypt.hashpw(plainPassword, BCrypt.gensalt()));
}
@Override
public void setDefaultAvatar(User user) {
Assert.notNull(user, "User must not be null");
StrBuilder gravatar = new StrBuilder("//cn.gravatar.com/avatar/");
gravatar.append(SecureUtil.md5(user.getEmail()));
gravatar.append("?s=256&d=mm");
user.setAvatar(gravatar.toString());
}
}

View File

@ -66,25 +66,16 @@ public class FileUtils {
public static void deleteFolder(@NonNull Path deletingPath) throws IOException {
Assert.notNull(deletingPath, "Deleting path must not be null");
if (Files.notExists(deletingPath)) {
return;
}
log.info("Deleting [{}]", deletingPath);
// Delete folder recursively
org.eclipse.jgit.util.FileUtils.delete(deletingPath.toFile(),
org.eclipse.jgit.util.FileUtils.RECURSIVE | org.eclipse.jgit.util.FileUtils.RETRY);
// try (Stream<Path> pathStream = Files.walk(deletingPath)) {
// pathStream.sorted(Comparator.reverseOrder())
// .peek(path -> log.debug("Try to delete [{}]", path.toString()))
// .forEach(path -> {
// try {
// Files.delete(path);
// log.debug("Deleted [{}] successfully", path.toString());
// } catch (IOException e) {
// throw new ServiceException("Failed to delete " + path.toString(), e).setErrorData(deletingPath.toString());
// }
// });
// }
log.info("Deleted [{}] successfully", deletingPath);
}

View File

@ -1 +1 @@
<!DOCTYPE html><html lang=zh-cmn-Hans><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><meta name=robots content=noindex,nofllow><meta name=generator content=Halo><link rel=icon href=/logo.png><title>Halo Dashboard</title><link href=/css/chunk-0337f7a6.4c6b622f.css rel=prefetch><link href=/css/chunk-1079f749.94b473ad.css rel=prefetch><link href=/css/chunk-14e0b302.32f796a8.css rel=prefetch><link href=/css/chunk-161dc990.5ac5144c.css rel=prefetch><link href=/css/chunk-1be69b35.43c1fc12.css rel=prefetch><link href=/css/chunk-31c8ea42.4a090118.css rel=prefetch><link href=/css/chunk-5000e55c.7fb9bc61.css rel=prefetch><link href=/css/chunk-6d8b31f6.ad8d17b2.css rel=prefetch><link href=/css/chunk-81d936d8.05888d95.css rel=prefetch><link href=/css/chunk-b2d0b040.389eca76.css rel=prefetch><link href=/css/chunk-bb4f0d4a.c1990d7c.css rel=prefetch><link href=/css/chunk-bfd5bbcc.6a83ae7d.css rel=prefetch><link href=/css/chunk-c0a1d3c4.09186be6.css rel=prefetch><link href=/css/chunk-cec31564.6f053d75.css rel=prefetch><link href=/css/fail.809a6bc5.css rel=prefetch><link href=/js/chunk-0337f7a6.11326d77.js rel=prefetch><link href=/js/chunk-0ba750a2.b786c9db.js rel=prefetch><link href=/js/chunk-1079f749.ec67c7db.js rel=prefetch><link href=/js/chunk-142c8832.0f8270b3.js rel=prefetch><link href=/js/chunk-14e0b302.a86d1254.js rel=prefetch><link href=/js/chunk-161dc990.5de9313f.js rel=prefetch><link href=/js/chunk-1be69b35.81559bfc.js rel=prefetch><link href=/js/chunk-2d0b64bf.61d5d7c3.js rel=prefetch><link href=/js/chunk-2d0d65a2.2249765a.js rel=prefetch><link href=/js/chunk-2d21a35c.eda7a11a.js rel=prefetch><link href=/js/chunk-2d228d13.85b46532.js rel=prefetch><link href=/js/chunk-31c8ea42.0b2feab9.js rel=prefetch><link href=/js/chunk-5000e55c.3bd9ce3a.js rel=prefetch><link href=/js/chunk-5bf599cc.ac9398f3.js rel=prefetch><link href=/js/chunk-6d8b31f6.b64e5366.js rel=prefetch><link href=/js/chunk-81d936d8.5c1d2539.js rel=prefetch><link href=/js/chunk-87e2df70.0ada7d4e.js rel=prefetch><link href=/js/chunk-b2d0b040.b0d70d07.js rel=prefetch><link href=/js/chunk-bb4f0d4a.048dc3a4.js rel=prefetch><link href=/js/chunk-bfd5bbcc.d2ca1e80.js rel=prefetch><link href=/js/chunk-c0a1d3c4.41d0d3f8.js rel=prefetch><link href=/js/chunk-cec31564.cfe3fd85.js rel=prefetch><link href=/js/fail.61f30b0f.js rel=prefetch><link href=/css/app.f8b02c30.css rel=preload as=style><link href=/css/chunk-vendors.ee4e8dbf.css rel=preload as=style><link href=/js/app.845922f6.js rel=preload as=script><link href=/js/chunk-vendors.2f7bce79.js rel=preload as=script><link href=/css/chunk-vendors.ee4e8dbf.css rel=stylesheet><link href=/css/app.f8b02c30.css rel=stylesheet></head><body><noscript><strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.2f7bce79.js></script><script src=/js/app.845922f6.js></script></body></html>
<!DOCTYPE html><html lang=zh-cmn-Hans><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><meta name=robots content=noindex,nofllow><meta name=generator content=Halo><link rel=icon href=/logo.png><title>Halo Dashboard</title><link href=/css/chunk-0337f7a6.4c6b622f.css rel=prefetch><link href=/css/chunk-1079f749.94b473ad.css rel=prefetch><link href=/css/chunk-14e0b302.32f796a8.css rel=prefetch><link href=/css/chunk-161dc990.5ac5144c.css rel=prefetch><link href=/css/chunk-1be69b35.43c1fc12.css rel=prefetch><link href=/css/chunk-31c8ea42.4a090118.css rel=prefetch><link href=/css/chunk-5000e55c.7fb9bc61.css rel=prefetch><link href=/css/chunk-6d8b31f6.ad8d17b2.css rel=prefetch><link href=/css/chunk-81d936d8.05888d95.css rel=prefetch><link href=/css/chunk-b2d0b040.389eca76.css rel=prefetch><link href=/css/chunk-bb4f0d4a.c1990d7c.css rel=prefetch><link href=/css/chunk-bfd5bbcc.6a83ae7d.css rel=prefetch><link href=/css/chunk-c0a1d3c4.09186be6.css rel=prefetch><link href=/css/chunk-cec31564.6f053d75.css rel=prefetch><link href=/css/fail.809a6bc5.css rel=prefetch><link href=/js/chunk-0337f7a6.11326d77.js rel=prefetch><link href=/js/chunk-0ba750a2.b786c9db.js rel=prefetch><link href=/js/chunk-1079f749.ec67c7db.js rel=prefetch><link href=/js/chunk-142c8832.0f8270b3.js rel=prefetch><link href=/js/chunk-14e0b302.a86d1254.js rel=prefetch><link href=/js/chunk-161dc990.5de9313f.js rel=prefetch><link href=/js/chunk-1be69b35.81559bfc.js rel=prefetch><link href=/js/chunk-2d0b64bf.61d5d7c3.js rel=prefetch><link href=/js/chunk-2d0d65a2.2249765a.js rel=prefetch><link href=/js/chunk-2d21a35c.eda7a11a.js rel=prefetch><link href=/js/chunk-2d228d13.85b46532.js rel=prefetch><link href=/js/chunk-31c8ea42.0b2feab9.js rel=prefetch><link href=/js/chunk-5000e55c.3bd9ce3a.js rel=prefetch><link href=/js/chunk-5bf599cc.6555f060.js rel=prefetch><link href=/js/chunk-6d8b31f6.b64e5366.js rel=prefetch><link href=/js/chunk-81d936d8.8bec77ac.js rel=prefetch><link href=/js/chunk-87e2df70.0ada7d4e.js rel=prefetch><link href=/js/chunk-b2d0b040.b0d70d07.js rel=prefetch><link href=/js/chunk-bb4f0d4a.ef7d1ded.js rel=prefetch><link href=/js/chunk-bfd5bbcc.d2ca1e80.js rel=prefetch><link href=/js/chunk-c0a1d3c4.41d0d3f8.js rel=prefetch><link href=/js/chunk-cec31564.cfe3fd85.js rel=prefetch><link href=/js/fail.61f30b0f.js rel=prefetch><link href=/css/app.852293da.css rel=preload as=style><link href=/css/chunk-vendors.ee4e8dbf.css rel=preload as=style><link href=/js/app.29b04043.js rel=preload as=script><link href=/js/chunk-vendors.2f7bce79.js rel=preload as=script><link href=/css/chunk-vendors.ee4e8dbf.css rel=stylesheet><link href=/css/app.852293da.css rel=stylesheet></head><body><noscript><strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.2f7bce79.js></script><script src=/js/app.29b04043.js></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,133 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="alternate" type="application/rss+xml" title="atom 1.0" href="${context!}/atom.xml">
<title>${error.status!} | ${error.error!}</title>
<style type="text/css">
body {
padding: 30px 20px;
font-family: -apple-system, BlinkMacSystemFont,
"Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell",
"Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
color: #727272;
line-height: 1.6;
}
.container {
max-width: 500px;
margin: 0 auto;
}
h1 {
margin: 0;
font-size: 60px;
line-height: 1;
color: #252427;
font-weight: 700;
display: inline-block;
}
h2 {
margin: 100px 0 0;
font-weight: 600;
letter-spacing: 0.1em;
color: #A299AC;
text-transform: uppercase;
}
p {
font-size: 16px;
margin: 1em 0;
}
@media screen and (min-width: 768px) {
body {
padding: 50px;
}
}
@media screen and (max-width: 480px) {
h1 {
font-size: 48px;
}
}
.title {
position: relative;
}
.title::before {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background-color: #000;
transform-origin: bottom right;
transform: scaleX(0);
transition: transform 0.5s ease;
}
.title:hover::before {
transform-origin: bottom left;
transform: scaleX(1);
}
.back-home button {
z-index: 1;
position: relative;
font-size: inherit;
font-family: inherit;
color: white;
padding: 0.5em 1em;
outline: none;
border: none;
background-color: hsl(0, 0%, 0%);
overflow: hidden;
transition: color 0.4s ease-in-out;
}
.back-home button::before {
content: '';
z-index: -1;
position: absolute;
top: 100%;
left: 100%;
width: 1em;
height: 1em;
border-radius: 50%;
background-color: #fff;
transform-origin: center;
transform: translate3d(-50%, -50%, 0) scale3d(0, 0, 0);
transition: transform 0.45s ease-in-out;
}
.back-home button:hover {
cursor: pointer;
color: #000;
}
.back-home button:hover::before {
transform: translate3d(-50%, -50%, 0) scale3d(15, 15, 15);
}
</style>
</head>
<body>
<div class="container">
<h2>${error.status!}</h2>
<h1 class="title">${error.error!}.</h1>
<p>${error.message!}</p>
<div class="back-home">
<button onclick="window.location.href='${context!}'">首页</button>
</div>
</div>
</body>
</html>

View File

@ -52,6 +52,9 @@
</#macro>
<#macro globalHeader>
<#if options.spider_disabled!false>
<meta name="robots" content="none">
</#if>
<meta name="generator" content="Halo ${version!}" />
<@custom_head />
<@verification />