mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-08-31 07:27:14 +00:00
Merge pull request #503 from nagisa77/feature/nuxt_opt_v2
fix: 前端水合前ui优化. 可以考虑评论➕框不出
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
package com.openisle.controller;
|
package com.openisle.controller;
|
||||||
|
|
||||||
|
import com.openisle.dto.ActivityDto;
|
||||||
import com.openisle.dto.MilkTeaInfoDto;
|
import com.openisle.dto.MilkTeaInfoDto;
|
||||||
import com.openisle.dto.MilkTeaRedeemRequest;
|
import com.openisle.dto.MilkTeaRedeemRequest;
|
||||||
|
import com.openisle.mapper.ActivityMapper;
|
||||||
import com.openisle.model.Activity;
|
import com.openisle.model.Activity;
|
||||||
import com.openisle.model.ActivityType;
|
import com.openisle.model.ActivityType;
|
||||||
import com.openisle.model.User;
|
import com.openisle.model.User;
|
||||||
@@ -12,6 +14,7 @@ import org.springframework.security.core.Authentication;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/activities")
|
@RequestMapping("/api/activities")
|
||||||
@@ -19,10 +22,13 @@ import java.util.List;
|
|||||||
public class ActivityController {
|
public class ActivityController {
|
||||||
private final ActivityService activityService;
|
private final ActivityService activityService;
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
|
private final ActivityMapper activityMapper;
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public List<Activity> list() {
|
public List<ActivityDto> list() {
|
||||||
return activityService.list();
|
return activityService.list().stream()
|
||||||
|
.map(activityMapper::toDto)
|
||||||
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/milk-tea")
|
@GetMapping("/milk-tea")
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.openisle.dto;
|
||||||
|
|
||||||
|
import com.openisle.model.ActivityType;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO representing an activity without participant details.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ActivityDto {
|
||||||
|
private Long id;
|
||||||
|
private String title;
|
||||||
|
private String icon;
|
||||||
|
private String content;
|
||||||
|
private LocalDateTime startTime;
|
||||||
|
private LocalDateTime endTime;
|
||||||
|
private ActivityType type;
|
||||||
|
private boolean ended;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.openisle.mapper;
|
||||||
|
|
||||||
|
import com.openisle.dto.ActivityDto;
|
||||||
|
import com.openisle.model.Activity;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/** Mapper for activity entities. */
|
||||||
|
@Component
|
||||||
|
public class ActivityMapper {
|
||||||
|
|
||||||
|
public ActivityDto toDto(Activity a) {
|
||||||
|
ActivityDto dto = new ActivityDto();
|
||||||
|
dto.setId(a.getId());
|
||||||
|
dto.setTitle(a.getTitle());
|
||||||
|
dto.setIcon(a.getIcon());
|
||||||
|
dto.setContent(a.getContent());
|
||||||
|
dto.setStartTime(a.getStartTime());
|
||||||
|
dto.setEndTime(a.getEndTime());
|
||||||
|
dto.setType(a.getType());
|
||||||
|
dto.setEnded(a.isEnded());
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,8 @@ spring.jpa.hibernate.ddl-auto=update
|
|||||||
app.jwt.secret=${JWT_SECRET:jwt_sec}
|
app.jwt.secret=${JWT_SECRET:jwt_sec}
|
||||||
app.jwt.reason-secret=${JWT_REASON_SECRET:jwt_reason_sec}
|
app.jwt.reason-secret=${JWT_REASON_SECRET:jwt_reason_sec}
|
||||||
app.jwt.reset-secret=${JWT_RESET_SECRET:jwt_reset_sec}
|
app.jwt.reset-secret=${JWT_RESET_SECRET:jwt_reset_sec}
|
||||||
app.jwt.expiration=${JWT_EXPIRATION:86400000}
|
# 30 days
|
||||||
|
app.jwt.expiration=${JWT_EXPIRATION:2592000000}
|
||||||
# Password strength: LOW, MEDIUM or HIGH
|
# Password strength: LOW, MEDIUM or HIGH
|
||||||
app.password.strength=${PASSWORD_STRENGTH:LOW}
|
app.password.strength=${PASSWORD_STRENGTH:LOW}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
--menu-text-color: black;
|
--menu-text-color: black;
|
||||||
--scroller-background-color: rgba(130, 175, 180, 0.5);
|
--scroller-background-color: rgba(130, 175, 180, 0.5);
|
||||||
--normal-background-color: rgb(241, 241, 241);
|
--normal-background-color: rgb(241, 241, 241);
|
||||||
|
--lottery-background-color: rgb(241, 241, 241);
|
||||||
--login-background-color: rgb(248, 248, 248);
|
--login-background-color: rgb(248, 248, 248);
|
||||||
--login-background-color-hover: #e0e0e0;
|
--login-background-color-hover: #e0e0e0;
|
||||||
--text-color: black;
|
--text-color: black;
|
||||||
@@ -42,6 +43,7 @@
|
|||||||
--menu-selected-background-color: rgba(255, 255, 255, 0.1);
|
--menu-selected-background-color: rgba(255, 255, 255, 0.1);
|
||||||
--menu-text-color: white;
|
--menu-text-color: white;
|
||||||
--normal-background-color: #000000;
|
--normal-background-color: #000000;
|
||||||
|
--lottery-background-color: #4e4e4e;
|
||||||
--login-background-color: #575757;
|
--login-background-color: #575757;
|
||||||
--login-background-color-hover: #717171;
|
--login-background-color-hover: #717171;
|
||||||
--text-color: #eee;
|
--text-color: #eee;
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ const selectMedal = async (medal) => {
|
|||||||
.achievements-list-item-description {
|
.achievements-list-item-description {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.not_completed {
|
.not_completed {
|
||||||
@@ -162,7 +163,10 @@ const selectMedal = async (medal) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.achievements-list-item {
|
.achievements-list-item {
|
||||||
min-width: calc(50% - 30px);
|
width: calc(50% - 27px);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -50,11 +50,12 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { authState, clearToken, loadCurrentUser } from '~/utils/auth'
|
import { authState, clearToken, loadCurrentUser } from '~/utils/auth'
|
||||||
import { watch, nextTick } from 'vue'
|
import { watch, nextTick, ref, computed } from 'vue'
|
||||||
import { fetchUnreadCount, notificationState } from '~/utils/notification'
|
import { fetchUnreadCount, notificationState } from '~/utils/notification'
|
||||||
import DropdownMenu from '~/components/DropdownMenu.vue'
|
import DropdownMenu from '~/components/DropdownMenu.vue'
|
||||||
import SearchDropdown from '~/components/SearchDropdown.vue'
|
import SearchDropdown from '~/components/SearchDropdown.vue'
|
||||||
import { useIsMobile } from '~/utils/screen'
|
import { useIsMobile } from '~/utils/screen'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
import { ClientOnly } from '#components'
|
import { ClientOnly } from '#components'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -66,18 +67,14 @@ export default {
|
|||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
avatar: '',
|
|
||||||
showSearch: false,
|
|
||||||
searchDropdown: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup() {
|
setup() {
|
||||||
const isLogin = computed(() => authState.loggedIn)
|
const isLogin = computed(() => authState.loggedIn)
|
||||||
const isMobile = useIsMobile()
|
const isMobile = useIsMobile()
|
||||||
const unreadCount = computed(() => notificationState.unreadCount)
|
const unreadCount = computed(() => notificationState.unreadCount)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const avatar = ref('')
|
||||||
|
const showSearch = ref(false)
|
||||||
|
const searchDropdown = ref(null)
|
||||||
|
|
||||||
const goToHome = () => {
|
const goToHome = () => {
|
||||||
router.push('/').then(() => {
|
router.push('/').then(() => {
|
||||||
@@ -144,6 +141,8 @@ export default {
|
|||||||
goToProfile,
|
goToProfile,
|
||||||
goToSignup,
|
goToSignup,
|
||||||
goToLogout,
|
goToLogout,
|
||||||
|
showSearch,
|
||||||
|
searchDropdown,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,19 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
link: [
|
link: [
|
||||||
|
{
|
||||||
|
rel: 'icon',
|
||||||
|
type: 'image/x-icon',
|
||||||
|
href: '/favicon.ico',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rel: 'apple-touch-icon',
|
||||||
|
href: '/apple-touch-icon.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rel: 'manifest',
|
||||||
|
href: '/manifest.webmanifest',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
rel: 'stylesheet',
|
rel: 'stylesheet',
|
||||||
href: 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css',
|
href: 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css',
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
@crop="onPrizeCropped"
|
@crop="onPrizeCropped"
|
||||||
/>
|
/>
|
||||||
<div class="prize-row">
|
<div class="prize-row">
|
||||||
|
<span class="prize-row-title">奖品图片</span>
|
||||||
<label class="prize-container">
|
<label class="prize-container">
|
||||||
<img v-if="prizeIcon" :src="prizeIcon" class="prize-preview" alt="prize" />
|
<img v-if="prizeIcon" :src="prizeIcon" class="prize-preview" alt="prize" />
|
||||||
<i v-else class="fa-solid fa-image default-prize-icon"></i>
|
<i v-else class="fa-solid fa-image default-prize-icon"></i>
|
||||||
@@ -51,11 +52,11 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="prize-name-row">
|
<div class="prize-name-row">
|
||||||
<span>奖品描述</span>
|
<span class="prize-row-title">奖品描述</span>
|
||||||
<input class="prize-name-input" v-model="prizeDescription" placeholder="奖品描述" />
|
<BaseInput v-model="prizeDescription" placeholder="奖品描述" />
|
||||||
</div>
|
</div>
|
||||||
<div class="prize-count-row">
|
<div class="prize-count-row">
|
||||||
<span>奖品数量</span>
|
<span class="prize-row-title">奖品数量</span>
|
||||||
<div class="prize-count-input">
|
<div class="prize-count-input">
|
||||||
<input
|
<input
|
||||||
class="prize-count-input-field"
|
class="prize-count-input-field"
|
||||||
@@ -66,7 +67,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="prize-time-row">
|
<div class="prize-time-row">
|
||||||
<span>抽奖结束时间</span>
|
<span class="prize-row-title">抽奖结束时间</span>
|
||||||
<client-only>
|
<client-only>
|
||||||
<flat-pickr v-model="endTime" :config="dateConfig" class="time-picker" />
|
<flat-pickr v-model="endTime" :config="dateConfig" class="time-picker" />
|
||||||
</client-only>
|
</client-only>
|
||||||
@@ -490,6 +491,7 @@ export default {
|
|||||||
.post-submit:hover {
|
.post-submit:hover {
|
||||||
background-color: var(--primary-color-hover);
|
background-color: var(--primary-color-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-submit.disabled:hover {
|
.post-submit.disabled:hover {
|
||||||
background-color: var(--primary-color-disabled);
|
background-color: var(--primary-color-disabled);
|
||||||
}
|
}
|
||||||
@@ -534,8 +536,21 @@ export default {
|
|||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.prize-row-title {
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.prize-row {
|
.prize-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prize-name-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prize-container {
|
.prize-container {
|
||||||
@@ -545,11 +560,15 @@ export default {
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
background-color: var(--lottery-background-color);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.default-prize-icon {
|
.default-prize-icon {
|
||||||
font-size: 100px;
|
font-size: 30px;
|
||||||
opacity: 0.5;
|
opacity: 0.1;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,8 +604,7 @@ export default {
|
|||||||
.prize-count-row,
|
.prize-count-row,
|
||||||
.prize-time-row {
|
.prize-time-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
gap: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.prize-count-input {
|
.prize-count-input {
|
||||||
@@ -612,11 +630,18 @@ export default {
|
|||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
|
background-color: var(--lottery-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-picker {
|
.time-picker {
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
|
background-color: var(--lottery-background-color);
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
padding: 0 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
|||||||
@@ -110,20 +110,35 @@
|
|||||||
<div class="prize-count">x {{ lottery.prizeCount }}</div>
|
<div class="prize-count">x {{ lottery.prizeCount }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="prize-end-time prize-info-right">
|
<div class="prize-end-time prize-info-right">
|
||||||
<div class="prize-end-time-title">离结束还有</div>
|
<div v-if="!isMobile" class="prize-end-time-title">离结束还有</div>
|
||||||
<div class="prize-end-time-value">{{ countdown }}</div>
|
<div class="prize-end-time-value">{{ countdown }}</div>
|
||||||
<div
|
<div v-if="!isMobile" class="join-prize-button-container-desktop">
|
||||||
v-if="loggedIn && !hasJoined && !lotteryEnded"
|
<div
|
||||||
class="join-prize-button"
|
v-if="loggedIn && !hasJoined && !lotteryEnded"
|
||||||
@click="joinLottery"
|
class="join-prize-button"
|
||||||
>
|
@click="joinLottery"
|
||||||
<div class="join-prize-button-text">参与抽奖</div>
|
>
|
||||||
</div>
|
<div class="join-prize-button-text">参与抽奖</div>
|
||||||
<div v-else-if="hasJoined" class="join-prize-button-disabled">
|
</div>
|
||||||
<div class="join-prize-button-text">已参与</div>
|
<div v-else-if="hasJoined" class="join-prize-button-disabled">
|
||||||
|
<div class="join-prize-button-text">已参与</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="isMobile" class="join-prize-button-container-mobile">
|
||||||
|
<div
|
||||||
|
v-if="loggedIn && !hasJoined && !lotteryEnded"
|
||||||
|
class="join-prize-button"
|
||||||
|
@click="joinLottery"
|
||||||
|
>
|
||||||
|
<div class="join-prize-button-text">参与抽奖</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="hasJoined" class="join-prize-button-disabled">
|
||||||
|
<div class="join-prize-button-text">已参与</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="prize-member-container">
|
<div class="prize-member-container">
|
||||||
<img
|
<img
|
||||||
@@ -145,17 +160,22 @@
|
|||||||
alt="avatar"
|
alt="avatar"
|
||||||
@click="gotoUser(w.id)"
|
@click="gotoUser(w.id)"
|
||||||
/>
|
/>
|
||||||
|
<div v-if="lotteryWinners.length === 1" class="prize-member-winner-name">
|
||||||
|
{{ lotteryWinners[0].username }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CommentEditor
|
<ClientOnly>
|
||||||
@submit="postComment"
|
<CommentEditor
|
||||||
:loading="isWaitingPostingComment"
|
@submit="postComment"
|
||||||
:disabled="!loggedIn"
|
:loading="isWaitingPostingComment"
|
||||||
:show-login-overlay="!loggedIn"
|
:disabled="!loggedIn"
|
||||||
:parent-user-name="author.username"
|
:show-login-overlay="!loggedIn"
|
||||||
/>
|
:parent-user-name="author.username"
|
||||||
|
/>
|
||||||
|
</ClientOnly>
|
||||||
|
|
||||||
<div class="comment-config-container">
|
<div class="comment-config-container">
|
||||||
<div class="comment-sort-container">
|
<div class="comment-sort-container">
|
||||||
@@ -1179,7 +1199,8 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
background-color: var(--normal-background-color);
|
background-color: var(--lottery-background-color);
|
||||||
|
border-radius: 10px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1188,6 +1209,12 @@ export default {
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.join-prize-button-container-mobile {
|
||||||
|
margin-top: 15px;
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prize-icon {
|
.prize-icon {
|
||||||
@@ -1254,6 +1281,7 @@ export default {
|
|||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.join-prize-button:hover {
|
.join-prize-button:hover {
|
||||||
@@ -1261,6 +1289,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.join-prize-button-disabled {
|
.join-prize-button-disabled {
|
||||||
|
text-align: center;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
background-color: var(--primary-color);
|
background-color: var(--primary-color);
|
||||||
color: white;
|
color: white;
|
||||||
@@ -1276,6 +1305,8 @@ export default {
|
|||||||
height: 30px;
|
height: 30px;
|
||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prize-member-winner {
|
.prize-member-winner {
|
||||||
@@ -1346,5 +1377,10 @@ export default {
|
|||||||
.loading-container {
|
.loading-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.join-prize-button,
|
||||||
|
.join-prize-button-disabled {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 116 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "OpenIsle",
|
||||||
|
"short_name": "OpenIsle",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user