Commit d88596f9 authored by hanfeng zhang's avatar hanfeng zhang

Merge branch 'main' of gitlab.33.cn:HF_web/NFT

parents da465b75 dd2b2aa0
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
"@tailwindcss/line-clamp": "^0.2.1", "@tailwindcss/line-clamp": "^0.2.1",
"ant-design-vue": "^1.7.5", "ant-design-vue": "^1.7.5",
"axios": "^0.21.1", "axios": "^0.21.1",
"clipboard": "^2.0.8",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"register-service-worker": "^1.7.1", "register-service-worker": "^1.7.1",
......
<template> <template>
<div class="cell flex justify-between py-3 " :class="boxType ==='border'?' border-b px-0 border-font-gray border-opacity-25':'bg-font-gray bg-opacity-20 px-2 rounded-md'" > <div
<div class="left flex items-center "> class="cell flex justify-between py-3 px-2 overflow-hidden"
<app-icon v-if='labelIcon' :name="labelIcon" size='18px'></app-icon> :class="
<div class=" text-font-dark-blue px-3"> boxType === 'border'
{{text}} ? ' border-b border-font-gray border-opacity-25'
</div> : 'bg-font-gray bg-opacity-20 rounded-md'
</div> "
<div class="right flex items-center"> >
<div v-if="type=='click'" @click="eventEmit(type)"> <div class="left flex items-center">
<div class='flex flex-row items-center'> <app-icon v-if="labelIcon" :name="labelIcon" size="18px"></app-icon>
<div>{{value}}</div> <div class="px-2">
<app-icon v-if="icon" :name='icon' class='self-center'></app-icon> {{ text }}
</div> </div>
</div> </div>
<div v-else-if="type=='input'"> <div class="right flex items-center overflow-hidden">
<input v-model="inputValue" type="text" class=" bg-transparent" placeholder='输入' @input="cellOnChange"> <div v-if="type == 'click'" @click="eventEmit(type)">
</div> <div class="flex flex-row items-center">
<div v-else-if="type==='pick'"> <div>{{ value }}</div>
<div class='flex flex-row items-center'> <app-icon v-if="icon" :name="icon" class="self-center"></app-icon>
<div>sdf</div> </div>
<div @click="eventEmit(type)"> </div>
<app-icon name="icon-xiayibu" size='18px'></app-icon> <div v-else-if="type == 'input'">
</div> <input
</div> v-model="inputValue"
</div> type="text"
class="bg-transparent"
:placeholder="placeholder"
@input="cellOnChange"
/>
</div>
<input
v-if="type === 'input-num'"
:value="inputValue"
type="number"
@input="handleInput"
class="bg-transparent"
:placeholder="placeholder"
/>
<div v-else-if="type === 'pick'">
<div class="flex flex-row items-center">
<input
@click="eventEmit(type)"
v-if="$store.state.create.pickedList.length === 0"
type="text"
class="bg-transparent"
:placeholder="placeholder"
/>
<app-tag
v-for="item in $store.state.create.pickedList"
:key="item.id"
:text="item.text"
:id="item.id"
class="text-sm rounded-xl text-font-white bg-app-red"
:active="true"
></app-tag>
<div @click="eventEmit(type)">
<app-icon name="icon-xiayibu" size="18px"></app-icon>
</div>
</div>
</div>
<div v-else-if="type === 'upload'" class="overflow-hidden">
<div class="flex flex-row items-center overflow-hidden">
<div v-if="name" class="overflow-hidden overflow-ellipsis">
{{ name }}
</div>
<input
v-else
@click="cellOnChange"
type="text"
class="bg-transparent"
:placeholder="placeholder"
/>
<div @click="cellOnChange">
<app-icon name="icon-xiayibu" size="18px"></app-icon>
</div>
</div> </div>
</div>
<div v-else-if="type === 'showText'" class="overflow-hidden">
<div class="flex flex-row items-center overflow-hidden">
<div
v-if="name"
class="overflow-hidden overflow-ellipsis"
id="copyNodeId"
:data-clipboard-text="name"
>
{{ name }}
</div>
<div
data-clipboard-target="#copyNodeId"
ref="btn"
@click="handleClickCopy"
>
<app-icon name="icon-fuzhi" size="18px"></app-icon>
</div>
</div>
</div>
<div v-else-if="type === 'select'" class="overflow-hidden">
<div class="flex flex-row items-center overflow-hidden">
<div class="overflow-hidden overflow-ellipsis">
{{ getNameOfSelect(selected) }}
</div>
<div @click="eventEmit(type)">
<app-icon name="icon-xiayibu" size="18px"></app-icon>
</div>
</div>
</div>
</div> </div>
<ActionSheet
v-model="show"
:actions="actions"
cancel-text="取消"
close-on-click-action
@cancel="onCancel"
@select="onSelect"
>
</ActionSheet>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from "vue";
// import { Switch } from 'ant-design-vue' import Clipboard from "clipboard";
// Vue.use(Switch) import { ActionSheet } from "vant";
export default Vue.extend({ export default Vue.extend({
components:{ components: {
'app-icon':()=>import('@/components/common/Icon.vue') "app-icon": () => import("@/components/common/Icon.vue"),
}, ActionSheet,
"app-tag": () => import("@/components/common/Tag2.vue"),
},
props: { props: {
value: String, value: [String, Number],
pickValue:Array, pickValue: Array,
size: String, size: String,
type: { type: {
type:String, type: String,
default:'click' default: "click",
},
boxType: {
type: String,
default: "background",
}, },
boxType:{ disabled: {
type: String, type: Boolean,
default:'background' default: false,
}, },
disabled:{ text: {
type:Boolean, type: String,
default:false required: true,
}, },
text:{ icon: String,
type: String, labelIcon: String,
required: true placeholder: {
type: String,
default: "输入",
}, },
icon:String, name: [String, Number],
labelIcon: String selected: [String, Number, Object],
list: Array,
}, },
data(){ data() {
return{ return {
inputValue:this.value, inputValue: this.value,
show: false,
};
},
mounted() {
console.log(this.value, "show value");
},
methods: {
handleInput(e: any) {
const temp = this.inputValue;
const value = e.target.value as string;
this.inputValue = value;
this.$emit("cellOnChange", Number(this.inputValue));
},
eventEmit(v: any) {
console.log(this.type);
if (this.disabled) {
return;
}
if (this.type === "click") {
this.$emit("onClick", v);
}
if (this.type === "pick") {
this.$emit("onClick", (cb: any) => {
console.log(cb);
return cb;
});
} }
if (this.type === "select") {
this.show = true;
}
},
cellOnChange() {
this.$emit("cellOnChange", this.inputValue);
},
handleClickCopy() {
const btn = this.$refs.btn as HTMLElement;
new Clipboard(btn);
},
onCancel() {},
onSelect(value: any) {
this.$emit("cellOnChange", value.value);
},
getNameOfSelect(selected: string) {
const item = this.list.find(
(item: any) => item.value === selected
) as any;
console.log(item, selected);
return item && item.name
},
}, },
methods:{ computed: {
eventEmit(v:any){ getSize() {
if(this.disabled){ switch (this.size) {
return case "full":
} return "w-full";
if(this.type === 'click'){ default:
this.$emit('onClick',v) return "w-20";
}
if(this.type === 'pick'){
this.$emit('onClick',(cb:any)=>{
console.log(cb);
return cb
})
}
},
cellOnChange(){
this.$emit('cellOnChange',this.inputValue)
} }
},
actions(): any[] {
return this.list || [];
},
}, },
computed:{
getSize(){
switch (this.size) {
case 'full':
return 'w-full'
default:
return 'w-20';
}
},
// getBoxType(){
// switch (this.boxType) {
// case 'border':
// return 'border-t border-b border-gray-500'
// // case 'background':
// // return ' '
// }
// }
}
}); });
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
input[type='text'], input[type='password'], input[type='number'], textarea{ input[type="text"],
text-align: right; input[type="password"],
padding: 0 25px; input[type="number"],
textarea {
text-align: right;
padding: 0 25px;
} }
input:not([type='range']), textarea{ input:not([type="range"]),
padding: 0 10px; textarea {
padding: 0 10px;
} }
</style> </style>
\ No newline at end of file
<template>
<div
class="tag flex justify-center text-2xs px-3 py-1 rounded-md"
:class="!active ? 'bg-opacity-0 border' : ''"
@click="tagOnclick"
>
{{ text }}
</div>
</template>
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
name: "AppTag",
props: {
disabled: {
type: Boolean,
default: false,
},
id: {
type: Number,
required: true,
},
text: {
type: String,
required: true,
},
active: {
type: Boolean,
default: false,
},
},
methods: {
tagOnclick() {
if (this.disabled === false) {
this.$emit("onclick", {
id: this.id,
text: this.text,
});
}
},
},
});
</script>
...@@ -46,6 +46,14 @@ const routes: Array<RouteConfig> = [ ...@@ -46,6 +46,14 @@ const routes: Array<RouteConfig> = [
title: '剧本题材' title: '剧本题材'
} }
}, },
{
path: '/Nft/create/upload',
name:'NftUpload',
component: () => import('@/view/NFT/Create/upload.vue'),
meta: {
title: '剧本附件'
}
},
] ]
}, },
{ {
......
import { Service } from './Service'
import {Service} from './Service' import { NFT_CREATE } from '@/types/Dto'
import {NFT_CREATE} from '@/types/Dto'
import { token } from '@/util/userInfoUtils' import { token } from '@/util/userInfoUtils'
export class NFTService extends Service { export class NFTService extends Service {
router = { router = {
create:{ path:'/nft/publish'}, create: { path: '/nft/publish' },
getMyList:{ path:'/nft/list/current',dataType:'application/x-www-form-urlencoded'}, getMyList: {
getList:{path:'/nft/list',dataType:'application/x-www-form-urlencoded'}, path: '/nft/list/current',
genId:{ path:'/nft/generateNftId', dataType:'application/x-www-form-urlencoded'}, dataType: 'application/x-www-form-urlencoded',
detail:{ path:'/nft/get/{id}', dataType:'application/x-www-form-urlencoded'}, },
themes:{ path:'/label/list',dataType:'application/x-www-form-urlencoded'}, getList: {
getCategory:{path:'/category/list',dataType:'application/x-www-form-urlencoded'} path: '/nft/list',
} dataType: 'application/x-www-form-urlencoded',
auth = 'Bearer ' + token.getToken() },
constructor(){ genId: {
super() path: '/nft/generateNftId',
} dataType: 'application/x-www-form-urlencoded',
},
detail: {
path: '/nft/get/{id}',
dataType: 'application/x-www-form-urlencoded',
},
themes: {
path: '/label/list',
dataType: 'application/x-www-form-urlencoded',
},
getMd5: { path: '/nft/file/md5', dataType: 'multipart/form-data' },
save: { path: '/nft/save', dataType: 'multipart/form-data' },
publish: { path: '/nft/publish', dataType: 'application/json' },
getCategory: {
path: '/category/list',
dataType: 'application/x-www-form-urlencoded',
},
}
auth = 'Bearer ' + token.getToken()
constructor() {
super()
}
/** /**
* 获取验证码 * 获取验证码
* @param phone * @param phone
* @param codeType 短信模板, 1:登录短信 2:修改密码 3:修改手机号 * @param codeType 短信模板, 1:登录短信 2:修改密码 3:修改手机号
*/ */
async create(data:NFT_CREATE){ async create(data: NFT_CREATE) {
return await this.service.post(this.router.create.path,data,{ return await this.service.post(this.router.create.path, data, {
headers:{ headers: {
"Authorization": this.auth Authorization: this.auth,
} },
}) })
} }
async getCategory():Promise<any[]>{ /**
return await this.service.get(this.router.getCategory.path) *
} * @returns 获取剧目主题表
*/
async getThemeList() {
return await this.service.get(this.router.themes.path, {
headers: {
Authorization: this.auth,
'Content-Type': this.router.themes.dataType,
},
})
}
async getCategory(): Promise<any[]> {
return await this.service.get(this.router.getCategory.path)
}
/**
* 获取我的NFT列表
* @param categoryId
* @returns
*/
async getMyList(categoryId?: number) {
return await this.service.get(this.router.getMyList.path, {
headers: {
Authorization: this.auth,
'Content-Type': this.router.getMyList.dataType,
},
params: { categoryId: categoryId ? categoryId : null },
})
}
/** /**
* * 获取所有NFT的列表
* @returns 获取剧目主题表 * @param pageNum
*/ * @param pageSize
async getThemeList(){ * @param categoryId
return await this.service.get(this.router.themes.path,{ * @returns
headers:{ */
"Authorization": this.auth, async getList(pageNum?: number, pageSize?: number, categoryId?: number) {
"Content-Type": this.router.themes.dataType} return await this.service.get(this.router.getList.path, {
}) headers: {
} Authorization: this.auth,
'Content-Type': this.router.getList.dataType,
},
params: {
categoryId: categoryId ? categoryId : null,
pageNum: pageNum,
pageSize: pageSize,
},
})
}
/** /**
* 获取我的NFT列表 * 生成id
* @param categoryId * @param categoryId
* @returns * @returns
*/ */
async getMyList(categoryId?:number){ async generateNftId(categoryId: number) {
return await this.service.get(this.router.getMyList.path,{ return await this.service.get(this.router.genId.path, {
headers:{ headers: {
"Authorization": this.auth, Authorization: this.auth,
"Content-Type": this.router.getMyList.dataType}, 'Content-Type': this.router.genId.dataType,
params:{"categoryId":categoryId?categoryId:null} },
}) params: { categoryId: categoryId },
} })
}
/** /**
* 获取所有NFT的列表 * 按照id查找nft详情
* @param pageNum * @param id
* @param pageSize */
* @param categoryId async detail(id: number) {
* @returns return await this.service.get(this.router.detail.path, {
*/ headers: {
async getList(pageNum?:number,pageSize?:number,categoryId?:number){ Authorization: this.auth,
return await this.service.get(this.router.getList.path,{ 'Content-Type': this.router.detail.dataType,
headers:{ },
"Authorization": this.auth, params: { id: id },
"Content-Type": this.router.getList.dataType}, })
params:{"categoryId":categoryId?categoryId:null,"pageNum":pageNum,'pageSize':pageSize } }
})
}
/** /**
* 生成id * 获取md5
* @param categoryId * @param id
* @returns */
*/ async getMd5(file: File) {
async generateNftId(categoryId:number){ const fd = new FormData()
return await this.service.get(this.router.genId.path,{ fd.append('file', file)
headers:{ return await this.service.post(this.router.getMd5.path, fd, {
"Authorization": this.auth, headers: {
"Content-Type": this.router.genId.dataType}, Authorization: this.auth,
params:{"categoryId":categoryId} 'Content-Type': this.router.getMd5.dataType,
}) },
} })
}
/** /**
* 按照id查找nft详情 * 发布
* @param id * @param id
*/ */
async detail(id:number){ async publish(obj: {
return await this.service.get(this.router.detail.path,{ fileHash: string
headers:{ id: number
"Authorization": this.auth, nftId: string
"Content-Type": this.router.detail.dataType wallet: string
}, }) {
params:{"id":id} return await this.service.post(this.router.publish.path, obj, {
}) headers: {
} Authorization: this.auth,
'Content-Type': this.router.publish.dataType,
},
})
}
} /**
* nft基本信息保存
* @param id
*/
async save(
author: string,
categoryId: number,
fileHash: string,
isArchives: number,
name: string,
synopsis: string,
theme: string,
file: File,
isGrant: number,
) {
const fd = new FormData()
fd.append('author', author)
fd.append('categoryId', categoryId.toString())
fd.append('fileHash', fileHash)
fd.append('isArchives', isArchives.toString())
fd.append('name', name)
fd.append('synopsis', synopsis)
fd.append('theme', theme)
fd.append('file', file)
fd.append('isGrant', String(isGrant))
return (await this.service.post(this.router.save.path, fd, {
headers: {
Authorization: this.auth,
'Content-Type': this.router.save.dataType,
},
})) as {
fileHash: string
id: number
nftId: string
wallet: string
}
}
}
const stateData = {
pickedList: [],
fileHash: '',
fileName: '',
file: undefined as undefined | File,
}
export type AppType = typeof stateData
export const create = {
namespaced: true,
state: () => ({
...stateData,
}),
mutations: {
SET_STATE(state: AppType, payload: any) {
Object.assign(state, payload)
},
},
actions: {},
getters: {},
}
import Vue from 'vue' import Vue from 'vue'
import Vuex from 'vuex' import Vuex from 'vuex'
import {appStore} from './app' import {appStore} from './app'
import {create} from "./create"
Vue.use(Vuex) Vue.use(Vuex)
export default new Vuex.Store({ export default new Vuex.Store({
modules: { modules: {
app:appStore app:appStore,
create
} }
}) })
This diff is collapsed.
<template> <template>
<Layout-Child> <Layout-Child>
<div class="pt-6"> <div class="pt-6">
<div class="text-font-red text-sm text-center">*最多支持三个选项</div> <div class="text-font-red text-sm text-center">*最多支持三个选项</div>
<div class=" grid grid-cols-3 w-11/12 mx-auto mt-6"> <div class="grid grid-cols-3 w-11/12 mx-auto mt-6">
<div class="box px-5 py-3" v-for="(i,index) in serviceData" :key="index"> <div
<app-tag :text='i.name' class="text-sm rounded-xl text-font-white bg-app-red" :active='isTagActived(i.id)' @onclick='tagOnclick' :id='i.id'></app-tag> class="box px-5 py-3"
</div> v-for="(i, index) in serviceData"
:key="index"
>
<app-tag
:text="i.name"
class="text-sm rounded-xl text-font-white bg-app-red"
@onclick="tagOnclick"
:active="isActive(i.id)"
:id="i.id"
></app-tag>
</div> </div>
<div class="fixed bottom-0 w-full left-0 flex flex-row z-30"> </div>
<app-btn text="取消" class=" w-5/12 mx-auto text-font-white rounded-2xl bg-font-blue" border='none' ></app-btn> <div class="fixed bottom-0 w-full left-0 flex flex-row z-30">
<app-btn text="确认" class="w-5/12 mx-auto text-font-white rounded-2xl bg-font-blue" border='none' :disabled='pickValid'></app-btn> <app-btn
</div> text="取消"
</div> @btnClicked="clickCancel"
</Layout-Child> class="w-5/12 mx-auto text-font-white rounded-2xl bg-font-blue"
border="none"
></app-btn>
<app-btn
text="确认"
class="w-5/12 mx-auto text-font-white rounded-2xl bg-font-blue"
border="none"
@btnClicked="clickConfirm"
:disabled="!pickValid"
></app-btn>
</div>
</div>
</Layout-Child>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from "vue";
import {remove as _remove} from 'lodash'
export default Vue.extend({ export default Vue.extend({
data(){ data() {
return{ return {
picked:[] as Array<{id:number,text:string}>, picked: [] as Array<{ id: number; text: string }>,
serviceData:{}, serviceData: {},
pickValid:true maxNum: 3,
} };
}, },
async mounted(){ async mounted() {
this.serviceData = await this.$service.nftService.getThemeList() this.serviceData = await this.$service.nftService.getThemeList();
}, },
components:{ components: {
'Layout-Child':()=>import('@/layout/Child.vue'), "Layout-Child": () => import("@/layout/Child.vue"),
'app-tag':()=>import('@/components/common/Tag.vue'), "app-tag": () => import("@/components/common/Tag2.vue"),
'app-btn':()=>import('@/components/common/Btn.vue') "app-btn": () => import("@/components/common/Btn.vue"),
}, },
methods: { methods: {
tagOnclick(item:any){ tagOnclick(item: any) {
if(this.picked.includes(item.id)){ if (!!this.picked.find((i) => i.id === item.id)) {
_remove(this.picked,(i)=>{ this.picked = this.picked.filter((i) => i.id !== item.id);
return i === item.id; return;
})
console.log(this.picked,'remove');
return
} }
this.picked.push(item.id) if (this.picked.length >= this.maxNum) {
} return;
},
computed:{
isTagDisabled(){
const that = this;
return function(id:any){
const exist = that.picked.includes(id)
if(that.picked.length>2 || exist){
return true
}
return false
} }
this.picked.push({
id: item.id,
text: item.text,
});
}, },
isTagActived(){ isActive(id: number) {
const that = this return !!this.picked.find((i) => i.id === id);
return function(id:any){ },
if(that.picked.includes(id)){ clickCancel() {
return false this.$router.back();
} },
return true clickConfirm() {
} this.$store.commit("create/SET_STATE", {
} pickedList: this.picked,
} });
this.$router.back();
},
},
computed: {
pickValid(): boolean {
return this.picked.length > 0 && this.picked.length <= this.maxNum;
},
},
}); });
</script> </script>
<template>
<Layout-Child class="page-scroll text-center">
<div class="text-font-white mt-20 text-center mb-20">请上传剧本附件</div>
<input
type="file"
id="upload"
class="hidden"
@change="handleFileChange"
ref="inputFile"
accept=".pdf"
/>
<label for="upload">
<div
ref="uploadBox"
class="
upload-box
text-center
flex
items-center
justify-center
mx-auto
text-font-white
"
>
<img
v-if="!fileName"
src="@/assets/icons/upload.png"
alt=""
class="mx-auto"
/>
<img v-else src="@/assets/icons/file.png" alt="" class="mx-auto" />
</div>
</label>
<div class="text-center text-font-white">
<div v-if="!fileName">支持扩展名格式:PDF</div>
<div v-if="fileName">
{{ fileName }}
</div>
<label for="upload">
<div v-if="fileName">重新上传</div>
</label>
</div>
<div class="flex justify-between fixed bottom-0 left-0 right-0">
<app-btn
text="取消"
class="w-5/12 mx-auto text-font-white rounded-2xl bg-font-blue"
border="none"
@btnClicked="$router.back()"
></app-btn>
<app-btn
text="确定"
class="w-5/12 mx-auto text-font-white rounded-2xl bg-font-blue"
border="none"
:disabled="nextBtnDisabled"
@btnClicked="$router.back()"
></app-btn>
</div>
</Layout-Child>
</template>
<script lang="ts">
import { Uploader } from "vant";
import Vue from "vue";
import { mapMutations, mapState } from "vuex";
export default Vue.extend({
data() {
return {};
},
components: {
"Layout-Child": () => import("@/layout/Child.vue"),
"app-btn": () => import("@/components/common/Btn.vue"),
Uploader,
},
computed: {
...mapState("create", ["fileName"]),
nextBtnDisabled(): boolean {
return !this.fileName;
},
},
methods: {
handleFileChange() {
const ele = this.$refs.inputFile as HTMLInputElement;
const files = ele.files;
if (files && files.length >= 1) {
this.afterRead(files[0]);
}
},
...mapMutations("create", {
setState: "SET_STATE",
}),
async afterRead(file: File) {
try {
const ret = await this.$service.nftService.getMd5(file);
this.setState({
fileName: file.name,
fileHash: ret,
file: file
});
} catch (err) {}
},
},
});
</script>
<style scoped>
.upload-box {
width: 140px;
height: 71px;
background: #1d2649;
border-radius: 15px;
border: 1px solid #0078ff;
}
</style>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment