Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a8e81bb5b | |||
| 0e4184057a | |||
| 18aa1199ec |
@@ -1,3 +1,23 @@
|
|||||||
|
# halo-plugin-douban
|
||||||
|
|
||||||
|
> 基于原作者v1.2.2代码修改
|
||||||
|
|
||||||
|
#### v1.2.2-3
|
||||||
|
1. 标题、描述接入搜索引擎;新增详情页
|
||||||
|
2. 控制台新增必填项;修改字数上限、时间格式
|
||||||
|
|
||||||
|
#### v1.2.2-2
|
||||||
|
1. 新增类别接口`/apis/api.douban.moony.la/v1alpha1/doubanmovies/-/types`(基于原作者v1.2.5代码)
|
||||||
|
|
||||||
|
#### v1.2.2-1
|
||||||
|
1. 修改前台页面路由为/books
|
||||||
|
2. 控制台类别value不再限制
|
||||||
|
3. 修复构建失败等异常
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# plugin-douban
|
# plugin-douban
|
||||||
|
|
||||||
* 豆瓣管理插件, 支持在 Console 进行管理以及为主题端提供 `/douban` 页面路由。
|
* 豆瓣管理插件, 支持在 Console 进行管理以及为主题端提供 `/douban` 页面路由。
|
||||||
|
|||||||
@@ -144,6 +144,28 @@
|
|||||||
"tags" : [ "api.douban.moony.la/v1alpha1/DoubanMovie" ]
|
"tags" : [ "api.douban.moony.la/v1alpha1/DoubanMovie" ]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/apis/api.douban.moony.la/v1alpha1/doubanmovies/-/types" : {
|
||||||
|
"get" : {
|
||||||
|
"description" : "List all douban types.",
|
||||||
|
"operationId" : "ListTypes",
|
||||||
|
"responses" : {
|
||||||
|
"default" : {
|
||||||
|
"content" : {
|
||||||
|
"*/*" : {
|
||||||
|
"schema" : {
|
||||||
|
"type" : "array",
|
||||||
|
"items" : {
|
||||||
|
"$ref" : "#/components/schemas/DoubanTypeVo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description" : "default response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags" : [ "api.douban.moony.la/v1alpha1/DoubanMovie" ]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/apis/api.douban.moony.la/v1alpha1/doubanmovies/-/getDoubanDetail" : {
|
"/apis/api.douban.moony.la/v1alpha1/doubanmovies/-/getDoubanDetail" : {
|
||||||
"get" : {
|
"get" : {
|
||||||
"description" : "getDoubanDetail.",
|
"description" : "getDoubanDetail.",
|
||||||
@@ -851,6 +873,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"DoubanTypeVo" : {
|
||||||
|
"type" : "object",
|
||||||
|
"properties" : {
|
||||||
|
"doubanCount" : {
|
||||||
|
"type" : "integer",
|
||||||
|
"format" : "int32"
|
||||||
|
},
|
||||||
|
"key" : {
|
||||||
|
"type" : "string"
|
||||||
|
},
|
||||||
|
"name" : {
|
||||||
|
"type" : "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"JsonPatch" : {
|
"JsonPatch" : {
|
||||||
"minItems" : 1,
|
"minItems" : 1,
|
||||||
"uniqueItems" : true,
|
"uniqueItems" : true,
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation platform('run.halo.tools.platform:plugin:2.20.0-SNAPSHOT')
|
implementation platform('run.halo.tools.platform:plugin:2.20.11')
|
||||||
compileOnly 'run.halo.app:api'
|
compileOnly 'run.halo.app:api'
|
||||||
|
|
||||||
testImplementation 'run.halo.app:api'
|
testImplementation 'run.halo.app:api'
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
version=1.2.2
|
version=1.2.2-3
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package la.moony.douban;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||||
import la.moony.douban.extension.DoubanMovie;
|
import la.moony.douban.extension.DoubanMovie;
|
||||||
|
import la.moony.douban.finders.DoubanFinder;
|
||||||
import la.moony.douban.service.DoubanService;
|
import la.moony.douban.service.DoubanService;
|
||||||
import la.moony.douban.vo.DoubanMovieVo;
|
import la.moony.douban.vo.DoubanMovieVo;
|
||||||
|
import la.moony.douban.vo.DoubanTypeVo;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -25,9 +27,12 @@ public class DoubanEndpoint implements CustomEndpoint {
|
|||||||
|
|
||||||
private final DoubanService doubanService;
|
private final DoubanService doubanService;
|
||||||
|
|
||||||
|
private final DoubanFinder doubanFinder;
|
||||||
|
|
||||||
public DoubanEndpoint(DoubanService doubanService) {
|
|
||||||
|
public DoubanEndpoint(DoubanService doubanService, DoubanFinder doubanFinder) {
|
||||||
this.doubanService = doubanService;
|
this.doubanService = doubanService;
|
||||||
|
this.doubanFinder = doubanFinder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -43,6 +48,13 @@ public class DoubanEndpoint implements CustomEndpoint {
|
|||||||
);
|
);
|
||||||
DoubanMovieQuery.buildParameters(builder);
|
DoubanMovieQuery.buildParameters(builder);
|
||||||
})
|
})
|
||||||
|
.GET("doubanmovies/-/types", this::ListTypes,
|
||||||
|
builder -> builder.operationId("ListTypes")
|
||||||
|
.description("List all douban types.")
|
||||||
|
.tag(doubanMovieTag)
|
||||||
|
.response(responseBuilder()
|
||||||
|
.implementationArray(DoubanTypeVo.class)
|
||||||
|
))
|
||||||
.GET("doubanmovies/-/genres", this::ListGenres,
|
.GET("doubanmovies/-/genres", this::ListGenres,
|
||||||
builder -> builder.operationId("ListGenres")
|
builder -> builder.operationId("ListGenres")
|
||||||
.description("List all douban genres.")
|
.description("List all douban genres.")
|
||||||
@@ -87,6 +99,12 @@ public class DoubanEndpoint implements CustomEndpoint {
|
|||||||
.flatMap(result -> ServerResponse.ok().bodyValue(result));
|
.flatMap(result -> ServerResponse.ok().bodyValue(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Mono<ServerResponse> ListTypes(ServerRequest request) {
|
||||||
|
return doubanFinder.listAllType()
|
||||||
|
.collectList()
|
||||||
|
.flatMap(result -> ServerResponse.ok().bodyValue(result));
|
||||||
|
}
|
||||||
|
|
||||||
private Mono<ServerResponse> getDoubanDetail(ServerRequest request) {
|
private Mono<ServerResponse> getDoubanDetail(ServerRequest request) {
|
||||||
String url = request.queryParam("url").orElse(null);
|
String url = request.queryParam("url").orElse(null);
|
||||||
return doubanService.getDoubanDetail(url)
|
return doubanService.getDoubanDetail(url)
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ public class DoubanPlugin extends BasePlugin {
|
|||||||
.setName("spec.name")
|
.setName("spec.name")
|
||||||
.setIndexFunc(
|
.setIndexFunc(
|
||||||
simpleAttribute(DoubanMovie.class, doubanMovie -> doubanMovie.getSpec().getName())));
|
simpleAttribute(DoubanMovie.class, doubanMovie -> doubanMovie.getSpec().getName())));
|
||||||
|
indexSpecs.add(new IndexSpec()
|
||||||
|
.setName("spec.cardSubtitle")
|
||||||
|
.setIndexFunc(simpleAttribute(DoubanMovie.class,
|
||||||
|
doubanMovie -> doubanMovie.getSpec().getCardSubtitle())));
|
||||||
indexSpecs.add(new IndexSpec()
|
indexSpecs.add(new IndexSpec()
|
||||||
.setName("spec.id")
|
.setName("spec.id")
|
||||||
.setIndexFunc(
|
.setIndexFunc(
|
||||||
|
|||||||
@@ -28,10 +28,19 @@ public class DoubanRouter {
|
|||||||
@Bean
|
@Bean
|
||||||
RouterFunction<ServerResponse> friendTemplateRoute() {
|
RouterFunction<ServerResponse> friendTemplateRoute() {
|
||||||
|
|
||||||
return RouterFunctions.route().GET("/douban",this::handlerFunction)
|
return RouterFunctions.route().GET("/books",this::handlerFunction)
|
||||||
|
.GET("/books/{doubanMovieName:\\S+}", this::doubanMovieDetail)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Mono<ServerResponse> doubanMovieDetail(ServerRequest request) {
|
||||||
|
String doubanMovieName = request.pathVariable("doubanMovieName");
|
||||||
|
return templateNameResolver.resolveTemplateNameOrDefault(request.exchange(), "douban-detail")
|
||||||
|
.flatMap(templateName -> ServerResponse.ok().render(templateName,
|
||||||
|
java.util.Map.of("douban", doubanFinder.get(doubanMovieName),
|
||||||
|
"title", getDoubanTitle())));
|
||||||
|
}
|
||||||
|
|
||||||
private Mono<ServerResponse> handlerFunction(ServerRequest request) {
|
private Mono<ServerResponse> handlerFunction(ServerRequest request) {
|
||||||
String type = request.queryParam("type")
|
String type = request.queryParam("type")
|
||||||
.filter(StringUtils::isNotBlank)
|
.filter(StringUtils::isNotBlank)
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package la.moony.douban.search;
|
||||||
|
|
||||||
|
import static la.moony.douban.search.DoubanMovieHaloDocumentsProvider.DOUBAN_MOVIE_DOCUMENT_TYPE;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import la.moony.douban.extension.DoubanMovie;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import run.halo.app.infra.ExternalUrlSupplier;
|
||||||
|
import run.halo.app.search.HaloDocument;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DoubanMovieDocumentConverter implements Converter<DoubanMovie, Mono<HaloDocument>> {
|
||||||
|
|
||||||
|
private final ExternalUrlSupplier externalUrlSupplier;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public Mono<HaloDocument> convert(DoubanMovie movie) {
|
||||||
|
var spec = movie.getSpec();
|
||||||
|
String title = StringUtils.defaultIfBlank(spec == null ? null : spec.getName(), "豆瓣记录");
|
||||||
|
String description = StringUtils.defaultIfBlank(
|
||||||
|
spec == null ? null : spec.getCardSubtitle(), "无描述");
|
||||||
|
var document = new HaloDocument();
|
||||||
|
document.setMetadataName(movie.getMetadata().getName());
|
||||||
|
document.setType(DOUBAN_MOVIE_DOCUMENT_TYPE);
|
||||||
|
document.setId(haloDocId(movie));
|
||||||
|
document.setTitle(title);
|
||||||
|
document.setDescription(description);
|
||||||
|
document.setContent(String.join(" ", title, description));
|
||||||
|
document.setExposed(true);
|
||||||
|
document.setTags(List.of());
|
||||||
|
document.setOwnerName("豆瓣");
|
||||||
|
document.setPublished(true);
|
||||||
|
document.setPermalink(externalUrlSupplier.get()
|
||||||
|
.resolve("books/" + movie.getMetadata().getName()).toString());
|
||||||
|
document.setCreationTimestamp(creationTimestamp(movie));
|
||||||
|
document.setUpdateTimestamp(creationTimestamp(movie));
|
||||||
|
return Mono.just(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
String haloDocId(DoubanMovie movie) {
|
||||||
|
return DOUBAN_MOVIE_DOCUMENT_TYPE + '-' + movie.getMetadata().getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Instant creationTimestamp(DoubanMovie movie) {
|
||||||
|
if (movie.getFaves() != null && movie.getFaves().getCreateTime() != null) {
|
||||||
|
return movie.getFaves().getCreateTime();
|
||||||
|
}
|
||||||
|
return movie.getMetadata().getCreationTimestamp() == null
|
||||||
|
? Instant.EPOCH : movie.getMetadata().getCreationTimestamp();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package la.moony.douban.search;
|
||||||
|
|
||||||
|
import la.moony.douban.extension.DoubanMovie;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
|
import run.halo.app.extension.ListResult;
|
||||||
|
import run.halo.app.extension.PageRequest;
|
||||||
|
import run.halo.app.extension.PageRequestImpl;
|
||||||
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
|
import run.halo.app.extension.router.selector.FieldSelector;
|
||||||
|
import run.halo.app.search.HaloDocument;
|
||||||
|
import run.halo.app.search.HaloDocumentsProvider;
|
||||||
|
|
||||||
|
import static run.halo.app.extension.index.query.QueryFactory.isNull;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DoubanMovieHaloDocumentsProvider implements HaloDocumentsProvider {
|
||||||
|
|
||||||
|
public static final String DOUBAN_MOVIE_DOCUMENT_TYPE = "doubanmovie.douban.moony.la";
|
||||||
|
private static final int PAGE_SIZE = 200;
|
||||||
|
|
||||||
|
private final ReactiveExtensionClient client;
|
||||||
|
private final DoubanMovieDocumentConverter converter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<HaloDocument> fetchAll() {
|
||||||
|
var options = new ListOptions();
|
||||||
|
options.setFieldSelector(FieldSelector.of(isNull("metadata.deletionTimestamp")));
|
||||||
|
var pageRequest = PageRequestImpl.of(1, PAGE_SIZE,
|
||||||
|
Sort.by("metadata.creationTimestamp", "metadata.name"));
|
||||||
|
return client.listBy(DoubanMovie.class, options, pageRequest)
|
||||||
|
.expand(result -> result.hasNext()
|
||||||
|
? client.listBy(DoubanMovie.class, options,
|
||||||
|
PageRequestImpl.of(result.getPage() + 1, result.getSize(), pageRequest.getSort()))
|
||||||
|
: Mono.empty())
|
||||||
|
.flatMap(result -> Flux.fromStream(result.get()))
|
||||||
|
.flatMap(converter::convert);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getType() {
|
||||||
|
return DOUBAN_MOVIE_DOCUMENT_TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package la.moony.douban.search;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import la.moony.douban.extension.DoubanMovie;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import run.halo.app.extension.ExtensionClient;
|
||||||
|
import run.halo.app.extension.ExtensionUtil;
|
||||||
|
import run.halo.app.extension.controller.Controller;
|
||||||
|
import run.halo.app.extension.controller.ControllerBuilder;
|
||||||
|
import run.halo.app.extension.controller.Reconciler;
|
||||||
|
import run.halo.app.search.event.HaloDocumentAddRequestEvent;
|
||||||
|
import run.halo.app.search.event.HaloDocumentDeleteRequestEvent;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DoubanMovieSearchReconciler implements Reconciler<Reconciler.Request> {
|
||||||
|
|
||||||
|
private static final String FINALIZER = "douban-movie-search-protection";
|
||||||
|
private final ApplicationEventPublisher eventPublisher;
|
||||||
|
private final ExtensionClient client;
|
||||||
|
private final DoubanMovieDocumentConverter converter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result reconcile(Request request) {
|
||||||
|
client.fetch(DoubanMovie.class, request.name()).ifPresent(movie -> {
|
||||||
|
if (ExtensionUtil.isDeleted(movie)) {
|
||||||
|
if (ExtensionUtil.removeFinalizers(movie.getMetadata(), Set.of(FINALIZER))) {
|
||||||
|
eventPublisher.publishEvent(new HaloDocumentDeleteRequestEvent(this,
|
||||||
|
List.of(converter.haloDocId(movie))));
|
||||||
|
client.update(movie);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ExtensionUtil.addFinalizers(movie.getMetadata(), Set.of(FINALIZER));
|
||||||
|
var document = converter.convert(movie).blockOptional().orElseThrow();
|
||||||
|
eventPublisher.publishEvent(new HaloDocumentAddRequestEvent(this, List.of(document)));
|
||||||
|
client.update(movie);
|
||||||
|
});
|
||||||
|
return Result.doNotRetry();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Controller setupWith(ControllerBuilder builder) {
|
||||||
|
return builder.extension(new DoubanMovie()).workerCount(1).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ metadata:
|
|||||||
rbac.authorization.halo.run/display-name: "查看朋友圈提交模板"
|
rbac.authorization.halo.run/display-name: "查看朋友圈提交模板"
|
||||||
rules:
|
rules:
|
||||||
- apiGroups: [ "api.douban.moony.la" ]
|
- apiGroups: [ "api.douban.moony.la" ]
|
||||||
resources: [ "doubanmovies","doubanmovies/genres","doubanmovies/getDoubanDetail"]
|
resources: [ "doubanmovies","doubanmovies/types","doubanmovies/genres","doubanmovies/getDoubanDetail"]
|
||||||
verbs: [ "get", "list" ]
|
verbs: [ "get", "list" ]
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -182,8 +182,8 @@ class HALO_DOUBAN {
|
|||||||
}${
|
}${
|
||||||
movie.spec.year > 0 ? " · " + movie.spec.year : ""
|
movie.spec.year > 0 ? " · " + movie.spec.year : ""
|
||||||
}</div><div class="db--title"><a href="${
|
}</div><div class="db--title"><a href="${
|
||||||
movie.spec.link
|
`/books/${movie.metadata.name}`
|
||||||
}" target="_blank">${movie.spec.name}</a></div>
|
}">${movie.spec.name}</a></div>
|
||||||
|
|
||||||
</div>`;
|
</div>`;
|
||||||
})
|
})
|
||||||
@@ -212,8 +212,8 @@ class HALO_DOUBAN {
|
|||||||
}${
|
}${
|
||||||
item.spec.year > 0 ? " · " + item.spec.year : ""
|
item.spec.year > 0 ? " · " + item.spec.year : ""
|
||||||
}</div><div class="db--title"><a href="${
|
}</div><div class="db--title"><a href="${
|
||||||
item.spec.link
|
`/books/${item.metadata.name}`
|
||||||
}" target="_blank">${item.spec.name}</a></div>
|
}">${item.spec.name}</a></div>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN" xmlns:th="https://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title th:text="${douban != null ? douban.spec.name : '豆瓣记录'}">豆瓣记录</title>
|
||||||
|
<link rel="stylesheet" th:href="@{/plugins/plugin-douban/assets/static/db.min.css}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="layoutSingleColumn layoutSingleColumn--wide u-paddingTop50">
|
||||||
|
<section class="doulist-item" th:if="${douban != null}">
|
||||||
|
<div class="doulist-subject">
|
||||||
|
<div class="doulist-post"><img class="db--image" th:src="${douban.spec.poster}" alt=""></div>
|
||||||
|
<div class="doulist-content">
|
||||||
|
<div class="doulist-title" th:text="${douban.spec.name}">标题</div>
|
||||||
|
<div class="abstract" th:text="${douban.spec.cardSubtitle}">描述</div>
|
||||||
|
<p th:text="${douban.faves.remark}">备注</p>
|
||||||
|
<a th:href="${douban.spec.link}" target="_blank" rel="external nofollow">查看豆瓣原页</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<p th:if="${douban == null}">未找到该条豆瓣记录。</p>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
|
|
||||||
</main>
|
</main>
|
||||||
<footer class="site--footer">
|
<footer class="site--footer">
|
||||||
<script th:src="@{/plugins/plugin-douban/assets/static/db.min.js?v={version}(version=${version})}"></script>
|
<script th:src="@{/plugins/plugin-douban/assets/static/db.js?v={version}(version=${version})}"></script>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"name": "@kunkunyu/plugin-douban",
|
"name": "@kunkunyu/plugin-douban",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite build --watch --mode=development",
|
"dev": "vite build --watch --mode=development",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
|||||||
Generated
+5124
-3316
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ import { ref, computed, nextTick, watch} from "vue";
|
|||||||
import cloneDeep from "lodash.clonedeep";
|
import cloneDeep from "lodash.clonedeep";
|
||||||
import {doubanCoreApiClient} from "@/api";
|
import {doubanCoreApiClient} from "@/api";
|
||||||
import type { DoubanMovie } from "@/api/generated";
|
import type { DoubanMovie } from "@/api/generated";
|
||||||
import {toDatetimeLocal, toISOString} from "@/utils/date";
|
import {toDateInput, toISOString} from "@/utils/date";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -55,6 +55,8 @@ const saving = ref<boolean>(false);
|
|||||||
const formVisible = ref(false);
|
const formVisible = ref(false);
|
||||||
const createTime = ref<string | undefined>(undefined);
|
const createTime = ref<string | undefined>(undefined);
|
||||||
|
|
||||||
|
const getToday = () => toDateInput(new Date());
|
||||||
|
|
||||||
|
|
||||||
const isUpdateMode = computed(() => {
|
const isUpdateMode = computed(() => {
|
||||||
return !!formState.value.metadata.creationTimestamp;
|
return !!formState.value.metadata.creationTimestamp;
|
||||||
@@ -73,6 +75,9 @@ const onVisibleChange = (visible: boolean) => {
|
|||||||
|
|
||||||
const handleResetForm = () => {
|
const handleResetForm = () => {
|
||||||
formState.value = cloneDeep(initialFormState);
|
formState.value = cloneDeep(initialFormState);
|
||||||
|
const today = getToday();
|
||||||
|
createTime.value = today;
|
||||||
|
formState.value.faves.createTime = toISOString(today);
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -94,9 +99,9 @@ watch(
|
|||||||
(doubanMovie) => {
|
(doubanMovie) => {
|
||||||
if (doubanMovie) {
|
if (doubanMovie) {
|
||||||
formState.value = cloneDeep(doubanMovie);
|
formState.value = cloneDeep(doubanMovie);
|
||||||
createTime.value = toDatetimeLocal(formState.value.faves.createTime);
|
createTime.value = toDateInput(formState.value.faves.createTime) || getToday();
|
||||||
}else {
|
}else {
|
||||||
createTime.value = undefined;
|
createTime.value = getToday();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -108,6 +113,9 @@ watch(
|
|||||||
() => createTime.value,
|
() => createTime.value,
|
||||||
(value) => {
|
(value) => {
|
||||||
formState.value.faves.createTime = value ? toISOString(value) : undefined;
|
formState.value.faves.createTime = value ? toISOString(value) : undefined;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const annotationsFormRef = ref();
|
const annotationsFormRef = ref();
|
||||||
@@ -218,32 +226,12 @@ const handleSaveFriend = async () => {
|
|||||||
></FormKit>
|
></FormKit>
|
||||||
<FormKit
|
<FormKit
|
||||||
v-if="formState.spec.dataType=='halo'"
|
v-if="formState.spec.dataType=='halo'"
|
||||||
:options="[
|
type="text"
|
||||||
{
|
|
||||||
label: '电影',
|
|
||||||
value: 'movie',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '图书',
|
|
||||||
value: 'book',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '音乐',
|
|
||||||
value: 'music',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '游戏',
|
|
||||||
value: 'game',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '舞台剧',
|
|
||||||
value: 'drama',
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
label="类型"
|
|
||||||
v-model="formState.spec.type"
|
v-model="formState.spec.type"
|
||||||
name="type"
|
name="type"
|
||||||
type="select"
|
label="类型"
|
||||||
|
placeholder="输入类型 value,例如 movie"
|
||||||
|
validation="required"
|
||||||
></FormKit>
|
></FormKit>
|
||||||
<FormKit
|
<FormKit
|
||||||
v-if="formState.spec.dataType=='halo'"
|
v-if="formState.spec.dataType=='halo'"
|
||||||
@@ -252,12 +240,10 @@ const handleSaveFriend = async () => {
|
|||||||
name="cardSubtitle"
|
name="cardSubtitle"
|
||||||
label="描述"
|
label="描述"
|
||||||
:rows="4"
|
:rows="4"
|
||||||
validation="required|length:0,300"
|
validation="required|length:0,1000"
|
||||||
></FormKit>
|
></FormKit>
|
||||||
<FormKit
|
<FormKit
|
||||||
type="datetime-local"
|
type="date"
|
||||||
min="0000-01-01T00:00"
|
|
||||||
max="9999-12-31T23:59"
|
|
||||||
v-model="createTime"
|
v-model="createTime"
|
||||||
name="createTime"
|
name="createTime"
|
||||||
validation="required"
|
validation="required"
|
||||||
@@ -273,6 +259,7 @@ const handleSaveFriend = async () => {
|
|||||||
v-model="formState.faves.status"
|
v-model="formState.faves.status"
|
||||||
name="status"
|
name="status"
|
||||||
type="select"
|
type="select"
|
||||||
|
validation="required"
|
||||||
></FormKit>
|
></FormKit>
|
||||||
<FormKit
|
<FormKit
|
||||||
type="textarea"
|
type="textarea"
|
||||||
|
|||||||
@@ -47,10 +47,16 @@ export function toDatetimeLocal(date: string | Date | undefined | null): string
|
|||||||
return dayjs(date).format("YYYY-MM-DDTHH:mm");
|
return dayjs(date).format("YYYY-MM-DDTHH:mm");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toDateInput(date: string | Date | undefined | null): string {
|
||||||
|
if (!date) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return dayjs(date).format("YYYY-MM-DD");
|
||||||
|
}
|
||||||
|
|
||||||
export function toISOString(date: string | Date | undefined | null): string {
|
export function toISOString(date: string | Date | undefined | null): string {
|
||||||
if (!date) {
|
if (!date) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return dayjs(date).utc(false).toISOString();
|
return dayjs(date).utc(false).toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-27
@@ -332,35 +332,12 @@ const onEditingModalClose = async () => {
|
|||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
<FilterDropdown
|
<FormKit
|
||||||
v-model="selectedType"
|
v-model="selectedType"
|
||||||
label="类型"
|
label="类型"
|
||||||
:items="[
|
placeholder="输入类型 value 过滤,例如 movie"
|
||||||
{
|
type="text"
|
||||||
label: '全部',
|
outer-class="!moments-p-0"
|
||||||
value: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '电影',
|
|
||||||
value: 'movie',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '图书',
|
|
||||||
value: 'book',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '音乐',
|
|
||||||
value: 'music',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '游戏',
|
|
||||||
value: 'game',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '舞台剧',
|
|
||||||
value: 'drama',
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
/>
|
/>
|
||||||
<FilterDropdown
|
<FilterDropdown
|
||||||
v-model="selectedSort"
|
v-model="selectedSort"
|
||||||
|
|||||||
+15
-1
@@ -1,9 +1,23 @@
|
|||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.web.json",
|
|
||||||
"include": ["./env.d.ts", "./src/**/*", "./src/**/*.vue"],
|
"include": ["./env.d.ts", "./src/**/*", "./src/**/*.vue"],
|
||||||
"exclude": ["./src/**/__tests__/*"],
|
"exclude": ["./src/**/__tests__/*"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.node.json",
|
"extends": "@tsconfig/node20/tsconfig.json",
|
||||||
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*"],
|
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user