Commit 5e6d28fd by lijinqi

api应用市场截止到创建api交易订单

parent f43c21f8
import request from '@/utils/request'
// 获取应用信息列表
export function getAppInfoList(params) {
return request({
url: '/apihub/api/page',
method: 'get',
params: params
})
}
export function getAppCategoryList(params) {
return request({
url: '/apihub/api-category/list',
method: 'get',
params: params
})
}
export function getAppInfoDetail(params) {
return request({
url: '/apihub/api/get',
method: 'get',
params: params
})
}
// 商家信息
export function getMerchantInfo(params) {
return request({
url: '/api/v1/merchantInfo',
method: 'get',
params: params
})
}
// 相关API
export function getAppRecommendList(params) {
return request({
url: '/api/v1/appRecommend',
method: 'get',
params: params
})
}
export function createApiOrderSubmit(query){
return request({
url: '/apihub/api-order/create',
method: 'post',
data: query
})
}
export function createPay(query){
return request({
url: '/pay/order/submit',
method: 'post',
data: query
})
}
\ No newline at end of file
...@@ -5,35 +5,13 @@ ...@@ -5,35 +5,13 @@
@import './sidebar.scss'; @import './sidebar.scss';
@import './btn.scss'; @import './btn.scss';
@import './ruoyi.scss'; @import './ruoyi.scss';
@import './reset.scss';
@import './media.scss';
body {
height: 100%;
margin: 0;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
}
label {
font-weight: 700;
}
html {
height: 100%;
box-sizing: border-box;
}
#app { #app {
height: 100%; height: 100%;
} }
*,
*:before,
*:after {
box-sizing: inherit;
}
.no-padding { .no-padding {
padding: 0px !important; padding: 0px !important;
} }
...@@ -41,24 +19,6 @@ html { ...@@ -41,24 +19,6 @@ html {
.padding-content { .padding-content {
padding: 4px 0; padding: 4px 0;
} }
a:focus,
a:active {
outline: none;
}
a,
a:focus,
a:hover {
cursor: pointer;
color: inherit;
text-decoration: none;
}
div:focus {
outline: none;
}
.fr { .fr {
float: right; float: right;
} }
...@@ -128,6 +88,38 @@ aside { ...@@ -128,6 +88,38 @@ aside {
flex-shrink: 1; flex-shrink: 1;
position: relative; position: relative;
} }
.custom-wrapper {
background-color: #EFF0F2;
padding: 0 0 120px 0;
overflow-x: hidden; // 防止横向滚动条
}
.custom-main-w {
margin: 0 auto;
position: relative;
max-width: 1300px; // 你可以改成 1140px / 1280px
padding: 0 20px; // 给点左右留白,避免贴边
box-sizing: border-box;
}
.com-breadcrumb{
background-color: #FFFFFF;
border-bottom: 1px solid #EBEDED;
padding: 30px;
margin-top:30px;
.el-breadcrumb{
font-size: 16px ;
}
.el-breadcrumb__inner{
color: #333!important;
}
.el-breadcrumb__item {
&:last-child .el-breadcrumb__inner {
color: #999!important;
}
}
}
.components-container { .components-container {
margin: 30px 50px; margin: 30px 50px;
...@@ -232,3 +224,145 @@ aside { ...@@ -232,3 +224,145 @@ aside {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
} }
.joinus-box{
// height: 160px;
width: 100%;
background-color: #2C53AD;
color:#Fff;
padding: 26px 0;
// padding-top: 15px;
}
.joinus-inner-box{
display: flex;
justify-content:space-between;
align-items: center;
height: 100%;
.left{
flex:1;
}
.title{
font-size: 30px;
font-weight: bold;
padding: 10px 0;
}
.desc{
font-size: 18px;
line-height: 3;
}
.a-btn{
cursor: pointer;
color: #Fff;
display: inline-block;
width: 160px;
height: 50px;
line-height: 48px;
border: 1px solid #fff;
border-radius: 24px;
font-weight: bold;
text-align: center;
font-size: 18px;
}
}
.com-tabs-wrapper{
.el-tabs__header{
padding:40px 0;
margin-bottom: 60px;
background-color: rgba(46,83,175,1);
}
.el-tabs__nav-wrap {
max-width: 1440px;
min-width: 1200px;
margin: 0 auto;
}
.el-tabs__nav-wrap::after{
content: none!important;
}
.el-tabs__nav {
width: 100%;
}
.el-tabs__item{
color:#a0b0d6;
font-size: 22px;
padding: 20px 0 20px 20px !important;
font-weight: 400;
justify-content:flex-start;
}
.el-tabs__item.is-active{
color:#FFF;
font-weight: bold;
font-size: 24px;
position: relative;
&::before{
position: absolute;
content: "";
left: 0;
top:5px;
bottom: 5px;
border-left: 2px solid #fff;
}
}
.el-tabs__active-bar{
background-color:transparent;
}
}
.com-breadcrumb-1 {
font-size: 14px;
color: #fff;
background: var(--el-color-primary);
padding: 12px 24px;
padding: 30px;
.el-breadcrumb {
font-size: 16px;
}
.el-breadcrumb__inner,
.el-breadcrumb__separator,
.el-breadcrumb__item:last-child .el-breadcrumb__inner {
color: #fff !important;
}
}
// 控制台的全局样式
.console-main{
margin: 1px 60px 0 30px;
padding: 20px 30px;
background-color: #fff;
// border-radius: 10px;
min-height: 100%;
}
.console-header{
font-size: 24px;
font-weight: 600;
margin: 0 0 20px;
border-bottom: 1px solid #eee;
padding: 10px 30px ;
.title{
height: 36px;
line-height: 36px;
}
}
.console-content{
padding: 10px 30px;
.search-bar {
display: flex;
align-items: center;
margin-bottom: 24px;
}
.search-bar label {
margin-right: 8px;
font-weight: 500;
}
.search-input {
width: 260px;
margin-right: 16px;
}
}
@media (min-width: 1600px) {
.app-wrapper{
width: 100%;
}
.custom-main-w{
max-width: 1440px;
width: 100%;
}
}
// 宽度大于等于1200px
@media (min-width: 1200px) and (max-width: 1600px) {
.app-wrapper{
width: 100%;
}
.custom-main-w{
max-width: 1440px;
min-width: 1180px;
}
}
// 宽度大于等于1200px
@media (max-width: 1200px) {
.app-wrapper{
width: 100%;
min-width: 1200px;
}
.custom-main-w{
max-width: 1160px;
}
.nav-bar {
position: relative!important;
}
.app-main{
padding: 0!important;
}
}
/**
* css reset
*/
html {
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei',
'微软雅黑', Arial, sans-serif;
line-height: 1.5;
background-color: #EFF0F2;
width: 100%;
font-size: 14px;
box-sizing: border-box;
min-height: 100%;
overflow-y: auto;
overflow-x: auto;
outline: 0;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
-webkit-backface-visibility:hidden;
}
html * {
outline: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0); }
*,*::after,*::before{
box-sizing: border-box;
}
body {
padding: 0;
margin: 0 auto;
outline: 0;
color: #333;
height: 100%;
width: 100%;
// overflow-x: auto;
-webkit-font-smoothing: antialiased;
-webkit-backface-visibility:hidden;
}
a,
button {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
article,
aside,
footer,
header,
nav,
section {
display: block;
}
body,
div,
dl,
dt,
dd,
ul,
ol,
li,
h1,
h2,
h3,
h4,
h5,
h6,
pre,
code,
form,
fieldset,
legend,
input,
textarea,
p,
blockquote,
th,
td,
hr,
button,
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
margin: 0;
padding: 0; }
input,
select,
textarea {
font-size: 100%; }
table {
border-collapse: collapse;
border-spacing: 0; }
fieldset,
img {
border: 0; }
abbr,
acronym {
border: 0;
font-variant: normal; }
del {
text-decoration: line-through; }
address,
caption,
cite,
code,
dfn,
em,
th,
var {
font-style: normal;
font-weight: 500; }
ol,
ul {
list-style: none; }
caption,
th {
text-align: left; }
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: 100%;
font-weight: 500; }
q:before,
q:after {
content: ''; }
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline; }
sup {
top: -0.5em; }
sub {
bottom: -0.25em; }
ins,
a {
text-decoration: none; }
/*input*/
button {
border: none;
outline: none;
}
button, html input[type='button'], input[type='reset'], input[type='submit'] {
-webkit-appearance: button;
text-transform: none;
outline: none; }
input::-webkit-input-placeholder,
textarea::-webkit-input-placeholder {
color: #999; }
input::-webkit-inner-spin-button {
-webkit-appearance: none; }
input::-webkit-outer-spin-button {
-webkit-appearance: none; }
textarea {
vertical-align: top; }
button, input {
line-height: normal; }
select {
margin: 0;
outline: 0; }
textarea.fixAndroidKeyboard:focus, input.fixAKeyboard:focus {
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
-webkit-user-modify: read-write-plaintext-only; }
input:-webkit-autofill {
-webkit-box-shadow: 0 0 0px 1000px white inset !important; }
input[type=submit],
input[type=reset],
input[type=button],
input[type=checkbox],
button, label {
cursor: pointer;
user-select: none;
-ms-user-select: none;
-moz-user-select: none;
-webkit-user-select: none; }
input[type=submit] {
-webkit-user-modify: read-plaintext-only;
-moz-user-modify: read-plaintext-only;
-ms-user-modify: read-plaintext-only;
-o-user-modify: read-plaintext-only;
user-modify: read-plaintext-only; }
input[type='search']::-webkit-search-cancel-button,
input[type='search']::-webkit-search-decoration {
-webkit-appearance: none; }
input[type='search'] {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
-webkit-appearance: textfield; }
li,
ol,
ul,
dl,
dt,
dd {
list-style: none;
}
b,
em,
h1,
h2,
h3,
h4,
h5,
h6,
i,
th,
td {
font-weight: 400;
font-style: normal;
}
/**
* 1. Remove the gray background on active links in IE 10.
* 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
*/
a {
background-color: transparent;
/* 1 */
-webkit-text-decoration-skip: objects;
/* 2 */
}
/**
* Remove the outline on focused links when they are also active or hovered
* in all browsers (opinionated).
*/
a {
color: #333;
}
a:active,
a:hover {
outline-width: 0;
color: #333;
}
button,
input,
select,
textarea,
img {
outline: 0;
color: #000;
font-size: 100%;
background-color: transparent;
}
button,
input[type='button'],
input[type='password'],
input[type='submit'],
input[type='text'],
textarea {
-webkit-appearance: none;
-webkit-appearance: button;
/* 2 */
}
a,
a:visited {
text-decoration: none;
/*color: #5c5658*/
}
img,
video {
vertical-align: middle;
border: 0;
border-style: none;
}
/**
pc
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type='button']::-moz-focus-inner,
[type='reset']::-moz-focus-inner,
[type='submit']::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* 1. Add the correct box sizing in IE 10-.
* 2. Remove the padding in IE 10-.
*/
[type='checkbox'],
[type='radio'] {
box-sizing: border-box;
/* 1 */
padding: 0;
/* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type='number']::-webkit-inner-spin-button,
[type='number']::-webkit-outer-spin-button {
height: auto;
}
*,
*:before,
*:after {
box-sizing: border-box;
margin: 0;
}
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
mode="horizontal" mode="horizontal"
style="--el-menu-horizontal-height:48px" style="--el-menu-horizontal-height:48px"
@select="menuSelect"> @select="menuSelect">
<el-menu-item index="/marketplace/ai">AI应用市场</el-menu-item>
<el-sub-menu index="computingResource"> <el-sub-menu index="computingResource">
<template #title>计算资源</template> <template #title>计算资源</template>
<el-menu-item <el-menu-item
......
...@@ -18,7 +18,10 @@ const whiteList = [ ...@@ -18,7 +18,10 @@ const whiteList = [
'/componentServices/componentServicesList', '/componentServices/componentServicesList',
'/partnership/partnershipList', '/partnership/partnershipList',
'/information/informationDetail', '/information/informationDetail',
'/information/informationList' '/information/informationList',
'/marketplace/ai',
'/marketplace/ai/detail',
'/marketplace/ai/orderConfirm',
] ]
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
......
...@@ -227,6 +227,30 @@ export const constantRoutes = [ ...@@ -227,6 +227,30 @@ export const constantRoutes = [
meta: {title: '审核认证', icon: 'order'} meta: {title: '审核认证', icon: 'order'}
} }
] ]
},
{
path: '/marketplace',
component: UserLayout,
children: [
{
path: 'ai',
component: () => import('@/views/marketplace/AIMarketplace.vue'),
name: 'AIMarketplace',
meta: { title: 'AI应用市场' }
},
{
path: 'ai/detail',
component: () => import('@/views/marketplace/AIMarketplaceDetail.vue'),
name: 'AIMarketplaceDetail',
meta: { title: 'API详情' }
},
{
path: 'orderConfirm',
component: () => import('@/views/marketplace/OrderConfirm.vue'),
name: 'OrderConfirm',
meta: { title: '订单确认' }
}
]
} }
] ]
......
<template>
<div class="app-container">
<!-- 头部搜索区域 -->
<div class="search-section">
<div class="custom-main-w">
<h1 class="page-title">API应用市场</h1>
<h2 class="page-subtitle">一站式API管理、配置、调用、维护</h2>
<div class="search-box">
<el-input
v-model="searchQuery"
placeholder="可输入关键词搜索"
@keyup.enter="handleSearch"
clearable
>
<template #append>
<el-button type="primary" @click="handleSearch">
<el-icon style="margin-right: 10px">
<Search />
</el-icon>
立即搜索
</el-button>
</template>
</el-input>
</div>
</div>
</div>
<!-- API分类标签 -->
<div class="category-tabs-wrapper">
<div class="custom-main-w">
<el-tabs
v-model="activeCategory"
class="category-tabs"
@tab-change="handleCategoryChange"
>
<el-tab-pane label="全部" name="" />
<el-tab-pane
v-for="category in categoryList"
:key="category.id"
:label="category.name"
:name="String(category.id)"
/>
</el-tabs>
</div>
</div>
<!-- API列表区域 -->
<div class="api-list custom-main-w">
<div v-if="loading" class="loading-container">
<el-skeleton :rows="3" animated />
</div>
<el-row :gutter="50" v-else-if="apiList.length > 0">
<el-col :span="8" v-for="api in apiList" :key="api.id">
<APICard
:id="api.id"
:coverImage="api.coverImage"
:priceInfo="api.priceInfo"
:title="api.name"
:description="api.description"
:tag="getAppTag(api)"
/>
</el-col>
</el-row>
<div v-else class="empty-container">
<el-empty description="暂无应用数据" />
</div>
</div>
<!-- 分页组件 -->
<div class="pagination-container custom-main-w" v-if="total > 0">
<el-pagination
v-model="currentPage"
:page-size="pageSize"
:page-sizes="[9, 18, 27, 36]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</template>
<script>
import { ElMessage } from 'element-plus';
import { Search } from '@element-plus/icons-vue';
import APICard from './components/APICard.vue';
import { getAppInfoList, getAppCategoryList } from '@/api/marketplace';
export default {
name: 'Marketplace',
components: {
APICard,
Search
},
data() {
return {
searchQuery: '',
activeCategory: '',
apiList: [],
categoryList: [],
loading: false,
currentPage: 1,
pageSize: 9,
total: 0,
_searchTimer: null
};
},
methods: {
async getCategoryList() {
try {
const response = await getAppCategoryList();
if (response.code === 0) {
this.categoryList = response.data || [];
} else {
ElMessage.error(response.msg || '获取分类列表失败');
}
} catch (error) {
console.error('获取分类列表失败:', error);
ElMessage.error('获取分类列表失败');
}
},
async getAppList() {
try {
this.loading = true;
const params = {
pageNo: this.currentPage,
pageSize: this.pageSize,
// activeCategory 为空(即选择了全部)时,不传 categoryId
categoryId: this.activeCategory
? parseInt(this.activeCategory, 10)
: undefined,
searchQuery: this.searchQuery || undefined
};
const response = await getAppInfoList(params);
if (response.code === 0) {
this.apiList = response.data.list || [];
this.total = response.data.total || 0;
} else {
ElMessage.error(response.msg || '获取应用列表失败');
}
} catch (error) {
console.error('获取应用列表失败:', error);
ElMessage.error('获取应用列表失败');
} finally {
this.loading = false;
}
},
handleSearch() {
this.currentPage = 1;
this.getAppList();
},
handleCategoryChange() {
this.currentPage = 1;
this.getAppList();
},
handleSizeChange(val) {
this.pageSize = val;
this.currentPage = 1;
this.getAppList();
},
handleCurrentChange(val) {
this.currentPage = val;
this.getAppList();
},
getAppImage() {
return ``;
},
getAppTag(api) {
const m = this.categoryList.find(c => c.id === api.categoryId);
return m ? m.name : '';
}
},
watch: {
searchQuery(newVal) {
if (this._searchTimer) clearTimeout(this._searchTimer);
this._searchTimer = setTimeout(() => {
if (newVal === '') this.handleSearch();
}, 500);
}
},
mounted() {
this.getCategoryList();
this.getAppList();
}
};
</script>
<style scoped lang="scss">
.search-section {
text-align: center;
padding: 170px 0 150px;
background: linear-gradient(
180deg,
rgba(225, 237, 255, 1) 0%,
rgba(255, 255, 255, 0) 100%
);
}
.page-title {
font-size: 40px;
margin-bottom: 16px;
color: #333;
}
.page-subtitle {
font-size: 26px;
color: #6c6c6c;
margin-bottom: 24px;
}
.search-box {
max-width: 800px;
margin: 30px auto 0;
:deep(.el-input__wrapper) {
padding: 15px 11px;
}
:deep(.el-input-group__append) {
color: #fff;
background-color: var(--el-color-primary) !important;
}
}
.api-list {
margin-bottom: 60px;
min-height: 400px;
}
.loading-container {
padding: 40px 0;
}
.empty-container {
padding: 60px 0;
text-align: center;
}
.pagination-container {
display: flex;
justify-content: center;
margin-bottom: 60px;
padding: 20px 0;
}
// 分类标签
// .category-title {
// position: absolute;
// left: -60px;
// color: #333;
// font-size: 24px;
// line-height: 60px;
// top: 0;
// font-weight: bold;
// }
.category-tabs-wrapper {
position: relative;
margin-bottom: 70px;
padding: 35px 0;
background-color: #fff;
.category-tabs {
// padding-left: 100px;
}
:deep(.el-tabs__header) {
margin: 0;
}
:deep(.el-tabs__nav-wrap) {
max-width: 1440px;
min-width: 1200px;
margin: 0 auto;
}
:deep(.el-tabs__nav-wrap::after) {
content: none !important;
}
:deep(.el-tabs__nav) {
width: 100%;
}
:deep(.el-tabs__item) {
color: #333;
font-size: 20px;
margin-right: 20px;
padding: 22px 24px !important;
font-weight: 400;
justify-content: flex-start;
}
:deep(.el-tabs__item.is-active) {
color: #fff;
font-weight: bold;
position: relative;
border-radius: 3px;
background-color: var(--el-color-primary) !important;
}
:deep(.el-tabs__active-bar) {
background-color: transparent;
}
}
</style>
<template>
<div class="custom-wrapper order-confirm-container">
<div class="com-breadcrumb-1">
<div class="custom-main-w">
<el-breadcrumb separator=">">
</el-breadcrumb>
</div>
</div>
<div class="custom-main-w order-main">
<h2 class="order-title">确认订单</h2>
<div class="order-info-list">
<!-- <div class="order-info-item">-->
<!-- <span class="label">应付金额:</span><span class="amount">¥1</span>-->
<!-- </div>-->
<div class="order-info-item">
<span class="label">套餐金额:</span>{{orderInfo.price}}
</div>
<div class="order-info-item">
<span class="label">有效期:</span>{{orderInfo.validDays}}
</div>
<div class="order-info-item">
<span class="label">调用量:</span>{{orderInfo.times}}
</div>
<div class="order-info-item">
<span class="label">购买数量:</span>{{orderInfo.quantity}}
</div>
<div class="order-info-item order-agreement">
<el-checkbox v-model="checked"
>同意并阅读《API服务购买协议》,并确保合法使用此数据。</el-checkbox
>
</div>
</div>
<div class="order-pay-btns">
<el-button type="success">立即支付</el-button>
</div>
<div class="order-tips">
<div class="tips-title">温馨提示:</div>
<ul>
<li>
1、一个接口是一组接入点,每个接入点有自己的计费率;资源包过期或用完,会立即失效;
</li>
<li>2、可购买多个资源包,会自动递延生效联使用</li>
<li>3、如需要更多的请求次数、更优惠的价格请与我们联系;</li>
<li>
4、您可以联系到我们的商务进行线下充值,确认到账后我们将会为您开通相应的数据技术服务;
</li>
</ul>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
const checked = ref(false);
import { useRoute } from "vue-router";
const route = useRoute();
const orderInfo = computed(() => ({
id: route.query.id,
name: route.query.name,
price: route.query.price,
validityDays: route.query.validDays,
times: route.query.times,
quantity: route.query.quantity,
amount: (route.query.price * route.query.quantity).toFixed(2),
}));
</script>
<style scoped lang="scss">
.order-confirm-container {
min-height: 100vh;
}
.order-main {
background: #fff;
margin: 0 auto;
box-shadow: 0 2px 12px #e6f0fa;
padding: 50px 160px 100px;
}
.order-title {
text-align: center;
font-size: 28px;
font-weight: bold;
margin-bottom: 32px;
}
.order-info-list {
margin-bottom: 30px;
}
.order-info-item {
font-size: 20px;
color: #666;
margin-bottom: 18px;
.amount {
color: #f56c6c;
font-size: 22px;
font-weight: bold;
}
}
.order-info-item .label {
min-width: 90px;
font-size: 20px;
color: #333;
margin-right: 20px;
display: inline-block;
}
.order-tips {
border: 1px solid var(--el-color-primary);
border-radius: 6px;
background: #f6fbff;
padding: 18px 24px;
margin: 24px 0 35px 0;
}
.tips-title {
color: var(--el-color-primary);
font-weight: bold;
margin-bottom: 8px;
}
.order-tips ul {
font-size: 15px;
padding-left: 18px;
}
.order-agreement {
:deep .el-checkbox__label {
font-size: 16px;
}
}
.order-pay-btns {
display: flex;
align-items: center;
gap: 18px;
margin-top: 18px;
padding-top: 40px;
border-top: 1px solid var(--el-border-color);
:deep .el-button {
height: 45px;
}
}
.order-pay-btns .balance {
color: #888;
font-size: 20px;
margin-left: 4px;
}
.order-pay-btns .recharge {
color: #f56c6c;
font-size: 20px;
margin-left: 4px;
cursor: pointer;
}
</style>
<template>
<el-card class="api-card" @click="goToDetail">
<div class="card-content">
<div v-if="props.tag" class="corner-tag">{{ props.tag }}</div>
<div class="icon-wrapper">
<el-image :src="props.coverImage" fit="cover" class="icon" />
<div class="title-wrapper">
<h3 class="title">
{{ props.title }}
</h3>
<!-- <div class="tags">-->
<!-- <el-tag effect="plain">{{props.tag}}</el-tag>-->
<!-- </div>-->
</div>
</div>
<div class="info-wrapper">
<p class="description">
{{ props.description }}{{ props.description }}
</p>
<div class="price">
<span class="main_currency"> 低至 ¥</span>
<span class="amount">{{ props.priceInfo }}</span>
<span class="unit"> /次</span>
</div>
</div>
</div>
</el-card>
</template>
<script setup lang="ts">
import { useRouter } from "vue-router";
interface Props {
id?: number;
coverImage: string;
title: string;
description: string;
priceInfo: string;
tag?: string;
}
const props = defineProps<Props>();
const router = useRouter();
const goToDetail = () => {
if (props.id) {
router.push(`/marketplace/ai/detail?id=${props.id}`);
}
};
</script>
<style scoped lang="scss">
.api-card {
width: 100%;
margin-bottom: 40px;
cursor: pointer;
transition: all 0.3s;
border-radius: 10px;
}
.api-card:hover {
transform: translateY(-2px);
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
:deep(.el-card__body) {
padding: 0 !important;
}
.card-content {
padding: 20px 30px;
position: relative;
}
.corner-tag {
position: absolute;
top: 0;
right: 0;
background-color: #f56c6c;
color: white;
font-size: 12px;
padding: 6px 15px;
border-bottom-left-radius: 10px;
}
.icon-wrapper {
position: relative;
margin-bottom: 12px;
border-bottom: 1px solid #e8e8e8;
padding: 0 10px 15px;
display: flex;
.icon {
width: 70px;
height: 70px;
margin-right: 20px;
object-fit: cover;
}
}
.title-wrapper {
flex: 1;
width: calc(100% - 74px);
}
.title {
font-size: 20px;
line-height: 40px;
height: 40px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 600;
margin: 0 0 8px 0;
}
.tags {
display: flex;
gap: 10px;
.el-tag {
height: 26px;
}
}
.description {
font-size: 16px;
color: #666;
margin: 10px 0;
line-height: 25px;
height: 50px;
overflow: hidden;
width: 100%;
text-indent: 32px;
display: -webkit-box;
-webkit-line-clamp: 2; /* 限制文本块显示两行 */
-webkit-box-orient: vertical;
}
.price {
display: flex;
align-items: baseline; // 保证文字基线对齐
gap: 4px; // 控制间距
color: #f56c6c;
.main-currency {
font-size: 14px;
font-weight: 500;
}
.amount {
font-size: 22px;
font-weight: bold;
line-height: 1;
}
.unit {
font-size: 14px;
color: #999;
}
}
</style>
<template>
<div class="related-apis">
<h3 class="title">相关API</h3>
<div
class="api-item"
v-for="api in apis"
:key="api.id"
@click="goToDetail(api.id)"
>
<img class="api-icon" :src="api.image" :alt="api.name" />
<div class="api-info">
<p class="api-title">{{ api.name }}</p>
<p class="api-desc">{{ api.description }}</p>
</div>
</div>
</div>
</template>
<script setup>
import { defineProps } from "vue";
import { useRouter } from "vue-router";
const router = useRouter();
const props = defineProps({
apis: {
type: Array,
required: true,
},
});
const goToDetail = (appId) => {
router.push({ path: "/marketplace/ai/detail", query: { id: appId } });
};
</script>
<style scoped>
.related-apis {
margin-top: 32px;
}
.title {
font-size: 18px;
font-weight: 600;
margin-bottom: 16px;
}
.api-item {
display: flex;
align-items: flex-start;
gap: 16px;
margin-bottom: 18px;
cursor: pointer;
transition: all 0.2s;
padding: 8px;
border-radius: 8px;
}
.api-item:hover {
background-color: #f5f5f5;
}
.api-icon {
width: 50px;
/* height: 40px; */
border-radius: 8px;
background: #f5f5f5;
/* width: 64px; */
height: auto;
max-height: 50px;
}
.api-info {
flex: 1;
}
.api-title {
font-size: 15px;
font-weight: 500;
margin-bottom: 4px;
}
.api-desc {
font-size: 13px;
color: #888;
height: 48px;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2; /* 显示两行 */
}
</style>
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 sign in to comment