|
@@ -0,0 +1,825 @@
|
|
|
+<template>
|
|
|
+ <div class="works con">
|
|
|
+ <div class="back-top" @click="onClickBackTop" v-show="isShowBackTopBtn">
|
|
|
+ <i class="iconfont icon-top"></i>
|
|
|
+ </div>
|
|
|
+ <div class="tab">
|
|
|
+ <span
|
|
|
+ >{{ myWorks }}
|
|
|
+ {{ workTotalNum !== undefined ? `(${workTotalNum})` : "" }}</span
|
|
|
+ >
|
|
|
+ <div class="tab-r">
|
|
|
+ <div class="filter">
|
|
|
+ <div
|
|
|
+ :class="{ active: isFilterFocus }"
|
|
|
+ @focusin="onFilterFocus"
|
|
|
+ @focusout="onFilterBlur"
|
|
|
+ >
|
|
|
+ <i class="iconfont iconworks_search search"></i>
|
|
|
+ <input type="text" :placeholder="search" v-model="searchKey" />
|
|
|
+ <i
|
|
|
+ v-if="searchKey"
|
|
|
+ @click="searchKey = ''"
|
|
|
+ class="iconfont icon-toast_red del"
|
|
|
+ ></i>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="mask" v-show="isShowMask"></div>
|
|
|
+ <!-- 断网时,输入关键字触发网络请求后,骨架图的隐藏会触发v-infinite-scroll,原因未知。进而导致循环触发,因此list为空时要禁用v-infinite-scroll -->
|
|
|
+ <ul
|
|
|
+ class="w-list"
|
|
|
+ v-if="!(list.length === 0 && !hasMoreData)"
|
|
|
+ v-infinite-scroll="requestMoreData"
|
|
|
+ :infinite-scroll-disabled="
|
|
|
+ !hasMoreData || isRequestingMoreData || list.length === 0
|
|
|
+ "
|
|
|
+ ref="w-list-ref"
|
|
|
+ @scroll.self="onWorkListScroll"
|
|
|
+ >
|
|
|
+ <!-- <li class="add-work" @click="add">
|
|
|
+ <div class="wrapper">
|
|
|
+ <div class="add-con">
|
|
|
+ <div>
|
|
|
+ <i class="iconfont icon-works_add"></i>
|
|
|
+ </div>
|
|
|
+ <span>{{ create }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </li> -->
|
|
|
+ <!-- 骨架图 -->
|
|
|
+ <template v-if="isRequestingMoreData && list.length === 0">
|
|
|
+ <li v-for="index in 19" :key="index">
|
|
|
+ <div class="wrapper">
|
|
|
+ <workCardSkeleton></workCardSkeleton>
|
|
|
+ </div>
|
|
|
+ </li>
|
|
|
+ </template>
|
|
|
+ <li
|
|
|
+ v-for="(item, i) in list"
|
|
|
+ :key="i"
|
|
|
+ :class="{ 'has-more-data': hasMoreData }"
|
|
|
+ >
|
|
|
+ <div class="wrapper">
|
|
|
+ <div class="li-hover">
|
|
|
+ <span class="lipreview" @click="handlePreview(item)">{{
|
|
|
+ preview
|
|
|
+ }}</span>
|
|
|
+ <ul class="oper">
|
|
|
+ <li class="comfirmhover" @click="edit(item)">
|
|
|
+ <i class="iconfont icon-works_editor"></i>{{ edittips }}
|
|
|
+ </li>
|
|
|
+ <li class="comfirmhover" @click="openShare(item)">
|
|
|
+ <i class="iconfont icon-works_share"></i>{{ share }}
|
|
|
+ </li>
|
|
|
+ <li class="cancelhover" @click="del(item, i)">
|
|
|
+ <i class="iconfont icon-works_delete"></i>{{ deltips }}
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ <div class="img" @click="handlePreview(item)">
|
|
|
+ <img class="real" :src="item.icon || $thumb" alt="" />
|
|
|
+ </div>
|
|
|
+ <div class="li-info">
|
|
|
+ <div>
|
|
|
+ <span class="shenglve tttttt" :title="item.name || no_title">{{
|
|
|
+ item.name || no_title
|
|
|
+ }}</span>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <span>{{ item.createTime.split(" ")[0] }}</span>
|
|
|
+ <div :title="item.visit">
|
|
|
+ <i class="iconfont icon-works_look"></i
|
|
|
+ >{{ item.visit > 10000 ? "1w+" : item.visit }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </li>
|
|
|
+ <div
|
|
|
+ class="work-list-loading-wrapper"
|
|
|
+ v-show="isRequestingMoreData && list.length !== 0"
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ class="work-list-loading"
|
|
|
+ :src="require('@/assets/images/icons/work-list-loading.gif')"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </ul>
|
|
|
+ <div
|
|
|
+ class="nodata"
|
|
|
+ v-if="list.length == 0 && !hasMoreData && lastestUsedSearchKey"
|
|
|
+ >
|
|
|
+ <img :src="$noresult" alt="" />
|
|
|
+ <span>{{ no_search_result }}~</span>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="nodata"
|
|
|
+ v-if="list.length == 0 && !hasMoreData && !lastestUsedSearchKey"
|
|
|
+ >
|
|
|
+ <img :src="config.empty" alt="" />
|
|
|
+ <span>{{ no_works }}</span>
|
|
|
+ <button @click="add" class="upload-btn-in-table">{{ create }}</button>
|
|
|
+ </div>
|
|
|
+ <share
|
|
|
+ :show="showShare"
|
|
|
+ :item="shareItem"
|
|
|
+ @close="showShare = false"
|
|
|
+ ></share>
|
|
|
+ <preview
|
|
|
+ v-if="showItem"
|
|
|
+ :name="showItem.name"
|
|
|
+ :show="showPreview"
|
|
|
+ :ifr="`./show.html?id=${showItem.id}&lang=${$lang}`"
|
|
|
+ :dark="false"
|
|
|
+ @close="showPreview = false"
|
|
|
+ />
|
|
|
+ <div class="dialog" style="z-index: 1000" v-if="isShowMaterialSelector">
|
|
|
+ <MaterialSelector
|
|
|
+ :isDarkTheme="false"
|
|
|
+ :title="select_material"
|
|
|
+ :selectableType="['pano', '3D']"
|
|
|
+ :isMultiSelection="true"
|
|
|
+ initialMaterialType="pano"
|
|
|
+ @cancel="isShowMaterialSelector = false"
|
|
|
+ @submit="handleSubmitFromMaterialSelector"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import share from "../popup/share";
|
|
|
+import preview from "@/components/preview";
|
|
|
+import workCardSkeleton from "@/components/workCardSkeleton.vue";
|
|
|
+import config from "@/config";
|
|
|
+import { debounce } from "@/utils/other.js";
|
|
|
+import MaterialSelector from "@/components/materialSelector.vue";
|
|
|
+import { mapGetters } from "vuex";
|
|
|
+import { i18n } from "@/lang";
|
|
|
+import { $waiting } from "@/components/shared/loading";
|
|
|
+
|
|
|
+import {
|
|
|
+ addWorks,
|
|
|
+ getCamWorksList,
|
|
|
+ delWorks,
|
|
|
+ getPanoInfo,
|
|
|
+ saveWorks,
|
|
|
+} from "@/api";
|
|
|
+
|
|
|
+export default {
|
|
|
+ components: {
|
|
|
+ share,
|
|
|
+ preview,
|
|
|
+ workCardSkeleton,
|
|
|
+ MaterialSelector,
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ ...mapGetters(["info"]),
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ myWorks: i18n.t("material.works.my"),
|
|
|
+ create: i18n.t("material.works.create"),
|
|
|
+ search: i18n.t("material.works.search"),
|
|
|
+ preview: i18n.t("material.works.preview"),
|
|
|
+ edittips: i18n.t("material.works.edit"),
|
|
|
+ share: i18n.t("material.works.share"),
|
|
|
+ deltips: i18n.t("material.works.delete"),
|
|
|
+ no_works: i18n.t("material.works.no_works"),
|
|
|
+ no_title: i18n.t("gather.no_title"),
|
|
|
+ no_search_result: i18n.t("gather.no_search_result"),
|
|
|
+ select_material: i18n.t("gather.select_material"),
|
|
|
+
|
|
|
+ config,
|
|
|
+ list: [],
|
|
|
+ workTotalNum: undefined,
|
|
|
+ hasMoreData: true,
|
|
|
+ isRequestingMoreData: false,
|
|
|
+
|
|
|
+ searchKey: "",
|
|
|
+ // 因为searchKey的变化经过debounce、异步请求的延时,才会反映到数据列表的变化上,所以是否显示、显示哪种无数据提示,也要等到数据列表变化后,根据数据列表是否为空,以及引发本次变化的那个searchKey瞬时值来决定。本变量就是用来保存那个瞬时值。
|
|
|
+ lastestUsedSearchKey: "",
|
|
|
+ isFilterFocus: false,
|
|
|
+
|
|
|
+ showShare: false,
|
|
|
+ showPreview: false,
|
|
|
+ showItem: "",
|
|
|
+ shareItem: "",
|
|
|
+
|
|
|
+ isBackingTop: false,
|
|
|
+ isShowBackTopBtn: false,
|
|
|
+
|
|
|
+ isShowMask: false,
|
|
|
+
|
|
|
+ isShowMaterialSelector: false,
|
|
|
+ newWorkId: "",
|
|
|
+ };
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.requestMoreData();
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ searchKey: {
|
|
|
+ handler: function (val) {
|
|
|
+ if (val.length > 0) {
|
|
|
+ this.selectedList = [];
|
|
|
+ }
|
|
|
+ this.refreshListDebounced();
|
|
|
+ },
|
|
|
+ immediate: false,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ onFilterFocus() {
|
|
|
+ this.isFilterFocus = true;
|
|
|
+ },
|
|
|
+ onFilterBlur() {
|
|
|
+ this.isFilterFocus = false;
|
|
|
+ },
|
|
|
+ refreshListDebounced: debounce(
|
|
|
+ function () {
|
|
|
+ this.list = [];
|
|
|
+ this.isRequestingMoreData = false;
|
|
|
+ this.hasMoreData = true;
|
|
|
+ this.requestMoreData();
|
|
|
+ },
|
|
|
+ 500,
|
|
|
+ false
|
|
|
+ ),
|
|
|
+ openShare(data) {
|
|
|
+ console.log(data);
|
|
|
+ getPanoInfo(data.id, (data) => {
|
|
|
+ if (data.scenes.length <= 0) {
|
|
|
+ return this.$msg.warning(this.$i18n.t("material.works.no_link"));
|
|
|
+ }
|
|
|
+ this.showShare = true;
|
|
|
+ this.shareItem = data;
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ handlePreview(item) {
|
|
|
+ getPanoInfo(item.id, (data) => {
|
|
|
+ if (data.scenes.length <= 0) {
|
|
|
+ return this.$msg.warning(this.$i18n.t("material.works.no_link"));
|
|
|
+ }
|
|
|
+ this.showItem = {
|
|
|
+ ...item,
|
|
|
+ ...data,
|
|
|
+ };
|
|
|
+ this.showPreview = true;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ add() {
|
|
|
+ // 新建作品,弹窗让用户给作品选择素材。
|
|
|
+ $waiting.show();
|
|
|
+ addWorks({}, (res) => {
|
|
|
+ $waiting.hide();
|
|
|
+ this.newWorkId = res.data.id;
|
|
|
+ this.isShowMaterialSelector = true;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ handleSubmitFromMaterialSelector(selected) {
|
|
|
+ $waiting.show();
|
|
|
+ // 拿新作品的初始数据
|
|
|
+ getPanoInfo(
|
|
|
+ this.newWorkId,
|
|
|
+ // 拿到了。
|
|
|
+ (data) => {
|
|
|
+ // 往里边添加用户选中的素材。
|
|
|
+ this.$store.commit("SetInfo", data);
|
|
|
+ console.log("selected", selected);
|
|
|
+ for (const [key, item] of Object.entries(selected)) {
|
|
|
+ if (item.materialType === "pano") {
|
|
|
+ let newScene = {
|
|
|
+ icon: item.icon,
|
|
|
+ sceneCode: item.sceneCode,
|
|
|
+ sceneTitle: item.name,
|
|
|
+ category: this.info.catalogs[0].id,
|
|
|
+ type: "pano",
|
|
|
+ id: "s_" + this.$randomWord(true, 8, 8),
|
|
|
+ };
|
|
|
+
|
|
|
+ console.log("key", key);
|
|
|
+ if (Number(key) === 0) {
|
|
|
+ //新建时开天空mask
|
|
|
+ newScene = Object.assign(newScene, this.info.scenes[0]);
|
|
|
+ newScene.customMask.sky.isShow = true;
|
|
|
+ this.info.scenes[0] = newScene;
|
|
|
+ } else {
|
|
|
+ newScene = Object.assign(newScene, {
|
|
|
+ customMask: this.info.scenes[0].customMask,
|
|
|
+ initVisual: this.info.scenes[0].initVisual
|
|
|
+ });
|
|
|
+ newScene.customMask.sky.isShow = true;
|
|
|
+ this.info.scenes.push(newScene);
|
|
|
+ }
|
|
|
+ } else if (item.materialType === "3D") {
|
|
|
+ let newScene = {
|
|
|
+ icon: item.thumb,
|
|
|
+ sceneCode: item.num,
|
|
|
+ sceneTitle: item.sceneName,
|
|
|
+ category: this.info.catalogs[0].id,
|
|
|
+ type: "4dkk",
|
|
|
+ id: "s_" + this.$randomWord(true, 8, 8),
|
|
|
+ };
|
|
|
+ if (Number(key) === 0) {
|
|
|
+ this.info.scenes[0] = null;
|
|
|
+ this.info.scenes[0] = newScene;
|
|
|
+ } else {
|
|
|
+ this.info.scenes.push(newScene);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存新作品
|
|
|
+ saveWorks(
|
|
|
+ {
|
|
|
+ id: this.newWorkId,
|
|
|
+ password: "",
|
|
|
+ someData: {
|
|
|
+ ...this.info,
|
|
|
+ status: 1,
|
|
|
+ icon: this.info.scenes[0].icon,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ // 保存成功
|
|
|
+ () => {
|
|
|
+ $waiting.hide();
|
|
|
+ // 隐藏素材选择弹窗
|
|
|
+ this.isShowMaterialSelector = false;
|
|
|
+
|
|
|
+ // 刷新作品列表
|
|
|
+ this.list = [];
|
|
|
+ this.isRequestingMoreData = false;
|
|
|
+ this.hasMoreData = true;
|
|
|
+ this.requestMoreData()
|
|
|
+ .then(() => {
|
|
|
+ // 刷新成功
|
|
|
+
|
|
|
+ // 弹出提示窗口
|
|
|
+ this.$confirm({
|
|
|
+ title: this.$i18n.t("tips_code.tips"),
|
|
|
+ content: this.$i18n.t("material.works.had_created"),
|
|
|
+ okText: this.$i18n.t("material.works.goto_preview"),
|
|
|
+ ok: () => {
|
|
|
+ this.handlePreview(this.list[0]);
|
|
|
+ this.newWorkId = "";
|
|
|
+ this.$store.commit("SetInfo", {});
|
|
|
+ },
|
|
|
+ ok2Text: this.$i18n.t("material.works.continue_edit"),
|
|
|
+ ok2: () => {
|
|
|
+ window.open(
|
|
|
+ `./edit.html?id=${this.newWorkId}&lang=${this.$lang}`
|
|
|
+ );
|
|
|
+ this.newWorkId = "";
|
|
|
+ this.$store.commit("SetInfo", {});
|
|
|
+ },
|
|
|
+ });
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ this.$msg.message(
|
|
|
+ this.$i18n.t("material.works.had_created_but_no_link")
|
|
|
+ );
|
|
|
+ console.error("已成功新建作品,但刷新作品列表失败。");
|
|
|
+ });
|
|
|
+ },
|
|
|
+ // 保存失败,删除新建的作品。
|
|
|
+ (error) => {
|
|
|
+ $waiting.hide();
|
|
|
+ console.error("保存失败:", error);
|
|
|
+ delWorks(this.newWorkId);
|
|
|
+ this.newWorkId = "";
|
|
|
+ this.$store.commit("SetInfo", {});
|
|
|
+ }
|
|
|
+ );
|
|
|
+ },
|
|
|
+ // 没拿到,删除新建的作品。
|
|
|
+ (error) => {
|
|
|
+ console.error("没拿到新建的作品数据:", error);
|
|
|
+ delWorks(this.newWorkId);
|
|
|
+ this.newWorkId = "";
|
|
|
+ }
|
|
|
+ );
|
|
|
+ },
|
|
|
+ edit(item) {
|
|
|
+ window.open(`./edit.html?id=${item.id}&lang=${this.$lang}`);
|
|
|
+ },
|
|
|
+ del(item, index) {
|
|
|
+ this.$confirm({
|
|
|
+ title: this.$i18n.t("material.works.delete_work"),
|
|
|
+ content: this.$i18n.t("material.works.comfirm_delete"),
|
|
|
+ ok: () => {
|
|
|
+ $waiting.show();
|
|
|
+
|
|
|
+ delWorks(item.id, () => {
|
|
|
+ this.$msg.success(this.$i18n.t("gather.delete_success"));
|
|
|
+ this.isRequestingMoreData = true;
|
|
|
+ const lastestUsedSearchKey = this.searchKey;
|
|
|
+ getCamWorksList(
|
|
|
+ {
|
|
|
+ pageNum: this.list.length,
|
|
|
+ pageSize: 1,
|
|
|
+ searchKey: this.searchKey,
|
|
|
+ },
|
|
|
+ (data) => {
|
|
|
+ $waiting.hide();
|
|
|
+ this.list.splice(index, 1);
|
|
|
+ this.list = this.list.concat(data.data.list);
|
|
|
+ if (this.list.length === data.data.total) {
|
|
|
+ this.hasMoreData = false;
|
|
|
+ }
|
|
|
+ this.isRequestingMoreData = false;
|
|
|
+ this.lastestUsedSearchKey = lastestUsedSearchKey;
|
|
|
+ if (!lastestUsedSearchKey) {
|
|
|
+ this.workTotalNum = data.data.total;
|
|
|
+ }
|
|
|
+ // TODO: 这是干啥呢?
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.$bus.emit("refreshTips");
|
|
|
+ });
|
|
|
+ },
|
|
|
+ () => {
|
|
|
+ $waiting.hide();
|
|
|
+ this.lastestUsedSearchKey = lastestUsedSearchKey;
|
|
|
+ this.isRequestingMoreData = false;
|
|
|
+ }
|
|
|
+ );
|
|
|
+ });
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+ requestMoreData() {
|
|
|
+ this.isRequestingMoreData = true;
|
|
|
+ const lastestUsedSearchKey = this.searchKey;
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ getCamWorksList(
|
|
|
+ {
|
|
|
+ pageNum: Math.floor(this.list.length / config.PAGE_SIZE) + 1,
|
|
|
+ pageSize: config.PAGE_SIZE,
|
|
|
+ searchKey: this.searchKey,
|
|
|
+ },
|
|
|
+ (data) => {
|
|
|
+ this.list = this.list.concat(data.data.list);
|
|
|
+ if (this.list.length === data.data.total) {
|
|
|
+ this.hasMoreData = false;
|
|
|
+ }
|
|
|
+ this.isRequestingMoreData = false;
|
|
|
+ this.lastestUsedSearchKey = lastestUsedSearchKey;
|
|
|
+ if (!lastestUsedSearchKey) {
|
|
|
+ this.workTotalNum = data.data.total;
|
|
|
+ }
|
|
|
+ // TODO: 这是干啥呢?
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.$bus.emit("refreshTips");
|
|
|
+ });
|
|
|
+ resolve();
|
|
|
+ },
|
|
|
+ () => {
|
|
|
+ this.isRequestingMoreData = false;
|
|
|
+ this.lastestUsedSearchKey = lastestUsedSearchKey;
|
|
|
+ reject();
|
|
|
+ }
|
|
|
+ );
|
|
|
+ });
|
|
|
+ },
|
|
|
+ onClickBackTop() {
|
|
|
+ if (this.isBackingTop) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.isBackingTop = true;
|
|
|
+
|
|
|
+ const startTime = Date.now();
|
|
|
+ const totalScroll = this.$refs["w-list-ref"].scrollTop;
|
|
|
+ const fn = () => {
|
|
|
+ if (this.$refs["w-list-ref"].scrollTop === 0) {
|
|
|
+ this.isBackingTop = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const nowTime = Date.now();
|
|
|
+ const assumeScrollTop =
|
|
|
+ totalScroll - ((nowTime - startTime) * totalScroll) / 500;
|
|
|
+ this.$refs["w-list-ref"].scrollTop =
|
|
|
+ assumeScrollTop > 0 ? assumeScrollTop : 0;
|
|
|
+ requestAnimationFrame(fn);
|
|
|
+ };
|
|
|
+ requestAnimationFrame(fn);
|
|
|
+ },
|
|
|
+ onWorkListScroll(e) {
|
|
|
+ if (e.target.scrollTop >= 30) {
|
|
|
+ !this.isShowMask && (this.isShowMask = true);
|
|
|
+ } else {
|
|
|
+ this.isShowMask && (this.isShowMask = false);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (e.target.scrollTop >= 600) {
|
|
|
+ this.isShowBackTopBtn = true;
|
|
|
+ } else {
|
|
|
+ this.isShowBackTopBtn = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="less" scoped>
|
|
|
+.works {
|
|
|
+ width: 100%;
|
|
|
+ flex-direction: column;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ .back-top {
|
|
|
+ position: absolute;
|
|
|
+ right: -80px;
|
|
|
+ bottom: 30px;
|
|
|
+ width: 60px;
|
|
|
+ height: 60px;
|
|
|
+ border-radius: 8px;
|
|
|
+ background-color: #fff;
|
|
|
+ z-index: 1;
|
|
|
+ color: #c8c9cc;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ color: #323233;
|
|
|
+ }
|
|
|
+
|
|
|
+ cursor: pointer;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ i {
|
|
|
+ font-size: 20px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .tab {
|
|
|
+ flex: 0 0 auto;
|
|
|
+ width: 100%;
|
|
|
+ display: flex;
|
|
|
+ background: #fff;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 20px 30px;
|
|
|
+
|
|
|
+ > span {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tab-r {
|
|
|
+ align-items: center;
|
|
|
+ display: flex;
|
|
|
+
|
|
|
+ .ui-button {
|
|
|
+ margin-right: 20px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .mask {
|
|
|
+ position: absolute;
|
|
|
+ width: 100%;
|
|
|
+ top: 200px;
|
|
|
+ height: 30px;
|
|
|
+ background: linear-gradient(rgb(239, 242, 244), rgba(255, 255, 255, 0));
|
|
|
+ z-index: 1;
|
|
|
+ pointer-events: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .w-list {
|
|
|
+ flex: 1 1 auto;
|
|
|
+ overflow: auto;
|
|
|
+ margin-top: 22px;
|
|
|
+ padding-top: 8px;
|
|
|
+ @gap: 20px;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-content: flex-start;
|
|
|
+ // 让宽度和视口等宽,为了保证鼠标列表显示区域以外时列表也能响应滚轮事件。
|
|
|
+ margin-left: calc((100vw - 100%) / -2);
|
|
|
+ padding-left: calc((100vw - 100%) / 2);
|
|
|
+ margin-right: calc((100vw - 100%) / -2);
|
|
|
+ padding-right: calc((100vw - 100%) / 2);
|
|
|
+
|
|
|
+ &::-webkit-scrollbar {
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ > li {
|
|
|
+ width: calc((100% - @gap * 4) / 5);
|
|
|
+ height: 322px;
|
|
|
+ margin-bottom: @gap;
|
|
|
+ margin-right: @gap;
|
|
|
+
|
|
|
+ &:nth-of-type(5n) {
|
|
|
+ margin-right: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 因为有个“创建作品”card占着空间,每次拿20个数据,每行五个,又不想每次拿到数据后最后一行只有一个card,所以把最后那个card隐藏掉。
|
|
|
+ &:last-of-type.has-more-data {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .wrapper {
|
|
|
+ height: 100%;
|
|
|
+ background: #fff;
|
|
|
+ position: relative;
|
|
|
+ border-radius: 6px;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .li-hover {
|
|
|
+ display: none;
|
|
|
+ width: 100%;
|
|
|
+ height: 240px;
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ z-index: 99;
|
|
|
+ background: rgba(0, 0, 0, 0.6);
|
|
|
+
|
|
|
+ .lipreview {
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ color: #fff;
|
|
|
+ display: inline-block;
|
|
|
+ line-height: 40px;
|
|
|
+ height: 40px;
|
|
|
+ width: 100px;
|
|
|
+ text-align: center;
|
|
|
+ border-radius: 22px;
|
|
|
+ cursor: pointer;
|
|
|
+ background-color: transparent;
|
|
|
+ border: 1px solid #fff;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ border: none;
|
|
|
+ background: #1983f6;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .oper {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-around;
|
|
|
+ align-items: center;
|
|
|
+ position: absolute;
|
|
|
+ bottom: 10px;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ > li {
|
|
|
+ color: #fff;
|
|
|
+ font-size: 13px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ > i {
|
|
|
+ font-size: 20px;
|
|
|
+ margin-right: 4px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .img {
|
|
|
+ width: 100%;
|
|
|
+ height: 240px;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ .real {
|
|
|
+ height: 100%;
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ z-index: 0;
|
|
|
+ transition: all ease 0.3s;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .li-info {
|
|
|
+ font-size: 14px;
|
|
|
+ padding: 10px;
|
|
|
+
|
|
|
+ > div {
|
|
|
+ text-align: left;
|
|
|
+
|
|
|
+ &:first-of-type {
|
|
|
+ > span {
|
|
|
+ font-weight: bold;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ display: inline-block;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ overflow: hidden;
|
|
|
+ white-space: nowrap;
|
|
|
+ cursor: pointer;
|
|
|
+ color: #323233;
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &:last-of-type {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ > span {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #969799;
|
|
|
+ }
|
|
|
+
|
|
|
+ > div {
|
|
|
+ color: #969799;
|
|
|
+
|
|
|
+ i {
|
|
|
+ margin-right: 6px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ .wrapper {
|
|
|
+ box-shadow: 0px 2px 12px 0px rgba(50, 50, 51, 0.12);
|
|
|
+ transform: translateY(-6px);
|
|
|
+
|
|
|
+ .li-hover {
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .img {
|
|
|
+ .real {
|
|
|
+ height: 108%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .add-work {
|
|
|
+ .wrapper {
|
|
|
+ .add-con {
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ text-align: center;
|
|
|
+
|
|
|
+ div {
|
|
|
+ width: 60px;
|
|
|
+ height: 60px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: linear-gradient(144deg, #00aefb 0%, #0076f6 100%);
|
|
|
+ position: relative;
|
|
|
+ cursor: pointer;
|
|
|
+ margin: 0 auto;
|
|
|
+
|
|
|
+ > i {
|
|
|
+ font-size: 16px;
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ span {
|
|
|
+ color: #333333;
|
|
|
+ display: inline-block;
|
|
|
+ margin-top: 8px;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .work-list-loading-wrapper {
|
|
|
+ width: 100%;
|
|
|
+ margin-top: 20px;
|
|
|
+ margin-bottom: 22px;
|
|
|
+
|
|
|
+ .work-list-loading {
|
|
|
+ display: block;
|
|
|
+ margin: 0 auto;
|
|
|
+ width: 50px;
|
|
|
+ height: 8px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|
|
|
+
|
|
|
+<style lang="less" scoped>
|
|
|
+@import "../style.less";
|
|
|
+</style>
|