Browse Source

Merge branch 'dev' of xiongzhu/raex_back into master

sunkean 3 năm trước cách đây
mục cha
commit
19f0926955

+ 3 - 0
src/main/java/com/izouma/nineth/config/CacheConfig.java

@@ -142,6 +142,9 @@ public class CacheConfig {
         cacheNamesConfigurationMap.put("blindBoxRare", RedisCacheConfiguration.defaultCacheConfig()
                 .entryTtl(Duration.ofSeconds(2))
                 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer())));
+        cacheNamesConfigurationMap.put("userHoldList", RedisCacheConfiguration.defaultCacheConfig()
+                .entryTtl(Duration.ofHours(1))
+                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer())));
 
         RedisCacheManager redisCacheManager = RedisCacheManager.builder()
                 .cacheWriter(RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory()))

+ 6 - 0
src/main/java/com/izouma/nineth/config/Constants.java

@@ -47,6 +47,12 @@ public interface Constants {
         String SSR    = "SSR";
         String SR     = "SR";
         String U     = "U";
+        String R     = "R";
+
+        String SSR_LIKE = "%SSR #%";
+        String SR_LIKE = "%SR #%";
+        String U_LIKE = "%U #%";
+        String R_LIKE = "%R #%";
 
         String ACTIVITY_RANK_ID = "activity_rank_id";
     }

+ 0 - 3
src/main/java/com/izouma/nineth/domain/Asset.java

@@ -225,9 +225,6 @@ public class Asset extends CollectionBaseEntity {
     @Column(columnDefinition = "TEXT")
     private String empower;
 
-    @Transient
-    private int num;
-
     private LocalDateTime lockAt;
 
     private LocalDateTime lockTo;

+ 40 - 0
src/main/java/com/izouma/nineth/dto/UserHoldDTO.java

@@ -0,0 +1,40 @@
+package com.izouma.nineth.dto;
+
+import com.alibaba.excel.annotation.ExcelIgnore;
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserHoldDTO {
+
+    @ExcelIgnore
+    private String name;
+
+    @ExcelIgnore
+    private String prefixName;
+
+    @ExcelProperty("用户id")
+    private Long userId;
+
+    @ExcelProperty("用户头像")
+    private String avatar;
+
+    @ExcelProperty("用户昵称")
+    private String nickname;
+
+    @ExcelProperty("用户名")
+    private String username;
+
+    @ExcelProperty("持仓数量")
+    private int num;
+
+    @ExcelProperty("预计价值")
+    private BigDecimal price = BigDecimal.ZERO;
+
+}

+ 8 - 4
src/main/java/com/izouma/nineth/repo/AssetRepo.java

@@ -12,10 +12,7 @@ import org.springframework.data.jpa.repository.Query;
 
 import javax.transaction.Transactional;
 import java.time.LocalDateTime;
-import java.util.Collection;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
+import java.util.*;
 
 public interface AssetRepo extends JpaRepository<Asset, Long>, JpaSpecificationExecutor<Asset> {
     @Query("update Asset t set t.del = true where t.id = ?1")
@@ -107,4 +104,11 @@ public interface AssetRepo extends JpaRepository<Asset, Long>, JpaSpecificationE
 
     List<Asset> findAllByUserIdAndTypeAndOpened(Long userId, CollectionType type, Boolean opened);
 
+    @Query(nativeQuery = true, value = "SELECT asset.user_id userId,user.nickname nickname,user.username username,user.avatar avatar,asset.name,asset.prefix_name prefixName,count(*) num FROM asset left join user on asset.user_id = user.id where status in ('NORMAL','TRADING','GIFTING','MINTING','AUCTIONING') GROUP BY user_id ORDER BY count(*) DESC limit ?1,?2")
+    List<Map<String, String>> findByPage(int start, int end);
+
+    @Query(nativeQuery = true, value = "SELECT count(distinct user_id) FROM asset where status in ('NORMAL','TRADING','GIFTING','MINTING','AUCTIONING') ")
+    int totalElements();
+
+    List<Asset> findAllByUserIdAndStatusIn(Long userId, List<AssetStatus> status);
 }

+ 6 - 0
src/main/java/com/izouma/nineth/repo/CollectionRepo.java

@@ -184,4 +184,10 @@ public interface CollectionRepo extends JpaRepository<Collection, Long>, JpaSpec
     Collection findFirstByOnShelfAndAssetId(boolean onShelf, Long assetId);
 
     List<Collection> findAllByOasisIdInAndSourceInAndStockGreaterThan(List<Long> oasisIds, List<CollectionSource> sources, int sale);
+
+    @Query("select min(price) from Collection where source = 'TRANSFER' and salable = true and onShelf = true and del = false and prefixName like ?1")
+    BigDecimal findMinPriceByPrefixName(String prefixName);
+
+    @Query("select min(price) from Collection where source = 'TRANSFER' and salable = true and onShelf = true and del = false and prefixName like ?1 and name like ?2 and name not like ?3")
+    BigDecimal findMinPriceByNameAndPrefixName(String prefixName,String nameLike, String nameNotLike);
 }

+ 1 - 0
src/main/java/com/izouma/nineth/security/WebSecurityConfig.java

@@ -137,6 +137,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
                 .antMatchers("/pay/v2/**/sandQuick").permitAll()
                 .antMatchers("/user/faceAuthNotify/*").permitAll()
                 .antMatchers("/blindBoxItem/rare/*").permitAll()
+                .antMatchers("/user/synchronizationData").permitAll()
                 // all other requests need to be authenticated
                 .anyRequest().authenticated().and()
                 // make sure we use stateless session; session won't be used to

+ 1 - 1
src/main/java/com/izouma/nineth/service/AirDropService.java

@@ -75,7 +75,7 @@ public class AirDropService {
             if (collection.isSalable()) {
                 throw new BusinessException("请先设置藏品为不可购买");
             }
-            if (!record.isIgnoreStockCheck() && collection.getStock() < record.getUserIds().size()) {
+            if (!record.isIgnoreStockCheck() && collection.getStock() < record.getTargets().stream().mapToInt(DropTarget::getNum).sum()) {
                 throw new BusinessException("藏品库存不足");
             }
 

+ 3 - 3
src/main/java/com/izouma/nineth/service/BlindBoxItemService.java

@@ -30,17 +30,17 @@ public class BlindBoxItemService {
     @Cacheable(value = "blindBoxRare", key = "#blindBoxId")
     public HashMap<String, String> getBlindBoxRare(Long blindBoxId) {
         HashMap<String, String> rare = new HashMap<>();
-        Map<String, BigDecimal> ssrMap = blindBoxItemRepo.getBlindBoxRare(blindBoxId, "%" + Constants.Rarity.SSR + " #%", "%" + Constants.Rarity.U + " #%");
+        Map<String, BigDecimal> ssrMap = blindBoxItemRepo.getBlindBoxRare(blindBoxId, Constants.Rarity.SSR_LIKE, Constants.Rarity.U_LIKE);
         String ssr = convertToStr(ssrMap);
         if (StringUtil.isNotBlank(ssr)) {
             rare.put(Constants.Rarity.SSR, ssr);
         }
-        Map<String, BigDecimal> srMap = blindBoxItemRepo.getBlindBoxRare(blindBoxId, "%" + Constants.Rarity.SR + " #%", "%" + Constants.Rarity.SSR + " #%");
+        Map<String, BigDecimal> srMap = blindBoxItemRepo.getBlindBoxRare(blindBoxId, Constants.Rarity.SR_LIKE, Constants.Rarity.SSR_LIKE);
         String sr = convertToStr(srMap);
         if (StringUtil.isNotBlank(sr)) {
             rare.put(Constants.Rarity.SR, sr);
         }
-        Map<String, BigDecimal> uMap = blindBoxItemRepo.getBlindBoxRare(blindBoxId, "%" + Constants.Rarity.U + " #%", "%" + Constants.Rarity.SR + " #%");
+        Map<String, BigDecimal> uMap = blindBoxItemRepo.getBlindBoxRare(blindBoxId, Constants.Rarity.U_LIKE, Constants.Rarity.SR_LIKE);
         String u = convertToStr(uMap);
         if (StringUtil.isNotBlank(u)) {
             rare.put(Constants.Rarity.U, u);

+ 5 - 5
src/main/java/com/izouma/nineth/service/DestroyRecordService.java

@@ -37,17 +37,17 @@ public class DestroyRecordService {
     }
 
     public List<RecordRank> destroyRecordRank(String rare) {
-        LocalDateTime countDateTime = LocalDateTime.of(2000,1,1,0,0,0);
+        LocalDateTime countDateTime = LocalDateTime.of(2000, 1, 1, 0, 0, 0);
         String not = null;
         if (Constants.Rarity.SSR.equals(rare)) {
-            not = "%" + Constants.Rarity.U + " #%";
+            not = Constants.Rarity.U_LIKE;
         }
         if (Constants.Rarity.SR.equals(rare)) {
-            countDateTime = LocalDateTime.of(2022,7,23,21,0,0);
-            not = "%" + Constants.Rarity.SSR + " #%";
+            countDateTime = LocalDateTime.of(2022, 7, 23, 21, 0, 0);
+            not = Constants.Rarity.SSR_LIKE;
         }
         if (Constants.Rarity.U.equals(rare)) {
-            not = "%" + Constants.Rarity.SR + " #%";
+            not = Constants.Rarity.SR_LIKE;
         }
         SysConfig sysConfig = sysConfigRepo.findByName(Constants.Rarity.ACTIVITY_RANK_ID).orElseThrow(new BusinessException("请先配置盲盒id"));
         if (StringUtil.isBlank(sysConfig.getValue())) {

+ 97 - 0
src/main/java/com/izouma/nineth/service/UserHoldCountService.java

@@ -0,0 +1,97 @@
+package com.izouma.nineth.service;
+
+import com.alibaba.fastjson.JSONArray;
+import com.izouma.nineth.config.Constants;
+import com.izouma.nineth.domain.Asset;
+import com.izouma.nineth.domain.Collection;
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.dto.PageWrapper;
+import com.izouma.nineth.dto.UserHoldDTO;
+import com.izouma.nineth.enums.AssetStatus;
+import com.izouma.nineth.repo.AssetRepo;
+import com.izouma.nineth.repo.CollectionRepo;
+import lombok.AllArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.*;
+
+@Service
+@AllArgsConstructor
+public class UserHoldCountService {
+
+    private AssetRepo assetRepo;
+
+    private CollectionRepo collectionRepo;
+
+    @Cacheable(value = "userHoldList", key = "#pageQuery.hashCode()")
+    public PageWrapper<UserHoldDTO> all(PageQuery pageQuery) {
+        int page = pageQuery.getPage();
+        int size = pageQuery.getSize();
+        int start = page * size;
+        Map<String, BigDecimal> minPriceMap = new HashMap<>();
+        int totalElements = assetRepo.totalElements();
+        List<Map<String, String>> assets = assetRepo.findByPage(start, size);
+        JSONArray jsonArray = new JSONArray();
+        jsonArray.addAll(assets);
+        List<UserHoldDTO> userHoldDTOS = jsonArray.toJavaList(UserHoldDTO.class);
+        userHoldDTOS.forEach(userHoldDTO -> {
+            List<Asset> userAssets = assetRepo.findAllByUserIdAndStatusIn(userHoldDTO.getUserId(), new ArrayList<>(Arrays.asList(AssetStatus.NORMAL, AssetStatus.TRADING, AssetStatus.GIFTING, AssetStatus.MINTING, AssetStatus.AUCTIONING)));
+            // 分类计算各资产寄售最低价
+            userAssets.forEach(asset -> {
+                if (StringUtils.isBlank(asset.getPrefixName())) {
+                    return;
+                }
+                if (asset.getName().contains(Constants.Rarity.SR_LIKE) && !asset.getName().contains(Constants.Rarity.SSR_LIKE)) {
+                    calculatePrice(userHoldDTO, asset, minPriceMap, Constants.Rarity.SR_LIKE, Constants.Rarity.SSR_LIKE);
+                    return;
+                }
+                if (asset.getName().contains(Constants.Rarity.U_LIKE)) {
+                    calculatePrice(userHoldDTO, asset, minPriceMap, Constants.Rarity.U_LIKE, Constants.Rarity.R_LIKE);
+                    return;
+                }
+                if (asset.getName().contains(Constants.Rarity.R_LIKE) && !asset.getName().contains(Constants.Rarity.SR_LIKE)) {
+                    calculatePrice(userHoldDTO, asset, minPriceMap, Constants.Rarity.R_LIKE, Constants.Rarity.SR_LIKE);
+                    return;
+                }
+                if (asset.getName().contains(Constants.Rarity.SSR_LIKE)) {
+                    calculatePrice(userHoldDTO, asset, minPriceMap, Constants.Rarity.SSR_LIKE, Constants.Rarity.U_LIKE);
+                    return;
+                }
+                calculatePrice(userHoldDTO, asset, minPriceMap, null, null);
+            });
+        });
+        return new PageWrapper<>(userHoldDTOS, page,
+                size, totalElements);
+    }
+
+    private void calculatePrice(UserHoldDTO userHoldDTO, Asset asset, Map<String, BigDecimal> minPriceMap, String nameLike, String nameNotLike) {
+        BigDecimal minPrice;
+        String minPriceMapKey = StringUtils.isBlank(nameLike) ? asset.getPrefixName() : asset.getPrefixName().concat(nameLike);
+        if (minPriceMap.containsKey(minPriceMapKey)) {
+            minPrice = minPriceMap.get(minPriceMapKey);
+        } else {
+            // 该系列寄售最低价
+            if (StringUtils.isBlank(nameLike)) {
+                minPrice = collectionRepo.findMinPriceByPrefixName(asset.getPrefixName());
+            } else {
+                minPrice = collectionRepo.findMinPriceByNameAndPrefixName(asset.getPrefixName(), nameLike, nameNotLike);
+            }
+            // 该系列没有寄售的话取originalPrice
+            if(Objects.isNull(minPrice)) {
+                Collection collection = collectionRepo.findById(asset.getCollectionId()).orElse(null);
+                if (Objects.isNull(collection)) {
+                    minPrice = BigDecimal.ZERO;
+                } else {
+                    minPrice = collection.getOriginalPrice();
+                }
+            }
+//            minPrice = Objects.isNull(minPrice) ? BigDecimal.ZERO : minPrice;
+            minPriceMap.put(minPriceMapKey, minPrice);
+        }
+        userHoldDTO.setPrice(userHoldDTO.getPrice().add(minPrice));
+    }
+
+}

+ 5 - 0
src/main/java/com/izouma/nineth/web/UserController.java

@@ -384,6 +384,11 @@ public class UserController extends BaseController {
                 "\n" +
                 "</html>";
     }
+
+    @PostMapping("synchronizationData")
+    public String synchronizationData(@RequestBody String parameter){
+        return parameter;
+    }
 }
 
 

+ 41 - 0
src/main/java/com/izouma/nineth/web/UserHoldCountController.java

@@ -0,0 +1,41 @@
+package com.izouma.nineth.web;
+
+import com.izouma.nineth.dto.PageQuery;
+import com.izouma.nineth.dto.UserHoldDTO;
+import com.izouma.nineth.service.UserHoldCountService;
+import com.izouma.nineth.utils.excel.ExcelUtils;
+import lombok.AllArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+@RestController
+@RequestMapping("/userHold")
+@AllArgsConstructor
+public class UserHoldCountController {
+
+    private UserHoldCountService userHoldCountService;
+
+    @PostMapping("/all")
+    public Page<UserHoldDTO> all(@RequestBody PageQuery pageQuery) {
+        return userHoldCountService.all(pageQuery).toPage();
+    }
+
+    @GetMapping("/top")
+    public List<UserHoldDTO> top() {
+        PageQuery pageQuery = new PageQuery();
+        pageQuery.setPage(0);
+        pageQuery.setSize(50);
+        return userHoldCountService.all(pageQuery).getContent();
+    }
+
+    @GetMapping("/excel")
+    @ResponseBody
+    public void excel(HttpServletResponse response, PageQuery pageQuery) throws IOException {
+        List<UserHoldDTO> data = all(pageQuery).getContent();
+        ExcelUtils.export(response, data);
+    }
+}

+ 8 - 0
src/main/vue/src/router.js

@@ -923,6 +923,14 @@ const router = new Router({
                     meta: {
                        title: '排行',
                     },
+               },
+               {
+                   path: '/userHoldCountList',
+                   name: 'UserHoldCountList',
+                   component: () => import(/* webpackChunkName: "userHoldCountList" */ '@/views/UserHoldCountList.vue'),
+                   meta: {
+                      title: '用户持仓统计',
+                   },
                }
                 /**INSERT_LOCATION**/
             ]

+ 5 - 2
src/main/vue/src/views/Dashboard.vue

@@ -39,6 +39,7 @@ import LineChartWidget from '../widgets/LineChartWidget';
 import BarChartWidget from '../widgets/BarChartWidget';
 import PieChartWidget from '../widgets/PieChartWidget';
 import TopWidget from '../widgets/TopWidget';
+import UserHoldTop from '../widgets/UserHoldTop';
 import MonthWidget from '../widgets/MonthWidget';
 
 export default {
@@ -66,7 +67,8 @@ export default {
                 // { x: 0, y: 12, w: 6, h: 6, i: '4', name: 'BarChartWidget' },
                 { x: 0, y: 4, w: 6, h: 12, i: '6', name: 'PieChartWidget' },
                 { x: 0, y: 20, w: 6, h: 10, i: '8', name: 'MonthWidget' },
-                { x: 6, y: 20, w: 6, h: 10, i: '7', name: 'TopWidget' }
+                { x: 6, y: 20, w: 6, h: 10, i: '7', name: 'TopWidget' },
+                { x: 6, y: 20, w: 12, h: 10, i: '9', name: 'UserHoldTop' }
             ];
         }
         this.$http.get('/statistic/total').then(res => {
@@ -89,7 +91,8 @@ export default {
         BarChartWidget,
         PieChartWidget,
         TopWidget,
-        MonthWidget
+        MonthWidget,
+        UserHoldTop
     }
 };
 </script>

+ 98 - 0
src/main/vue/src/views/UserHoldCountList.vue

@@ -0,0 +1,98 @@
+<template>
+    <div class="list-view">
+        <page-title>
+            <el-button
+                @click="download"
+                icon="el-icon-upload2"
+                :loading="downloading"
+                :disabled="fetchingData"
+                class="filter-item"
+            >
+                导出
+            </el-button>
+        </page-title>
+        <el-table
+            :data="tableData"
+            row-key="id"
+            ref="table"
+            header-row-class-name="table-header-row"
+            header-cell-class-name="table-header-cell"
+            row-class-name="table-row"
+            cell-class-name="table-cell"
+            :height="tableHeight"
+            v-loading="fetchingData"
+        >
+            <el-table-column prop="userId" label="user_id" width="300"></el-table-column>
+            <el-table-column prop="nickname" label="用户昵称"></el-table-column>
+            <el-table-column prop="username" label="用户名"></el-table-column>
+            <el-table-column prop="num" label="持有总数 (个)"></el-table-column>
+            <el-table-column prop="price" label="价值预估 (元)"></el-table-column>
+        </el-table>
+        <div class="pagination-wrapper">
+            <el-pagination
+                background
+                @size-change="onSizeChange"
+                @current-change="onCurrentChange"
+                :current-page="page"
+                :page-sizes="[10, 20, 30, 40, 50]"
+                :page-size="pageSize"
+                layout="total, sizes, prev, pager, next, jumper"
+                :total="totalElements"
+            >
+            </el-pagination>
+        </div>
+    </div>
+</template>
+<script>
+import pageableTable from '@/mixins/pageableTable';
+
+export default {
+    name: 'UserHoldCountList',
+    mixins: [pageableTable],
+    data() {
+        return {
+            url: '/userHold/all',
+            downloading: false
+        };
+    },
+    computed: {
+        selection() {
+            return this.$refs.table.selection.map(i => i.id);
+        }
+    },
+    methods: {
+        beforeGetData() {
+            return {
+                query: {},
+                sort:''
+            };
+        },
+        download() {
+            this.downloading = true;
+            this.$axios
+                .get('/userHold/excel', {
+                    responseType: 'blob',
+                    params: { size: 10000 }
+                })
+                .then(res => {
+                    console.log(res);
+                    this.downloading = false;
+                    const downloadUrl = window.URL.createObjectURL(new Blob([res.data]));
+                    const link = document.createElement('a');
+                    link.href = downloadUrl;
+                    link.setAttribute('download', res.headers['content-disposition'].split('filename=')[1]);
+                    document.body.appendChild(link);
+                    link.click();
+                    link.remove();
+                })
+                .catch(e => {
+                    console.log(e);
+                    this.downloading = false;
+                    this.$message.error(e.error);
+                });
+        }
+    }
+};
+</script>
+<style lang="less" scoped>
+</style>

+ 126 - 0
src/main/vue/src/widgets/UserHoldTop.vue

@@ -0,0 +1,126 @@
+<template>
+    <widget-card :bodyStyle="bodyStyle">
+        <template #header>
+            <div class="header">
+                <span>持仓Top50</span>
+            </div>
+        </template>
+        <div class="list">
+            <div class="empty" v-if="list.length === 0">
+                暂无记录
+            </div>
+            <router-link
+                :to="{
+                    path: '/minterEdit',
+                    query: {
+                        id: item.userId
+                    }
+                }"
+                class="card"
+                v-for="(item, index) in list"
+                :key="index"
+            >
+                <div class="no">NO.{{ index + 1 }}</div>
+                <div class="content">
+                    <div class="text1">{{ item.nickname ? item.nickname : item.username }}</div>
+                    <div class="text2">{{ item.num || 0 }}个</div>
+                    <div class="text2">{{ item.price || 0 }}元</div>
+                </div>
+            </router-link>
+        </div>
+    </widget-card>
+</template>
+<script>
+import WidgetCard from './WidgetCard';
+
+export default {
+    data() {
+        return {
+            bodyStyle: {
+                overflow: 'auto'
+            },
+            value: '',
+            options: [],
+            list: []
+        };
+    },
+    components: {
+        WidgetCard
+    },
+     mounted() {
+        this.$http
+            .get('/userHold/top')
+            .then(res => {
+                this.list = res;
+            });
+     }
+}
+</script>
+<style lang="less" scoped>
+.header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+}
+
+/deep/.el-card {
+    overflow: hidden;
+}
+
+.list {
+    display: flex;
+    flex-wrap: wrap;
+    .card {
+        display: flex;
+        align-items: center;
+        padding: 25px 25px;
+        border-radius: 4px;
+        cursor: pointer;
+        background-color: #f5f7fa;
+        margin: 20px 30px 0 0;
+        border: 1px solid #f2f4f5;
+        box-shadow: 0 2px 10px 0px #f2f4f5;
+        .no {
+            color: #4dcc6f;
+            font-size: 18px;
+            font-weight: bold;
+        }
+        .content {
+            margin-left: 10px;
+            .text1 {
+                font-size: 14px;
+                color: #666666;
+                font-weight: bold;
+                text-align: center;
+            }
+            .text2 {
+                font-size: 16px;
+                color: #000;
+                text-align: center;
+                margin-top: 3px;
+            }
+        }
+
+        &:nth-child(n + 4) {
+            .no {
+                color: #000;
+            }
+        }
+
+        &:hover {
+            box-shadow: 0 2px 10px 0px #feb30e70;
+            .text2 {
+                color: #feb30e;
+                font-weight: bold;
+            }
+        }
+    }
+}
+
+.empty {
+    font-size: 14px;
+    text-align: center;
+    flex-grow: 1;
+    padding: 30px;
+}
+</style>