Commit 6ce31513 by haojie

Initial commit

parent 077d52ce
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use App\Service\InscriptionService;
class InscriptionController extends Controller
{
// 根据钱包地址获取铭文列表
public function wallet(Request $request){
$address = $request->input('address');
$result = app(InscriptionService::class)->getWalletInscription($address);
return $this->success($result);
}
}
......@@ -2,6 +2,7 @@
namespace App\Http\Controllers;
use App\Http\Traits\Response;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
......@@ -9,5 +10,5 @@
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
use AuthorizesRequests, DispatchesJobs, ValidatesRequests, Response;
}
<?php
namespace App\Http\Controllers\Web;
use App\Http\Controllers\Controller;
use Inertia\Inertia;
class InscriptionController extends Controller
{
// 铭文市场
public function market(){
$title = '铭文市场';
return Inertia::render('InscriptionMarket/index', [
'info' => [
'title' => $title,
],
]);
}
// 钱包查询
public function wallet(){
$title = '钱包查询';
return Inertia::render('WalletSearch/index', [
'info' => [
'title' => $title,
],
]);
}
// 铭文查询
public function search(){
$title = '铭文查询';
return Inertia::render('InscriptionSearch/index', [
'info' => [
'title' => $title,
],
]);
}
}
......@@ -5,18 +5,17 @@
use App\Http\Controllers\Controller;
use Inertia\Inertia;
class HomeController extends Controller
class OtcController extends Controller
{
public function show()
// otc 交易
public function index()
{
# 自定义页面头部
$title = 'Home';
return Inertia::render('Home/index', [
$title = 'OTC';
return Inertia::render('OTCTrading/index', [
'info' => [
'title' => $title,
],
]);
// @vue/server-renderer
// vue-loader
}
}
<?php
namespace App\Http\Traits;
trait Response
{
/**
* @param string $message
* @param array $data
* @param int $status_code
* @return mixed
*/
public function success($message = 'success', $data = [], $status_code = 200)
{
return self::responseJson(0, $message, $data, [], $status_code);
}
protected static function responseJson($code, $message, $data, $errors, $status_code)
{
$data = [
'code' => $code,
'message' => $message,
'data' => $data,
'time' => microtime(true) - LARAVEL_START,
'errors' => $errors,
'status_code' => $status_code,
];
return response()->json($data, $status_code);
}
/**
* 成功响应
*
* @param string $message
* @param int $code
* @param int $status_code
* @param array $errors
* @param null $data
* @return mixed
*/
public function error(string $message = 'error', int $code = 1, $status_code = 200, $errors = [], $data = null)
{
return self::responseJson($code, $message, $data, $errors, $status_code);
}
}
<?php
namespace App\Service;
use App\Service\RequestService;
use Illuminate\Support\Facades\Log;
class InscriptionService{
// 铭文请求地址
public const InscriptionDomain = 'https://eth-script-indexer-eca25c4cf43b.herokuapp.com';
public function getWalletInscription($address = ''){
if(!$address){
return false;
}
$url =self::InscriptionDomain . "/api/ethscriptions/owned_by/$address";
return app(RequestService::class)->get($url);
}
}
\ No newline at end of file
<?php
namespace App\Service;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Support\Facades\Log;
class RequestService
{
private $client;
public function __construct()
{
$this->client = new Client();
}
private function request($method, $url, $options = [])
{
try {
$response = $this->client->request($method, $url, $options);
return $this->parseResponse($response);
} catch (RequestException $e) {
if ($e->hasResponse()) {
$response = $e->getResponse();
$statusCode = $response->getStatusCode();
}
Log::error('Error occurred while sending HTTP request', [
'url' => $url,
'method' => $method,
'error' => $e->getMessage(),
]);
return false;
} catch (Exception $e) {
Log::error('Error occurred while sending HTTP request', [
'url' => $url,
'method' => $method,
'error' => $e->getMessage(),
]);
return false;
}
}
/**
* 根据响应格式返回类型
*
* @param [type] $response
* @return array
*/
private function parseResponse($response)
{
$contentType = $response->getHeaderLine('Content-Type');
if (strpos($contentType, 'application/json') !== false) {
return json_decode($response->getBody(), true);
} else if (strpos($contentType, 'text/xml') !== false) {
return simplexml_load_string($response->getBody());
} else {
return (string)$response->getBody();
}
}
public function get($url, $query = [], $options = [])
{
$options['query'] = $query;
$result = $this->request('GET', $url, $options);
return $result;
}
public function post($url, $data = [], $options = [])
{
$options['form_params'] = $data;
$result = $this->request('POST', $url, $options);
return $result;
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -8,14 +8,18 @@
"devDependencies": {
"@inertiajs/vue3": "^1.0.7",
"@vitejs/plugin-vue": "^4.2.1",
"@vitejs/plugin-vue-jsx": "^3.0.1",
"@vue/server-renderer": "^3.3.4",
"axios": "^1.1.2",
"laravel-vite-plugin": "^0.7.2",
"less": "^4.1.3",
"lodash": "^4.17.19",
"postcss": "^8.1.14",
"tdesign-vue-next": "^1.3.8",
"typescript": "^5.0.4",
"vite": "^4.0.0",
"vite-plugin-compression": "^0.5.1",
"vite-svg-loader": "^4.0.0",
"vue": "^3.2.36"
}
}
#app {
color: red;
body {
padding: 0;
margin: 0;
}
a {
text-decoration: none;
}
<template>
<Head :title="info.title"/>
<h1>{{ info.title }}</h1>
{{ num }}
<button @click="onNumChange">点击</button>
<p>新的内容是否加载</p>
</template>
<script setup lang="ts">
import {Head} from '@inertiajs/vue3'
import {ref} from 'vue';
defineProps({info: Object})
const dialog_visible = ref<boolean>(false)
const num = ref(1)
const onNumChange = () => {
num.value += 1;
}
</script>
<template>
<Layout> 铭文市场 </Layout>
</template>
<script lang="ts" setup>
import Layout from "@/layout/index.vue";
</script>
<style lang="less"></style>
<template>
<Head :title="info.title" />
<Layout> </Layout>
</template>
<script setup lang="ts">
import Layout from "@/layout/index.vue";
import { Head } from "@inertiajs/vue3";
import { ref } from "vue";
defineProps({ info: Object });
</script>
<template>
<Layout> otc交易 </Layout>
</template>
<script lang="ts" setup>
import Layout from "@/layout/index.vue";
</script>
<style lang="less"></style>
<template>
<div class="inscription-held">
<div class="label">持有铭文</div>
<div
v-for="item in list"
:key="item.time"
class="inscription-held-card"
></div>
</div>
</template>
<script lang="ts" setup>
const props = withDefaults(
defineProps<{
list: any[];
}>(),
{}
);
</script>
<style lang="less">
.inscription-held {
margin-bottom: 20px;
.label {
font-size: 16px;
font-weight: 400;
}
.inscription-held-card {
width: 133px;
height: 55px;
border-radius: 5px;
border: 1px solid #dedede;
}
}
</style>
<template>
<Head :title="info.title" />
<Layout>
<Navbar
title="钱包查询"
content="输入你的钱包地址查询铭文数量、铭文是否有效"
></Navbar>
<div class="inscription-search-box">
<!-- input -->
<CustomInput v-model="inputValue" :clear="true" @search="onSearch">
</CustomInput>
</div>
<div class="inscription-search-content">
<InscriptionHeld :list="[]"></InscriptionHeld>
<div>
<CustomGroupButton
:list="groupBtns"
v-model="currentBtn"
></CustomGroupButton>
</div>
<div class="inscription-search-table">
<CustomTable
:data="tableList"
:columns="columns"
:loading="loading"
></CustomTable>
<div class="pagination-box">
<CustomPagination
:total="total"
:pageNum="pageNum"
:pageSize="pageSize"
@pageChange="pageChange"
></CustomPagination>
</div>
</div>
</div>
</Layout>
</template>
<script lang="tsx" setup>
import Layout from "@/layout/index.vue";
import { Head } from "@inertiajs/vue3";
import Navbar from "@/components/navbar.vue";
import CustomTable from "@/components/table.vue";
import CustomInput from "@/components/input/index.vue";
import InscriptionHeld from "./components/InscriptionHeld.vue";
import { ref } from "vue";
import CustomGroupButton from "@/components/groupButton.vue";
import TipsSvg from "@/assets/svg/content/tips.svg";
import CustomTooltip from "@/components/tooltip.vue";
import CustomPagination from "@/components/pagination.vue";
import { getUserInscriptionList } from "@/utils/api/external";
import { showMessage } from "@/utils/tool";
defineProps({ info: Object });
// 搜索框的值
const inputValue = ref("");
// 当前选中的按钮
const currentBtn = ref("assets");
// 按钮组
const groupBtns = [
{
label: "资产",
value: "assets",
},
{
label: "铸造",
value: "cast",
},
{
label: "发送",
value: "send",
},
{
label: "收到",
value: "receive",
},
];
const pageNum = ref(1);
const pageSize = ref(10);
const total = ref(0);
const loading = ref(false);
const tableList = ref([]);
const columns = [
{
colKey: "applicant",
title: "名称",
align: "center",
},
{
colKey: "applicant",
title: "方法",
align: "center",
},
{
colKey: "applicant",
title: "数量",
align: "center",
},
{
colKey: "applicant",
title: (h, { colIndex }) => (
<span class="table-tip-label">
Hash
<CustomTooltip
content="您当前拥有的资产被铸造出来时的hash,在erc20协议中,
该hash的转移为表示所有转移权"
>
<span class="tip">
<TipsSvg></TipsSvg>
</span>
</CustomTooltip>
</span>
),
align: "center",
},
{
colKey: "applicant",
title: (h, { colIndex }) => (
<span class="table-tip-label">
链接
<CustomTooltip
content="您当前拥有的资产来源。如果是铸造,则会跳转到铸造对应的
Hash,如果是收到,那么会跳转到r收到的这笔Hash。"
>
<span class="tip">
<TipsSvg></TipsSvg>
</span>
</CustomTooltip>
</span>
),
align: "center",
},
{
colKey: "applicant",
title: "操作",
align: "center",
},
];
const getList = async () => {
try {
if (!inputValue.value) {
showMessage("未输入钱包地址");
return;
}
const res: any = await getUserInscriptionList(inputValue.value);
} catch (e) {
console.log(e);
}
};
// 开始搜索
const onSearch = () => {
getList();
};
// 页数变化
const pageChange = (value: number) => {
pageNum.value = value;
// 请求接口
// getList();
};
</script>
<style lang="less">
.inscription-search-box {
height: 130px;
display: flex;
justify-content: center;
align-items: center;
}
.inscription-search-content {
max-width: 1430px;
border: 1px solid #dedede;
flex: 1;
border-radius: 8px;
background: white;
padding: 20px;
margin: 0 auto;
display: flex;
flex-direction: column;
.inscription-search-table {
margin-top: 20px;
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.table-tip-label {
display: flex;
justify-content: center;
align-items: center;
.tip {
margin-left: 6px;
cursor: pointer;
display: flex;
align-items: center;
}
}
.pagination-box {
}
}
}
</style>
import {createApp, createSSRApp, h} from 'vue'
import {createInertiaApp} from '@inertiajs/vue3'
import '../css/app.css'
// 引入组件库的少量全局样式变量
import "tdesign-vue-next/es/style/index.css";
import "../css/app.css";
import { createApp, createSSRApp, h } from "vue";
import { createInertiaApp } from "@inertiajs/vue3";
import Tdesign from "./utils/tdesign";
createInertiaApp({
id: 'app',
resolve: name => {
const pages = import.meta.glob('./Pages/**/*.vue', {eager: true})
return pages[`./Pages/${name}.vue`]
id: "app",
resolve: (name) => {
const pages = import.meta.glob("./Pages/**/*.vue", { eager: true });
return pages[`./Pages/${name}.vue`];
},
setup({el, App, props, plugin}) {
setup({ el, App, props, plugin }) {
// const app = createSSRApp({render: () => h(App, props)});
const app = createApp({render: () => h(App, props)});
const app = createApp({ render: () => h(App, props) });
app.use(plugin);
app.use(Tdesign);
app.mount(el);
},
})
});
<svg width="69" height="69" viewBox="0 0 69 69" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M34.8602 5.75V26.9156C34.8602 27.0054 34.7703 27.0953 34.6805 27.0953C34.0515 27.3699 33.5124 27.642 32.8835 27.9142C32.0749 28.2761 31.1764 28.6406 30.3678 29.095L27.4028 30.4556L24.9769 31.5466L22.0119 32.9097C21.2033 33.2742 20.3947 33.6362 19.4962 34.0906C18.8673 34.3627 18.1485 34.7272 17.5195 34.9993C17.4297 34.9993 17.4297 35.0891 17.3398 34.9993H17.25C17.5195 34.5449 17.7891 34.1804 18.0586 33.7286C19.4962 31.2745 21.0236 28.8229 22.4612 26.3688C23.9886 23.7375 25.6058 21.1011 27.1333 18.4673C28.5708 16.0157 30.0084 13.5642 31.4459 11.2025C32.5241 9.38242 33.6023 7.65991 34.5906 5.83985C34.7703 5.83985 34.7703 5.75 34.8602 5.75C34.7703 5.75 34.8602 5.75 34.8602 5.75V5.75Z" fill="#8C8C8C"/>
<path d="M52.2908 34.9994C50.9431 35.9081 49.5055 36.7245 48.1578 37.5408C43.7553 40.1772 39.4426 42.7212 35.0401 45.355C34.9502 45.355 34.9502 45.4448 34.8604 45.4448C34.7705 45.4448 34.7705 45.355 34.7705 45.355V27.0953C34.7705 27.0055 34.8604 27.0055 34.9502 27.0055C35.3096 27.1852 35.669 27.37 36.1182 27.5497C37.1964 28.0965 38.3644 28.5509 39.4426 29.0951C40.4309 29.5495 41.3294 30.0038 42.3177 30.3658C43.306 30.8202 44.2045 31.2745 45.1929 31.7289C46.0015 32.0934 46.9 32.4554 47.7086 32.9098C48.5172 33.2743 49.4157 33.6363 50.2243 34.0906C50.8533 34.3627 51.4822 34.7273 52.201 34.9994C52.201 34.9095 52.201 34.9994 52.2908 34.9994V34.9994Z" fill="#141414"/>
<path d="M34.8602 63.2501C34.8602 63.2501 34.7703 63.2501 34.8602 63.2501C34.7703 63.2501 34.7703 63.2501 34.6805 63.1577C32.8835 60.6162 31.1764 58.1621 29.3794 55.6181L23.9886 47.9888C22.2815 45.5372 20.4845 43.0857 18.7774 40.5391L17.4297 38.6318C17.4297 38.5419 17.3398 38.5419 17.3398 38.4521C17.4297 38.4521 17.4297 38.5419 17.5195 38.5419C19.9454 39.9949 22.4612 41.4479 24.8871 42.9008C27.6723 44.6285 30.5475 46.2637 33.3327 47.9888C33.782 48.2609 34.3211 48.533 34.7703 48.8051C34.8602 48.8051 34.8602 48.8975 34.8602 48.9874V63.2501Z" fill="#8C8C8C"/>
<path d="M17.25 34.0486C17.25 33.9587 17.25 33.9587 17.25 34.0486C18.1485 33.5942 19.047 33.2322 19.9454 32.7779L23.4495 31.1426C24.348 30.6883 25.2465 30.3263 26.1449 29.8719C27.4028 29.2353 28.7505 28.6911 30.0084 28.0544C30.9069 27.6899 31.8053 27.2356 32.7038 26.8736C33.3328 26.6015 33.9617 26.3294 34.5906 25.9648C34.6805 25.9648 34.6805 25.875 34.7703 25.875V44.4042C34.6805 44.4966 34.6805 44.4042 34.5906 44.4042C34.3211 44.2245 34.0515 44.1321 33.8718 43.9498L17.4297 34.141C17.3398 34.0486 17.25 34.0486 17.25 34.0486ZM52.0211 37.4115C52.0211 37.5013 52.0211 37.5013 51.9312 37.5911C46.7201 45.0408 41.5089 52.3981 36.2977 59.8477C35.7586 60.6641 35.2196 61.3905 34.6805 62.2094V47.7645C35.8485 47.038 37.0165 46.3115 38.1845 45.6774L51.9312 37.5013C51.9312 37.4115 52.0211 37.4115 52.0211 37.4115V37.4115Z" fill="#3C3C3B"/>
<path d="M34.8604 27.0054V5.84497L52.1111 34.7297C52.201 34.8196 52.2908 34.9094 52.2908 35.0019C51.9314 34.8222 51.572 34.6373 51.1228 34.4576C50.6736 34.2754 50.2243 34.0033 49.7751 33.821C49.5055 33.7312 49.236 33.5489 48.8766 33.4565C48.4274 33.2768 47.8883 33.0021 47.439 32.8224C47.1695 32.73 46.9 32.5503 46.6304 32.4579L44.7436 31.6416C44.3842 31.4593 44.1147 31.3694 43.7553 31.1872C43.306 31.0049 42.8568 30.7328 42.4076 30.5505C42.138 30.4607 41.8685 30.2784 41.5989 30.1886L39.7121 29.3697C39.3527 29.19 39.0832 29.0976 38.7238 28.9153C38.2746 28.7356 37.8253 28.4609 37.3761 28.2787C37.1065 28.099 36.7472 28.0066 36.4776 27.8269L34.8604 27.008V27.0054Z" fill="#343434"/>
</svg>
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_49_1246)">
<path d="M12.5 25C5.59687 25 0 19.4031 0 12.5C0 5.59687 5.59687 0 12.5 0C19.4031 0 25 5.59687 25 12.5C25 19.4031 19.4031 25 12.5 25ZM12.5 11.0271L7.33958 5.86458C7.2428 5.76848 7.12804 5.69239 7.00185 5.64064C6.87566 5.58889 6.74052 5.5625 6.60413 5.56299C6.46774 5.56347 6.33279 5.59082 6.20697 5.64346C6.08115 5.6961 5.96693 5.77301 5.87083 5.86979C5.77368 5.96568 5.69644 6.07983 5.64356 6.20567C5.59068 6.33152 5.56321 6.46658 5.56273 6.60308C5.56224 6.73959 5.58876 6.87484 5.64074 7.00106C5.69272 7.12728 5.76915 7.24197 5.86562 7.33854L11.0271 12.5L5.86458 17.6604C5.76848 17.7572 5.69239 17.872 5.64064 17.9981C5.58889 18.1243 5.5625 18.2595 5.56299 18.3959C5.56347 18.5323 5.59082 18.6672 5.64346 18.793C5.6961 18.9189 5.77301 19.0331 5.86979 19.1292C5.96568 19.2263 6.07983 19.3036 6.20567 19.3564C6.33152 19.4093 6.46658 19.4368 6.60308 19.4373C6.73959 19.4378 6.87484 19.4112 7.00106 19.3593C7.12728 19.3073 7.24197 19.2309 7.33854 19.1344L12.5 13.9729L17.6604 19.1344C17.7572 19.2305 17.872 19.3066 17.9981 19.3583C18.1243 19.4101 18.2595 19.4365 18.3959 19.436C18.5323 19.4355 18.6672 19.4081 18.793 19.3555C18.9189 19.3029 19.0331 19.226 19.1292 19.1292C19.2263 19.0333 19.3036 18.9191 19.3564 18.7933C19.4093 18.6674 19.4368 18.5324 19.4373 18.3959C19.4378 18.2594 19.4112 18.1241 19.3593 17.9979C19.3073 17.8717 19.2309 17.757 19.1344 17.6604L13.9729 12.5L19.1344 7.33958C19.2305 7.2428 19.3066 7.12804 19.3583 7.00185C19.4101 6.87566 19.4365 6.74052 19.436 6.60413C19.4355 6.46774 19.4081 6.33279 19.3555 6.20697C19.3029 6.08115 19.226 5.96693 19.1292 5.87083C19.0333 5.77368 18.9191 5.69644 18.7933 5.64356C18.6674 5.59068 18.5324 5.56321 18.3959 5.56273C18.2594 5.56224 18.1241 5.58876 17.9979 5.64074C17.8717 5.69272 17.757 5.76915 17.6604 5.86562L12.5 11.0271Z" fill="#868686"/>
</g>
<defs>
<clipPath id="clip0_49_1246">
<rect width="25" height="25" fill="white"/>
</clipPath>
</defs>
</svg>
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.2333 18.0233L24.543 22.3329C25.1531 22.943 25.1531 23.9326 24.543 24.5426C23.933 25.1527 22.9434 25.1527 22.3334 24.5426L18.0236 20.233M20.6257 11.2506C20.6257 6.07292 16.4284 1.87558 11.2506 1.87558C6.07329 1.87558 1.876 6.07292 1.876 11.2506C1.876 16.428 6.07329 20.6253 11.2506 20.6253C16.4284 20.6253 20.6257 16.428 20.6257 11.2506ZM18.0236 20.233C13.3057 23.7907 6.6391 23.0913 2.7616 18.632C-1.11594 14.1732 -0.882318 7.47402 3.29588 3.29582C7.47443 -0.882687 14.1733 -1.11636 18.6325 2.76114C23.0917 6.63869 23.7911 13.3052 20.2333 18.0233" fill="#231815"/>
</svg>
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_49_1659)">
<path d="M7.5 0.9375C3.88126 0.9375 0.9375 3.88126 0.9375 7.5C0.9375 11.1187 3.88126 14.0625 7.5 14.0625C11.1187 14.0625 14.0625 11.1187 14.0625 7.5C14.0625 3.88126 11.1187 0.9375 7.5 0.9375ZM7.5 12.1927C7.11187 12.1927 6.79687 11.8777 6.79687 11.4895C6.79687 11.1014 7.11187 10.7864 7.5 10.7864C7.88813 10.7864 8.20313 11.1014 8.20313 11.4895C8.20313 11.8777 7.88813 12.1927 7.5 12.1927ZM8.79751 7.40016C8.3897 7.80752 7.96876 8.22846 7.96876 8.60673V9.39939C7.96876 9.65815 7.75876 9.86815 7.5 9.86815C7.24124 9.86815 7.03124 9.65815 7.03124 9.39939V8.60673C7.03124 7.83985 7.61717 7.25391 8.13467 6.7369C8.51388 6.35769 8.90624 5.96581 8.90624 5.65315C8.90624 4.87175 8.2753 4.23612 7.5 4.23612C6.71157 4.23612 6.09376 4.84456 6.09376 5.62127C6.09376 5.88003 5.88376 6.09001 5.625 6.09001C5.36624 6.09001 5.15624 5.88001 5.15624 5.62127C5.15624 4.34065 6.20763 3.29862 7.5 3.29862C8.79237 3.29862 9.84376 4.35471 9.84376 5.65315C9.84376 6.35436 9.31173 6.88594 8.79751 7.40016Z" fill="#868686"/>
</g>
<defs>
<clipPath id="clip0_49_1659">
<rect width="15" height="15" fill="white"/>
</clipPath>
</defs>
</svg>
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.0892 17.8016C22.1207 16.8939 22.9629 15.9589 23.5523 15.2433C24.0051 14.6935 24.2316 14.4186 24.2316 14C24.2316 13.5814 24.0051 13.3065 23.5523 12.7567C21.8955 10.7452 18.2413 7 13.9996 7C12.927 7 11.892 7.23946 10.9174 7.62981L13.7935 10.506C13.8617 10.502 13.9304 10.5 13.9996 10.5C15.9326 10.5 17.4996 12.067 17.4996 14C17.4996 14.0692 17.4976 14.1379 17.4936 14.2061L21.0892 17.8016ZM11.0936 12.0487C10.7185 12.6062 10.4996 13.2775 10.4996 14C10.4996 15.933 12.0666 17.5 13.9996 17.5C14.722 17.5 15.3934 17.2811 15.9509 16.906L18.6424 19.5975C17.2425 20.4217 15.6664 21 13.9996 21C9.75788 21 6.10363 17.2548 4.44686 15.2433C3.99401 14.6935 3.76758 14.4186 3.76758 14C3.76758 13.5814 3.99401 13.3065 4.44686 12.7567C5.26698 11.761 6.57655 10.3404 8.20455 9.15965L11.0936 12.0487Z" fill="#B9BDCA"/>
<path d="M5.83301 2.3335L24.4997 21.0002" stroke="#B9BDCA" stroke-width="2"/>
</svg>
\ No newline at end of file
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.2731 14C24.2731 13.6466 24.0798 13.4072 23.6931 12.9284C22.1093 10.9673 18.366 7 14.0003 7C9.63468 7 5.89132 10.9673 4.30753 12.9284C3.92087 13.4072 3.72754 13.6466 3.72754 14C3.72754 14.3534 3.92087 14.5928 4.30753 15.0716C5.89132 17.0327 9.63468 21 14.0003 21C18.366 21 22.1093 17.0327 23.6931 15.0716C24.0798 14.5928 24.2731 14.3534 24.2731 14ZM14.0003 17.5C15.9333 17.5 17.5003 15.933 17.5003 14C17.5003 12.067 15.9333 10.5 14.0003 10.5C12.0673 10.5 10.5003 12.067 10.5003 14C10.5003 15.933 12.0673 17.5 14.0003 17.5Z" fill="#CCD2E3"/>
</svg>
\ No newline at end of file
<template>
<div class="custom-group-buttons">
<div
class="group-button"
:class="{
'group-button_active': item.value === currentBtn,
}"
v-for="item in list"
:key="item.value"
@click="change(item.value)"
>
{{ item.label }}
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
const props = withDefaults(
defineProps<{
modelValue: string;
list: any[];
}>(),
{}
);
const emit = defineEmits(["update:modelValue"]);
const currentBtn = ref(props.modelValue);
const change = (value: string) => {
currentBtn.value = value;
};
watch(
() => props.modelValue,
(v) => {
if (v) {
currentBtn.value = v;
}
}
);
watch(
() => currentBtn.value,
(v) => {
if (v) {
emit("update:modelValue", v);
}
}
);
</script>
<style lang="less">
.custom-group-buttons {
height: 40px;
border-radius: 4px;
border: 1px solid #dedede;
display: inline-flex;
align-items: center;
background: #f5f5f5;
padding: 0 6px;
.group-button {
background: transparent;
border-radius: 4px;
font-size: 13px;
font-weight: 400;
transition: all 0.2s;
height: 32px;
width: 105px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.group-button_active {
background: #ffffff;
transition: all 0.2s;
}
}
</style>
export interface RulesType {
required?: boolean;
type?: string;
message?: string;
min?: number;
max?: number;
validator?: Function;
}
<template>
<div class="custom-input-global">
<div
class="custom-input-box"
:class="{ 'custom-input-error': ruleError.status }"
>
<slot name="leftIcon">
<span class="left-input-icon"></span>
</slot>
<input
:type="input_type"
v-model="input_value"
class="cust-input"
:disabled="disabled"
:placeholder="placeholder"
@focus="onInputFocus"
@blur="onInputBlur"
@input="numberInput(input_value)"
/>
<slot name="rightIcon">
<span v-if="type === 'password'" class="custom-pwd-hide-button">
<PrivatePwdSvg
v-if="Cur_pwd_type === 'private'"
@click="changePwd('text')"
></PrivatePwdSvg>
<PublicPwdSvg
v-else
@click="changePwd('password')"
></PublicPwdSvg>
</span>
<span
v-if="clear && input_value"
class="input-clear-icon"
@click="clearInputValue"
>
<ClearSvg></ClearSvg>
</span>
<span
v-if="searchIcon"
class="input-search-icon"
@click="onSearch"
>
<SearchSvg></SearchSvg>
</span>
</slot>
<!-- remember -->
<transition name="remember-fade">
<div
class="remember-select-box"
v-if="needSelect && input_focus && selectList.length"
>
<div
v-for="item in selectList"
:key="item.account"
class="line"
@click="QuickInputAccount(item)"
>
<div class="account">{{ item.account }}</div>
<div class="password">********</div>
</div>
</div>
</transition>
</div>
<div class="custom-input-rule" v-if="rules && rules.length">
{{ ruleError.message }}
</div>
</div>
</template>
<script lang="ts" setup>
import PrivatePwdSvg from "@/assets/svg/login/privatePwd.svg";
import PublicPwdSvg from "@/assets/svg/login/publicPwd.svg";
import { reactive, ref } from "@vue/reactivity";
import { watch } from "@vue/runtime-core";
import { RulesType } from "./Interfase";
import { emailReg } from "@/constants/token";
import SearchSvg from "@/assets/svg/content/search.svg";
import ClearSvg from "@/assets/svg/content/clear.svg";
const input_value = ref<string>("");
const props = withDefaults(
defineProps<{
type?: string;
placeholder?: string;
num?: number;
rules?: Array<RulesType>;
modelValue: string;
disabled?: boolean;
needSelect?: boolean;
selectList?: any;
searchIcon?: boolean;
clear?: boolean;
}>(),
{
// 输入框类型
type: "text",
placeholder: "",
needSelect: false,
selectList: [],
// rules: [],
disabled: false,
searchIcon: true,
clear: false,
}
);
const emit = defineEmits([
"update:modelValue",
"submitType",
"submitAccount",
"inputChange",
"search",
]);
// 是否聚焦
const input_focus = ref(false);
// 校验显示的错误
const ruleError = reactive({
status: false,
message: "",
});
const input_type = ref<string>("text");
// 当输入框类型为password时,展示私有密码svg
const Cur_pwd_type = ref<string>("private");
// 输入框校验
const numberInput = (e: string) => {
const { type } = props;
if (type == "number") {
input_value.value = e.replace(/[^\d.]/g, "");
}
// 提交输入事件
emit("inputChange", input_value.value);
};
// 搜索icon触发
const onSearch = () => {
emit("search");
};
// 清空输出框的值
const clearInputValue = () => {
input_value.value = "";
};
// 聚焦事件
const onInputFocus = () => {
if (props.needSelect) {
input_focus.value = true;
}
};
// 失去焦点
const onInputBlur = () => {
if (props.needSelect) {
input_focus.value = false;
}
};
// 错误状态
const errorinput = (rule: any) => {
ruleError.status = true;
ruleError.message = rule.message;
// 提交失败信息
emit("submitType", false);
};
// 提交用户的账号密码
const QuickInputAccount = (item: any) => {
emit("submitAccount", item);
};
// 重置输入框状态
const ResetInput = () => {
ruleError.status = false;
ruleError.message = "";
// 提交通过信息
emit("submitType", true);
};
if (props.type == "password") {
input_type.value = props.type;
}
//
const changePwd = (value: string) => {
if (value === "text") {
Cur_pwd_type.value = "public";
} else {
Cur_pwd_type.value = "private";
}
input_type.value = value;
};
// 表单校验
const FormValidation = () => {
const { rules } = props;
let v = input_value.value;
if (rules) {
for (let i in rules) {
let rule: any = rules[i];
if (rule.required && !v) {
// 校验空input
errorinput(rule);
return;
} else {
ResetInput();
}
// 自定义校验逻辑
if (rule.validator) {
let res = rule.validator(v);
if (!res.status) {
errorinput(res);
return;
} else {
ResetInput();
}
}
// 邮箱验证模块
if (rule.type == "email") {
if (!emailReg.test(v)) {
// 邮箱校验不通过
errorinput(rule);
return;
} else {
ResetInput();
}
}
// 输入框最小长度校验
if (rule.min && v.length < rule.min) {
errorinput(rule);
return;
} else {
ResetInput();
}
}
}
};
watch(
() => input_value.value,
(v) => {
emit("update:modelValue", v);
// 判断输入的内容是否符合校验规则
// 校验
FormValidation();
}
);
//
watch(
() => props.num,
(v) => {
// 校验
FormValidation();
}
);
watch(
() => props.modelValue,
(v) => {
input_value.value = v;
}
);
</script>
<style lang="less">
@import "@/style/flex.less";
.custom-input-global {
height: 55px;
width: 875px;
.custom-input-box {
height: 100%;
background: var(--theme-color-15);
/* 背景线条 */
border: 1px solid #b1b1b1;
border-radius: 8px;
transition: all 0.3s;
position: relative;
.da();
.cust-input {
box-sizing: border-box;
height: 100%;
width: 0;
flex: 1;
outline: none;
border: none;
border-radius: 8px;
padding-left: 12px;
background: transparent;
color: var(--theme-color-4);
}
.left-input-icon {
.da();
}
.custom-pwd-hide-button {
.da();
padding: 0 8px;
}
.input-clear-icon {
cursor: pointer;
padding-right: 12px;
}
.input-search-icon {
padding-right: 16px;
cursor: pointer;
}
.remember-select-box {
z-index: 200;
position: absolute;
top: 46px;
width: 100%;
box-shadow: 0 3px 14px 2px rgba(0, 0, 0, 0.05),
0 8px 10px 1px rgba(0, 0, 0, 6%),
0 5px 5px -3px rgba(0, 0, 0, 10%);
background: var(--theme-color-34);
border-radius: 4px;
max-height: 200px;
overflow-y: auto;
.line {
padding: 6px 12px;
font-weight: bold;
.account {
color: var(--theme-color-35);
}
.password {
color: var(--theme-color-4);
}
}
}
&:focus-within {
border-color: #1e2329;
box-shadow: 0 0 6px 1px #1e2329;
}
}
.custom-input-error {
border-color: #f05451 !important;
transition: all 0.3s;
}
.custom-input-rule {
color: #f05451;
font-weight: 400;
font-size: 12px;
line-height: 26px;
padding-left: 4px;
min-height: 26px;
}
}
//内容打开动画
.remember-fade-enter-active {
transition: all 0.15s ease-out;
}
.remember-fade-leave-active {
transition: all 0.15s cubic-bezier(1, 0.5, 0.8, 1);
}
.remember-fade-enter-from,
.remember-fade-leave-to {
transform: translateY(-6px);
opacity: 0;
}
</style>
<template>
<div class="custom-inscription-navbar">
<div>
<template v-if="icon">
{{ icon }}
</template>
<template v-else>
<EthSvg></EthSvg>
</template>
</div>
<div class="inscription-navbar-content">
<div class="title">
{{ title }}
</div>
<div class="content">
{{ content }}
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import EthSvg from "@/assets/svg/chain/eth.svg";
const props = withDefaults(
defineProps<{
title?: string;
content?: string;
icon?: any;
}>(),
{
title: "",
content: "",
icon: "",
}
);
</script>
<style lang="less">
.custom-inscription-navbar {
width: 100%;
height: 130px;
background: #1e2329;
display: flex;
justify-content: center;
align-items: center;
.inscription-navbar-content {
display: flex;
flex-direction: column;
align-items: center;
margin-left: 50px;
.title {
font-size: 24px;
color: #ffffff;
font-weight: 700;
}
.content {
font-size: 14px;
font-weight: 400;
color: #848e9c;
padding-top: 12px;
}
}
}
</style>
<template>
<t-pagination
v-model="cpageNum"
v-model:pageSize="cpageSize"
:total="total"
show-sizer
@currentChange="onCurrentChange"
:pageSizeOptions="pageSizeOptions"
/>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { Pagination as TPagination } from "tdesign-vue-next";
const props = withDefaults(
defineProps<{
pageNum: number;
pageSize: number;
total: number;
pageSizeOptions?: any[];
}>(),
{
pageSizeOptions: [],
}
);
const emit = defineEmits(["pageChange"]);
const cpageNum = ref(props.pageNum);
const cpageSize = ref(props.pageSize);
const onCurrentChange = (value: number) => {
cpageNum.value = value;
emit("pageChange", value);
};
</script>
<style lang="less"></style>
<template>
<t-table
class="custom-table-default"
row-key="index"
:data="data"
:columns="columns"
@row-click="handleRowClick"
:loading="loading"
>
</t-table>
</template>
<script lang="tsx" setup>
const props = withDefaults(
defineProps<{
data: any[];
columns: any[];
loading: boolean;
}>(),
{}
);
const emit = defineEmits(["handleRowClick"]);
// 行点击
const handleRowClick = () => {
//
emit("handleRowClick");
};
</script>
<style lang="less">
.custom-table-default {
thead {
tr {
th {
font-size: 16px;
color: #000000;
}
}
}
tbody {
}
}
</style>
<template>
<t-tooltip
:content="content"
:popupProps="{
overlayClassName: 'custom-tooltip',
}"
>
<slot></slot>
</t-tooltip>
</template>
<script lang="ts" setup>
const props = withDefaults(
defineProps<{
content: string;
}>(),
{}
);
</script>
<style lang="less">
.custom-tooltip {
// background-color: #1e2329 !important;
}
.t-tooltip--default {
.t-popup__content {
background-color: #1e2329 !important;
opacity: 0.8;
}
}
</style>
// 邮箱正则
export const emailReg = /^\w{3,}(\.\w+)*@[A-z0-9]+(\.[A-z]{2,8}){1,2}$/;
<template>
<div class="custom-layout-head">
<div class="layout-head-left">
<div
class="layout-chose-button"
v-for="item in btns"
:key="item.path"
@click="changeBtn(item)"
:class="{ active: item.path === currentBtn }"
>
{{ item.label }}
</div>
</div>
<div class="layout-head-right">
<!-- <t-button class="logout" @click="logout"> 退出 </t-button> -->
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from "@vue/reactivity";
import { computed } from "@vue/runtime-core";
import { MessagePlugin } from "tdesign-vue-next";
import { getRoute } from "@/utils/tool";
const { pathname } = getRoute();
const currentBtn = ref(pathname);
const btns = [
{
label: "铭文市场",
path: "/inscription/market",
},
{
label: "OTC交易",
path: "/otcTrade",
},
{
label: "钱包查询",
path: "/inscription/wallet",
},
{
label: "铭文查询",
path: "/inscription/search",
},
];
const logout = async () => {
// try {
// let res: any = await useLogout();
// if (res.code == 0) {
// // 清空账户列表
// MessagePlugin.success("退出成功");
// router.replace({
// path: "/",
// });
// }
// } catch (e) {
// console.log(e);
// }
};
const changeBtn = (item: any) => {
currentBtn.value = item.path;
// 路由跳转
// router.replace({
// path: item.path,
// });
window.location.href = item.path;
};
</script>
<style lang="less">
@import "@/style/flex.less";
@import "@/style/variables.less";
.custom-layout-head {
height: 64px;
background: @header-theme-color;
.dja(space-between);
padding: 0 12px;
.layout-head-left {
.da();
span {
font-weight: 700;
font-size: 24px;
}
.layout-chose-button {
margin-left: 60px;
font-size: 14px;
color: #eaecef;
transition: all 0.1s;
cursor: pointer;
}
.active {
color: #fd1753;
font-size: 20px;
font-weight: 600;
transition: all 0.1s;
}
}
.layout-head-right {
.da();
.logout {
background: #fd1753;
border: none;
margin-right: 20px;
--ripple-color: #fd6053 !important;
}
}
}
</style>
<template>
<div class="custom-layout">
<Header></Header>
<div class="custom-content">
<slot></slot>
</div>
</div>
</template>
<script lang="ts" setup>
import Header from "./header.vue";
</script>
<style lang="less">
.custom-layout {
height: 100%;
display: flex;
justify-content: center;
flex-direction: column;
height: 100vh;
.custom-content {
flex: 1;
background: #ffffff;
max-height: calc(100vh - 64px);
overflow-y: auto;
display: flex;
flex-direction: column;
}
}
</style>
//默认center
.dja(@flex:center,@align:center) {
display: flex;
justify-content: @flex;
align-items: @align;
}
.dj(@flex:center) {
display: flex;
justify-content: @flex;
}
.da(@align:center) {
display: flex;
align-items: @align;
}
// 导航栏颜色
@header-theme-color: #181a20;
// 动画
@anim-time-fn-easing: cubic-bezier(0.38, 0, 0.24, 1);
@anim-time-fn-ease-out: cubic-bezier(0, 0, 0.15, 1);
@anim-time-fn-ease-in: cubic-bezier(0.82, 0, 1, 0.9);
@anim-duration-base: 0.2s;
@anim-duration-moderate: 0.24s;
@anim-duration-slow: 0.28s;
import request from "../request/other";
/**
* 外部接口都放这里
*/
// 根据钱包地址获取铭文列表
export const getUserInscriptionList = (address: string) => {
return request.get("/api/ethscriptions/owned_by/" + address);
};
import request from "../request/index";
// 根据钱包地址获取铭文列表
export const getUserInscriptionList = (address: string) => {
return request.get("/api/inscription/ethscriptions/owned_by", {
params: {
address: address,
},
});
};
import axios from "axios";
import { MessagePlugin } from "tdesign-vue-next";
const instance = axios.create({
// baseURL: mode == 'development' ? 'http://mxcus.net' : undefined,
timeout: 1000,
// withCredentials: mode == 'development' ? false : true,
withCredentials: true,
});
// 请求头
instance.interceptors.request.use((config) => {
return config;
});
instance.defaults.timeout = 60000;
instance.interceptors.response.use(
(response) => {
const { data } = response;
if (data.code === 0) {
return data;
} else {
//@ts-ignore
if (response.config.needCode) {
return data;
} else {
MessagePlugin.closeAll();
MessagePlugin.error(data.msg || "请求错误");
return Promise.reject(data.msg);
}
}
},
(err) => {
if ("response" in err) {
const { message: msg, status_code } = err.response.data;
if (status_code == 403) {
MessagePlugin.closeAll();
// router.push({
// path: "/login",
// });
// // 清除cookie
// store.commit("user/removeToken");
// store.commit("user/removeUserInfo");
// store.commit("user/removeBindInfo");
return;
}
MessagePlugin.closeAll();
MessagePlugin.error(msg || "请求错误");
return err.response;
}
}
);
export default instance;
import axios from "axios";
import { MessagePlugin } from "tdesign-vue-next";
const mode = import.meta.env.MODE;
const getBaseUrl = () => {
if (mode == "development") {
return "";
// return "http://127.0.0.1:3008";
// return "https://eth-script-indexer-eca25c4cf43b.herokuapp.com";
} else {
return "";
}
};
// 请求外部接口
const instance = axios.create({
baseURL: getBaseUrl(),
timeout: 20000,
withCredentials: false,
});
// 请求头
instance.interceptors.request.use((config) => {
return config;
});
instance.interceptors.response.use(
(response) => {
const { data } = response;
console.log(response);
if (data.code === 0) {
return data;
} else {
MessagePlugin.closeAll();
MessagePlugin.error(data.msg || "请求错误");
return Promise.reject(data.msg);
}
},
(err) => {
if ("response" in err) {
const { message: msg, status_code } = err.response.data;
if (status_code == 403) {
MessagePlugin.closeAll();
// router.push({
// path: "/login",
// });
// // 清除cookie
// store.commit("user/removeToken");
// store.commit("user/removeUserInfo");
// store.commit("user/removeBindInfo");
return;
}
MessagePlugin.closeAll();
MessagePlugin.error(msg || "请求错误");
return err.response;
}
}
);
export default instance;
import {
Dialog as TDialog,
Button as TButton,
Table as TTable,
Tooltip as TTooltip,
} from "tdesign-vue-next";
const components = [TDialog, TButton, TTooltip];
export default {
install(app: any) {
components.forEach((component, index) => {
app.component(component.name, component);
});
app.component("t-table", TTable);
},
};
import { MessagePlugin } from "tdesign-vue-next";
// 获取路由中的路径,参数等
export const getRoute = () => {
return window.location;
};
export const showMessage = (
value: string,
type: "warning" | "success" | "info" | "error" = "warning",
close: boolean = true
) => {
// CustomNotification.success('你好');
if (close) {
MessagePlugin.closeAll();
}
if (type == "warning") {
MessagePlugin.warning(value);
} else if (type == "success") {
MessagePlugin.success(value);
} else if (type == "info") {
MessagePlugin.info(value);
} else if (type == "error") {
MessagePlugin.error(value);
}
// 类型报错-所以用上面的方法
// MessagePlugin[type](value);
};
......@@ -14,6 +14,26 @@
|
*/
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user();
Route::group([
'namespace' => '\App\Http\Controllers\Api',
'middleware' => 'api',
], static function () {
Route::group([
'prefix' => '/inscription',
], function () {
// 不需要登录
Route::group([
], function () {
// 指定钱包的铭文列表
Route::get('/ethscriptions/owned_by','InscriptionController@wallet');
});
// // 需要登录
// Route::group([
// 'middleware' => 'auth:admin_api',
// ], function () {
// });
});
});
......@@ -15,5 +15,20 @@
Route::group([
'namespace' => '\App\Http\Controllers\Web',
], function () {
Route::get('/', 'HomeController@show')->name('home.index');
// 首页
Route::get('/', 'InscriptionController@market')->name('home.index');
// OTC 交易
Route::get('/otcTrade', 'OtcController@index')->name('otc.index');
// 铭文
Route::group([
'prefix' => '/inscription',
], function () {
// 铭文市场
Route::get('/market', 'InscriptionController@market')->name('inscription.market');
// 钱包查询
Route::get('/wallet', 'InscriptionController@wallet')->name('inscription.wallet');
// 铭文查询(是否被铸造)
Route::get('/search', 'InscriptionController@search')->name('inscription.search');
});
});
import {defineConfig} from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';
import ViteCompression from 'vite-plugin-compression'
import { defineConfig } from "vite";
import laravel from "laravel-vite-plugin";
import vue from "@vitejs/plugin-vue";
import ViteCompression from "vite-plugin-compression";
import svgLoader from "vite-svg-loader";
import vueJsx from "@vitejs/plugin-vue-jsx";
export default defineConfig({
plugins: [
vue(),
laravel({
input: ['resources/js/app.js'],
input: ["resources/js/app.js"],
// ssr: 'resources/js/ssr.js',
refresh: true, // 保存时刷新
}),
ViteCompression(),
svgLoader(),
vueJsx(),
],
resolve: {
alias: {
"@": "/resources/js",
},
},
server: {
port: 3008,
host: '127.0.0.1',
host: "127.0.0.1",
proxy: {},
},
proxy: {
"/api/ethscriptions": {
target: "https://eth-script-indexer-eca25c4cf43b.herokuapp.com", //需要代理的域名,目标域名
changeOrigin: true, //需要代理跨域
// rewrite: (path) => path.replace(/^\/outside\/inscription/, ""), //路径重写,把'/api'替换为''
},
},
});
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