博客和组合商品接口对接

v1.3.0
于肖磊 2025-03-13 16:53:12 +08:00
parent 2b0fff4cd7
commit b0f6021915
22 changed files with 524 additions and 151 deletions

View File

@ -6,4 +6,6 @@ NODE_ENV='dev'
VITE_APP_TITLE = 'shopxo'
VITE_APP_PORT = 3000
VITE_APP_BASE_API = '/dev-api'
VITE_APP_BASE_API_URL = 'http://shopxo.com/admin.php/'
VITE_APP_BASE_API_URL = 'http://shopxo.com/admin.php/'
VITE_APP_BASE_API_PHP = '/dev-php'
VITE_APP_BASE_API_PHP_URL = 'http://shopxo.com/api.php'

20
src/api/binding.ts Normal file
View File

@ -0,0 +1,20 @@
import api_request from '@/utils/api-request';
class BlogAPI {
/** 博客自动数据 */
static getAutoList(data: any) {
return api_request({
url: `?s=plugins/index/pluginsname/binding/pluginscontrol/diybinding/pluginsaction/index.html`,
method: 'post',
data,
});
}
}
export default BlogAPI;
// 分类树结构
export interface Tree {
/** 主键 */
id: string;
}

View File

@ -1,32 +1,17 @@
import request from '@/utils/request';
import api_request from '@/utils/api-request';
class ArticleAPI {
/** 链接初始化接口 */
static getInit() {
return request({
url: `diyapi/linkinit`,
method: 'post',
});
}
/** 文章指定数据 */
static getAppointList(data: any) {
return request({
url: `diyapi/articleappointdata`,
method: 'post',
data,
});
}
/** 文章自动数据 */
class BlogAPI {
/** 博客自动数据 */
static getAutoList(data: any) {
return request({
url: `diyapi/articleautodata`,
return api_request({
url: `?s=plugins/index/pluginsname/blog/pluginscontrol/diyblog/pluginsaction/autobloglist`,
method: 'post',
data,
});
}
}
export default ArticleAPI;
export default BlogAPI;
// 分类树结构
export interface Tree {

View File

@ -16,7 +16,14 @@ class ShopAPI {
data,
});
}
// 获取多商户的数据
static getShopList(data: any) {
return request({
url: `plugins/index/pluginsname/shop/pluginscontrol/diyshop/pluginsaction/index`,
method: 'post',
data,
});
}
}
export default ShopAPI;

View File

@ -1,4 +1,5 @@
import request from '@/utils/request';
import api_request from '@/utils/api-request';
class UrlValueAPI {
/** 获取商品列表 */
@ -58,6 +59,23 @@ class UrlValueAPI {
data,
});
}
/** 博客指定数据 */
static getblogList(data: any) {
return api_request({
url: `?s=plugins/index/pluginsname/blog/pluginscontrol/diyblog/pluginsaction/index.html`,
method: 'post',
data,
});
}
/** 组合搭配指定数据 */
static getBindingList(data: any) {
return api_request({
url: `?s=plugins/index/pluginsname/binding/pluginscontrol/diybinding/pluginsaction/autobindinglist.html`,
method: 'post',
data,
});
}
}
export default UrlValueAPI;

View File

@ -1,14 +1,15 @@
<template>
<el-form-item label="读取方式">
<el-radio-group v-model="form.data_type">
<el-radio v-for="item in option_list" :key="item.value" :value="item.value">{{ item.name }}</el-radio>
</el-radio-group>
</el-form-item>
<div v-show="form.data_type === '0'" class="nav-list">
<drag-group :list="drag_list" :img-params="img_params" @onsort="data_list_sort" @remove="data_list_remove" @replace="data_list_replace"></drag-group>
<el-button class="mt-20 w" @click="add">+</el-button>
</div>
<!-- 商品 -->
<template v-if="type === 'goods'">
<el-form-item label="读取方式">
<el-radio-group v-model="form.data_type">
<el-radio v-for="item in baseList.product_list" :key="item.value" :value="item.value">{{ item.name }}</el-radio>
</el-radio-group>
</el-form-item>
<div v-show="form.data_type === '0'" class="nav-list">
<drag-group :list="drag_list" img-params="images" @onsort="data_list_sort" @remove="data_list_remove" @replace="data_list_replace"></drag-group>
<el-button class="mt-20 w" @click="add">+</el-button>
</div>
<div v-show="form.data_type === '1'" class="w h">
<el-form-item label="关键字">
<el-input v-model="keywords" placeholder="请输入商品关键字" clearable @blur="keyword_blur"></el-input>
@ -38,22 +39,14 @@
</el-form-item>
</div>
</template>
<template v-else-if="type === 'article' || type === 'blog'">
<el-form-item label="读取方式">
<el-radio-group v-model="form.data_type">
<el-radio v-for="item in baseList.data_type_list" :key="item.value" :value="item.value">{{ item.name }}</el-radio>
</el-radio-group>
</el-form-item>
<div v-show="form.data_type === '0'" class="nav-list">
<drag-group :list="drag_list" img-params="cover" @onsort="data_list_sort" @remove="data_list_remove" @replace="data_list_replace"></drag-group>
<el-button class="mtb-20 w" @click="add">+</el-button>
</div>
<!-- 文章 博客 博客选项卡-->
<template v-else-if="type === 'article' || ['blog', 'blog-tabs'].includes(type)">
<div v-show="form.data_type === '1'" class="w h">
<el-form-item label="关键字">
<el-input v-model="keywords" placeholder="请输入文章关键字" clearable @blur="keyword_blur"></el-input>
</el-form-item>
<el-form-item :label="`${ type === 'article' ? '文章' : '博客' }分类`">
<el-select v-model="form.category_ids" multiple collapse-tags filterable placeholder="请选择文章分类">
<el-select v-model="form.category_ids" multiple collapse-tags filterable :placeholder="`请选择${ type === 'article' ? '文章' : '博客' }分类`">
<template v-if="type === 'article'">
<el-option v-for="item in common_store.common.article_category" :key="item.id" :label="item.name" :value="item.id" />
</template>
@ -83,18 +76,46 @@
<el-form-item label="封面图片">
<el-switch v-model="form.is_cover" active-value="1" inactive-value="0" />
</el-form-item>
<template v-if="['blog', 'blog-tabs'].includes(type)">
<el-form-item label="是否推荐">
<el-switch v-model="form.is_recommended" active-value="1" inactive-value="0" />
</el-form-item>
<el-form-item label="是否热门">
<el-switch v-model="form.is_hot" active-value="1" inactive-value="0" />
</el-form-item>
</template>
</div>
</template>
<!-- 组合搭配 -->
<template v-else-if="type == 'binding'">
<div v-show="form.data_type === '1'" class="w h">
<el-form-item label="关键字">
<el-input v-model="keywords" placeholder="请输入组合搭配关键字" clearable @blur="keyword_blur"></el-input>
</el-form-item>
<el-form-item label="类型">
<el-select v-model="form.binding_type" multiple collapse-tags filterable placeholder="请选择类型">
<el-option v-for="item in common_store.common.brand_list" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="显示数量">
<el-input-number v-model="form.number" :min="1" :max="50" type="number" placeholder="请输入显示数量" value-on-clear="min" class="w number-show" controls-position="right"></el-input-number>
</el-form-item>
<el-form-item label="排序类型">
<el-radio-group v-model="form.order_by_type">
<el-radio v-for="item in common_store.common.brand_order_by_type_list" :key="item.index" :value="item.index">{{ item.name }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="排序规则">
<el-radio-group v-model="form.order_by_rule">
<el-radio v-for="item in common_store.common.data_order_by_rule_list" :key="item.index" :value="item.index">{{ item.name }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="是否推荐">
<el-switch v-model="form.is_recommended" active-value="1" inactive-value="0" />
</el-form-item>
</div>
</template>
<template v-else>
<el-form-item label="读取方式">
<el-radio-group v-model="form.data_type">
<el-radio v-for="item in baseList.brand_data_type_list" :key="item.value" :value="item.value">{{ item.name }}</el-radio>
</el-radio-group>
</el-form-item>
<div v-show="form.data_type === '0'" class="nav-list">
<drag-group :list="drag_list" img-params="logo" @onsort="data_list_sort" @remove="data_list_remove" @replace="data_list_replace"></drag-group>
<el-button class="mt-20 w" @click="add">+</el-button>
</div>
<div v-show="form.data_type === '1'" class="w h">
<el-form-item label="关键字">
<el-input v-model="keywords" placeholder="请输入品牌关键字" clearable @blur="keyword_blur"></el-input>
@ -146,8 +167,28 @@
const keywords = ref(props.value.keywords);
const form = ref(props.value);
const drag_list = ref(props.list);
type option = {
name: string,
value: string,
}
const option_list = ref<option[]>([]);
const img_params = ref('logo');
//
watchEffect(() => {
if (props.type === 'goods') {
option_list.value = props.baseList.product_list;
img_params.value = 'images';
} else if (props.type === 'article' || ['blog', 'blog-tabs'].includes(props.type)) {
option_list.value = props.baseList.data_type_list;
img_params.value = 'cover';
} else if (props.type === 'binding') {
option_list.value = props.baseList.data_type_list;
img_params.value = 'images';
} else {
option_list.value = props.baseList.brand_data_type_list;
img_params.value = 'logo';
}
//
keywords.value = props.value.keywords;
form.value = props.value;
//

View File

@ -0,0 +1,171 @@
<template>
<!-- 商品分类 -->
<div class="container">
<div class="flex-row jc-e gap-20 mb-20">
<el-select v-model="binding_type" class="search-w" placeholder="请选择组合搭配类型" filterable clearable @change="handle_search">
<el-option v-for="item in article_category_list" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-input v-model="search_value" placeholder="请输入搜索内容" class="search-w" @change="handle_search">
<template #suffix>
<icon name="search" size="16" color="9" class="c-pointer" @click="handle_search"></icon>
</template>
</el-input>
</div>
<div class="content">
<el-table v-loading="loading" :data="tableData" class="w" :header-cell-style="{ background: '#f7f7f7' }" row-key="id" height="438" fixed @row-click="row_click" @select="handle_select" @select-all="handle_select">
<el-table-column v-if="multiple" type="selection" width="60" />
<el-table-column v-else label="#" width="60" type="">
<template #default="scope">
<el-radio v-model="template_selection" :label="scope.$index + ''">&nbsp;</el-radio>
</template>
</el-table-column>
<el-table-column prop="id" label="ID" width="80" type="" />
<el-table-column prop="images" label="封面">
<template #default="scope">
<div class="flex-row align-c gap-10">
<image-empty v-if="scope.row.images" v-model="scope.row.images" class="img"></image-empty>
<!-- <div class="flex-1">{{ scope.row.title }}</div> -->
</div>
</template>
</el-table-column>
<el-table-column prop="title" label="标题"></el-table-column>
<el-table-column prop="describe" label="描述"></el-table-column>
<el-table-column prop="type_name" label="类型"></el-table-column>
<template #empty>
<no-data></no-data>
</template>
</el-table>
<div class="mt-10 flex-row jc-e">
<el-pagination :current-page="page" background :page-size="page_size" :pager-count="5" layout="prev, pager, next" :total="data_total" @current-change="get_list" />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import UrlValueAPI from '@/api/url-value';
import { commonStore } from '@/store';
const common_store = commonStore();
const props = defineProps({
//
reset: {
type: Boolean,
default: () => false,
},
multiple: {
type: Boolean,
default: () => false,
},
//
selectIsUrl: {
type: Boolean,
default: false,
},
});
watch(
() => props.reset,
(val) => {
if (val) {
init();
}
}
);
onMounted(() => {
init();
});
const modelValue = defineModel({ type: Object, default: {} });
const tableData = ref<pageLinkList[]>([]);
const search_value = ref('');
const loading = ref(false);
const init = () => {
template_selection.value = '';
binding_type.value = '';
search_value.value = '';
article_category_list.value = common_store.common.article_category;
get_list(1);
};
const handle_search = () => {
get_list(1);
};
const binding_type = ref('');
interface articleCategory {
id: string;
name: string;
url: string;
}
const article_category_list = ref<articleCategory[]>([]);
const template_selection = ref('');
//#region -----------------------------------------------start
//
const page = ref(1);
//
const page_size = ref(30);
//
const data_total = ref(0);
//
const get_list = (new_page: number) => {
let new_data = {
page: new_page,
keywords: search_value.value,
type: binding_type.value,
page_size: page_size.value,
};
loading.value = true;
UrlValueAPI.getBindingList(new_data).then((res: any) => {
tableData.value = res.data;
data_total.value = res.data.length - 1;
page.value = new_page;
setTimeout(() => {
loading.value = false;
}, 500);
});
};
//#region -----------------------------------------------end
const row_click = (row: any) => {
if (!props.multiple) {
const new_table_data = JSON.parse(JSON.stringify(tableData.value));
template_selection.value = new_table_data.findIndex((item: pageLinkList) => item.id == row.id).toString();
if (props.selectIsUrl) {
const page = '/pages/plugins/binding/detail/detail?id=' + row.id;
const new_row = {
id: row.id,
name: row.name || row.title || page,
page: page,
};
modelValue.value = [new_row];
} else {
modelValue.value = [row];
}
}
};
const handle_select = (selection: any) => {
if (props.selectIsUrl) {
// selection
const new_selection = selection.map((item: any) => {
const page = '/pages/plugins/binding/detail/detail?id=' + item.id;
return {
id: item.id,
name: item.name || item.title || page,
page: page,
};
});
modelValue.value = new_selection;
} else {
modelValue.value = selection;
}
};
</script>
<style lang="scss" scoped>
.container {
.search-w {
width: 22.5rem;
}
.content {
:deep(.el-table__inner-wrapper:before) {
background-color: transparent;
}
.img {
width: 3.6rem;
}
}
}
</style>

View File

@ -2,7 +2,7 @@
<!-- 商品分类 -->
<div class="container">
<div class="flex-row jc-e gap-20 mb-20">
<el-select v-model="category_ids" class="search-w" placeholder="请选择" filterable clearable @change="handle_search">
<el-select v-model="category_ids" class="search-w" placeholder="请选择博客分类" filterable clearable @change="handle_search">
<el-option v-for="item in article_category_list" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-input v-model="search_value" placeholder="请输入搜索内容" class="search-w" @change="handle_search">
@ -95,7 +95,7 @@ const template_selection = ref('');
//
const page = ref(1);
//
const page_size = ref(30);
const page_size = ref(10);
//
const data_total = ref(0);
//
@ -107,7 +107,7 @@ const get_list = (new_page: number) => {
page_size: page_size.value,
};
loading.value = true;
UrlValueAPI.getArticleList(new_data).then((res: any) => {
UrlValueAPI.getblogList(new_data).then((res: any) => {
tableData.value = res.data.data_list;
data_total.value = res.data.data_total;
page.value = res.data.page;
@ -122,7 +122,7 @@ const row_click = (row: any) => {
const new_table_data = JSON.parse(JSON.stringify(tableData.value));
template_selection.value = new_table_data.findIndex((item: pageLinkList) => item.id == row.id).toString();
if (props.selectIsUrl) {
const page = '/pages/article-detail/article-detail?id=' + row.id;
const page = '/pages/plugins/blog/detail/detail?id=' + row.id;
const new_row = {
id: row.id,
name: row.name || row.title || page,
@ -138,7 +138,7 @@ const handle_select = (selection: any) => {
if (props.selectIsUrl) {
// selection
const new_selection = selection.map((item: any) => {
const page = '/pages/article-detail/article-detail?id=' + item.id;
const page = '/pages/plugins/blog/detail/detail?id=' + item.id;
return {
id: item.id,
name: item.name || item.title || page,

View File

@ -76,8 +76,8 @@ const init = () => {
template_selection.value = '';
type.value = '';
search_value.value = '';
if (!is_obj_empty(common_store.common.plugins) && !is_obj_empty(common_store.common.plugins.coupon) && common_store.common.plugins.coupon.coupon_type_list.length > 0) {
coupon_type_list.value = common_store.common.plugins.coupon.coupon_type_list;
if (!is_obj_empty(common_store.common.plugins) && !is_obj_empty(common_store.common.plugins.coupon) && common_store.common.plugins.coupon.type_list.length > 0) {
coupon_type_list.value = common_store.common.plugins.coupon.type_list;
}
get_list(1);
};

View File

@ -54,6 +54,10 @@
<template v-else-if="link_select == 'coupon'">
<link-coupon v-model="link_value" :multiple="multiple" :reset="reset_compontent"></link-coupon>
</template>
<!-- 组合搭配 -->
<template v-else-if="link_select == 'binding'">
<link-binding v-model="link_value" :multiple="multiple" :reset="reset_compontent"></link-binding>
</template>
</div>
</div>
<template #footer>

View File

@ -24,14 +24,14 @@
<div class="flex-row align-c jc-sb">
<div class="flex-col gap-6">
<div class="flex-row align-c">
<span :style="trends_config('price_symbol', 'data')">{{ match_item.price_symbol }}</span>
<span :style="trends_config('price', 'data')">{{ match_item.price }}</span>
<span :style="trends_config('price_symbol', 'data')"></span>
<span :style="trends_config('price', 'data')">{{ match_item.estimate_price }}</span>
</div>
<div class="flex-row align-c gap-3">
<img-or-icon-or-text :value="props.value" type="data_discounts" />
<div>
<span :style="trends_config('save_price_symbol', 'data')">{{ match_item.price_symbol }}</span>
<span :style="trends_config('save_price', 'data')">{{ match_item.price }}</span>
<span :style="trends_config('save_price_symbol', 'data')"></span>
<span :style="trends_config('save_price', 'data')">{{ match_item.estimate_discount_price || 0 }}</span>
</div>
</div>
</div>
@ -49,14 +49,14 @@
<div class="flex-row align-c jc-sb">
<div class="flex-col gap-6">
<div class="flex-row align-c">
<span :style="trends_config('price_symbol', 'data')">{{ match_item.price_symbol }}</span>
<span :style="trends_config('price', 'data')">{{ match_item.price }}</span>
<span :style="trends_config('price_symbol', 'data')"></span>
<span :style="trends_config('price', 'data')">{{ match_item.estimate_price }}</span>
</div>
<div class="flex-row align-c gap-3">
<img-or-icon-or-text :value="props.value" type="data_discounts" />
<div>
<span :style="trends_config('save_price_symbol', 'data')">{{ match_item.price_symbol }}</span>
<span :style="trends_config('save_price', 'data')">{{ match_item.price }}</span>
<span :style="trends_config('save_price_symbol', 'data')"></span>
<span :style="trends_config('save_price', 'data')">{{ match_item.estimate_discount_price || 0 }}</span>
</div>
</div>
</div>
@ -70,7 +70,7 @@
<div :style="goods_content_img_style">
<div :class="outer_class" :style="onter_style">
<template v-if="!['3'].includes(theme)">
<div v-for="(item, index) in match_item.good_list" :key="index" class="re oh" :class="layout_type" :style="layout_style">
<div v-for="(item, index) in match_item.goods" :key="index" class="re oh" :class="layout_type" :style="layout_style">
<div :class="['oh w h', ['0' , '2'].includes(theme) ? 'flex-row' : 'flex-col' ]" :style="layout_img_style">
<template v-if="!isEmpty(item) && is_show('goods_img')">
<div class="oh re" :class="`flex-img${theme}`">
@ -83,14 +83,14 @@
{{ item.title }}
</div>
<div v-if="is_show('price')" class="flex-row align-c text-line-1">
<span :style="trends_config('price_symbol', 'goods')">{{ match_item.price_symbol }}</span>
<span :style="trends_config('price', 'goods')">{{ match_item.price }}</span>
<span :style="trends_config('price_symbol', 'goods')">{{ item.show_price_symbol }}</span>
<span :style="trends_config('price', 'goods')">{{ item.price }}</span>
</div>
<div v-if="is_show('save_price')" class="flex-row align-c gap-3">
<img-or-icon-or-text :value="props.value" type="goods_discounts" />
<div class="flex-1 text-line-1">
<span :style="trends_config('save_price_symbol', 'goods')">{{ match_item.price_symbol }}</span>
<span :style="trends_config('save_price', 'goods')">{{ match_item.price }}</span>
<span :style="trends_config('save_price_symbol', 'goods')">{{ item.show_price_symbol }}</span>
<span :style="trends_config('save_price', 'goods')">{{ item.discount_price || 0 }}</span>
</div>
</div>
</div>
@ -100,7 +100,7 @@
</template>
<template v-else>
<swiper :key="carouselKey" class="w flex" direction="horizontal" :loop="true" :autoplay="autoplay" :slides-per-view="form.carousel_col" :slides-per-group="slides_per_group" :allow-touch-move="false" :space-between="goods_content_outer_spacing" :pause-on-mouse-enter="true" :modules="modules">
<swiper-slide v-for="(item, index) in match_item.good_list" :key="index">
<swiper-slide v-for="(item, index) in match_item.goods" :key="index">
<div :class="layout_type" :style="layout_style">
<div :class="['oh w h', ['0', '2'].includes(theme) ? 'flex-row' : 'flex-col' ]" :style="layout_img_style">
<template v-if="!isEmpty(item) && is_show('goods_img')">
@ -114,14 +114,14 @@
{{ item.title }}
</div>
<div v-if="is_show('price')" class="flex-row align-c text-line-1">
<span :style="trends_config('price_symbol', 'goods')">{{ match_item.price_symbol }}</span>
<span :style="trends_config('price', 'goods')">{{ match_item.price }}</span>
<span :style="trends_config('price_symbol', 'goods')">{{ item.show_price_symbol }}</span>
<span :style="trends_config('price', 'goods')">{{ item.price }}</span>
</div>
<div v-if="is_show('save_price')" class="flex-row align-c gap-3">
<img-or-icon-or-text :value="props.value" type="goods_discounts" />
<div class="flex-1 text-line-1">
<span :style="trends_config('save_price_symbol', 'goods')">{{ match_item.price_symbol }}</span>
<span :style="trends_config('save_price', 'goods')">{{ match_item.price }}</span>
<span :style="trends_config('save_price_symbol', 'goods')">{{ item.show_price_symbol }}</span>
<span :style="trends_config('save_price', 'goods')">{{ item.discount_price || 0 }}</span>
</div>
</div>
</div>
@ -137,7 +137,7 @@
<!-- 底部展开收起按钮区域 -->
<div :style="bottom_button_style">
<div class="flex-row align-c jc-sb" :style="bottom_button_img_style">
<span :style="trends_config('button', 'bottom')">{{ form.is_default_show_goods == '1' ? '收起' : '展开'}}组合商品</span>
<span :style="trends_config('button', 'bottom')">{{ form.is_default_show_goods == '1' ? '收起' : '展开'}}{{ match_item.type_name }}商品</span>
<icon :name="form.is_default_show_goods == '1' ? 'arrow-top' : 'arrow-bottom'" :color="new_style.bottom_button_icon_color" :size="new_style.bottom_button_icon_size + ''"></icon>
</div>
</div>
@ -153,7 +153,7 @@
import { common_styles_computer, common_img_computer, get_math, gradient_handle, margin_computer, border_computer, box_shadow_computer, radius_computer, background_computer, padding_computer } from '@/utils';
import { old_margin } from "@/utils/common";
import { isEmpty, cloneDeep } from 'lodash';
import ShopAPI from '@/api/shop';
import BindingAPI from '@/api/binding';
import { Swiper, SwiperSlide } from 'swiper/vue';
import { Autoplay } from 'swiper/modules';
const modules = [Autoplay];
@ -177,57 +177,51 @@ const new_style = computed(() => props.value?.style || {});
//#region
type goods_list = {
title: string,
min_original_price: string,
discount_price: string,
show_original_price_symbol: string,
min_price: string,
price: string,
show_price_symbol: string,
sales_count: string,
images: string,
}
type data_list = {
title: string,
price_symbol: string;
price: string,
save_price_symbol: string,
save_pice: string,
estimate_price: string,
estimate_discount_price: string,
images: string,
type_name: string;
new_cover: string[],
good_list: goods_list[]
goods: goods_list[]
}
const default_list = {
title: '测试组合搭配标题',
price_symbol: '¥',
price: '8970.00-9200.00',
save_price_symbol: '¥',
save_pice: '8970.00-9200.00',
estimate_price: '8970.00-9200.00',
estimate_discount_price: '8970.00-9200.00',
type_name: '组合',
images: '',
new_cover: [],
good_list: [
goods: [
{
title: '测试商品标题',
min_original_price: '41.2',
discount_price: '41.2',
show_original_price_symbol: '¥',
min_price: '51',
price: '51',
show_price_symbol: '¥',
sales_count: '1000',
images: '',
},
{
title: '测试商品标题',
min_original_price: '41.2',
discount_price: '41.2',
show_original_price_symbol: '¥',
min_price: '51',
price: '51',
show_price_symbol: '¥',
sales_count: '1000',
images: '',
},
{
title: '测试商品标题',
min_original_price: '41.2',
discount_price: '41.2',
show_original_price_symbol: '¥',
min_price: '51',
price: '51',
show_price_symbol: '¥',
sales_count: '1000',
images: '',
}
]
@ -250,30 +244,30 @@ onMounted(() => {
}
});
const get_products = () => {
const { category_ids, brand_ids, number, order_by_type, order_by_rule, keywords } = form.value;
const get_binding = () => {
const { keywords, binding_type, number, order_by_type, order_by_rule, is_home_show } = form.value;
const params = {
goods_keywords: keywords,
goods_category_ids: category_ids,
goods_brand_ids: brand_ids,
goods_order_by_type: order_by_type,
goods_order_by_rule: order_by_rule,
goods_number: number,
binding_keywords: keywords,
binding_type: binding_type,
binding_order_by_type: order_by_type,
binding_order_by_rule: order_by_rule,
binding_number: number,
binding_is_home_show: is_home_show,
};
list.value = Array(4).fill(default_list);
//
// ShopAPI.getShopLists(params).then((res: any) => {
// if (!isEmpty(res.data)) {
// list.value = res.data;
// } else {
// list.value = Array(4).fill(default_list);
// }
// });
//
BindingAPI.getAutoList(params).then((res: any) => {
if (!isEmpty(res.data.data_list)) {
list.value = res.data.data_list;
} else {
list.value = Array(4).fill(default_list);
}
});
};
//
const watch_data = computed(() => {
const { category_ids, brand_ids, number, order_by_type, order_by_rule, data_type, data_list, keywords } = form.value;
return { category_ids: category_ids, brand_ids: brand_ids, number: number, order_by_type: order_by_type, order_by_rule: order_by_rule, data_type: data_type, data_list: data_list, keyword: keywords };
const { keywords, binding_type, number, order_by_type, order_by_rule, is_home_show, data_type, data_list } = form.value;
return { keywords, binding_type, number, order_by_type, order_by_rule, is_home_show, data_type, data_list };
})
// ,
watch(() => watch_data.value, (val, oldVal) => {
@ -290,7 +284,7 @@ watch(() => watch_data.value, (val, oldVal) => {
list.value = Array(4).fill(default_list);
}
} else {
get_products();
get_binding();
}
}
}, { deep: true });

View File

@ -56,7 +56,7 @@
</el-tabs>
</card-container>
</el-form>
<url-value-dialog v-model:dialog-visible="url_value_dialog_visible" :type="['realstore']" :multiple="url_value_multiple_bool" @update:model-value="url_value_dialog_call_back"></url-value-dialog>
<url-value-dialog v-model:dialog-visible="url_value_dialog_visible" :type="['binding']" :multiple="url_value_multiple_bool" @update:model-value="url_value_dialog_call_back"></url-value-dialog>
</div>
</template>
<script setup lang="ts">

View File

@ -157,7 +157,7 @@ const new_content = computed(() => props.value?.content || {});
const new_style = computed(() => props.value?.style || {});
//
const get_auto_data_list = async () => {
const { category_ids, number, order_by_type, order_by_rule, is_cover, keywords } = new_content.value;
const { category_ids, number, order_by_type, order_by_rule, is_cover, keywords, is_recommended, is_hot } = new_content.value;
const new_data = {
blog_keywords: keywords,
blog_category_ids: category_ids.join(','),
@ -165,6 +165,8 @@ const get_auto_data_list = async () => {
blog_order_by_rule: order_by_rule,
blog_number: number,
blog_is_cover: is_cover,
blog_is_recommended: is_recommended,
blog_is_hot: is_hot,
};
const res = await blogAPI.getAutoList(new_data);
new_content.value.data_auto_list = [];
@ -207,8 +209,8 @@ onMounted(() => {
});
// new_content
const data_list_computer = computed(() => {
const { data_type, category_ids, number, order_by_type, order_by_rule, is_cover, data_list, keywords } = new_content.value;
return { data_type, category_ids, number, order_by_type, order_by_rule, is_cover, data_list, keywords };
const { data_type, category_ids, number, order_by_type, order_by_rule, is_cover, data_list, keywords, is_recommended, is_hot } = new_content.value;
return { data_type, category_ids, number, order_by_type, order_by_rule, is_cover, data_list, keywords, is_recommended, is_hot };
});
// new_content
watch(

View File

@ -32,7 +32,7 @@
<template v-else>
<el-form-item label="类型">
<el-select v-model="form.type" multiple collapse-tags filterable placeholder="请选择优惠券类型">
<el-option v-for="item in base_list.coupon_type_list" :key="item.value" :label="item.name" :value="item.value" />
<el-option v-for="item in base_list.type_list" :key="item.value" :label="item.name" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="展示数量">
@ -89,7 +89,7 @@ const base_list = reactive({
name: `风格${index + 1}`,
url: `${new_url.value}theme-${index + 1}.png`,
})),
coupon_type_list: [] as select_2[],
type_list: [] as select_2[],
});
onMounted(async () => {
//
@ -100,8 +100,8 @@ onMounted(async () => {
});
nextTick(() => {
// common_store.common.article_category
if (!is_obj_empty(common_store.common.plugins) && !is_obj_empty(common_store.common.plugins.coupon) && common_store.common.plugins.coupon.coupon_type_list.length > 0) {
base_list.coupon_type_list = common_store.common.plugins.coupon.coupon_type_list;
if (!is_obj_empty(common_store.common.plugins) && !is_obj_empty(common_store.common.plugins.coupon) && common_store.common.plugins.coupon.type_list.length > 0) {
base_list.type_list = common_store.common.plugins.coupon.type_list;
}
});
});

View File

@ -16,7 +16,9 @@
<div :style="topic_style" class="pl-6 pr-6 radius-sm">{{ form.title }}</div>
</template>
<el-carousel :key="carouselKey" class="flex-1" indicator-position="none" :interval="interval_time" arrow="never" :direction="direction_type" :autoplay="true">
<el-carousel-item v-for="(item, index) in notice_list" :key="index" class="text-line-1" :style="`${news_style} color: ${new_style.news_color}`">{{ item.notice_title }}</el-carousel-item>
<el-carousel-item v-for="(item, index) in notice_list" :key="index" class="flex-row align-c">
<div class="text-line-1" :style="`${news_style} color: ${new_style.news_color}`">{{ item.notice_title }}</div>
</el-carousel-item>
</el-carousel>
<div v-if="form.is_right_button == '1'" :style="`color: ${new_style.right_button_color}; font-size: ${ new_style.right_button_size }px`">{{ form.right_title }}<icon name="arrow-right" :color="new_style.right_button_color || '#999'"></icon></div>
</div>

View File

@ -25,7 +25,7 @@
<upload v-model="form.img_src" v-model:icon-value="form.icon_class" is-icon :limit="1" size="50"></upload>
</el-form-item>
<el-form-item v-if="is_text" label="标题文字">
<el-input v-model="form.title" placeholder="请输入标题" maxlength="4" show-word-limit clearable></el-input>
<el-input v-model="form.title" placeholder="请输入标题" maxlength="30" clearable></el-input>
</el-form-item>
<template v-if="!is_card">
<el-form-item label="滚动方式">
@ -61,7 +61,7 @@
<template #default="scoped">
<div class="flex-col align-c jc-s gap-20 flex-1">
<el-form-item label="标题" class="w mb-0" label-width="40">
<el-input v-model="scoped.row.notice_title" placeholder="请输入标题" maxlength="20" show-word-limit clearable></el-input>
<el-input v-model="scoped.row.notice_title" placeholder="请输入标题" maxlength="100" clearable></el-input>
</el-form-item>
<el-form-item label="链接" class="w mb-0" label-width="40">
<url-value v-model="scoped.row.notice_link"></url-value>

View File

@ -171,13 +171,13 @@ const get_products = () => {
};
list.value = Array(4).fill(default_list);
//
// ShopAPI.getShopLists(params).then((res: any) => {
// if (!isEmpty(res.data)) {
// list.value = res.data;
// } else {
// list.value = Array(4).fill(default_list);
// }
// });
ShopAPI.getShopList(params).then((res: any) => {
if (!isEmpty(res.data)) {
list.value = res.data;
} else {
list.value = Array(4).fill(default_list);
}
});
};
//
const watch_data = computed(() => {

View File

@ -58,7 +58,15 @@ interface defaultRealstore {
carousel_col: number;
goods_save_price: string;
goods_save_price_row: string;
data_type: string;
data_list: string[];
data_auto_list: string[],
keywords: string,
binding_type: string[],
number: number,
order_by_type: string,
order_by_rule: string,
is_home_show: string,
is_goods_show: string[];
is_default_show_goods: string;
is_details_show: string;
@ -169,7 +177,15 @@ const defaultRealstore: defaultRealstore = {
carousel_col: 1,
goods_save_price: '1',
goods_save_price_row: '1',
data_type: '0',
data_list: [],
data_auto_list: [],
keywords: '',
binding_type: [],
number: 4,
order_by_type: '',
order_by_rule: '',
is_home_show: '',
is_goods_show: ['title', 'goods_img', 'price', 'save_price'],
is_default_show_goods: '1',
is_details_show: '1',

View File

@ -27,6 +27,8 @@ interface DefaultBlogList {
field_show: string[];
field_desc_row: string,
is_cover: string;
is_recommended: string;
is_hot: string;
seckill_subscript_show: string;
subscript_type: string;
subscript_img_src: uploadList[];
@ -101,6 +103,8 @@ const defaultBlogList: DefaultBlogList = {
field_show: ['0', '1', '3'],
field_desc_row: '1',
is_cover: defaultSetting.is_cover,
is_recommended: '0',
is_hot: '0',
// 角标配置
seckill_subscript_show: '0',
subscript_type: 'text',

114
src/utils/api-request.ts Normal file
View File

@ -0,0 +1,114 @@
import axios, { InternalAxiosRequestConfig, AxiosResponse } from 'axios';
import { ElMessage, ElMessageBox, type MessageHandler } from 'element-plus';
import { get_cookie } from './index';
// 提示拦截
let messageInstance: MessageHandler;
const message_error = (info: string) => {
if (messageInstance) {
messageInstance.close();
}
messageInstance = ElMessage.error({
type: 'error',
message: info,
duration: 30000,
showClose: true,
});
};
// 创建一个状态变量来跟踪是否已经弹出了退出登录的弹窗
const isLogoutModalShown = ref(true);
// 用于存储每个请求的CancelToken
const pendingRequests = new Map();
// 不需要认证的接口
const release_url = ['diyapi/attachmentupload'];
// 创建 axios 实例
const index = window.location.href.lastIndexOf('?s=');
const new_data = window.location.href.substring(0, index);
const new_index = new_data.lastIndexOf('/');
const pro_url = window.location.href.substring(0, new_index);
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API_PHP == '/dev-php' ? import.meta.env.VITE_APP_BASE_API_PHP : pro_url + '/api.php',
timeout: 60000,
headers: { 'Content-Type': 'application/json;charset=utf-8', 'X-Requested-With': 'XMLHttpRequest' },
});
/** @ts-ignore */
// 请求拦截器
service.interceptors.request.use(
async (config: InternalAxiosRequestConfig) => {
// 如果是本地则使用静态tonken如果是线上则使用cookie的token
const cookie = get_cookie('admin_info') || '';
const symbol = config.url?.includes('?') ? '&' : '?';
if (import.meta.env.VITE_APP_BASE_API_PHP == '/dev-php') {
let temp_data = await import(import.meta.env.VITE_APP_BASE_API_PHP == '/dev-php' ? '../../temp.d' : '../../temp_pro.d');
config.url = config.url + symbol + 'token=' + temp_data.default.temp_token;
} else {
if (cookie && cookie !== null && cookie !== 'null') {
config.url = config.url + '&token=' + (JSON.parse(cookie) !== 'null' ? JSON.parse(cookie)?.token : '');
}
}
// 判断是否是包含不需要认证的接口
const release_list = release_url.filter(item => config.url?.includes(item));
if (release_list.length === 0) {
// 检查是否有相同请求正在进行,如果有则取消, 防止重复请求导致返回数据有误
if (pendingRequests.has(config.url)) {
const cancelToken = pendingRequests.get(config.url);
cancelToken.cancel('canceled');
pendingRequests.delete(config.url);
}
// 创建一个新的 CancelToken
const source = axios.CancelToken.source();
config.cancelToken = source.token;
pendingRequests.set(config.url, source);
}
return config;
},
(error: any) => {
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
// 请求完成后从pendingRequests中移除
pendingRequests.delete(response.config.url);
const { code, msg, message, data } = response.data;
if (code == 0) {
return response.data;
} else if (code == -400) {
if (isLogoutModalShown.value) {
isLogoutModalShown.value = false;
ElMessageBox.alert(msg, '温馨提示', {
confirmButtonText: '确定',
showClose: false,
type: 'warning',
}).then(() => {
localStorage.clear(); // @vueuse/core 自动导入
window.location.href = data.logout;
});
}
} else {
message_error(msg || message || '系统出错');
return Promise.reject('Error');
// return Promise.reject(new Error(msg || 'Error'));
}
},
(error: any) => {
if (error.response && error.response.data) {
const { msg, message } = error.response.data;
message_error(msg || message || '系统出错');
} else if (error.message == 'canceled') {
console.log('请求已取消');
} else {
message_error(error.message);
}
return Promise.reject(error.message);
}
);
// 导出 axios 实例
export default service;

View File

@ -527,19 +527,6 @@ onMounted(async () => {
const interval = setInterval(() => {
//
if (common_store.common.module_list.length > 0) {
common_store.common.module_list.forEach((item) => {
if (item.key == 'plugins') {
const data = [
{ key: "blog", name: "博客" },
{ key: "blog-tabs", name: "博客选项卡" },
{ key: "binding", name: "组合搭配" },
{ key: "realstore", name: "多门店" },
{ key: "shop", name: "多商户" },
{ key: "salerecords", name: "销售记录" },
]
item.data = item.data.concat(data);
}
});
components.value = common_store.common.module_list;
clearInterval(interval);
}

View File

@ -39,6 +39,12 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
changeOrigin: true,
rewrite: (path) => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), ''), // 替换 /dev-api 为 target 接口地址
},
// 反向代理解决跨域
[env.VITE_APP_BASE_API_PHP]: {
target: env.VITE_APP_BASE_API_PHP_URL, // 接口地址
changeOrigin: true,
rewrite: (path) => path.replace(new RegExp('^' + env.VITE_APP_BASE_API_PHP), ''), // 替换 /dev-api 为 target 接口地址
},
},
},
plugins: [