This commit is contained in:
吴昊天 2026-09-24 10:02:16 +08:00
commit 639b4d1800
204 changed files with 269 additions and 39186 deletions

View File

@ -527,6 +527,12 @@ function del(row, tit, num) {
### 13.2 巡检整改记录(2026-09-09)
**已修复**(2026-09-23,全站页面标题头补充描述):
- 全站带标题头页面统一补充 `description` 说明文字:`<pages-header>` 用法直接加 `description` 属性(44 处,含存量模型管理、小程序一键上传共 46 处),直写 `.i-layout-page-header.header-title` 的 16 处改为「`.header-main` 包裹标题 + `.header-description`(前置 `<el-icon>` + `InfoFilled`)」结构,与组件渲染结果一致。仅 setApp 占位页(空壳)与 SchemaPage DetailTemplate(标题由后端 config 下发)未加。
- `.header-main` / `.header-description` / `.header-description-icon` 三类样式真源由 `pagesHeader/index.vue` scoped 迁移至 `styles/style.scss` 全局(组件内定义已删),直写页面直接复用全局类与全局注册的 `InfoFilled` 图标,页面无需自带样式。
- 注意 `header-title` 是 flex space-between,直写时描述必须与标题同包在 `.header-main` 内,否则会被推到容器最右侧。
**已修复**(2026-09-23,表单数字输入框宽度与对齐):
- 「添加优惠券」数字框(面值/天数/领取数量等)挂了为文本框设计的 `content_width`(414px),且 EP 数字默认居中显示。同类问题全站共 6 文件 17 处(优惠券 ×5、拼团 ×7、砍价 ×3、秒杀 ×1、渠道码 ×1)。

View File

@ -5,7 +5,7 @@
direction="rtl"
size="720"
:wrapper-closable="false"
custom-class="schema-drawer"
class="schema-drawer"
@closed="emit('closed')"
>
<div class="schema-drawer__content">

View File

@ -822,9 +822,6 @@ defineExpose({ modals, handleCheckChange });
</script>
<style lang="scss" scoped>
:deep(.el-dialog__body) {
}
:deep(.el-tree-node__content) {
height: 30px;
}

View File

@ -51,24 +51,7 @@ function goBack() {
.header-title {
justify-content: space-between;
}
/* 标题行:返回钮 + 标题 + 描述,基线对齐(描述字号小于标题,用 baseline 不用 flex-end) */
.header-main {
display: flex;
align-items: baseline;
min-width: 0;
gap: 16px;
}
.header-description {
color: var(--prev-color-text-secondary);
font-size: 12px;
line-height: 18px;
}
.header-description-icon {
font-size: 14px;
color: var(--prev-color-primary);
vertical-align: -2px;
margin-right: 2px;
}
/* .header-main / .header-description / .header-description-icon 全局定义见 styles/style.scss */
.after-line {
display: inline-block;
position: relative;

View File

@ -15,7 +15,7 @@
<!-- <rightBtn :activeIndex="activeIndex" :configObj="configObj"></rightBtn> -->
<el-dialog
custom-class="custom-design-dialog"
class="custom-design-dialog"
v-model="modals"
width="100%"
fullscreen

View File

@ -1,7 +1,13 @@
<template>
<div>
<div class="i-layout-page-header header-title">
<span class="ivu-page-header-title mr20">{{ $route.meta.title }}</span>
<div class="header-main">
<span class="ivu-page-header-title mr20">{{ $route.meta.title }}</span>
<span class="header-description">
<el-icon class="header-description-icon"><InfoFilled /></el-icon>
装修页面与模板列表,支持新增页面、导入模板、编辑设计与设为默认。
</span>
</div>
<div>
<div style="float: right" v-if="cardShow == 1 || cardShow == 2">
<el-button class="bnt" type="primary" v-db-click @click="submit" :loading="loadingExist">保存</el-button>

View File

@ -1,6 +1,11 @@
<template>
<div>
<pages-header ref="pageHeader" title="页面设计" :backUrl="routePre + '/setting/pages/devise/0'"></pages-header>
<pages-header
ref="pageHeader"
title="页面设计"
:backUrl="routePre + '/setting/pages/devise/0'"
description="配置页面组件与属性,实时预览移动端效果,保存后同步到前台。"
></pages-header>
<el-card :bordered="false" shadow="never" class="ivu-mt mt15">
<div class="flex-wrapper">
<!-- :src="iframeUrl" -->

View File

@ -1,7 +1,13 @@
<template>
<div>
<div class="i-layout-page-header header-title">
<span class="ivu-page-header-title mr20">{{ $route.meta.title }}</span>
<div class="header-main">
<span class="ivu-page-header-title mr20">{{ $route.meta.title }}</span>
<span class="header-description">
<el-icon class="header-description-icon"><InfoFilled /></el-icon>
切换移动端商城主题色系,保存后前台立即生效。
</span>
</div>
<div>
<div style="float: right">
<el-button class="bnt" type="primary" v-db-click @click="submit">保存</el-button>

View File

@ -7,7 +7,6 @@
:show-close="false"
append-to-body
:modal="true"
:modal-append-to-body="true"
:lock-scroll="true"
@close="handleClose"
>

View File

@ -68,11 +68,10 @@
v-model="styleDialogVisible"
width="1188px"
top="9vh"
custom-class="theme-dialog"
class="theme-dialog"
:show-close="false"
append-to-body
:modal="true"
:modal-append-to-body="true"
:lock-scroll="true"
@close="handleClose"
>
@ -576,34 +575,13 @@ function saveAndClose() {
}
}
}
// 弹窗样式
:deep(.theme-dialog) {
border-radius: 8px;
overflow: hidden;
margin-top: 50px !important;
margin-bottom: 50px !important;
width: calc(100vw - 140px) !important;
height: calc(100vh - 100px) !important;
margin-left: 70px !important;
margin-right: 70px !important;
.el-dialog__header {
border-bottom: 1px solid #f0f0f0;
padding: 0px 20px !important;
}
}
}
:deep(.el-dialog__body) {
max-height: max-content;
padding: 0px !important;
overflow-y: auto;
}
.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 15px 24px;
.dialog-header-tabs {
background: #fff;
@ -634,7 +612,6 @@ function saveAndClose() {
.dialog-content {
display: flex;
flex-direction: column;
height: calc(100% - 20px);
background-color: #f5f7fa;
.content-wrapper {
@ -666,6 +643,7 @@ function saveAndClose() {
overflow-y: auto;
padding-right: 5px;
flex: 1;
max-height: calc(100vh - 340px);
.theme-item {
background: #fff;
@ -1033,3 +1011,24 @@ function saveAndClose() {
}
}
</style>
<style lang="scss">
/* 弹窗壳层覆盖(非 scoped):append-to-body 弹窗 teleport 到 body 后
脱离组件样式作用域,scoped :deep 无法命中,必须用业务前缀全局类。
高度随内容自适应,滚动交给内部网格/详情区,避免内容少时大片空白 */
.theme-dialog {
border-radius: 6px;
overflow: hidden;
/* header 内边距归零,留白由内部 .dialog-header(15px 24px)提供 */
.el-dialog__header {
padding: 0 !important;
border-bottom: 1px solid var(--prev-border-color-lighter);
}
.el-dialog__body {
padding: 0 !important;
max-height: calc(100vh - 100px);
}
}
</style>

View File

@ -1,8 +1,12 @@
<template>
<div>
<div class="i-layout-page-header header-title">
<div class="fl_header">
<div class="header-main">
<span class="ivu-page-header-title">{{ $route.meta.title }}</span>
<span class="header-description">
<el-icon class="header-description-icon"><InfoFilled /></el-icon>
自定义移动端页面,添加后可通过链接在菜单、广告位等场景跳转使用。
</span>
</div>
</div>
<el-card :bordered="false" shadow="never" class="ivu-mt">

View File

@ -2,8 +2,12 @@
<div class="my-theme-page">
<!-- 顶部标题栏 -->
<div class="i-layout-page-header header-title">
<div class="fl_header">
<div class="header-main">
<span class="ivu-page-header-title">我的主题</span>
<span class="header-description">
<el-icon class="header-description-icon"><InfoFilled /></el-icon>
新建或导入商城主题,支持导出备份、扫码预览与一键启用。
</span>
</div>
</div>

View File

@ -87,17 +87,7 @@ service.interceptors.response.use(
case 401:
redirectToLogin();
return Promise.reject({ ...obj, msg: obj.msg || '未登录' });
case 402:
removeCookies('kefuInfo');
removeCookies('kefu_token');
removeCookies('kefu_expires_time');
removeCookies('kefu_uuid');
router.replace({ path: Setting.routePre + '/kefu' }).catch(() => {});
return Promise.reject({ msg: '未登录' });
case 403:
// 403 仅由 AdminEditorTokenMiddleware(文件编辑 token 过期)抛出,跳转文件管理登录页
// 路由名见 pages/plugin/system_file_manage/routes.js:pre('system_file_') + opendir_login
router.replace({ name: 'system_file_opendir_login' }).catch(() => {});
return Promise.reject({ msg: '没有权限' });
default:
return Promise.reject(obj || { msg: '未知错误' });

View File

@ -1,7 +1,13 @@
<template>
<div :style="bgcolors">
<div class="i-layout-page-header header-title">
<span class="ivu-page-header-title mr20">{{ $route.meta.title }}</span>
<div class="header-main">
<span class="ivu-page-header-title mr20">{{ $route.meta.title }}</span>
<span class="header-description">
<el-icon class="header-description-icon"><InfoFilled /></el-icon>
维护 APP 服务协议与隐私政策内容,右侧实时预览移动端弹窗效果。
</span>
</div>
<div style="float: right">
<el-button class="bnt" type="primary" v-db-click @click="onsubmit('formValidate')">保存</el-button>
</div>

View File

@ -4,6 +4,7 @@
ref="pageHeader"
:title="route.params.id ? '编辑文章' : '添加文章'"
:backUrl="Setting.routePre + '/cms/article/index'"
description="填写文章标题、分类、封面与正文,保存后在前台文章模块展示。"
></pages-header>
<el-card :bordered="false" shadow="never" class="mt16">
<el-form

View File

@ -1,6 +1,10 @@
<template>
<div>
<pages-header class="mb16" :title="$route.meta.title"></pages-header>
<pages-header
class="mb16"
:title="$route.meta.title"
description="配置 IM 机器人通知渠道并启停驱动,系统告警与业务消息推送经此发送。"
></pages-header>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16">
<el-table :data="list" ref="table" v-loading="loading" highlight-current-row row-key="id">
<el-table-column label="排序" width="80" prop="sort">

View File

@ -1,6 +1,10 @@
<template>
<div>
<pages-header class="mb16" :title="$route.meta.title"></pages-header>
<pages-header
class="mb16"
:title="$route.meta.title"
description="接入商品采集服务商,按支持平台启用并填写账号密钥,采集商品时自动调用。"
></pages-header>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16">
<el-table :data="list" ref="table" v-loading="loading" highlight-current-row row-key="id">
<el-table-column label="排序" width="80" prop="sort">

View File

@ -1,6 +1,10 @@
<template>
<div>
<pages-header class="mb16" :title="$route.meta.title"></pages-header>
<pages-header
class="mb16"
:title="$route.meta.title"
description="配置邮件服务器参数并启停驱动,邮箱验证与通知邮件经此发送。"
></pages-header>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16">
<el-table :data="list" ref="table" v-loading="loading" highlight-current-row row-key="id">
<el-table-column label="排序" width="80" prop="sort">

View File

@ -1,6 +1,10 @@
<template>
<div>
<pages-header class="mb16" :title="$route.meta.title"></pages-header>
<pages-header
class="mb16"
:title="$route.meta.title"
description="配置电子面单服务商账号并启停驱动,面单模板与寄件信息在电子面单设置中维护。"
></pages-header>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16">
<el-alert
title="这里仅管理电子面单服务商驱动;物流公司、打印机编号和寄件信息请到“设置 > 电子面单设置”配置。"

View File

@ -1,6 +1,10 @@
<template>
<div>
<pages-header class="mb16" :title="$route.meta.title"></pages-header>
<pages-header
class="mb16"
:title="$route.meta.title"
description="对接电子发票服务商并启停驱动,开票请求由启用驱动统一处理。"
></pages-header>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16">
<el-table :data="list" ref="table" v-loading="loading" highlight-current-row row-key="id">
<el-table-column label="排序" width="80" prop="sort">

View File

@ -1,6 +1,10 @@
<template>
<div>
<pages-header class="mb16" :title="$route.meta.title"></pages-header>
<pages-header
class="mb16"
:title="$route.meta.title"
description="配置物流查询服务商并启停驱动,订单物流轨迹经此获取。"
></pages-header>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16">
<el-table :data="list" ref="table" v-loading="loading" highlight-current-row row-key="id">
<el-table-column label="排序" width="80" prop="sort">

View File

@ -1,6 +1,10 @@
<template>
<div>
<pages-header class="mb16" :title="$route.meta.title"></pages-header>
<pages-header
class="mb16"
:title="$route.meta.title"
description="查看各支付方式的支持端与驱动状态并启停,支付参数在各驱动的配置中维护。"
></pages-header>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16">
<el-table :data="list" ref="table" v-loading="loading" highlight-current-row row-key="id">
<el-table-column label="排序" width="80" prop="sort">

View File

@ -1,6 +1,11 @@
<template>
<div class="printer-content-page">
<pages-header ref="pageHeader" title="小票配置" :backUrl="$routeProStr + '/setting/printer'"></pages-header>
<pages-header
ref="pageHeader"
title="小票配置"
:backUrl="$routeProStr + '/setting/printer'"
description="勾选小票需打印的商家、商品、支付等信息项,保存后打印即时生效。"
></pages-header>
<el-card :bordered="false" shadow="never" class="mt16">
<div class="flex justify-between warpper" v-loading="loading">
<el-form

View File

@ -1,6 +1,10 @@
<template>
<div>
<pages-header class="mb16" :title="$route.meta.title"></pages-header>
<pages-header
class="mb16"
:title="$route.meta.title"
description="添加并维护打印机设备,绑定驱动、设置打印时机与打印联数。"
></pages-header>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16">
<el-row class="action-row mb14">
<el-col :span="24">

View File

@ -1,6 +1,10 @@
<template>
<div>
<pages-header class="mb16" :title="$route.meta.title"></pages-header>
<pages-header
class="mb16"
:title="$route.meta.title"
description="配置打印服务商账号并启停驱动,具体打印机设备在打印设置中维护。"
></pages-header>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16">
<el-alert
title="在这里配置打印服务商账号和密钥;具体设备只在“设置 > 打印设置”中填写打印机 ID。"

View File

@ -1,6 +1,10 @@
<template>
<div>
<pages-header class="mb16" :title="$route.meta.title"></pages-header>
<pages-header
class="mb16"
:title="$route.meta.title"
description="配置短信服务商参数并启停驱动,驱动为互斥模式,同时仅一个生效。"
></pages-header>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16">
<el-table :data="list" ref="table" v-loading="loading" highlight-current-row row-key="id">
<el-table-column label="排序" width="80" prop="sort">

View File

@ -1,6 +1,10 @@
<template>
<div>
<pages-header class="mb16" :title="$route.meta.title"></pages-header>
<pages-header
class="mb16"
:title="$route.meta.title"
description="管理本地与云存储驱动并切换启用,图片附件将上传至启用的存储平台。"
></pages-header>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16">
<el-table :data="list" ref="table" v-loading="loading" highlight-current-row row-key="id">
<el-table-column label="排序" width="80" prop="sort">

View File

@ -1,7 +1,13 @@
<template>
<div>
<div class="i-layout-page-header header-title">
<span class="ivu-page-header-title">{{ $route.meta.title }}</span>
<div class="header-main">
<span class="ivu-page-header-title">{{ $route.meta.title }}</span>
<span class="header-description">
<el-icon class="header-description-icon"><InfoFilled /></el-icon>
用户资金流水明细,按类型与时间筛选,支持导出对账。
</span>
</div>
</div>
<el-card :bordered="false" shadow="never">
<el-form

View File

@ -4,6 +4,7 @@
ref="pageHeader"
:title="route.params.id && isEdit ? '编辑优惠券' : '添加优惠券'"
:backUrl="Setting.routePre + '/marketing/store_coupon_issue/index'"
description="设置优惠券名称、面值、领取与有效期等规则,保存后可发放给用户。"
></pages-header>
<el-card :bordered="false" shadow="never" class="mt16">
<el-form :model="formData" label-width="160px">

View File

@ -5,7 +5,6 @@
@close="$emit('update:visible', false)"
title="物流查询"
size="420px"
:modal-append-to-body="true"
:wrapper-closable="true"
class="order_box2"
>
@ -76,19 +75,6 @@ function copyNum() {
</script>
<style lang="scss" scoped>
.order_box2 {
:deep(.el-drawer__wrapper) {
z-index: 3000 !important;
}
:deep(.el-drawer__body) {
padding: 0;
display: flex;
flex-direction: column;
height: 100%;
}
}
/* 快递单号信息区域 */
.express-header {
display: flex;
@ -201,3 +187,15 @@ function copyNum() {
font-size: 14px;
}
</style>
<style lang="scss">
/* 抽屉壳层覆盖(非 scoped):抽屉根类自锚的 scoped :deep 在组件作用域外无法命中;
body 满高 flex 布局供底部滚动区使用。原 .el-drawer__wrapper z-index 覆盖为
iView 遗留死规则(选择器结构写反且 EP 层级由弹层管理),不再保留 */
.order_box2 .el-drawer__body {
padding: 0;
display: flex;
flex-direction: column;
height: 100%;
}
</style>

View File

@ -954,11 +954,6 @@ function formatFullTime(timestamp) {
}
/* 运行详情回放 */
.run-detail-dialog {
:deep(.el-dialog__body) {
padding-top: 10px;
}
}
.run-detail-head {
display: flex;
align-items: center;
@ -1039,3 +1034,11 @@ function formatFullTime(timestamp) {
color: #f56c6c;
}
</style>
<style lang="scss">
/* 运行详情弹窗壳层(非 scoped):append-to-body 弹窗 teleport 到 body 后
脱离组件样式作用域,scoped :deep 无法命中,必须用全局类 */
.run-detail-dialog .el-dialog__body {
padding-top: 10px;
}
</style>

View File

@ -1,970 +0,0 @@
<template>
<div>
<pages-header
:title="$route.params.id ? '编辑砍价商品' : '添加砍价商品'"
:backUrl="routePre + '/marketing/bargain/product'"
></pages-header>
<el-card :bordered="false" shadow="never" class="mt16">
<el-row class="mt30 acea-row row-middle row-center">
<el-col :span="20">
<steps :stepList="stepList" :isActive="current"></steps>
</el-col>
<el-col :span="23" v-loading="spinShow">
<el-form
class="form mt30"
ref="formValidateRef"
:rules="ruleValidate"
:model="formValidate"
@on-validate="validate"
:label-width="labelWidth"
:label-position="labelPosition"
@submit.prevent
>
<el-form-item label="选择商品:" prop="image_input" v-show="current === 0">
<div class="picBox" v-db-click @click="changeGoods">
<div class="pictrue" v-if="formValidate.image">
<img v-lazy="formValidate.image" />
</div>
<div class="upLoad acea-row row-center-wrapper" v-else>
<i class="el-icon-goods" style="font-size: 24px"></i>
</div>
</div>
</el-form-item>
<el-row v-show="current === 1">
<el-col :span="24">
<el-form-item label="商品主图:" prop="image">
<div class="picBox" v-db-click @click="modalPicTap('dan', 'danFrom')">
<div class="pictrue" v-if="formValidate.image">
<img v-lazy="formValidate.image" />
</div>
<div class="upLoad acea-row row-center-wrapper" v-else>
<i class="el-icon-picture-outline" style="font-size: 24px"></i>
</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="商品轮播图:" prop="images">
<div class="acea-row">
<div
class="pictrue"
v-for="(item, index) in formValidate.images"
:key="index"
draggable="true"
@dragstart="handleDragStart($event, item)"
@dragover.prevent="handleDragOver($event, item)"
@dragenter="handleDragEnter($event, item)"
@dragend="handleDragEnd($event, item)"
>
<img v-lazy="item" />
<i class="el-icon-circle-close btndel" v-db-click @click="handleRemove(index)"></i>
</div>
<div
v-if="formValidate.images.length < 10"
class="upLoad acea-row row-center-wrapper"
v-db-click
@click="modalPicTap('duo')"
>
<i class="el-icon-picture-outline" style="font-size: 24px"></i>
</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-col v-bind="grid">
<el-form-item label="砍价活动名称:" prop="title" label-for="title">
<el-input
placeholder="请输入砍价活动名称"
v-model="formValidate.title"
class="content_width"
maxlength="30"
show-word-limit
/>
</el-form-item>
</el-col>
</el-col>
<el-col :span="24">
<el-col v-bind="grid">
<el-form-item label="砍价活动简介:" prop="info" label-for="info">
<el-input
placeholder="请输入砍价活动简介"
type="textarea"
:rows="4"
v-model="formValidate.info"
class="content_width"
maxlength="100"
show-word-limit
/>
</el-form-item>
</el-col>
</el-col>
<el-col :span="24">
<el-form-item label="活动时间:" prop="section_time">
<div>
<el-date-picker
clearable
:editable="false"
type="datetimerange"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
@change="onchangeTime"
style="width: 460px"
v-model="formValidate.section_time"
></el-date-picker>
<div class="grey">设置活动开启结束时间,用户可以在设置时间内发起参与砍价</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24" v-if="formValidate.virtual_type == 0">
<el-form-item label="物流方式:" prop="logistics">
<el-checkbox-group v-model="formValidate.logistics">
<el-checkbox label="1">快递</el-checkbox>
<el-checkbox label="2">到店</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-col>
<el-col :span="24" v-if="formValidate.virtual_type == 0 && formValidate.logistics.includes('1')">
<el-form-item label="运费设置:" :prop="formValidate.freight != 1 ? 'freight' : ''">
<el-radio-group v-model="formValidate.freight">
<el-radio :label="2" :value="2">固定邮费</el-radio>
<el-radio :label="3" :value="3">运费模板</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col
:span="24"
v-if="
formValidate.freight != 3 &&
formValidate.freight != 1 &&
formValidate.virtual_type == 0 &&
formValidate.logistics.includes('1')
"
>
<el-form-item label="">
<div class="acea-row">
<el-input-number
:controls="false"
:min="0"
:max="9999999999"
v-model="formValidate.postage"
placeholder="请输入金额"
class="content_width"
/>
</div>
</el-form-item>
</el-col>
<el-col :span="24" v-if="formValidate.freight == 3 && formValidate.virtual_type == 0">
<el-form-item label="" prop="temp_id">
<div class="acea-row">
<el-select
v-model="formValidate.temp_id"
clearable
placeholder="请选择运费模板"
class="content_width"
>
<el-option
v-for="(item, index) in templateList"
:value="item.id"
:key="index"
:label="item.name"
></el-option>
</el-select>
<span class="addfont" v-db-click @click="freight">新增运费模板</span>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="砍价人数:" prop="people_num" label-for="people_num">
<div>
<el-input-number
:controls="false"
placeholder="请输入砍价人数"
element-id="people_num"
:min="2"
:max="10000"
:precision="0"
v-model="formValidate.people_num"
class="input-number-unit-class"
class-unit="人"
/>
<div class="grey">需要多少人砍价成功</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="帮砍次数:" prop="bargain_num" label-for="bargain_num">
<div>
<el-input-number
:controls="false"
placeholder="请输入帮砍次数"
:min="1"
:max="10000"
:precision="0"
v-model="formValidate.bargain_num"
class="input-number-unit-class"
class-unit="次"
/>
<div class="grey">
单个商品用户可以帮砍的次数,例:次数设置为1,甲和乙同时将商品A的砍价链接发给丙,丙只能帮甲或乙其中一个人砍价
</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="单位:" prop="unit_name" label-for="unit_name">
<el-input
placeholder="请输入单位"
element-id="unit_name"
v-model="formValidate.unit_name"
class="content_width"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="购买数量限制:" prop="num">
<div>
<el-input-number
:controls="false"
placeholder="购买数量限制"
:min="1"
:max="10000"
:precision="0"
v-model="formValidate.num"
class="input-number-unit-class"
:class-unit="formValidate.unit_name || '件'"
/>
<div class="grey">单个活动每个用户发起砍价次数限制</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="排序:">
<el-input-number
:controls="false"
placeholder="请输入排序"
element-id="sort"
:min="0"
:max="10000"
:precision="0"
v-model="formValidate.sort"
class="content_width"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="砍价是否参与分销:" props="is_commission" label-for="is_commission">
<div>
<el-switch
class="defineSwitch"
:active-value="1"
:inactive-value="0"
v-model="formValidate.is_commission"
size="large"
active-text="开启"
inactive-text="关闭"
>
</el-switch>
<div class="grey">商品是否参与商城分销返佣</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="活动状态:" props="status" label-for="status">
<el-switch
class="defineSwitch"
:active-value="1"
:inactive-value="0"
v-model="formValidate.status"
size="large"
active-text="开启"
inactive-text="关闭"
>
</el-switch>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="规格选择:">
<el-table :data="specsData" border>
<el-table-column width="50">
<template #default="scope">
<el-radio
type="index"
v-model="templateRadio"
:label="scope.$index"
:value="scope.$index"
@change="getTemplateRow"
>&nbsp;</el-radio
>
</template>
</el-table-column>
<el-table-column
:label="item.title"
:min-width="item.minWidth"
v-for="(item, index) in columns"
:key="index"
>
<template #default="scope">
<template v-if="item.key">
<div>
<span>{{ scope.row[item.key] }}</span>
</div>
</template>
<template v-else-if="item.slot === 'pic'">
<div
class="acea-row row-middle row-center-wrapper"
v-db-click
@click="modalPicTap('dan', 'danTable', scope.$index)"
>
<div class="pictrue pictrueTab" v-if="scope.row.pic">
<img v-lazy="scope.row.pic" />
</div>
<div class="upLoad pictrueTab acea-row row-center-wrapper" v-else>
<i class="el-icon-picture-outline" style="font-size: 24px"></i>
</div>
</div>
</template>
<template v-else-if="item.slot === 'price'">
<el-input-number
:controls="false"
v-model="scope.row.price"
:min="0"
:precision="2"
class="priceBox"
:active-change="false"
></el-input-number>
</template>
<template v-else-if="item.slot === 'min_price'">
<el-input-number
:controls="false"
v-model="scope.row.min_price"
:min="0"
:precision="2"
class="priceBox"
:active-change="false"
></el-input-number>
</template>
<template v-else-if="item.slot === 'quota'">
<el-input-number
:controls="false"
v-model="scope.row.quota"
:min="1"
active-change
class="priceBox"
></el-input-number>
</template>
</template>
</el-table-column>
</el-table>
</el-form-item>
</el-col>
</el-row>
<div v-if="current === 2">
<el-form-item label="内容:">
<WangEditor
style="width: 90%"
:content="formValidate.description"
:ai-default-prompt="aiDefaultPrompt"
@editorContent="getEditorContent"
></WangEditor>
</el-form-item>
</div>
<div v-if="current === 3">
<el-form-item label="规则:">
<WangEditor
style="width: 90%"
:content="formValidate.rule"
@editorContent="getEditorContent2"
></WangEditor>
</el-form-item>
</div>
<el-form-item>
<el-button
v-if="current !== 0"
class="submission"
v-db-click
@click="step"
:disabled="($route.params.id && $route.params.id !== '0' && current === 1) || current === 0"
>上一步</el-button
>
<el-button
type="primary"
:disabled="submitOpen && current === 3"
class="submission"
v-db-click
@click="next"
>{{ current === 3 ? '提交' : '下一步' }}</el-button
>
</el-form-item>
</el-form>
</el-col>
</el-row>
</el-card>
<!-- 选择商品-->
<el-dialog v-model="modals" title="商品列表" class="paymentFooter" width="1000px">
<goods-list ref="goodslist" @getProductId="getProductId"></goods-list>
</el-dialog>
<!-- 上传图片-->
<el-dialog v-model="modalPic" width="950px" title="上传商品图" :close-on-click-modal="false">
<uploadPictures
:isChoice="isChoice"
@getPic="getPic"
@getPicD="getPicD"
:gridBtn="gridBtn"
:gridPic="gridPic"
v-if="modalPic"
></uploadPictures>
</el-dialog>
<!-- 运费模板-->
<freight-template ref="template" @addSuccess="productGetTemplate"></freight-template>
</div>
</template>
<script setup>
import { computed, nextTick, onMounted, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useRoute, useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import goodsList from '@/components/goodsList/index';
import uploadPictures from '@/components/uploadPictures';
import { bargainInfoApi, bargainCreatApi, productAttrsApi } from '@/api/marketing';
import { productGetTemplateApi } from '@/api/product';
import freightTemplate from '@/components/freightTemplate/index';
import WangEditor from '@/components/wangEditor/index.vue';
import steps from '@/components/steps/index';
import setting from '@/setting';
import { useMediaStore } from '@/store/module/media';
defineOptions({ name: 'bargainCreate' });
const route = useRoute();
const router = useRouter();
const routePre = setting.routePre;
const { isMobile } = storeToRefs(useMediaStore());
const labelWidth = computed(() => (isMobile.value ? undefined : '140px'));
const labelPosition = computed(() => (isMobile.value ? 'top' : 'right'));
const formValidateRef = ref(null);
const goodslist = ref(null);
const template = ref(null);
const templateRadio = ref(0);
const submitOpen = ref(false);
const spinShow = ref(false);
const myConfig = {
autoHeightEnabled: false,
initialFrameHeight: 500,
initialFrameWidth: '100%',
UEDITOR_HOME_URL: '/UEditor/',
serverUrl: '',
};
const stepList = ['选择砍价商品', '填写基础信息', '修改商品详情', '修改商品规则'];
const isChoice = ref('');
const current = ref(0);
const modalPic = ref(false);
const grid = {
xl: 12,
lg: 20,
md: 24,
sm: 24,
xs: 24,
};
const grid2 = {
xl: 8,
lg: 8,
md: 12,
sm: 24,
xs: 24,
};
const gridPic = {
xl: 6,
lg: 8,
md: 12,
sm: 12,
xs: 12,
};
const gridBtn = {
xl: 4,
lg: 8,
md: 8,
sm: 8,
xs: 8,
};
const modals = ref(false);
const modal_loading = ref(false);
const images = ref([]);
const templateList = ref([]);
const columns = ref([]);
const specsData = ref([]);
const formValidate = ref({
images: [],
info: '',
title: '',
store_name: '',
image: '',
unit_name: '',
price: 0,
min_price: 0,
bargain_max_price: 10,
bargain_min_price: 0.01,
cost: 0,
bargain_num: 1,
people_num: 2,
stock: 1,
sales: 0,
sort: 0,
num: 1,
give_integral: 0,
is_postage: 0,
is_hot: 0,
status: 0,
section_time: [],
description: '',
rule: '',
id: 0,
product_id: 0,
temp_id: '',
attrs: [],
items: [],
logistics: [],
freight: 2,
postage: 1,
is_commission: 0,
});
// AI 写作默认提示词:优先带入当前商品名称(弹窗内未被用户修改过的提示词会跟随刷新)
const aiDefaultPrompt = computed(() => {
const title = (formValidate.value.store_name || formValidate.value.title || '').trim();
return title
? `请围绕商品《${title}》撰写一篇产品推广文章,介绍产品亮点、使用场景与适用人群,结构清晰,800字左右。`
: '写一篇产品推广文章,介绍产品亮点、使用场景与适用人群,结构清晰,800字左右。';
});
const description = ref('');
const rule = ref('');
const ruleValidate = {
image: [{ required: true, message: '请选择主图', trigger: 'change' }],
images: [
{
required: true,
type: 'array',
message: '请选择主图',
trigger: 'change',
},
{
type: 'array',
min: 1,
message: 'Choose two hobbies at best',
trigger: 'change',
},
],
title: [{ required: true, message: '请输入砍价活动名称', trigger: 'blur' }],
info: [{ required: true, message: '请输入砍价活动简介', trigger: 'blur' }],
store_name: [{ required: true, message: '请输入砍价商品名称', trigger: 'blur' }],
section_time: [
{
required: true,
type: 'array',
message: '请选择活动时间',
trigger: 'change',
},
],
unit_name: [{ required: true, message: '请输入单位', trigger: 'blur' }],
price: [
{
required: true,
type: 'number',
message: '请输入原价',
trigger: 'blur',
},
],
min_price: [
{
required: true,
type: 'number',
message: '请输入最低购买价',
trigger: 'blur',
},
],
cost: [
{
required: true,
type: 'number',
message: '请输入成本价',
trigger: 'blur',
},
],
bargain_num: [
{
required: true,
type: 'number',
message: '请输入帮砍次数',
trigger: 'blur',
},
],
people_num: [
{
required: true,
type: 'number',
message: '请输入砍价人数',
trigger: 'blur',
},
],
stock: [
{
required: true,
type: 'number',
message: '请输入库存',
trigger: 'blur',
},
],
num: [
{
required: true,
type: 'number',
message: '请输入单次允许购买数量',
trigger: 'blur',
},
],
temp_id: [
{
required: true,
message: '请选择运费模板',
trigger: 'change',
type: 'number',
},
],
};
const currentid = ref(0);
const picTit = ref('');
const tableIndex = ref(0);
const copy = ref(0);
const dragging = ref(null);
onMounted(() => {
if (route.params.id !== '0' && route.params.id) {
copy.value = route.params.copy;
current.value = 1;
getInfo();
}
productGetTemplate();
});
function getEditorContent(data) {
description.value = data;
}
function getEditorContent2(data) {
rule.value = data;
}
function freight() {
template.value.id = 0;
template.value.isTemplate = true;
}
function productAttrs(row) {
productAttrsApi(row.id, 2)
.then((res) => {
let data = res.data.info;
columns.value = data.header;
specsData.value = data.attrs;
formValidate.value.items = data.items;
formValidate.value['attrs'] = [specsData.value[0]];
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function getTemplateRow(index) {
currentid.value = index;
formValidate.value['attrs'] = [specsData.value[index]];
}
function productGetTemplate() {
productGetTemplateApi().then((res) => {
templateList.value = res.data;
});
}
function getProductId(row) {
modal_loading.value = false;
modals.value = false;
setTimeout(() => {
formValidate.value = {
images: row.slider_image,
info: row.store_info,
title: row.store_name,
store_name: row.store_name,
image: row.image,
unit_name: row.unit_name,
price: 0,
min_price: 0,
bargain_max_price: 10,
bargain_min_price: 0.01,
cost: row.cost,
bargain_num: 1,
people_num: 2,
stock: row.stock,
sales: row.sales,
sort: row.sort,
num: 1,
give_integral: row.give_integral,
is_postage: row.is_postage,
is_hot: row.is_hot,
status: 0,
section_time: [],
description: '',
rule: '',
id: 0,
product_id: row.id,
temp_id: row.temp_id,
logistics: row.temp_id ? ['2'] : ['1'],
freight: row.freight,
postage: row.postage,
custom_form: row.custom_form,
virtual_type: row.virtual_type,
is_commission: row.is_commission,
description: row.description,
};
productAttrs(row);
}, 500);
}
function cancel() {
modals.value = false;
}
function handleDragStart(e, item) {
dragging.value = item;
}
function handleDragEnd(e, item) {
dragging.value = null;
}
function handleDragOver(e) {
e.dataTransfer.dropEffect = 'move';
}
function handleDragEnter(e, item) {
e.dataTransfer.effectAllowed = 'move';
if (item === dragging.value) {
return;
}
const newItems = [...formValidate.value.images];
const src = newItems.indexOf(dragging.value);
const dst = newItems.indexOf(item);
newItems.splice(dst, 0, ...newItems.splice(src, 1));
formValidate.value.images = newItems;
}
function onchangeTime(e) {
formValidate.value.section_time = e;
}
function getInfo() {
spinShow.value = true;
bargainInfoApi(route.params.id)
.then(async (res) => {
let info = res.data.info;
formValidate.value = info;
formValidate.value.rule = info.rule === null ? '' : info.rule;
formValidate.value['items'] = info.attrs.items;
description.value = formValidate.value.description;
columns.value = info.attrs.header;
specsData.value = info.attrs.value;
let defaultAttrs = [];
info.attrs.value.forEach(function (item, index) {
if (item.opt) {
defaultAttrs.push(item);
currentid.value = index;
templateRadio.value = index;
formValidate.value['attrs'] = defaultAttrs;
}
});
spinShow.value = false;
})
.catch((res) => {
spinShow.value = false;
ElMessage.error(res.msg);
});
}
function next() {
if (current.value === 3) {
formValidate.value.description = description.value;
formValidate.value.rule = rule.value;
formValidateRef.value.validate((valid) => {
if (valid) {
if (copy.value == 1) formValidate.value.copy = 1;
formValidate.value.id = route.params.id || 0;
submitOpen.value = true;
bargainCreatApi(formValidate.value)
.then(async (res) => {
submitOpen.value = false;
ElMessage.success(res.msg);
setTimeout(() => {
router.push({
path: routePre + '/marketing/bargain/product',
});
}, 500);
})
.catch((res) => {
submitOpen.value = false;
ElMessage.error(res.msg);
});
} else {
return false;
}
});
} else if (current.value === 1) {
formValidateRef.value.validate((valid) => {
if (valid) {
if (currentid.value === '') {
return ElMessage.error('请选择属性规格');
} else {
if (formValidate.value.attrs[0].quota <= 0) {
return ElMessage.error('砍价限量必须大于0');
}
if (formValidate.value.attrs[0].quota > formValidate.value.attrs[0]['stock']) {
return ElMessage.error('砍价限量不能超过规格库存');
}
}
current.value += 1;
} else {
return ElMessage.warning('请完善您的信息');
}
});
} else {
if (formValidate.value.image) {
current.value += 1;
if (current.value == 3) {
setTimeout((e) => {
formValidate.value.rule += ' ';
}, 0);
}
} else {
ElMessage.warning('请选择商品');
}
}
}
function step() {
current.value--;
}
function getContent(val) {
formValidate.value.description = val;
}
function getRole(val) {
formValidate.value.rule = val;
}
function modalPicTap(tit, picTitVal, index) {
modalPic.value = true;
isChoice.value = tit === 'dan' ? '单选' : '多选';
picTit.value = picTitVal;
tableIndex.value = index;
}
function getPic(pc) {
switch (picTit.value) {
case 'danFrom':
formValidate.value.image = pc.att_dir;
break;
default:
specsData.value[tableIndex.value].pic = pc.att_dir;
formValidate.value.attrs[0].pic = pc.att_dir;
}
modalPic.value = false;
}
function getPicD(pc) {
images.value = pc;
images.value.map((item) => {
formValidate.value.images.push(item.att_dir);
formValidate.value.images = formValidate.value.images.splice(0, 10);
});
modalPic.value = false;
}
function handleRemove(i) {
images.value.splice(i, 1);
formValidate.value.images.splice(i, 1);
}
function changeGoods() {
modals.value = true;
nextTick((e) => {
goodslist.value.formValidate.is_show = -1;
goodslist.value.formValidate.type = 3;
goodslist.value.getList();
goodslist.value.goodsCategory();
});
}
function validate(prop, status, error) {
if (status === false) {
ElMessage.error(error);
}
}
</script>
<style lang="scss" scoped>
.content_width {
width: 460px;
}
.grey {
color: #999;
font-size: 12px;
}
.maxW :deep(.ivu-select-dropdown) {
max-width: 600px;
}
.ivu-table-wrapper {
border-left: 1px solid #dcdee2;
border-top: 1px solid #dcdee2;
}
.tabBox_img {
width: 50px;
height: 50px;
}
.tabBox_img img {
width: 100%;
height: 100%;
}
.priceBox {
width: 100%;
}
.form {
.picBox {
display: inline-block;
cursor: pointer;
}
.pictrue {
width: 60px;
height: 60px;
border: 1px dotted rgba(0, 0, 0, 0.1);
margin-right: 15px;
display: inline-block;
position: relative;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
.btndel {
position: absolute;
z-index: 9;
width: 20px !important;
height: 20px !important;
left: 46px;
top: -4px;
}
}
.upLoad {
width: 58px;
height: 58px;
line-height: 58px;
border: 1px dotted rgba(0, 0, 0, 0.1);
border-radius: 4px;
background: rgba(0, 0, 0, 0.02);
cursor: pointer;
}
.col {
color: #2d8cf0;
cursor: pointer;
}
}
.addfont {
font-size: 12px;
color: var(--prev-color-primary);
margin-left: 14px;
cursor: pointer;
margin-left: 10px;
cursor: pointer;
}
</style>

View File

@ -1,258 +0,0 @@
<template>
<div class="article-manager">
<el-card :bordered="false" shadow="never" :body-style="{ padding: 0 }">
<div class="padding-add">
<el-form :model="formValidate" :label-width="labelWidth" label-position="right" @submit.prevent inline>
<el-form-item label="时间选择:">
<date-range-picker
clearable
v-model="timeVal"
:editable="false"
@change="onchangeTime"
format="YYYY/MM/DD"
value-format="YYYY/MM/DD"
class="mr20"
></date-range-picker>
</el-form-item>
<el-form-item label="砍价状态:">
<el-select
v-model="formValidate.status"
placeholder="请选择"
clearable
@change="userSearchs"
class="form_content_width"
>
<el-option :value="1" label="进行中"></el-option>
<el-option :value="2" label="已失败"></el-option>
<el-option :value="3" label="已成功"></el-option>
</el-select>
</el-form-item>
</el-form>
</div>
</el-card>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16">
<el-table
:data="tableList"
v-loading="loading"
highlight-current-row
no-userFrom-text="暂无数据"
no-filtered-userFrom-text="暂无筛选结果"
>
<el-table-column label="头像" width="80">
<template #default="scope">
<div class="tabBox_img" v-viewer>
<img v-lazy="scope.row.avatar" />
</div>
</template>
</el-table-column>
<el-table-column label="发起用户" min-width="100">
<template #default="scope">
<span> {{ scope.row.nickname + ' / ' + scope.row.uid }}</span>
</template>
</el-table-column>
<el-table-column label="开启时间" min-width="110">
<template #default="scope">
<span> {{ scope.row.add_time }}</span>
</template>
</el-table-column>
<el-table-column label="砍价商品" min-width="300">
<template #default="scope">
<span> {{ scope.row.title }}</span>
</template>
</el-table-column>
<el-table-column label="最低价" min-width="60">
<template #default="scope">
<span> {{ scope.row.bargain_price_min }}</span>
</template>
</el-table-column>
<el-table-column label="当前价" min-width="60">
<template #default="scope">
<span> {{ scope.row.now_price }}</span>
</template>
</el-table-column>
<el-table-column label="总砍价次数" min-width="70">
<template #default="scope">
<span> {{ scope.row.people_num }}</span>
</template>
</el-table-column>
<el-table-column label="剩余砍价次数" min-width="100">
<template #default="scope">
<span> {{ scope.row.num }}</span>
</template>
</el-table-column>
<el-table-column label="结束时间" min-width="150">
<template #default="scope">
<span> {{ scope.row.datatime }}</span>
</template>
</el-table-column>
<el-table-column label="状态" min-width="100">
<template #default="scope">
<el-tag size="default" type="info" v-show="scope.row.status === 1">进行中</el-tag>
<el-tag size="default" type="danger" v-show="scope.row.status === 2">已失败</el-tag>
<el-tag size="default" v-show="scope.row.status === 3">已成功</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="100">
<template #default="scope">
<a v-db-click @click="Info(scope.row)">查看详情</a>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total"
:total="total"
v-model:page="formValidate.page"
v-model:limit="formValidate.limit"
@pagination="getList"
/>
</div>
</el-card>
<!-- 详情模态框-->
<el-dialog v-model="modals" class="tableBox" title="查看详情" :close-on-click-modal="false" width="720px">
<el-table :data="tabList3" v-loading="loading2" empty-text="暂无数据" highlight-current-row max-height="600">
<el-table-column label="用户ID" width="80">
<template #default="scope">
<span>{{ scope.row.uid }}</span>
</template>
</el-table-column>
<el-table-column label="用户头像" min-width="100">
<template #default="scope">
<div class="tabBox_img" v-viewer>
<img v-lazy="scope.row.avatar" />
</div>
</template>
</el-table-column>
<el-table-column label="用户名称" min-width="100">
<template #default="scope">
<span> {{ scope.row.nickname }}</span>
</template>
</el-table-column>
<el-table-column label="砍价金额" min-width="100">
<template #default="scope">
<span>{{ scope.row.price }}</span>
</template>
</el-table-column>
<el-table-column label="砍价时间" min-width="130">
<template #default="scope">
<span>{{ scope.row.add_time }}</span>
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script setup>
import { computed, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { ElMessage } from 'element-plus';
import { formatDate } from '@/utils/validate';
import { bargainUserListApi, bargainUserInfoApi } from '@/api/marketing';
import { useMediaStore } from '@/store/module/media';
defineOptions({ name: 'bargainPink' });
const { isMobile } = storeToRefs(useMediaStore());
const labelWidth = computed(() => (isMobile.value ? undefined : '80px'));
const labelPosition = computed(() => (isMobile.value ? 'top' : 'right'));
const cardLists = ref([]);
const modals = ref(false);
const loading = ref(false);
const formValidate = ref({
status: '',
data: '',
page: 1,
limit: 15,
});
const tableList = ref([]);
const total = ref(0);
const timeVal = ref([]);
const loading2 = ref(false);
const tabList3 = ref([]);
const rows = ref({});
// 原 filters:转为普通 function
function formatDateFilter(time) {
if (time !== 0) {
let date = new Date(time * 1000);
return formatDate(date, 'yyyy-MM-dd hh:mm');
}
}
getList();
// 查看详情
function Info(row) {
modals.value = true;
rows.value = row;
bargainUserInfoApi(row.id)
.then(async (res) => {
let data = res.data;
tabList3.value = data.list;
loading.value = false;
})
.catch((res) => {
loading.value = false;
ElMessage.error(res.msg);
});
}
// 具体日期
function onchangeTime(e) {
timeVal.value = e || [];
formValidate.value.data = timeVal.value[0] ? (timeVal.value ? timeVal.value.join('-') : '') : '';
formValidate.value.page = 1;
getList();
}
// 选择时间
function selectChange(tab) {
formValidate.value.page = 1;
formValidate.value.data = tab;
timeVal.value = [];
getList();
}
// 列表
function getList() {
loading.value = true;
formValidate.value.status = formValidate.value.status || '';
bargainUserListApi(formValidate.value)
.then(async (res) => {
let data = res.data;
tableList.value = data.list;
total.value = res.data.count;
loading.value = false;
})
.catch((res) => {
loading.value = false;
ElMessage.error(res.msg);
});
}
// 表格搜索
function userSearchs() {
formValidate.value.page = 1;
getList();
}
</script>
<style lang="scss" scoped>
:deep(.ivu-tag-cyan .ivu-tag-text) {
color: #19be6b !important;
}
.ivu-tag-cyan {
background: rgba(25, 190, 170, 0.1);
border-color: #19be6b !important;
}
.tabBox_img {
width: 36px;
height: 36px;
border-radius: 4px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
</style>

View File

@ -1,337 +0,0 @@
<template>
<div>
<el-card :bordered="false" shadow="never" :body-style="{ padding: 0 }">
<div class="padding-add">
<el-form :model="tableFrom" :label-width="labelWidth" :label-position="labelPosition" @submit.prevent inline>
<el-form-item label="上架状态:">
<el-select
placeholder="请选择"
v-model="tableFrom.status"
clearable
@change="userSearchs"
class="form_content_width"
>
<el-option value="1" label="上架"></el-option>
<el-option value="0" label="下架"></el-option>
</el-select>
</el-form-item>
<el-form-item label="商品搜索:" label-for="store_name">
<el-input
clearable
placeholder="请输入砍价名称,ID"
v-model="tableFrom.store_name"
class="form_content_width"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" v-db-click @click="userSearchs">查询</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16">
<el-button v-auth="['marketing-store_bargain-create']" type="primary" class="action-row" v-db-click @click="add"
>添加砍价商品</el-button
>
<el-button
v-auth="['export-storeBargain']"
class="action-row export"
icon="ios-share-outline"
v-db-click
@click="exportList"
>导出</el-button
>
<el-table
:data="tableList"
v-loading="loading"
highlight-current-row
no-userFrom-text="暂无数据"
no-filtered-userFrom-text="暂无筛选结果"
class="mt14"
>
<el-table-column label="ID" width="80">
<template #default="scope">
<div>{{ scope.row.id }}</div>
</template>
</el-table-column>
<el-table-column label="砍价图片" min-width="80">
<template #default="scope">
<div class="tabBox_img" v-viewer>
<img v-lazy="scope.row.image" />
</div>
</template>
</el-table-column>
<el-table-column label="砍价名称" min-width="150">
<template #default="scope">
<el-tooltip placement="top" :open-delay="600">
<template #content
><div>{{ scope.row.title }}</div></template
>
<span class="line2">{{ scope.row.title }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="砍价价格" min-width="100">
<template #default="scope">
<div>{{ scope.row.price }}</div>
</template>
</el-table-column>
<el-table-column label="最低价" min-width="100">
<template #default="scope">
<div>{{ scope.row.min_price }}</div>
</template>
</el-table-column>
<el-table-column label="参与人数" min-width="100">
<template #default="scope">
<div>{{ scope.row.count_people_all }}</div>
</template>
</el-table-column>
<el-table-column label="帮忙砍价人数" min-width="100">
<template #default="scope">
<div>{{ scope.row.count_people_help }}</div>
</template>
</el-table-column>
<el-table-column label="砍价成功人数" min-width="100">
<template #default="scope">
<div>{{ scope.row.count_people_success }}</div>
</template>
</el-table-column>
<el-table-column label="限量" min-width="80">
<template #default="scope">
<div>{{ scope.row.quota_show }}</div>
</template>
</el-table-column>
<el-table-column label="限量剩余" min-width="80">
<template #default="scope">
<div>{{ scope.row.quota }}</div>
</template>
</el-table-column>
<el-table-column label="活动状态" min-width="100">
<template #default="scope">
<el-tag size="default" v-show="scope.row.start_name === '进行中'">进行中</el-tag>
<el-tag size="default" type="warning" v-show="scope.row.start_name === '未开始'">未开始</el-tag>
<el-tag size="default" type="info" v-show="scope.row.start_name === '已结束'">已结束</el-tag>
</template>
</el-table-column>
<el-table-column label="活动时间" min-width="180">
<template #default="scope">
<p>开始:{{ scope.row.start_time }}</p>
<p>结束:{{ scope.row.stop_time }}</p>
</template>
</el-table-column>
<el-table-column label="上架状态" min-width="100">
<template #default="scope">
<el-switch
class="defineSwitch"
:active-value="1"
:inactive-value="0"
v-model="scope.row.status"
:value="scope.row.status"
@change="onchangeIsShow(scope.row)"
size="large"
active-text="上架"
inactive-text="下架"
>
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="170">
<template #default="scope">
<a v-if="scope.row.stop_status === 0" v-db-click @click="edit(scope.row, 0)">编辑</a>
<el-divider v-if="scope.row.stop_status === 0" direction="vertical" />
<a v-db-click @click="edit(scope.row, 1)">复制</a>
<el-divider direction="vertical"></el-divider>
<a v-db-click @click="del(scope.row, '删除砍价商品', scope.$index)">删除</a>
<el-divider direction="vertical"></el-divider>
<a v-db-click @click="viewInfo(scope.row)">统计</a>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total"
:total="total"
v-model:page="tableFrom.page"
v-model:limit="tableFrom.limit"
@pagination="getList"
/>
</div>
</el-card>
</div>
</template>
<script setup>
import { computed, onActivated, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useRoute, useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import { bargainListApi, bargainSetStatusApi } from '@/api/marketing';
import { formatDate } from '@/utils/validate';
import { exportBargainList } from '@/api/export';
import { modalSure } from '@/utils/public';
import exportExcel from '@/utils/newToExcel.js';
import setting from '@/setting';
import { useMediaStore } from '@/store/module/media';
defineOptions({ name: 'bargainProduct' });
const route = useRoute();
const router = useRouter();
const routePre = setting.routePre;
const { isMobile } = storeToRefs(useMediaStore());
const labelWidth = computed(() => (isMobile.value ? undefined : '80px'));
const labelPosition = computed(() => (isMobile.value ? 'top' : 'right'));
const loading = ref(false);
const tableList = ref([]);
const grid = {
xl: 7,
lg: 10,
md: 12,
sm: 24,
xs: 24,
};
const tableFrom = ref({
status: '',
store_name: '',
page: 1,
limit: 15,
});
const tableFrom2 = ref({
status: '',
store_name: '',
export: 1,
});
const total = ref(0);
// 原 filters:转为普通 function
function formatDateFilter(time) {
if (time !== 0) {
let date = new Date(time * 1000);
return formatDate(date, 'yyyy-MM-dd hh:mm:ss');
}
}
getList();
onActivated(() => {
getList();
});
// 添加
function add() {
router.push({ path: routePre + '/marketing/bargain/create' });
}
// 用户导出
async function exportList() {
tableFrom.value.status = tableFrom.value.status || '';
let [th, filekey, data, fileName] = [[], [], [], ''];
let excelData = JSON.parse(JSON.stringify(tableFrom.value));
excelData.page = 1;
excelData.limit = 50;
for (let i = 0; i < excelData.page + 1; i++) {
let lebData = await getExcelData(excelData);
if (!fileName) fileName = lebData.filename;
if (!filekey.length) {
filekey = lebData.fileKey;
}
if (!th.length) th = lebData.header;
if (lebData.export.length) {
data = data.concat(lebData.export);
excelData.page++;
} else {
exportExcel(th, filekey, fileName, data);
return;
}
}
}
function getExcelData(excelData) {
return new Promise((resolve, reject) => {
exportBargainList(excelData).then((res) => {
resolve(res.data);
});
});
}
// 编辑 / 复制 type 0 编辑 1 复制
function edit(row, type) {
router.push({
path: routePre + `/marketing/bargain/create/${row.id}/${type}`,
});
}
// 删除
function del(row, tit, num) {
let delfromData = {
title: tit,
num: num,
url: `marketing/bargain/${row.id}`,
method: 'DELETE',
ids: '',
};
modalSure(delfromData)
.then((res) => {
ElMessage.success(res.msg);
tableList.value.splice(num, 1);
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function viewInfo(row) {
router.push({
path: routePre + '/marketing/bargain/statistics/' + row.id,
});
}
// 列表
function getList() {
loading.value = true;
tableFrom.value.status = tableFrom.value.status || '';
tableFrom.value.product_id = route.params.product_id || '';
bargainListApi(tableFrom.value)
.then(async (res) => {
let data = res.data;
tableList.value = data.list;
total.value = res.data.count;
loading.value = false;
})
.catch((res) => {
loading.value = false;
ElMessage.error(res.msg);
});
}
// 表格搜索
function userSearchs() {
tableFrom.value.page = 1;
getList();
}
// 修改是否显示
function onchangeIsShow(row) {
let data = {
id: row.id,
status: row.status,
};
bargainSetStatusApi(data)
.then(async (res) => {
ElMessage.success(res.msg);
getList();
})
.catch((res) => {
ElMessage.error(res.msg);
row.status = !row.status;
});
}
</script>
<style lang="scss" scoped>
.tabBox_img {
width: 36px;
height: 36px;
border-radius: 4px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
</style>

View File

@ -1,361 +0,0 @@
<template>
<div>
<pages-header :title="$route.meta.title" :backUrl="routePre + '/marketing/bargain/product'"></pages-header>
<cards-data :cardLists="cardLists" v-if="cardLists.length" class="ivu-mt-16"></cards-data>
<el-card :bordered="false" shadow="never">
<el-form :model="pagination" :label-width="labelWidth" label-position="right" @submit.prevent inline>
<el-form-item v-if="type == 1" label="订单状态:" label-for="status">
<el-select
v-model="pagination.status"
placeholder="请选择订单状态"
clearable
@change="searchList"
class="form_content_width"
>
<el-option value="1" label="待发货"></el-option>
<el-option value="2" label="待收货"></el-option>
<el-option value="3" label="待评价"></el-option>
<el-option value="4" label="交易完成"></el-option>
</el-select>
</el-form-item>
<el-form-item label="搜索:" label-for="title">
<el-input
v-model="pagination.real_name"
:placeholder="type == 1 ? '请输入用户姓名|订单号|UID' : '请输入用户UID'"
class="form_content_width"
clearable
/>
</el-form-item>
<el-form-item>
<el-button type="primary" v-db-click @click="searchList">查询</el-button>
</el-form-item>
</el-form>
<el-tabs v-model="type" @tab-change="onClickTab">
<el-tab-pane v-for="(item, index) in tabs" :label="item.label" :name="item.type" :key="index" />
</el-tabs>
<el-table
:data="tbody"
v-loading="loading"
highlight-current-row
no-userFrom-text="暂无数据"
no-filtered-userFrom-text="暂无筛选结果"
>
<el-table-column
:label="item.title"
:min-width="item.minWidth"
v-for="(item, index) in type == 1 ? thead2 : thead"
:key="index"
>
<template #default="scope">
<template v-if="item.key">
<div>
<span>{{ scope.row[item.key] }}</span>
</div>
</template>
<template v-else-if="item.slot === 'avatar'">
<div class="tabBox_img" v-viewer>
<img v-lazy="scope.row.avatar" />
</div>
</template>
<template v-else-if="item.slot === 'status'">
<el-tag size="default" type="info" v-show="scope.row.status === 1">进行中</el-tag>
<el-tag size="default" type="danger" v-show="scope.row.status === 2">已失败</el-tag>
<el-tag size="default" v-show="scope.row.status === 3">已成功</el-tag>
</template>
<template v-else-if="item.slot === 'action'">
<a v-db-click @click="Info(scope.row)">查看详情</a>
</template>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total"
:total="total"
v-model:page="pagination.page"
v-model:limit="pagination.limit"
@pagination="getList"
/>
</div>
</el-card>
<!-- 详情模态框-->
<el-dialog v-model="modals" class="tableBox" title="查看详情" :close-on-click-modal="false" width="720px">
<el-table :data="tabList3" v-loading="loading2" empty-text="暂无数据" highlight-current-row max-height="600">
<el-table-column label="用户ID" width="80">
<template #default="scope">
<span>{{ scope.row.uid }}</span>
</template>
</el-table-column>
<el-table-column label="用户头像" min-width="90">
<template #default="scope">
<div class="tabBox_img" v-viewer>
<img v-lazy="scope.row.avatar" />
</div>
</template>
</el-table-column>
<el-table-column label="砍价金额" min-width="130">
<template #default="scope">
<span>{{ scope.row.price }}</span>
</template>
</el-table-column>
<el-table-column label="砍价时间" min-width="130">
<template #default="scope">
<span>{{ scope.row.add_time }}</span>
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { ElMessage } from 'element-plus';
import cardsData from '@/components/cards/cards';
import {
getbargainStatistics,
getbargainStatisticsPeople,
getbargainStatisticsOrder,
bargainUserInfoApi,
} from '@/api/marketing';
import setting from '@/setting';
defineOptions({ name: 'bargainStatistics' });
const route = useRoute();
const routePre = setting.routePre;
const modals = ref(false);
const tabList3 = ref([]);
const loading2 = ref(false);
const grid = {
xl: 7,
lg: 7,
md: 12,
sm: 24,
xs: 24,
};
const id = ref(0);
const tbody = ref([]);
const labelWidth = '80px';
const total = ref(0);
const tabs = [
{
type: '0',
label: '发起砍价',
},
{
type: '1',
label: '活动订单',
},
];
const currentTab = ref(0);
const loading = ref(false);
const thead = [
{
title: '头像',
slot: 'avatar',
},
{
title: '发起用户',
key: 'nickname',
},
{
title: '发起时间',
key: 'add_time',
},
{
title: '帮砍人数',
key: 'already_num',
},
{
title: '结束时间',
key: 'datatime',
},
{
title: '砍价状态',
slot: 'status',
},
{
title: '操作',
slot: 'action',
},
];
const thead2 = [
{
title: '订单号',
key: 'order_id',
},
{
title: '用户',
key: 'real_name',
},
{
title: '订单状态',
key: 'status',
},
{
title: '订单支付金额',
key: 'pay_price',
},
{
title: '订单商品数',
key: 'total_num',
},
{
title: '下单时间',
key: 'add_time',
},
{
title: '支付时间',
key: 'pay_time',
},
];
const cardLists = ref([
{
col: 4,
count: 0,
name: '活动参与人数(人)',
className: 'iconcanyurenshu',
},
{
col: 4,
count: 0,
name: '砍价人数(人)',
className: 'iconxiadanrenshu',
},
{
col: 4,
count: 0,
name: '发起砍价数',
className: 'icontuiguangrenshu',
},
{
col: 4,
count: 0,
name: '砍价成功数',
className: 'iconkanjiachenggong',
},
{
col: 4,
count: 0,
name: '支付订单额(元)',
className: 'iconzhifudingdan',
},
{
col: 4,
count: 0,
name: '支付人数(人)',
className: 'iconzhifurenshu',
},
]);
const pagination = ref({
page: 1,
limit: 15,
real_name: '',
status: '',
});
const type = ref('0');
const rows = ref({});
id.value = route.params.id;
getStatistics(id.value);
getList();
function getStatistics(id) {
getbargainStatistics(id).then((res) => {
let arr = ['people_count', 'spread_count', 'start_count', 'success_count', 'pay_price', 'pay_count', 'pay_rate'];
cardLists.value.map((i, index) => {
i.count = res.data[arr[index]];
});
});
}
function getList() {
loading.value = true;
if (type.value == 0) {
getbargainStatisticsPeople(id.value, pagination.value).then((res) => {
loading.value = false;
const { count, list } = res.data;
total.value = count;
tbody.value = list;
});
} else {
getbargainStatisticsOrder(id.value, pagination.value).then((res) => {
loading.value = false;
const { count, list } = res.data;
total.value = count;
tbody.value = list;
});
}
}
function onClickTab() {
pagination.value.page = 1;
pagination.value.real_name = '';
pagination.value.status = '';
getList();
}
function searchList() {
pagination.value.page = 1;
getList();
}
function Info(row) {
modals.value = true;
rows.value = row;
bargainUserInfoApi(row.id)
.then(async (res) => {
let data = res.data;
tabList3.value = data.list;
loading.value = false;
})
.catch((res) => {
loading.value = false;
ElMessage.error(res.msg);
});
}
</script>
<style lang="scss" scoped>
.cl {
margin-right: 20px;
}
.code-row-bg {
display: flex;
flex-wrap: nowrap;
}
.code-row-bg .ivu-mt {
width: 100%;
margin: 0 5px;
}
.ech-box {
margin-top: 10px;
}
.change-style {
border: 1px solid #ccc;
border-radius: 15px;
padding: 0px 10px;
cursor: pointer;
}
.table-head {
display: flex;
justify-content: space-between;
align-items: center;
}
.return {
margin-bottom: 6px;
}
:deep(.ivu-tabs-nav-scroll) {
background-color: #fff;
padding-top: 5px;
}
.tabBox_img {
width: 36px;
height: 36px;
border-radius: 4px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
</style>

View File

@ -1,125 +0,0 @@
/**
* 砍价扩展路由
*/
import setting from '@/setting';
const routePre = setting.routePre;
const pre = 'bargain_';
export default [
// ===== 新路径 =====
{
path: routePre + '/marketing/bargain/product',
section: 'bargain',
action: 'product',
meta: {
auth: ['marketing-store_bargain'],
title: '砍价商品',
},
component: () => import('@/layout'),
children: [
{
path: '',
name: `${pre}product`,
component: () => import('./pages/product/index.vue'),
meta: {
auth: ['marketing-store_bargain'],
title: '砍价商品',
},
},
],
},
{
path: routePre + '/marketing/bargain/create/:id?/:copy?',
section: 'bargain',
action: 'create',
meta: {
auth: ['marketing-store_bargain-create'],
title: '添加/编辑砍价商品',
},
component: () => import('@/layout'),
children: [
{
path: '',
name: `${pre}create`,
component: () => import('./pages/create/index.vue'),
meta: {
auth: ['marketing-store_bargain-create'],
title: '添加/编辑砍价商品',
activeMenu: routePre + '/marketing/bargain/product',
},
},
],
},
{
path: routePre + '/marketing/bargain/pink',
section: 'bargain',
action: 'pink',
meta: {
auth: ['marketing-store_bargain-bargain_list'],
title: '砍价列表',
},
component: () => import('@/layout'),
children: [
{
path: '',
name: `${pre}pink`,
component: () => import('./pages/pink/index.vue'),
meta: {
auth: ['marketing-store_bargain-bargain_list'],
title: '砍价列表',
},
},
],
},
{
path: routePre + '/marketing/bargain/statistics/:id',
section: 'bargain',
action: 'statistics',
meta: {
auth: ['marketing-store_bargain-statistics'],
title: '砍价统计',
},
component: () => import('@/layout'),
children: [
{
path: '',
name: `${pre}statistics`,
component: () => import('./pages/statistics/index.vue'),
meta: {
auth: ['marketing-store_bargain-statistics'],
title: '砍价统计',
activeMenu: routePre + '/marketing/bargain/product',
},
},
],
},
// ===== 兼容旧路由(无组件,仅用于菜单选中) =====
{
path: routePre + '/marketing/store_bargain/index',
section: 'bargain',
action: 'product',
name: `${pre}legacyIndex`,
meta: { title: '砍价商品' },
},
{
path: routePre + '/marketing/store_bargain/bargain_list',
section: 'bargain',
action: 'pink',
name: `${pre}legacyPink`,
meta: { title: '砍价列表' },
},
{
path: routePre + '/marketing/store_bargain/create/:id?/:copy?',
section: 'bargain',
action: 'create',
name: `${pre}legacyCreate`,
meta: { title: '添加/编辑砍价商品' },
},
{
path: routePre + '/marketing/store_bargain/statistics/:id',
section: 'bargain',
action: 'statistics',
name: `${pre}legacyStatistics`,
meta: { title: '砍价统计' },
},
];

View File

@ -1,45 +0,0 @@
<template>
<div class="cardkey-app">
<div class="section-content">
<component :is="actionComponent" />
</div>
</div>
</template>
<script setup>
/**
* 卡密商品扩展根组件
* 由 PageContainer 动态加载,根据 action 切换子页面。
* 路由参数(editId/orderNo)由子页面从 $route.params 自行读取,路由 path 保持不变。
*/
import { computed } from 'vue';
import ProductList from './index.vue';
import ProductAdd from './add/index.vue';
import StockList from './stock/List.vue';
import OrderList from './order/List.vue';
import OrderDetail from './order/Detail.vue';
const actionComponents = {
list: ProductList,
add: ProductAdd,
stock: StockList,
order: OrderList,
order_detail: OrderDetail,
};
defineOptions({ name: 'CardkeyApp' });
const props = defineProps({
section: { type: String, default: 'cardkey' },
action: { type: String, default: 'list' },
params: { type: Object, default: () => ({}) },
});
const actionComponent = computed(() => actionComponents[props.action] || ProductList);
</script>
<style scoped>
.cardkey-app {
height: 100%;
}
</style>

View File

@ -1,73 +0,0 @@
<template>
<div class="cardkey-setting">
<el-form label-width="120px" label-position="right">
<el-form-item label="卡密类型:" prop="card_type">
<el-radio-group v-model="formValidate.card_type">
<el-radio label="single" value="single">单卡密</el-radio>
<el-radio label="combo" value="combo">组合卡密</el-radio>
</el-radio-group>
<div class="tip">单卡密:一个卡密对应一个密码;组合卡密:卡号 + 密码组合</div>
</el-form-item>
<el-form-item label="卡密有效期:" prop="valid_days">
<el-input-number v-model="formValidate.valid_days" :min="0" :precision="0" :controls="false" />
<span class="tip"> 天(0 表示永久有效)</span>
</el-form-item>
<el-form-item label="使用说明:" prop="usage_tip">
<el-input
type="textarea"
:rows="5"
v-model="formValidate.usage_tip"
placeholder="填写卡密的使用方法说明,下单后展示给买家"
></el-input>
</el-form-item>
<el-form-item label="卡密库存:">
<span class="tip">当前可用卡密 {{ cardCount }} 条,请在商品列表页导入卡密内容</span>
</el-form-item>
</el-form>
</div>
</template>
<script setup>
import { ref } from 'vue';
import request from '@/libs/request';
defineOptions({ name: 'cardkeySetting' });
const props = defineProps({
formValidate: {
type: Object,
required: true,
},
// 当前商品ID,用于展示卡密库存数量
productId: {
type: Number,
default: 0,
},
});
const cardCount = ref(0);
// 加载当前商品的卡密库存数量(编辑时展示)
async function loadCardCount() {
try {
const res = await request({
url: `cardkey/product/${props.productId}/card_list`,
method: 'get',
});
cardCount.value = Array.isArray(res.data) ? res.data.length : 0;
} catch (e) {
cardCount.value = 0;
}
}
// created:编辑时加载卡密库存
if (props.productId) {
loadCardCount();
}
</script>
<style scoped>
.cardkey-setting {
padding: 20px;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -1,69 +0,0 @@
import request from '@/libs/request';
export function cardkeyProductList(params) {
return request({
url: 'cardkey/product/list',
method: 'get',
params,
});
}
export function cardkeyProductDetail(id) {
return request({
url: `cardkey/product/${id}`,
method: 'get',
});
}
export function cardkeyProductSave(data) {
return request({
url: 'cardkey/product',
method: 'post',
data,
});
}
export function cardkeyProductUpdate(id, data) {
return request({
url: `cardkey/product/${id}`,
method: 'put',
data,
});
}
export function cardkeyProductDelete(id) {
return request({
url: `cardkey/product/${id}`,
method: 'delete',
});
}
export function cardkeyProductCardList(id) {
return request({
url: `cardkey/product/${id}/card_list`,
method: 'get',
});
}
export function cardkeyProductImportCard(id, cards) {
return request({
url: `cardkey/product/${id}/import_card`,
method: 'post',
data: { cards },
});
}
export function cardkeyOrderList(params) {
return request({
url: 'cardkey/order/list',
method: 'get',
params,
});
}
export function cardkeyOrderDetail(orderNo) {
return request({
url: `cardkey/order/${orderNo}`,
method: 'get',
});
}

View File

@ -1,12 +0,0 @@
{
"key": "cardkey",
"title": "卡密商品",
"version": "1.0.0",
"pages": [
{ "path": "list", "title": "卡密商品列表" },
{ "path": "add", "title": "添加/编辑卡密商品" },
{ "path": "stock", "title": "卡密库存" },
{ "path": "order", "title": "卡密订单" },
{ "path": "order_detail", "title": "卡密订单详情" }
]
}

View File

@ -1,9 +0,0 @@
/**
* 卡密商品扩展 UMD 入口
* 构建后通过 PageContainer 动态加载(window.__ext_cardkey)
*/
import CardkeyApp from './App.vue';
import routes from './routes.js';
export default CardkeyApp;
export { routes };

View File

@ -1,778 +0,0 @@
<template>
<div class="article-manager">
<el-card :bordered="false" shadow="never" :body-style="{ padding: 0 }">
<div class="padding-add">
<el-form :model="artFrom" label-width="80px" label-position="right" inline @submit.prevent>
<div class="acea-row search-form">
<div class="search-form-box">
<el-form-item label="商品搜索:" label-for="store_name">
<el-input
clearable
placeholder="请输入商品名称/关键字/ID"
v-model="artFrom.store_name"
class="form_content_width"
/>
</el-form-item>
<el-form-item label="商品分类:" label-for="pid">
<el-cascader
v-model="artFrom.cate_id"
:options="treeSelect"
:props="{ multiple: false, emitPath: false, checkStrictly: true }"
clearable
class="form_content_width"
></el-cascader>
</el-form-item>
<template v-if="collapse">
<el-form-item label="会员专属:">
<el-select v-model="artFrom.vip_product" clearable placeholder="全部" class="form_content_width">
<el-option label="全部" value="" />
<el-option label="不限制" value="0" />
<el-option label="仅会员可见可买" value="1" />
<el-option label="所有人可见仅会员可买" value="2" />
</el-select>
</el-form-item>
<el-form-item label="添加时间:">
<date-range-picker
class="form_range_content_width"
clearable
v-model="timeVal"
:editable="false"
@change="onchangeTime"
format="YYYY/MM/DD"
value-format="YYYY/MM/DD"
></date-range-picker>
</el-form-item>
<el-form-item label="库存:" label-for="store_name">
<el-input
clearable
placeholder="最小值"
v-model="artFrom.stock_s[0]"
class="form_range_content_width"
/>
~
<el-input
clearable
placeholder="最大值"
v-model="artFrom.stock_s[1]"
class="form_range_content_width"
/>
</el-form-item>
<el-form-item label="价格:" label-for="store_name">
<el-input
clearable
placeholder="最小值"
v-model="artFrom.price_s[0]"
class="form_range_content_width"
/>
~
<el-input
clearable
placeholder="最大值"
v-model="artFrom.price_s[1]"
class="form_range_content_width"
/>
</el-form-item>
<el-form-item label="销量:" label-for="store_name">
<el-input
clearable
placeholder="最小值"
v-model="artFrom.sales_s[0]"
class="form_range_content_width"
/>
~
<el-input
clearable
placeholder="最大值"
v-model="artFrom.sales_s[1]"
class="form_range_content_width"
/>
</el-form-item>
</template>
</div>
<div class="search-form-sub">
<el-button type="primary" v-db-click @click="userSearchs">查询</el-button>
<el-button class="ResetSearch" v-db-click @click="reset">重置</el-button>
<a class="ivu-ml-8 font12 ml10" v-db-click @click="collapse = !collapse">
<template v-if="!collapse"> 展开 <i class="el-icon-arrow-down" /> </template>
<template v-else> 收起 <i class="el-icon-arrow-up" /> </template>
</a>
</div>
</div>
</el-form>
</div>
</el-card>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16" :body-style="{ padding: '20px 20px 20px' }">
<div class="Button">
<router-link v-auth="['admin-cardkey-product-save']" :to="$routeProStr + '/product/cardkey/add'"
><el-button type="primary" class="mr14">添加卡密商品</el-button></router-link
>
<el-dropdown class="bnt mr14" @command="batchSelect">
<el-button>批量修改<i class="el-icon-arrow-down el-icon--right"></i></el-button>
<template #dropdown
><el-dropdown-menu>
<el-dropdown-item :command="1">商品分类</el-dropdown-item>
<el-dropdown-item :command="3">购买送积分</el-dropdown-item>
<el-dropdown-item :command="4">购买送优惠券</el-dropdown-item>
<el-dropdown-item :command="5">关联用户标签</el-dropdown-item>
<el-dropdown-item v-if="artFrom.type === '1'" :command="7">批量下架</el-dropdown-item>
<el-dropdown-item v-if="artFrom.type === '2'" :command="8">批量上架</el-dropdown-item>
<el-dropdown-item :command="9">设置商品标签</el-dropdown-item>
<el-dropdown-item v-if="artFrom.type !== '6'" :command="11">移到回收站</el-dropdown-item>
<el-dropdown-item v-if="artFrom.type == '6'" :command="12">恢复商品</el-dropdown-item>
</el-dropdown-menu></template
>
</el-dropdown>
</div>
<el-table
ref="table"
:data="tableList"
class="mt14"
v-loading="loading"
highlight-current-row
:row-key="getRowKey"
@selection-change="handleSelectRow"
empty-text="暂无数据"
>
<el-table-column type="selection" width="60" :reserve-selection="true"> </el-table-column>
<el-table-column label="商品ID" width="80">
<template #default="scope">
<span>{{ scope.row.id }}</span>
</template>
</el-table-column>
<el-table-column label="商品图" min-width="90">
<template #default="scope">
<div class="tabBox_img" v-viewer>
<img v-lazy="scope.row.image" />
</div>
</template>
</el-table-column>
<el-table-column label="商品名称" min-width="250">
<template #default="scope">
<span>{{ scope.row.store_name }}</span>
</template>
</el-table-column>
<el-table-column label="卡密类型" min-width="100">
<template #default="scope">
<span>{{ scope.row.card_type === 'combo' ? '组合卡密' : '单卡密' }}</span>
</template>
</el-table-column>
<el-table-column label="卡密数量" min-width="100">
<template #default="scope">
<span>{{ scope.row.card_count || 0 }}</span>
</template>
</el-table-column>
<el-table-column label="商品售价" min-width="100">
<template #default="scope">
<span>{{ scope.row.price }}</span>
</template>
</el-table-column>
<el-table-column label="销量" min-width="100">
<template #default="scope">
<span>{{ scope.row.sales }}</span>
</template>
</el-table-column>
<el-table-column label="排序" min-width="100">
<template #default="scope">
<span>{{ scope.row.sort }}</span>
</template>
</el-table-column>
<el-table-column label="状态" min-width="100">
<template #default="scope">
<el-switch
class="defineSwitch"
:active-value="1"
:inactive-value="0"
v-model="scope.row.is_show"
:value="scope.row.is_show"
:disabled="scope.row.stop_status ? true : false"
@change="changeSwitch(scope.row)"
size="large"
active-text="上架"
inactive-text="下架"
>
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" minWidth="100">
<template #default="scope">
<a v-db-click @click="edit(scope.row)">编辑</a>
<el-divider direction="vertical"></el-divider>
<el-dropdown>
<span class="el-dropdown-link">更多<i class="el-icon-arrow-down el-icon--right"></i> </span>
<template #dropdown
><el-dropdown-menu>
<el-dropdown-item>
<router-link :to="{ path: $routeProStr + '/product/product_reply/' + scope.row.id }"
><a>查看评论</a></router-link
>
</el-dropdown-item>
<el-dropdown-item @click="openModal(scope.row, 'vipPriceSet')">会员价管理</el-dropdown-item>
<el-dropdown-item @click="openModal(scope.row, 'brokerageSet')">佣金管理</el-dropdown-item>
<el-dropdown-item v-if="artFrom.type === '6'" @click="del(scope.row, '恢复商品', scope.$index)"
>恢复商品</el-dropdown-item
>
<el-dropdown-item v-if="artFrom.type === '6'" @click="fullDel(scope.row, '彻底删除', scope.$index)"
>彻底删除</el-dropdown-item
>
<el-dropdown-item v-else @click="del(scope.row, '移入回收站', scope.$index)"
>移到回收站</el-dropdown-item
>
</el-dropdown-menu></template
>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total"
:total="total"
v-model:page="artFrom.page"
v-model:limit="artFrom.limit"
@pagination="getDataList"
/>
</div>
</el-card>
<brokerageSet ref="brokerageSetRef" :productId="productId"></brokerageSet>
<vipPriceSet ref="vipPriceSetRef" :productId="productId"></vipPriceSet>
<el-dialog
v-model="batchModal"
class="batch-box"
title="批量设置"
:show-close="true"
:close-on-click-modal="false"
width="540px"
>
<el-form
class="batchFormData"
:rules="ruleBatch"
:model="batchFormData"
label-width="90px"
label-position="right"
@submit.prevent
>
<el-row :gutter="24">
<el-col :span="24" v-if="batchType == 1">
<el-form-item label="商品分类:" prop="cate_id">
<el-cascader
v-model="batchFormData.cate_id"
:options="treeSelect"
:props="{ multiple: true, emitPath: false, checkStrictly: true }"
clearable
style="width: 400px"
></el-cascader>
</el-form-item>
</el-col>
<el-col :span="24" v-if="[3, 4, 5].includes(batchType)">
<el-form-item label="赠送积分:" prop="give_integral" v-if="batchType == 3">
<el-input-number
:controls="false"
v-model="batchFormData.give_integral"
:min="0"
:max="9999999999"
placeholder="请输入积分"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="赠送优惠券:" v-if="batchType == 4">
<div v-if="couponName.length" class="mb20">
<el-tag closable v-for="(item, index) in couponName" :key="index" @close="handleClose(item)">{{
item.title
}}</el-tag>
</div>
<el-button type="primary" v-db-click @click="addCoupon">添加优惠券</el-button>
</el-form-item>
<el-form-item label="关联标签:" prop="label_id" v-if="batchType == 5">
<div class="acea-row label_width">
<div class="labelInput acea-row row-between-wrapper" v-db-click @click="openLabel">
<div style="width: auto">
<div v-if="dataLabel.length">
<el-tag
class="m-r-2"
closable
v-for="(item, index) in dataLabel"
@close="closeLabel(item)"
:key="index"
>{{ item.label_name }}</el-tag
>
</div>
<span class="span" v-else>选择用户关联标签</span>
</div>
<div class="iconfont iconxiayi"></div>
</div>
</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer
><span class="dialog-footer">
<el-button v-db-click @click="clearBatchData">取 消</el-button>
<el-button type="primary" v-db-click @click="batchSub">确 定</el-button>
</span></template
>
</el-dialog>
<el-dialog
v-model="labelShow"
title="请选择用户标签"
width="540px"
:show-close="true"
:close-on-click-modal="false"
>
<userLabel @activeData="activeData" @close="labelClose"></userLabel>
</el-dialog>
<el-dialog v-model="tagShow" title="请选择商品标签" :show-close="true" width="540px" :close-on-click-modal="false">
<goodsLabel :defaultLabelList="goodsLabelList" @activeLabel="activeGoodsLabel" @close="labelClose"></goodsLabel>
</el-dialog>
<coupon-list ref="couponTemplates" @nameId="nameId" :couponids="batchFormData.coupon_ids"></coupon-list>
</div>
</template>
<script setup>
import { nextTick, onActivated, onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import { cardkeyProductList } from '@/pages/plugin/cardkey/cardkeyProduct';
import {
PostgoodsIsShow,
cascaderListApi,
productShowApi,
productUnshowApi,
batchSetting,
productLabelUseListApi,
} from '@/api/product';
import userLabel from '@/components/labelList';
import goodsLabel from '@/components/goodsLabel';
import couponList from '@/components/couponList';
import brokerageSet from '../../../pages/product/components/brokerageSet.vue';
import vipPriceSet from '../../../pages/product/components/vipPriceSet.vue';
import { modalSure } from '@/utils/public';
import setting from '@/setting';
defineOptions({ name: 'cardkeyProduct' });
const router = useRouter();
const routePre = setting.routePre;
const table = ref(null);
const brokerageSetRef = ref(null);
const vipPriceSetRef = ref(null);
const couponTemplates = ref(null);
const batchModal = ref(false);
const labelShow = ref(false);
const tagShow = ref(false);
const batchType = ref(1);
const batchFormData = ref({
cate_id: [],
give_integral: 0,
label_id: [],
coupon_ids: [],
});
const ruleBatch = ref({});
const couponName = ref([]);
const dataLabel = ref([]);
const artFrom = ref({
page: 1,
limit: 15,
store_name: '',
cate_id: '',
vip_product: '',
sales_s: ['', ''],
stock_s: ['', ''],
price_s: ['', ''],
time: '',
type: '1',
});
const tableList = ref([]);
const total = ref(0);
const loading = ref(false);
const treeSelect = ref([]);
const ids = ref([]);
const multipleSelection = ref([]);
const productId = ref(0);
const goodsLabelList = ref([]);
const timeVal = ref([]);
const collapse = ref(false);
function refreshAll() {
goodsCategory();
getLabelList();
getDataList();
}
onMounted(() => {
// 首次挂载加载数据(activated 依赖 keep-alive 缓存,未缓存时不会触发)
refreshAll();
});
onActivated(() => {
// keep-alive 缓存后再次进入时刷新数据
refreshAll();
});
function onchangeTime(e) {
timeVal.value = e;
artFrom.value.time = timeVal.value ? timeVal.value.join('-') : '';
artFrom.value.page = 1;
getDataList();
}
function getLabelList() {
productLabelUseListApi()
.then((res) => {
res.data.map((el) => {
if (el.list && el.list.length) {
el.list.map((label) => {
label.active = false;
});
}
});
goodsLabelList.value = res.data;
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function openModal(row, type) {
productId.value = row.id;
if (type === 'vipPriceSet') {
vipPriceSetRef.value.visible = true;
} else {
brokerageSetRef.value.visible = true;
}
}
function batchSub() {
let data = batchFormData.value;
data.ids = ids.value;
data.type = batchType.value;
let activeIds = [];
dataLabel.value.forEach((item) => {
activeIds.push(item.id);
});
data.label_id = activeIds;
batchSetting(data)
.then((res) => {
ElMessage.success(res.msg);
getDataList();
clearBatchData(false);
ids.value = [];
})
.catch((err) => {
ElMessage.error(err.msg);
});
}
function clearBatchData(status) {
if (!status) {
batchFormData.value = {
cate_id: [],
give_integral: null,
label_id: [],
coupon_ids: [],
label_list: [],
};
dataLabel.value = [];
}
batchModal.value = false;
table.value.clearSelection();
}
function batchSelect(type) {
if (!ids.value.length) {
ElMessage.warning('请选择要修改的商品');
} else if (type === 7) {
onDismount();
} else if (type === 8) {
onShelves();
} else if (type === 9) {
batchType.value = type;
tagShow.value = true;
} else if (type === 11) {
batchGoodsSetting('全部移到回收站', 1);
} else if (type === 12) {
batchGoodsSetting('恢复选中商品', 2);
} else {
batchType.value = type;
batchModal.value = true;
}
}
function batchGoodsSetting(tit, type) {
let url = type == 1 ? 'product/product/batch_delete' : 'product/product/batch_recover';
let delfromData = {
title: tit,
url,
method: 'post',
ids: {
ids: ids.value,
},
un: 1,
};
modalSure(delfromData)
.then((res) => {
ElMessage.success(res.msg);
getDataList();
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function activeData(dataLabelVal) {
labelShow.value = false;
dataLabel.value = dataLabelVal;
}
function nameId(id, names) {
batchFormData.value.coupon_ids = id;
couponName.value = unique(names);
}
function handleClose(name) {
let index = couponName.value.indexOf(name);
couponName.value.splice(index, 1);
batchFormData.value.coupon_ids.splice(index, 1);
}
function unique(arr) {
const res = new Map();
return arr.filter((arr) => !res.has(arr.id) && res.set(arr.id, 1));
}
function labelClose() {
labelShow.value = false;
tagShow.value = false;
}
function activeGoodsLabel(data) {
tagShow.value = false;
batchFormData.value.label_list = Array.from(new Set(data));
batchSub();
}
function openLabel() {
labelShow.value = true;
}
function closeLabel(label) {
let index = dataLabel.value.indexOf(dataLabel.value.filter((d) => d.id == label.id)[0]);
dataLabel.value.splice(index, 1);
}
function addCoupon() {
couponTemplates.value.isTemplate = true;
couponTemplates.value.tableList();
}
function onShelves() {
if (ids.value.length === 0) {
ElMessage.warning('请选择要上架的商品');
} else {
let data = {
ids: ids.value,
};
productShowApi(data)
.then((res) => {
ElMessage.success(res.msg);
getDataList();
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
}
function onDismount() {
if (ids.value.length === 0) {
ElMessage.warning('请选择要下架的商品');
} else {
let data = {
ids: ids.value,
};
productUnshowApi(data)
.then((res) => {
ElMessage.success(res.msg);
artFrom.value.page = 1;
getDataList();
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
}
function getRowKey(row) {
return row.id;
}
function handleSelectRow(selection) {
const uniqueArr = [];
const idArr = [];
for (let i = 0; i < selection.length; i++) {
const item = selection[i];
if (!idArr.includes(item.id)) {
uniqueArr.push(item);
idArr.push(item.id);
}
}
ids.value = idArr;
multipleSelection.value = uniqueArr;
}
function goodsCategory() {
cascaderListApi(1)
.then((res) => {
treeSelect.value = res.data;
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function getDataList() {
loading.value = true;
artFrom.value.cate_id = artFrom.value.cate_id || '';
cardkeyProductList(artFrom.value)
.then((res) => {
tableList.value = res.data.list || [];
total.value = res.data.count || 0;
nextTick(() => {
showSelectData();
});
loading.value = false;
})
.catch((res) => {
loading.value = false;
ElMessage.error(res.msg || '获取列表失败');
});
}
function showSelectData() {
if (multipleSelection.value.length > 0) {
tableList.value.forEach((row) => {
multipleSelection.value.forEach((item) => {
if (row.id === item.id) {
table.value.toggleRowSelection(item, true);
}
});
});
}
}
function userSearchs() {
artFrom.value.page = 1;
getDataList();
}
function changeSwitch(row) {
PostgoodsIsShow(row.id, row.is_show)
.then((res) => {
ElMessage.success(res.msg);
getDataList();
})
.catch((res) => {
row.is_show = !row.is_show ? 1 : 0;
ElMessage.error(res.msg);
});
}
function edit(row) {
router.push({ path: routePre + '/product/cardkey/add/' + row.id });
}
function del(row, tit, num) {
let delfromData = {
title: tit,
num: num,
url: `product/product/${row.id}`,
method: 'DELETE',
ids: '',
un: 1,
};
modalSure(delfromData)
.then((res) => {
ElMessage.success(res.msg);
tableList.value.splice(num, 1);
getDataList();
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function fullDel(row, tit, num) {
let delfromData = {
title: tit,
num: num,
url: `product/full_del/${row.id}`,
method: 'DELETE',
};
modalSure(delfromData)
.then((res) => {
ElMessage.success(res.msg);
tableList.value.splice(num, 1);
getDataList();
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function reset() {
artFrom.value = {
page: 1,
limit: 15,
store_name: '',
cate_id: '',
vip_product: '',
sales_s: ['', ''],
stock_s: ['', ''],
price_s: ['', ''],
time: '',
type: '1',
};
timeVal.value = [];
getDataList();
}
</script>
<style scoped>
.mt16 {
margin-top: 16px;
}
.tabBox_img {
width: 36px;
height: 36px;
border-radius: 4px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
.labelInput {
border: 1px solid #dcdee2;
padding: 0 15px 0 10px;
border-radius: 5px;
min-height: 30px;
cursor: pointer;
font-size: 12px;
.span {
color: #c5c8ce;
}
.iconxiayi {
margin-left: 5px;
font-size: 12px;
}
}
.el-dropdown-link {
cursor: pointer;
color: var(--prev-color-primary);
font-size: 12px;
}
.el-icon-arrow-down {
font-size: 12px;
}
.el-dropdown-menu__item {
a {
color: #606266;
}
}
.label_width {
width: 400px;
}
.search-form {
display: flex;
justify-content: space-between;
.search-form-box {
display: flex;
flex-wrap: wrap;
flex: 1;
}
.search-form-sub {
display: flex;
align-items: baseline;
}
}
.batch-box {
:deep(.ivu-modal-body) {
overflow: auto;
min-height: 350px;
}
}
</style>

View File

@ -1,627 +0,0 @@
<template>
<div>
<div v-loading="detailLoading">
<div v-if="detailData" class="order-detail">
<div class="head">
<div class="full">
<img class="order_icon" :src="orderIcon" alt="" />
<div class="text">
<div class="title">卡密商品订单</div>
<div>订单号:{{ detailData.order_no }}</div>
</div>
</div>
<ul class="list">
<li class="item">
<div class="title">订单状态</div>
<div>
{{ getStatusLabel(detailData.status_code) }}
<span v-if="detailData.refund_status == 1" style="color: #ed4014">
{{ detailData.is_all_refund ? '退款中' : '部分退款中' }}
</span>
</div>
</li>
<li class="item">
<div class="title">订单金额</div>
<div class="value1">¥{{ detailData.total_price || '0.00' }}</div>
</li>
<li class="item" v-if="detailData.refund_status == 2">
<div class="title">实际退款</div>
<div>¥{{ detailData.refunded_price || '0.00' }}</div>
</li>
<li class="item">
<div class="title">支付方式</div>
<div>{{ payType(detailData.pay_type) }}</div>
</li>
<li class="item">
<div class="title">支付时间</div>
<div>{{ detailData.pay_time ? formatDate(detailData.pay_time) : '--' }}</div>
</li>
</ul>
</div>
<el-tabs type="border-card" v-model="activeTab">
<el-tab-pane label="订单信息" name="detail">
<div class="section" v-if="orderItems.length">
<div class="section-title">商品信息</div>
<el-table :data="orderItems" stripe class="goods-table">
<el-table-column label="商品" min-width="300">
<template #default="{ row }">
<div class="goods-cell">
<el-image :src="row.image" class="goods-img" />
<div class="goods-meta">
<p class="goods-name">{{ row.store_name }}</p>
<p v-if="row.sku" class="goods-sku">{{ row.sku }}</p>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="单价" width="100">
<template #default="{ row }">¥{{ row.true_price || row.price }}</template>
</el-table-column>
<el-table-column label="数量" width="80" prop="cart_num" />
<el-table-column label="小计" width="120">
<template #default="{ row }"
>¥{{ (parseFloat(row.true_price || row.price) * row.cart_num).toFixed(2) }}</template
>
</el-table-column>
</el-table>
</div>
<div class="section" v-if="detailData.card_info && detailData.card_info.length">
<div class="section-title">卡密信息</div>
<el-table :data="detailData.card_info" stripe class="goods-table">
<el-table-column label="卡号" min-width="200">
<template #default="{ row }">{{ row.card_no || '--' }}</template>
</el-table-column>
<el-table-column label="卡密" min-width="200">
<template #default="{ row }">
<div class="card-key-wrapper">
<span class="card-key">{{ row.card_key || '--' }}</span>
<a v-db-click @click="copyCardKey(row.card_key)">复制</a>
</div>
</template>
</el-table-column>
<el-table-column label="有效期" width="150">
<template #default="{ row }">{{
row.expire_time ? formatDate(row.expire_time) : '永久有效'
}}</template>
</el-table-column>
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.status == 1 ? 'success' : 'warning'">{{
row.status == 1 ? '已使用' : '未使用'
}}</el-tag>
</template>
</el-table-column>
</el-table>
</div>
<div class="section">
<div class="section-title">用户信息</div>
<el-row :gutter="24" class="detail-grid">
<el-col :span="8"
><div class="detail-item">
<span class="label">用户名称:</span
><span class="value">{{ detailData.real_name || detailData.contact?.name || '--' }}</span>
</div></el-col
>
<el-col :span="8"
><div class="detail-item">
<span class="label">联系电话:</span
><span class="value">{{ detailData.user_phone || detailData.contact?.phone || '--' }}</span>
</div></el-col
>
</el-row>
</div>
<div class="section">
<div class="section-title">订单信息</div>
<el-row :gutter="24" class="detail-grid">
<el-col :span="8"
><div class="detail-item">
<span class="label">创建时间:</span
><span class="value">{{ formatDate(detailData.add_time) }}</span>
</div></el-col
>
<el-col :span="8"
><div class="detail-item">
<span class="label">商品总数:</span><span class="value">{{ detailData.total_num }}</span>
</div></el-col
>
<el-col :span="8"
><div class="detail-item">
<span class="label">商品总价:</span><span class="value">¥{{ detailData.total_price }}</span>
</div></el-col
>
<el-col :span="8"
><div class="detail-item">
<span class="label">优惠券金额:</span
><span class="value">-¥{{ detailData.coupon_price || '0.00' }}</span>
</div></el-col
>
<el-col :span="8"
><div class="detail-item">
<span class="label">积分抵扣:</span
><span class="value">-¥{{ detailData.deduction_price || '0.00' }}</span>
</div></el-col
>
<el-col :span="8"
><div class="detail-item">
<span class="label">会员等级优惠:</span
><span class="value">-¥{{ detailData.member_level_discount || '0.00' }}</span>
</div></el-col
>
<el-col :span="8"
><div class="detail-item">
<span class="label">付费会员优惠:</span
><span class="value">-¥{{ detailData.vip_discount || '0.00' }}</span>
</div></el-col
>
<el-col :span="8"
><div class="detail-item">
<span class="label price-label">实际支付:</span
><span class="value price-value">¥{{ detailData.pay_price || '0.00' }}</span>
</div></el-col
>
</el-row>
</div>
<div
class="section"
v-if="
detailData.one_brokerage ||
detailData.two_brokerage ||
detailData.staff_brokerage ||
detailData.agent_brokerage ||
detailData.division_brokerage
"
>
<div class="section-title">返佣信息(金额|用户UID)</div>
<el-row :gutter="24" class="detail-grid">
<el-col :span="8"
><div class="detail-item">
<span class="label">一级佣金:</span
><span class="value"
>{{ detailData.one_brokerage || '0.00' }} | {{ detailData.spread_uid || '0' }}</span
>
</div></el-col
>
<el-col :span="8"
><div class="detail-item">
<span class="label">二级佣金:</span
><span class="value"
>{{ detailData.two_brokerage || '0.00' }} | {{ detailData.spread_two_uid || '0' }}</span
>
</div></el-col
>
<el-col :span="8"
><div class="detail-item">
<span class="label">员工返佣:</span
><span class="value"
>{{ detailData.staff_brokerage || '0.00' }} | {{ detailData.staff_id || '0' }}</span
>
</div></el-col
>
<el-col :span="8"
><div class="detail-item">
<span class="label">代理商佣金:</span
><span class="value"
>{{ detailData.agent_brokerage || '0.00' }} | {{ detailData.agent_id || '0' }}</span
>
</div></el-col
>
<el-col :span="8"
><div class="detail-item">
<span class="label">事业部佣金:</span
><span class="value"
>{{ detailData.division_brokerage || '0.00' }} | {{ detailData.division_id || '0' }}</span
>
</div></el-col
>
</el-row>
</div>
<div class="section" v-if="detailData.invoice && Object.keys(detailData.invoice).length">
<div class="section-title">发票信息</div>
<el-row :gutter="24" class="detail-grid">
<el-col :span="8"
><div class="detail-item">
<span class="label">发票抬头:</span
><span class="value">{{ detailData.invoice.name || '--' }}</span>
</div></el-col
>
<el-col :span="8" v-if="detailData.invoice.header_type === 2 && detailData.invoice.type === 1"
><div class="detail-item">
<span class="label">企业税号:</span
><span class="value">{{ detailData.invoice.duty_number || '--' }}</span>
</div></el-col
>
<el-col :span="8"
><div class="detail-item">
<span class="label">开票状态:</span
><span class="value">{{ detailData.invoice.is_invoice ? '已开票' : '未开票' }}</span>
</div></el-col
>
</el-row>
</div>
<div class="section">
<div class="section-title">买家留言</div>
<el-row :gutter="24" class="detail-grid">
<el-col :span="24"
><div class="detail-item">
<span class="value">{{ detailData.mark || '--' }}</span>
</div></el-col
>
</el-row>
</div>
<div class="section" v-if="detailData.custom_form && detailData.custom_form.length">
<div class="section-title">表单信息</div>
<el-row :gutter="24" class="detail-grid">
<template v-for="(item, idx) in detailData.custom_form" :key="idx">
<el-col :span="8" v-if="item.label !== 'img'">
<div class="detail-item">
<span class="label">{{ item.title }}:</span><span class="value">{{ item.value }}</span>
</div>
</el-col>
<el-col :span="24" v-else>
<div class="detail-item">
<span class="label">{{ item.title }}:</span>
<span class="value">
<img v-for="(img, i) in item.value" :key="i" :src="img" class="form-img" v-viewer />
</span>
</div>
</el-col>
</template>
</el-row>
</div>
<div class="section">
<div class="section-title">订单备注</div>
<el-row :gutter="24" class="detail-grid">
<el-col :span="24"
><div class="detail-item">
<span class="value">{{ detailData.remark || '--' }}</span>
</div></el-col
>
</el-row>
</div>
</el-tab-pane>
<el-tab-pane label="订单进度" name="timeline">
<div class="tl-section" v-if="detailData.timeline && detailData.timeline.length">
<el-timeline>
<el-timeline-item
v-for="(item, index) in detailData.timeline"
:key="index"
:timestamp="item.time"
:color="index === 0 ? '#409eff' : '#e4e7ed'"
>
<span class="tl-title">{{ item.title }}</span>
<span class="tl-desc">{{ item.desc }}</span>
</el-timeline-item>
</el-timeline>
</div>
<div v-else class="empty-tips">暂无订单进度</div>
</el-tab-pane>
</el-tabs>
</div>
<div v-else-if="!detailLoading" class="empty-tip">
<el-empty description="暂无订单数据"></el-empty>
</div>
</div>
</div>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { ElMessage } from 'element-plus';
import __asset_order_icon_0 from '@/assets/images/order_icon.png';
import request from '@/libs/request';
defineOptions({ name: 'cardkeyOrderDetail' });
const route = useRoute();
const detailData = ref(null);
const detailLoading = ref(false);
const activeTab = ref('detail');
const orderIcon = __asset_order_icon_0;
const orderItems = computed(() => {
if (!detailData.value) return [];
return detailData.value.order_items || detailData.value.cartInfo || [];
});
function loadDetail() {
const orderNo = route.params.orderNo;
if (!orderNo) return;
detailLoading.value = true;
request
.get('cardkey/order/' + orderNo)
.then((res) => {
detailData.value = res.data;
detailLoading.value = false;
})
.catch((res) => {
detailLoading.value = false;
ElMessage.error(res.msg || '加载订单详情失败');
});
}
function copyCardKey(key) {
if (!key) return;
const input = document.createElement('input');
input.value = key;
document.body.appendChild(input);
input.select();
document.execCommand('copy');
document.body.removeChild(input);
ElMessage.success('复制成功');
}
function getStatusLabel(code) {
const map = {
pending_payment: '待支付',
processing: '待处理',
completed: '已完成',
canceled: '已取消',
refunding: '退款中',
refunded: '已退款',
};
return map[code] || code || '--';
}
// 原 filters.payType
function payType(val) {
const obj = {
yue: '余额',
weixin: '微信支付',
alipay: '支付宝支付',
offline: '线下支付',
balance: '余额支付',
integral: '积分支付',
};
return obj[val] || val || '--';
}
function formatDate(ts) {
if (!ts) return '--';
const d = new Date(ts * 1000);
return `${d.getFullYear()}/${(d.getMonth() + 1).toString().padStart(2, '0')}/${d
.getDate()
.toString()
.padStart(2, '0')} ${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}:${d
.getSeconds()
.toString()
.padStart(2, '0')}`;
}
onMounted(() => {
loadDetail();
});
</script>
<style lang="scss" scoped>
:deep(.el-tabs--border-card > .el-tabs__header .el-tabs__item.is-active) {
border-bottom: none;
}
:deep(.el-tabs__item) {
height: 40px !important;
line-height: 40px !important;
}
.head {
padding: 0 30px 24px;
.full {
display: flex;
align-items: center;
.order_icon {
width: 60px;
height: 60px;
}
.text {
align-self: center;
flex: 1;
min-width: 0;
padding-left: 12px;
font-size: 13px;
color: #606266;
.title {
margin-bottom: 10px;
font-weight: 500;
font-size: 16px;
line-height: 16px;
color: rgba(0, 0, 0, 0.85);
}
}
}
.list {
display: flex;
margin-top: 20px;
overflow: hidden;
list-style: none;
padding: 0;
.item {
flex: none;
width: 200px;
font-size: 14px;
line-height: 14px;
color: rgba(0, 0, 0, 0.85);
.title {
margin-bottom: 12px;
font-size: 13px;
line-height: 13px;
color: #666666;
}
.value1 {
color: #f56022;
font-weight: bold;
}
}
}
}
:deep(.el-tabs--border-card > .el-tabs__content) {
padding: 0 35px;
}
:deep(.el-tabs--border-card > .el-tabs__header),
:deep(.el-tabs--border-card > .el-tabs__header .el-tabs__item:active) {
border: none;
height: 40px;
}
:deep(.el-tabs--border-card > .el-tabs__header .el-tabs__item.is-active) {
border: none;
border-top: 2px solid var(--prev-color-primary);
font-size: 13px;
font-weight: 500;
color: #303133;
line-height: 16px;
}
:deep(.el-tabs--border-card > .el-tabs__header .el-tabs__item) {
border: none;
}
:deep(.el-tabs--border-card > .el-tabs__header .el-tabs__item) {
margin-top: 0;
transition: none;
height: 40px !important;
line-height: 40px !important;
width: 92px !important;
font-size: 13px;
font-weight: 400;
color: #303133;
line-height: 16px;
}
:deep(.el-tabs--border-card) {
border: none;
box-shadow: none;
}
.section {
padding: 20px 0;
border-bottom: 1px dashed #eee;
}
.section:last-child {
border-bottom: none;
}
.section-title {
padding-left: 10px;
border-left: 3px solid var(--prev-color-primary, #1890ff);
font-size: 15px;
font-weight: 500;
color: #303133;
margin-bottom: 16px;
}
.detail-grid {
padding: 0;
}
.detail-item {
display: flex;
padding: 8px 0;
font-size: 13px;
color: #666;
}
.detail-item .label {
flex-shrink: 0;
color: #909399;
margin-right: 4px;
}
.detail-item .value {
flex: 1;
word-break: break-all;
}
.price-label {
color: #ed4014 !important;
}
.price-value {
color: #ed4014;
font-weight: bold;
}
.goods-table {
border: 1px solid #ebeef5;
border-radius: 4px;
}
.goods-cell {
display: flex;
align-items: center;
}
.goods-img {
width: 50px;
height: 50px;
border-radius: 4px;
margin-right: 12px;
flex-shrink: 0;
}
.goods-meta {
min-width: 0;
}
.goods-name {
margin: 0;
font-size: 13px;
color: #303133;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.goods-sku {
margin: 2px 0 0;
font-size: 12px;
color: #909399;
}
.card-key-wrapper {
display: flex;
align-items: center;
}
.card-key {
font-family: monospace;
font-size: 12px;
color: #303133;
margin-right: 8px;
}
.form-img {
width: 80px;
height: 80px;
object-fit: cover;
margin-right: 8px;
cursor: pointer;
}
.tl-section {
padding: 16px 0;
}
:deep(.el-timeline-item__content .tl-title) {
font-size: 13px;
font-weight: 500;
color: #303133;
}
:deep(.el-timeline-item__content .tl-desc) {
margin-left: 8px;
font-size: 12px;
color: #909399;
}
.empty-tip {
padding: 40px;
}
.empty-tips {
padding: 20px 0;
color: #909399;
font-size: 13px;
}
</style>

View File

@ -1,194 +0,0 @@
<template>
<div class="cardkey-order-list">
<el-card :bordered="false" shadow="never" :body-style="{ padding: 0 }">
<div class="padding-add">
<el-form :inline="true" @submit.prevent="onSearch">
<el-form-item label="订单号">
<el-input
v-model="search.keyword"
placeholder="请输入订单号/收货人/电话"
clearable
@keyup.enter="onSearch"
style="width: 220px"
/>
</el-form-item>
<el-form-item label="订单状态">
<el-select v-model="search.status_code" placeholder="全部" clearable style="width: 120px">
<el-option label="待支付" value="pending_payment" />
<el-option label="待发货" value="pending_delivery" />
<el-option label="已发货" value="delivered" />
<el-option label="已完成" value="completed" />
<el-option label="已取消" value="canceled" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSearch">查询</el-button>
<el-button @click="onReset">重置</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16" :body-style="{ padding: '20px 20px 20px' }">
<el-table :data="list" v-loading="loading" stripe empty-text="暂无订单数据" row-key="id">
<el-table-column label="订单号" min-width="180" prop="order_no" show-overflow-tooltip />
<el-table-column label="商品" min-width="220">
<template #default="{ row }">
<div class="goods-cell">
<el-image v-if="row.image" :src="row.image" class="goods-img" />
<span class="goods-name">{{ row.title || '-' }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="商品总价" width="100">
<template #default="{ row }">{{ row.total_price || '0.00' }}</template>
</el-table-column>
<el-table-column label="实付金额" width="100">
<template #default="{ row }">
<span class="pay-price">{{ row.pay_price || '0.00' }}</span>
</template>
</el-table-column>
<el-table-column label="订单状态" width="110">
<template #default="{ row }">
<el-tag :type="getStatusType(row.status_code)">{{ getStatusText(row.status_code) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="支付状态" width="100">
<template #default="{ row }">
<el-tag :type="row.pay_status == 1 ? 'success' : 'info'">{{
row.pay_status == 1 ? '已支付' : '未支付'
}}</el-tag>
</template>
</el-table-column>
<el-table-column label="下单时间" width="160">
<template #default="{ row }">{{ formatDate(row.add_time) }}</template>
</el-table-column>
<el-table-column label="操作" width="100" fixed="right">
<template #default="{ row }">
<a v-db-click @click="goDetail(row.order_no)">详情</a>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total"
:total="total"
v-model:page="search.page"
v-model:limit="search.limit"
@pagination="loadList"
/>
</div>
</el-card>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import { cardkeyOrderList } from '../cardkeyProduct';
import setting from '@/setting';
defineOptions({ name: 'cardkeyOrderList' });
const router = useRouter();
const routePre = setting.routePre;
const loading = ref(false);
const list = ref([]);
const total = ref(0);
const search = ref({ page: 1, limit: 15, keyword: '', status_code: '' });
async function loadList() {
loading.value = true;
try {
const params = { page: search.value.page, limit: search.value.limit };
if (search.value.keyword) params.keyword = search.value.keyword;
if (search.value.status_code) params.status_code = search.value.status_code;
const res = await cardkeyOrderList(params);
const data = res.data || {};
list.value = data.list || data.data || [];
total.value = data.count || data.total || 0;
} catch (e) {
ElMessage.error(e.message || '获取订单列表失败');
}
loading.value = false;
}
function onSearch() {
search.value.page = 1;
loadList();
}
function onReset() {
search.value = { page: 1, limit: 15, keyword: '', status_code: '' };
loadList();
}
function goDetail(orderNo) {
router.push({ path: routePre + '/product/cardkey/order/detail/' + orderNo });
}
function getStatusText(status) {
const map = {
pending_payment: '待支付',
pending_delivery: '待发货',
delivered: '已发货',
completed: '已完成',
canceled: '已取消',
refunding: '退款中',
refunded: '已退款',
};
return map[status] || status || '-';
}
function getStatusType(status) {
const map = {
pending_payment: 'warning',
pending_delivery: 'primary',
delivered: 'primary',
completed: 'success',
canceled: 'info',
refunding: 'warning',
refunded: 'danger',
};
return map[status] || '';
}
function formatDate(ts) {
if (!ts) return '--';
const d = new Date(ts * 1000);
const y = d.getFullYear();
const M = ('0' + (d.getMonth() + 1)).slice(-2);
const day = ('0' + d.getDate()).slice(-2);
const h = ('0' + d.getHours()).slice(-2);
const m = ('0' + d.getMinutes()).slice(-2);
return y + '-' + M + '-' + day + ' ' + h + ':' + m;
}
onMounted(() => {
loadList();
});
</script>
<style scoped>
.mt16 {
margin-top: 16px;
}
.goods-cell {
display: flex;
align-items: center;
}
.goods-img {
width: 40px;
height: 40px;
border-radius: 4px;
margin-right: 8px;
flex-shrink: 0;
}
.goods-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
}
.pay-price {
color: #f56022;
font-weight: bold;
}
</style>

View File

@ -1,123 +0,0 @@
/**
* 卡密商品路由(UMD 扩展模式)
* 页面通过 PageContainer 运行时加载 UMD 扩展包(/admin/static/extensions/cardkey/),
* 主构建不再打包页面源码;扩展未安装(UMD 缺失)时页面显示"扩展未安装"。
* 路由参数(:id? / :orderNo)由子页面从 $route.params 自行读取。
*/
import setting from '@/setting';
const routePre = setting.routePre;
const pre = 'product_';
const pageContainer = () => import('@/pages/system/plugin/PageContainer');
export default [
{
path: routePre + '/product/cardkey/list',
meta: {
auth: ['admin-cardkey-product-list'],
title: '卡密商品',
keepAlive: true,
},
component: () => import('@/layout'),
children: [
{
path: '',
name: `${pre}cardkeyProductList`,
component: pageContainer,
props: { typeKey: 'cardkey', section: 'cardkey', action: 'list' },
meta: {
auth: ['admin-cardkey-product-list'],
title: '卡密商品',
keepAlive: true,
},
},
],
},
{
path: routePre + '/product/cardkey/add',
name: `${pre}cardkeyProductAdd`,
meta: {
auth: ['admin-cardkey-product-add'],
title: '添加商品',
},
component: () => import('@/layout'),
children: [
{
path: ':id?',
name: `${pre}cardkeyProductAddId`,
component: pageContainer,
props: { typeKey: 'cardkey', section: 'cardkey', action: 'add' },
meta: {
auth: ['admin-cardkey-product-add'],
title: '添加商品',
activeMenu: routePre + '/product/cardkey/list',
},
},
],
},
{
path: routePre + '/product/cardkey/stock/list',
meta: {
auth: ['admin-cardkey-stock-list'],
title: '卡密库存',
keepAlive: true,
},
component: () => import('@/layout'),
children: [
{
path: '',
name: `${pre}cardkeyStockList`,
component: pageContainer,
props: { typeKey: 'cardkey', section: 'cardkey', action: 'stock' },
meta: {
auth: ['admin-cardkey-stock-list'],
title: '卡密库存',
keepAlive: true,
},
},
],
},
{
path: routePre + '/product/cardkey/order/list',
meta: {
auth: ['admin-cardkey-order-list'],
title: '订单管理',
keepAlive: true,
},
component: () => import('@/layout'),
children: [
{
path: '',
name: `${pre}cardkeyOrderList`,
component: pageContainer,
props: { typeKey: 'cardkey', section: 'cardkey', action: 'order' },
meta: {
auth: ['admin-cardkey-order-list'],
title: '订单管理',
keepAlive: true,
},
},
],
},
{
path: routePre + '/product/cardkey/order/detail/:orderNo',
meta: {
auth: ['admin-cardkey-order-list'],
title: '订单详情',
},
component: () => import('@/layout'),
children: [
{
path: '',
name: `${pre}cardkeyOrderDetail`,
component: pageContainer,
props: { typeKey: 'cardkey', section: 'cardkey', action: 'order_detail' },
meta: {
auth: ['admin-cardkey-order-list'],
title: '订单详情',
activeMenu: routePre + '/product/cardkey/order/list',
},
},
],
},
];

View File

@ -1,248 +0,0 @@
<template>
<div class="cardkey-stock">
<el-card :bordered="false" shadow="never" :body-style="{ padding: 0 }">
<div class="padding-add">
<el-form :inline="true" @submit.prevent="onSearch">
<el-form-item label="商品名称">
<el-input
v-model="search.store_name"
placeholder="请输入商品名称/关键字/ID"
clearable
@keyup.enter="onSearch"
style="width: 220px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSearch">查询</el-button>
<el-button @click="onReset">重置</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16" :body-style="{ padding: '20px 20px 20px' }">
<el-table :data="list" v-loading="loading" stripe empty-text="暂无商品数据" row-key="id">
<el-table-column label="商品ID" width="80" prop="id" />
<el-table-column label="商品图" width="90">
<template #default="{ row }">
<div class="tabBox_img" v-viewer>
<img v-lazy="row.image" />
</div>
</template>
</el-table-column>
<el-table-column label="商品名称" min-width="220">
<template #default="{ row }">
<span>{{ row.store_name }}</span>
</template>
</el-table-column>
<el-table-column label="卡密类型" width="110">
<template #default="{ row }">
<span>{{ row.card_type === 'single' ? '单卡密' : '组合卡密' }}</span>
</template>
</el-table-column>
<el-table-column label="卡密库存" width="110">
<template #default="{ row }">
<el-tag :type="(row.card_count || 0) > 0 ? 'success' : 'danger'"> {{ row.card_count || 0 }} 张 </el-tag>
</template>
</el-table-column>
<el-table-column label="商品售价" width="100">
<template #default="{ row }">{{ row.price }}</template>
</el-table-column>
<el-table-column label="操作" width="160" fixed="right">
<template #default="{ row }">
<a v-db-click @click="openCard(row)">卡密管理</a>
<a v-db-click @click="openImport(row)">导入卡密</a>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total"
:total="total"
v-model:page="search.page"
v-model:limit="search.limit"
@pagination="loadList"
/>
</div>
</el-card>
<!-- 卡密列表弹窗 -->
<el-dialog v-model="cardDialog" title="卡密库存明细" width="720px" :close-on-click-modal="false">
<el-table :data="cardList" v-loading="cardLoading" stripe empty-text="暂无卡密,请先导入">
<el-table-column label="ID" width="70" prop="id" />
<el-table-column label="卡号" min-width="160">
<template #default="{ row }">{{ row.card_content || '--' }}</template>
</el-table-column>
<el-table-column label="卡密" min-width="160">
<template #default="{ row }">{{ row.card_password || '--' }}</template>
</el-table-column>
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="getCardStatusType(row.status)">{{ getCardStatusText(row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="关联订单" width="180">
<template #default="{ row }">{{ row.order_no || '--' }}</template>
</el-table-column>
<el-table-column label="发放时间" width="160">
<template #default="{ row }">{{ row.sent_time ? formatDate(row.sent_time) : '--' }}</template>
</el-table-column>
</el-table>
</el-dialog>
<!-- 导入卡密弹窗 -->
<el-dialog
v-model="importDialog"
:title="'导入卡密 - ' + (currentProduct ? currentProduct.store_name : '')"
width="640px"
:close-on-click-modal="false"
>
<el-alert
type="info"
show-icon
:closable="false"
title="每行一张卡密,格式:卡号,卡密(英文逗号分隔)"
style="margin-bottom: 16px"
/>
<el-input
type="textarea"
v-model="importText"
:rows="10"
placeholder="例如:&#10;CARD001,ABC123456&#10;CARD002,DEF789012"
/>
<template #footer
><span class="dialog-footer">
<el-button @click="importDialog = false">取 消</el-button>
<el-button type="primary" :loading="importLoading" @click="submitImport">确 定</el-button>
</span></template
>
</el-dialog>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { ElMessage } from 'element-plus';
import { cardkeyProductList, cardkeyProductCardList, cardkeyProductImportCard } from '../cardkeyProduct';
defineOptions({ name: 'cardkeyStockList' });
const loading = ref(false);
const list = ref([]);
const total = ref(0);
const search = ref({ page: 1, limit: 15, store_name: '' });
const cardDialog = ref(false);
const cardLoading = ref(false);
const cardList = ref([]);
const currentProduct = ref(null);
const importDialog = ref(false);
const importLoading = ref(false);
const importText = ref('');
async function loadList() {
loading.value = true;
try {
const params = { page: search.value.page, limit: search.value.limit };
if (search.value.store_name) params.store_name = search.value.store_name;
const res = await cardkeyProductList(params);
const data = res.data || {};
list.value = data.list || [];
total.value = data.count || 0;
} catch (e) {
ElMessage.error(e.message || '获取商品列表失败');
}
loading.value = false;
}
function onSearch() {
search.value.page = 1;
loadList();
}
function onReset() {
search.value = { page: 1, limit: 15, store_name: '' };
loadList();
}
function openCard(row) {
currentProduct.value = row;
cardDialog.value = true;
cardLoading.value = true;
cardkeyProductCardList(row.id)
.then((res) => {
cardList.value = res.data || [];
})
.catch((e) => {
ElMessage.error(e.message || '获取卡密列表失败');
})
.finally(() => {
cardLoading.value = false;
});
}
function openImport(row) {
currentProduct.value = row;
importText.value = '';
importDialog.value = true;
}
async function submitImport() {
const lines = importText.value
.split('\n')
.map((line) => line.trim())
.filter((line) => line !== '');
if (!lines.length) {
ElMessage.warning('请先输入卡密内容');
return;
}
const cards = lines.map((line) => {
// 支持 "卡号,卡密" 或仅 "卡密" 两种格式
const parts = line.split(/[,,\s]+/).filter((p) => p !== '');
return { card_content: parts[0] || '', card_password: parts[1] || parts[0] || '' };
});
importLoading.value = true;
try {
const res = await cardkeyProductImportCard(currentProduct.value.id, cards);
ElMessage.success(res.msg || `导入成功,共导入${cards.length}条卡密`);
importDialog.value = false;
loadList();
} catch (e) {
ElMessage.error(e.message || '导入失败');
}
importLoading.value = false;
}
function getCardStatusText(status) {
const map = { unused: '可用', reserved: '待支付预留', sent: '已发放', used: '已使用', recycled: '已回收' };
return map[status] || status || '-';
}
function getCardStatusType(status) {
const map = { unused: 'info', reserved: 'warning', sent: 'success', used: 'info', recycled: 'danger' };
return map[status] || '';
}
function formatDate(ts) {
if (!ts) return '--';
const d = new Date(ts * 1000);
const y = d.getFullYear();
const M = ('0' + (d.getMonth() + 1)).slice(-2);
const day = ('0' + d.getDate()).slice(-2);
const h = ('0' + d.getHours()).slice(-2);
const m = ('0' + d.getMinutes()).slice(-2);
return y + '-' + M + '-' + day + ' ' + h + ':' + m;
}
onMounted(() => {
loadList();
});
</script>
<style scoped>
.mt16 {
margin-top: 16px;
}
.tabBox_img {
width: 36px;
height: 36px;
border-radius: 4px;
cursor: pointer;
}
.tabBox_img img {
width: 100%;
height: 100%;
}
</style>

View File

@ -1,108 +0,0 @@
/**
* 渠道码模块 API
*/
import request from '@/libs/request';
/**
* @description 渠道码分类列表
*/
export function wechatQrcodeTree() {
return request({
url: `marketing/channel_code/cate/list`,
method: 'get',
});
}
/**
* @description 获取渠道码创建、编辑分类表单
*/
export function wechatQrcodeCreate(id) {
return request({
url: `marketing/channel_code/cate/create/${id}`,
method: 'get',
});
}
/**
* @description 获取渠道码列表
*/
export function wechatQrcodeList(params) {
return request({
url: `marketing/channel_code/list`,
method: 'GET',
params,
});
}
/**
* @description 渠道码修改状态
*/
export function wechatQrcodeStatusApi(data) {
return request({
url: `marketing/channel_code/set_status/${data.id}/${data.status}`,
method: 'PUT',
});
}
/**
* @description 获取渠道码用户列表
*/
export function getUserList(params) {
return request({
url: `marketing/channel_code/user_list/${params.id}`,
method: 'get',
params,
});
}
/**
* @description 获取渠道码详情
*/
export function wechatQrcodeDetail(id) {
return request({
url: `marketing/channel_code/info/${id}`,
method: 'get',
});
}
/**
* @description 渠道码保存
*/
export function wechatQrcodeSaveApi(id, data) {
return request({
url: `marketing/channel_code/save/${id}`,
method: 'POST',
data,
});
}
/**
* @description 渠道码统计
*/
export function wechatQrcodeStatistic(id, params) {
return request({
url: `marketing/channel_code/statistic/${id}`,
method: 'GET',
params,
});
}
/**
* @description 删除渠道码分类
*/
export function wechatQrcodeCateDelApi(id) {
return request({
url: `marketing/channel_code/cate/del/${id}`,
method: 'DELETE',
});
}
/**
* @description 删除渠道码
*/
export function wechatQrcodeDelApi(id) {
return request({
url: `marketing/channel_code/del/${id}`,
method: 'DELETE',
});
}

View File

@ -1,10 +0,0 @@
{
"key": "channel_code",
"title": "渠道码",
"version": "1.0.0",
"pages": [
{ "path": "channelCodeIndex", "title": "渠道码管理" },
{ "path": "createCode", "title": "创建渠道码" },
{ "path": "codeStatistic", "title": "渠道码统计" }
]
}

View File

@ -1,609 +0,0 @@
<template>
<div>
<el-row class="ivu-mt box-wrapper">
<el-col :span="4" class="left-wrapper">
<div class="tree_tit" v-db-click @click="addSort">
<i class="el-icon-circle-plus"></i>
添加分组
</div>
<div class="tree">
<el-tree
:data="labelSort"
node-key="id"
default-expand-all
highlight-current
:expand-on-click-node="false"
@node-click="bindMenuItem"
:current-node-key="treeId"
>
<template #default="{ data }"
><span class="custom-tree-node">
<div class="file-name">
<img v-if="!data.pid" class="icon" src="../../../../assets/images/file.jpg" />
<el-tooltip class="item" effect="dark" :content="data.cate_name" placement="top">
<div class="text line1">
{{ data.cate_name }}
</div>
</el-tooltip>
</div>
<span v-if="data.id">
<el-dropdown @command="(command) => clickMenu(data, command)">
<i class="el-icon-more el-icon--right"></i>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="1">编辑分组</el-dropdown-item>
<el-dropdown-item v-if="data.id" command="2">删除分组</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</span>
</span></template
>
</el-tree>
</div>
</el-col>
<el-col :span="20">
<el-card :bordered="false" shadow="never" class="left-radius-none">
<el-row class="action-row mb14">
<el-col :span="18">
<el-button v-auth="['marketing-channel_code-create']" type="primary" v-db-click @click="add"
>新建渠道码</el-button
>
<!-- <el-button v-auth="['marketing-channel_code-create']" type="success" v-db-click @click="addSort">添加分组</el-button> -->
</el-col>
<el-col :span="6">
<div class="flex">
<el-input class="mr10" v-model="tableFrom.name" search placeholder="请输入渠道码名称"> </el-input>
<el-button type="primary" v-db-click @click="userSearchs">搜索</el-button>
</div>
</el-col>
</el-row>
<el-table
:data="tableList"
v-loading="loading"
highlight-current-row
no-userFrom-text="暂无数据"
no-filtered-userFrom-text="暂无筛选结果"
>
<el-table-column label="渠道码" width="80">
<template #default="scope">
<div class="tabBox_img" v-viewer>
<img v-lazy="scope.row.image" />
</div>
</template>
</el-table-column>
<el-table-column label="渠道码名称" min-width="80">
<template #default="scope">
<span>{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column label="总关注数" min-width="80">
<template #default="scope">
<span>{{ scope.row.follow }}</span>
</template>
</el-table-column>
<el-table-column label="昨日新增关注" min-width="80">
<template #default="scope">
<span>{{ scope.row.y_follow }}</span>
</template>
</el-table-column>
<el-table-column label="用户标签" min-width="80">
<template #default="scope">
<el-tag class="label-name" v-for="(item, index) in scope.row.label_name" :key="index">{{
item
}}</el-tag>
</template>
</el-table-column>
<el-table-column label="时间" min-width="80">
<template #default="scope">
<span v-if="scope.row.stop === 0"> 永久 </span>
<span v-if="scope.row.stop === 1">
<div>{{ scope.row.add_time }}</div>
<div>-</div>
<div>{{ scope.row.end_time }}</div>
</span>
<span v-if="scope.row.stop === -1">已过期</span>
</template>
</el-table-column>
<el-table-column label="关联推广员" min-width="80">
<template #default="scope">
<div class="tabBox_img" v-viewer>
<img v-lazy="scope.row.avatar" />
</div>
</template>
</el-table-column>
<el-table-column label="状态" min-width="80">
<template #default="scope">
<el-switch
class="defineSwitch"
:active-value="1"
:inactive-value="0"
v-model="scope.row.status"
:value="scope.row.status"
:disabled="scope.row.lottery_status == 2 ? true : false"
@change="onchangeIsShow(scope.row)"
size="large"
active-text="开启"
inactive-text="关闭"
>
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="170">
<template #default="scope">
<a v-db-click @click="edit(scope.row)">编辑</a>
<el-divider direction="vertical"></el-divider>
<a v-db-click @click="del(scope.row, '删除二维码', scope.$index)">删除</a>
<el-divider direction="vertical"></el-divider>
<el-dropdown @command="changeMenu(scope.row, $event)" :transfer="true">
<span class="el-dropdown-link">更多<i class="el-icon-arrow-down el-icon--right"></i> </span>
<template #dropdown
><el-dropdown-menu>
<el-dropdown-item command="1">下载</el-dropdown-item>
<el-dropdown-item command="2">统计</el-dropdown-item>
<el-dropdown-item command="3">用户列表</el-dropdown-item>
</el-dropdown-menu></template
>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total"
:total="total"
v-model:page="tableFrom.page"
v-model:limit="tableFrom.limit"
@pagination="getList"
/>
</div>
</el-card>
</el-col>
</el-row>
<el-dialog v-model="modals" title="渠道码用户列表" :close-on-click-modal="false" width="900px">
<el-table :data="tabList" empty-text="暂无数据" highlight-current-row max-height="400">
<el-table-column label="UID" min-width="120">
<template #default="scope">
<span>{{ scope.row.uid }}</span>
</template>
</el-table-column>
<el-table-column label="用户头像" min-width="120">
<template #default="scope">
<div class="tabBox_img" v-viewer>
<img v-lazy="scope.row.avatar" />
</div>
</template>
</el-table-column>
<el-table-column label="用户昵称" min-width="120">
<template #default="scope">
<span>{{ scope.row.nickname }}</span>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total2"
:total="total2"
v-model:page="userData.page"
v-model:limit="userData.limit"
@pagination="getUserList"
/>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { computed, onActivated, onMounted, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import {
wechatQrcodeList,
wechatQrcodeCreate,
wechatQrcodeTree,
wechatQrcodeStatusApi,
getUserList as getUserListApi,
wechatQrcodeCateDelApi,
wechatQrcodeDelApi,
} from './../api';
import { scrollTop } from '@/libs/util';
import modalForm from '@/utils/modalForm';
import { modalSure } from '@/utils/public';
import setting from '@/setting';
import { useMediaStore } from '@/store/module/media';
defineOptions({ name: 'marketing_channel_code' });
const router = useRouter();
const routePre = setting.routePre;
const { isMobile } = storeToRefs(useMediaStore());
const labelWidth = computed(() => (isMobile.value ? undefined : '80px'));
const labelPosition = computed(() => (isMobile.value ? 'top' : 'right'));
const treeId = ref('');
const isChat = true;
const formValidate3 = ref({
page: 1,
limit: 15,
});
const total3 = ref(0);
const loading3 = ref(false);
const modals3 = ref(false);
const tabList = ref([]);
const formValidate5 = ref({
page: 1,
limit: 15,
uid: 0,
to_uid: 0,
id: 0,
});
const tableList5 = ref([]);
const FromData = ref(null);
const formValidate = ref({
page: 1,
limit: 15,
data: '',
type: '',
nickname: '',
});
const tableList2 = ref([]);
const modals = ref(false);
const total = ref(0);
const tableFrom = ref({
page: 1,
limit: 15,
cate_id: 0,
name: '',
});
const userData = ref({
id: 0,
page: 1,
limit: 15,
});
const timeVal = ref([]);
const loading = ref(false);
const tableList = ref([]);
const loading2 = ref(false);
const total2 = ref(0);
const addFrom = ref({
uids: [],
});
const selections = ref([]);
const rows = ref({});
const rowRecord = ref({});
const theme3 = 'light';
const labelSort = ref([]);
const sortName = ref('');
const current = ref(0);
const uid = ref(0);
const orderId = ref(0);
// 原 filters:转为普通 function
function typeFilter(status) {
const statusMap = {
wechat: '微信用户',
routine: '小程序用户',
};
return statusMap[status];
}
onActivated(() => {
getUserLabelAll();
});
onMounted(() => {
getUserLabelAll();
});
function changeMenu(row, name) {
orderId.value = row.id;
switch (name) {
case '1':
downLoadCode(row.image);
break;
case '2':
// vue-router 4 中 path 内拼接的 query 会被丢弃,必须用 query 对象传递参数
router.push({
path: routePre + '/marketing/channel_code/code_statistic',
query: { id: row.id },
});
break;
case '3':
modals.value = true;
userData.value.id = row.id;
getUserList();
break;
default:
}
}
function downLoadCode(url) {
if (!url) return ElMessage.warning('暂无二维码');
var image = new Image();
image.src = url;
// 解决跨域 Canvas 污染问题
image.setAttribute('crossOrigin', 'anonymous');
image.onload = function () {
var canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
var context = canvas.getContext('2d');
context.drawImage(image, 0, 0, image.width, image.height);
var url = canvas.toDataURL(); //得到图片的base64编码数据
var a = document.createElement('a'); // 生成一个a元素
var event = new MouseEvent('click'); // 创建一个单击事件
a.download = name || 'photo'; // 设置图片名称
a.href = url; // 将生成的URL设置为a.href属性
a.dispatchEvent(event); // 触发a的单击事件
};
}
// 获取渠道码用户列表
function getUserList() {
getUserListApi(userData.value)
.then(async (res) => {
let data = res.data;
let arr = [];
data.list.map((i) => {
arr.push(i.user);
});
tabList.value = arr;
total2.value = data.count;
loading2.value = false;
})
.catch((res) => {
loading.value = false;
tabList.value = [];
ElMessage.error(res.msg);
});
}
// 获取分组
function getUserLabelAll(key) {
wechatQrcodeTree().then((res) => {
let data = res.data.data;
let obj = {
cate_name: '全部',
id: '',
};
data.unshift(obj);
data.forEach((el) => {
el.status = false;
});
if (!key) {
sortName.value = data[0].id;
tableFrom.value.cate_id = data[0].id;
getList();
}
labelSort.value = data;
});
}
// 添加分类
function addSort() {
modalForm(wechatQrcodeCreate(0)).then(() => getUserLabelAll());
}
//编辑标签
function labelEdit(item) {
modalForm(wechatQrcodeCreate(item.id)).then(() => getUserLabelAll(1));
}
function deleteSort(row, tit) {
let num = labelSort.value.findIndex((e) => {
return e.id == row.id;
});
let delfromData = {
title: tit,
num: num,
url: `marketing/channel_code/cate/del/${row.id}`,
method: 'DELETE',
ids: '',
};
modalSure(delfromData)
.then((res) => {
ElMessage.success(res.msg);
labelSort.value.splice(num, 1);
labelSort.value = [];
getUserLabelAll();
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
// 显示标签小菜单
function showMenu(item) {
labelSort.value.forEach((el) => {
if (el.id == item.id) {
el.status = item.status ? false : true;
} else {
el.status = false;
}
});
}
function bindMenuItem(name, index) {
tableFrom.value.page = 1;
current.value = index;
labelSort.value.forEach((el) => {
el.status = false;
});
tableFrom.value.cate_id = name.id;
getList();
}
function cancel() {
formValidate.value = {
page: 1,
limit: 15,
data: '',
type: '',
nickname: '',
};
}
// 编辑
function edit(row) {
// vue-router 4 中 path 内拼接的 query 会被丢弃,必须用 query 对象传递参数
router.push({
path: routePre + '/marketing/channel_code/create',
query: { id: row.id },
});
}
// 添加
function add() {
router.push({
path: routePre + '/marketing/channel_code/create',
});
}
//
function getListService() {
loading2.value = true;
kefucreateApi(formValidate.value)
.then(async (res) => {
let data = res.data;
tableList2.value = data.list;
total2.value = data.count;
tableList2.value.map((item) => {
item._isChecked = false;
});
loading2.value = false;
})
.catch((res) => {
loading2.value = false;
ElMessage.error(res.msg);
});
}
// 搜索
function userSearchs() {
formValidate.value.page = 1;
getList();
}
// 删除
function del(row, tit, num) {
let delfromData = {
title: tit,
num: num,
url: `/marketing/channel_code/del/${row.id}`,
method: 'DELETE',
ids: '',
};
modalSure(delfromData)
.then((res) => {
ElMessage.success(res.msg);
tableList.value.splice(num, 1);
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
// 列表
function getList() {
loading.value = true;
wechatQrcodeList(tableFrom.value)
.then(async (res) => {
let data = res.data;
tableList.value = data.list;
total.value = res.data.count;
loading.value = false;
})
.catch((res) => {
loading.value = false;
ElMessage.error(res.msg);
});
}
// 修改是否显示
function onchangeIsShow(row) {
let data = {
id: row.id,
status: row.status,
};
wechatQrcodeStatusApi(data)
.then(async (res) => {
ElMessage.success(res.msg);
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
// 点击菜单
function clickMenu(data, name) {
if (name == 1) {
labelEdit(data);
} else if (name == 2) {
deleteSort(data, '删除分类');
}
}
</script>
<style lang="scss" scoped>
.showOn {
color: #2d8cf0;
background: #f0faff;
z-index: 2;
}
.tabBox_img {
width: 36px;
height: 36px;
border-radius: 4px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
.modelBox {
:deep(.ivu-table-header) {
width: 100% !important;
}
}
.label-name {
margin: 2px 2px;
}
.trees-coadd {
width: 100%;
height: 385px;
.scollhide {
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: scroll;
}
}
.scollhide::-webkit-scrollbar {
display: none;
}
:deep(.ivu-menu-vertical .ivu-menu-item-group-title) {
display: none;
}
:deep(.ivu-menu-vertical.ivu-menu-light:after) {
display: none;
}
.left-wrapper {
height: 904px;
background: #fff;
border-right: 1px solid #f2f2f2;
}
.menu-item {
z-index: 50;
position: relative;
display: flex;
justify-content: space-between;
word-break: break-all;
.icon-box {
z-index: 3;
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
display: none;
}
&:hover .icon-box {
display: block;
}
.right-menu {
z-index: 10;
position: absolute;
right: -106px;
top: -11px;
width: auto;
min-width: 121px;
}
}
</style>

View File

@ -1,275 +0,0 @@
<template>
<div v-loading="spinShow">
<!-- <div class="i-layout-page-header header-title">
<div class="fl_header">
<span>
<el-button icon="ios-arrow-back" type="text" v-db-click @click="$router.go(-1)">返回</el-button>
</span>
<el-divider direction="vertical"></el-divider>
<span class="ivu-page-header-title">{{ $route.meta.title }}</span>
</div>
</div> -->
<pages-header
:title="$route.meta.title"
:backUrl="routePre + '/marketing/channel_code/channelCodeIndex'"
></pages-header>
<cards-data class="mt16" :cardLists="cardLists" v-if="cardLists.length >= 0"></cards-data>
<el-card :bordered="false" shadow="never" class="ivu-mt">
<div class="table-head">
<h3>关注趋势</h3>
<date-range-picker
:editable="false"
clearable
@change="onchangeTime"
v-model="timeVal"
format="YYYY/MM/DD"
value-format="YYYY/MM/DD"
range-separator="-"
class="mr20"
></date-range-picker>
</div>
<echarts-new :option-data="optionData" :styles="style" height="100%" width="100%" v-if="optionData"></echarts-new>
</el-card>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import cardsData from '@/components/cards/cards';
import echartsNew from '@/components/echartsNew/index';
import { getBasic, getChannel, getType } from '@/api/statistic';
import { wechatQrcodeStatistic as wechatQrcodeStatisticApi } from './../api';
import { formatDate } from '@/utils/validate';
import echartsFrom from '@/components/echarts/index';
import setting from '@/setting';
defineOptions({ name: 'index' });
const route = useRoute();
const routePre = setting.routePre;
const timeVal = ref([]);
const style = { height: '400px' };
const infoList = ref({});
const infoList2 = ref({});
const echartLeft = ref(true);
const echartRight = ref(false);
const loading = ref(false);
const loading2 = ref(false);
const fromList = ref({
title: '选择时间',
custom: true,
fromTxt: [
{ text: '全部', val: '' },
{ text: '今天', val: 'today' },
{ text: '本周', val: 'week' },
{ text: '本月', val: 'month' },
{ text: '本季度', val: 'quarter' },
{ text: '本年', val: 'year' },
],
});
const formValidate = ref({
time: '',
});
const cardLists = ref([
{
col: 6,
count: 0,
name: '昨日新增关注',
className: 'md-rose',
},
{
col: 6,
count: 0,
name: '昨日参与',
className: 'ios-speedometer-outline',
},
{
col: 6,
count: 0,
name: '总关注',
className: 'ios-speedometer-outline',
},
{
col: 6,
count: 0,
name: '参与总人数',
className: 'md-rose',
},
]);
const optionData = ref({});
const spinShow = ref(false);
const id = route.query.id;
const end = new Date();
const start = new Date();
start.setTime(start.setTime(new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate() - 29)));
timeVal.value = [start, end];
formValidate.value.time = formatDate(start, 'yyyy/MM/dd') + '-' + formatDate(end, 'yyyy/MM/dd');
// getBasic();
// getTrend();
// getChannel();
// getType();
getQrcodeStatistic();
function getQrcodeStatistic() {
wechatQrcodeStatisticApi(id, formValidate.value).then((res) => {
let arr = ['y_follow', 'y_scan', 'all_follow', 'all_scan'];
cardLists.value.map((i, index) => {
i.count = res.data[arr[index]];
});
getTrend(res.data.trend.series, res.data.trend.xAxis);
});
}
function timeG(dd) {
var d = new Date(dd);
var datetime =
d.getFullYear() +
'-' +
(d.getMonth() + 1) +
'-' +
d.getDate() +
' ' +
d.getHours() +
':' +
d.getMinutes() +
':' +
d.getSeconds();
return datetime;
}
function selectChange(e) {}
// 具体日期
function onchangeTime(e) {
timeVal.value = e;
formValidate.value.time = timeVal.value ? timeVal.value.join('-') : '';
getQrcodeStatistic();
}
// 统计图
function getTrend(seriesData, xAxisData) {
spinShow.value = true;
let legend = seriesData.map((item) => {
return item.name;
});
let xAxis = xAxisData;
let col = ['#5B8FF9', '#5AD8A6', '#FFAB2B', '#5D7092'];
let series = [];
seriesData.map((item, index) => {
series.push({
name: item.name,
type: 'line',
data: item.data,
itemStyle: {
normal: {
color: col[index],
},
},
smooth: 0,
});
});
optionData.value = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985',
},
},
},
legend: {
x: 'center',
data: legend,
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
toolbox: {
feature: {
saveAsImage: {},
},
},
xAxis: {
type: 'category',
boundaryGap: true,
// axisTick:{
// show:false
// },
// axisLine:{
// show:false
// },
// splitLine: {
// show: false
// },
axisLabel: {
interval: 0,
rotate: 40,
textStyle: {
color: '#000000',
},
},
data: xAxis,
},
yAxis: {
type: 'value',
axisLine: {
show: false,
},
axisTick: {
show: false,
},
axisLabel: {
textStyle: {
color: '#7F8B9C',
},
},
splitLine: {
show: true,
lineStyle: {
color: '#F5F7F9',
},
},
},
series: series,
};
spinShow.value = false;
}
</script>
<style scoped>
.cl {
margin-right: 20px;
}
.code-row-bg {
display: flex;
flex-wrap: nowrap;
}
.code-row-bg .ivu-mt {
width: 100%;
margin: 0 5px;
}
.ech-box {
margin-top: 10px;
}
.change-style {
border: 1px solid #ccc;
border-radius: 15px;
padding: 0px 10px;
cursor: pointer;
}
.table-head {
display: flex;
justify-content: space-between;
align-items: center;
}
.return {
margin-bottom: 6px;
}
.i-layout-page-header {
padding-left: 13px;
}
</style>

View File

@ -1,566 +0,0 @@
<template>
<div>
<pages-header
:title="$route.meta.title"
:backUrl="routePre + '/marketing/channel_code/channelCodeIndex'"
></pages-header>
<el-card :bordered="false" shadow="never" class="mt16">
<el-form :model="formData" label-width="100px" :rules="ruleValidate">
<el-form-item label="渠道码名称:">
<el-input
clearable
v-model="formData.name"
placeholder="请输入渠道码名称"
class="content_width"
maxlength="20"
show-word-limit
></el-input>
</el-form-item>
<el-form-item label="渠道码分组:">
<el-select clearable v-model="formData.cate_id" class="content_width">
<el-option
:value="item.id"
v-for="(item, index) in labelSort"
:key="index"
:label="item.cate_name"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="用户标签:">
<div style="display: flex">
<div class="labelInput acea-row row-between-wrapper" v-db-click @click="openLabel">
<div style="width: 90%">
<div v-if="dataLabel.length">
<el-tag closable v-for="(item, index) in dataLabel" @close="closeLabel(item)" :key="index">{{
item.label_name
}}</el-tag>
</div>
<span class="span" v-else>选择用户关联标签</span>
</div>
<div class="ivu-icon ivu-icon-ios-arrow-down"></div>
</div>
<span class="addfont" v-db-click @click="addLabel">新增标签</span>
</div>
</el-form-item>
<el-form-item label="关联推广员:">
<div class="picBox" v-db-click @click="customer">
<div class="pictrue" v-if="formData.avatar">
<img v-lazy="formData.avatar" />
</div>
<div class="upLoad acea-row row-center-wrapper" v-else>
<i class="el-icon-user" style="font-size: 24px"></i>
</div>
</div>
<div class="trip">扫码注册的新用户,将自动成为此推广员的下级,与分销推广功能一致</div>
</el-form-item>
<el-form-item label="有效期:">
<el-radio-group v-model="isReceiveTime">
<el-radio :label="0" :value="0">永久</el-radio>
<el-radio :label="1" :value="1">有效期</el-radio>
</el-radio-group>
<div v-show="isReceiveTime">
<el-input-number
:controls="false"
:max="10000"
:precision="0"
v-model="formData.time"
placeholder="请输入天数"
class="input-number-unit-class"
class-unit="天"
></el-input-number>
</div>
<div class="trip">临时码过期后不能再扫码,永久二维码最大创建数量为10万个</div>
</el-form-item>
<el-form-item label="回复内容:">
<el-radio-group v-model="formData.type">
<el-radio label="text" value="text">文字内容</el-radio>
<el-radio label="voice" value="voice">声音消息</el-radio>
<el-radio label="image" value="image">图片消息</el-radio>
<el-radio label="news" value="news">图文消息</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="消息内容:" prop="content" v-if="formData.type === 'text' || formData.type === 'url'">
<el-input
type="textarea"
rows="2"
clearable
v-model="formData.content.content"
:placeholder="formData.type === 'text' ? '请填写消息内容' : '请填写网址链接'"
class="content_width"
></el-input>
</el-form-item>
<el-form-item label="选取图文:" v-if="formData.type === 'news'">
<el-button v-db-click @click="modals = true">选择图文消息</el-button>
<div class="news-box" v-if="formData.content.list.title">
<img class="news_pic" :src="formData.content.list.image_input[0]" />
<span>{{ formData.content.list.title }}</span>
<i class="el-icon-error del_icon" v-db-click @click="delContent"></i>
</div>
</el-form-item>
<el-form-item
:label="formData.type === 'image' ? '图片地址:' : '语音地址:'"
prop="src"
v-if="formData.type === 'image' || formData.type === 'voice'"
>
<div class="acea-row row-middle">
<el-input
readonly="readonly"
placeholder="请填入链接地址"
class="content_width mr15"
v-model="formData.content.src"
/>
<el-upload
:show-file-list="false"
:action="fileUrl"
:on-success="handleSuccess"
:format="formData.type === 'image' ? formatImg : formatVoice"
:max-size="2048"
:headers="header"
:on-format-error="handleFormatError"
:on-exceeded-size="handleMaxSize"
class="mr20"
style="margin-top: 1px"
accept="image/*,.mp3"
:before-upload="beforeUpload"
>
<el-button type="primary">上传</el-button>
</el-upload>
</div>
<span v-show="formData.type === 'image'">文件最大2Mb,支持bmp/png/jpeg/jpg/gif格式</span>
<span v-show="formData.type === 'voice'">文件最大2Mb,支持mp3格式,播放长度不超过60s</span>
</el-form-item>
<el-form-item>
<el-button class="submit" type="primary" v-db-click @click="save" :loading="loading" :disabled="disabled"
>立即提交</el-button
>
</el-form-item>
</el-form>
</el-card>
<el-dialog v-model="customerShow" title="请选择商城用户" :show-close="true" width="1000px">
<customerInfo v-if="customerShow" @imageObject="imageObject"></customerInfo>
</el-dialog>
<!--图文消息 -->
<el-dialog v-model="modals" title="发送消息" width="1200px" class="modelBox">
<news-category
v-if="modals"
@getCentList="getCentList"
:scrollerHeight="scrollerHeight"
:contentTop="contentTop"
:contentWidth="contentWidth"
:maxCols="maxCols"
></news-category>
</el-dialog>
<el-dialog
v-model="labelShow"
scrollable
title="请选择用户标签"
:closable="false"
width="540px"
:footer-hide="true"
:mask-closable="false"
>
<userLabel ref="userLabelRef" @activeData="activeData" @close="labelClose"></userLabel>
</el-dialog>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import userLabel from '@/components/labelList';
import goodsList from '@/components/goodsList/index';
import newsCategory from '@/components/newsCategory/index';
import { labelListApi } from '@/api/product';
import { userLabelAddApi } from '@/api/user';
import { wechatQrcodeSaveApi, wechatQrcodeTree, wechatQrcodeDetail } from './../api';
import Setting from '@/setting';
import { getCookies } from '@/libs/util';
import customerInfo from '@/components/customerInfo';
import { isPicUpload, isVoiceUpload } from '@/utils';
import modalForm from '@/utils/modalForm';
defineOptions({ name: 'createCode' });
const route = useRoute();
const router = useRouter();
const routePre = Setting.routePre;
const userLabelRef = ref(null);
const customerShow = ref(false);
const labelShow = ref(false);
const disabled = ref(false);
const maxCols = 4;
const labelSelect = ref([]);
const scrollerHeight = '600';
const contentTop = '10';
const contentWidth = '98%';
const formatImg = ['jpg', 'jpeg', 'png', 'bmp', 'gif'];
const formatVoice = ['mp3', 'wma', 'wav', 'amr'];
const header = ref({});
const fileUrl = Setting.apiBaseURL + '/file/upload/1';
const formData = ref({
name: '',
type: 'text',
time: undefined,
label_id: [],
image: '',
cate_id: '',
content: {
content: '',
src: '',
list: {},
},
});
const labelSort = ref([]);
const isReceiveTime = ref(0);
const modals = ref(false);
const ruleValidate = {
name: [
{
required: true,
message: '请填写二维码名称',
trigger: 'blur',
},
],
cate_id: [
{
required: true,
message: '请选择二维码分组',
trigger: 'change',
},
],
};
const id = ref(0);
const dataLabel = ref([]);
const loading = ref(false);
getUserLabelAll();
getLabelList();
getToken();
if (route.query.id) {
id.value = route.query.id;
getDetail();
}
function beforeUpload(file) {
if (formData.value.type === 'image') {
return isPicUpload(file);
}
if (formData.value.type === 'voice') {
return isVoiceUpload(file);
}
}
function activeData(val) {
labelShow.value = false;
dataLabel.value = val;
}
// 标签弹窗关闭
function labelClose() {
labelShow.value = false;
}
function openLabel(row) {
labelShow.value = true;
if (dataLabel.value.length) userLabelRef.value.userLabel(JSON.parse(JSON.stringify(dataLabel.value)));
}
function closeLabel(label) {
let index = dataLabel.value.indexOf(dataLabel.value.filter((d) => d.id == label.id)[0]);
dataLabel.value.splice(index, 1);
}
function getDetail() {
wechatQrcodeDetail(id.value).then((res) => {
formData.value = res.data;
if (res.data.time > 0) {
isReceiveTime.value = 1;
}
if (res.data.label_id.length) {
dataLabel.value = res.data.label_id;
}
});
}
function customer() {
customerShow.value = true;
}
function addLabel() {
modalForm(userLabelAddApi(0)).then(() => getLabelList());
}
// 用户标签
function getLabelList() {
labelListApi()
.then((res) => {
labelSelect.value = res.data.list;
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function imageObject(e) {
customerShow.value = false;
formData.value.uid = e.uid;
formData.value.avatar = e.image;
}
function getCentList(val) {
formData.value.content.list = val.new[0];
modals.value = false;
}
function delContent() {
formData.value.content.list = {};
}
// 上传成功
function handleSuccess(res, file) {
if (res.status === 200) {
formData.value.content.src = res.data.src;
ElMessage.success(res.msg);
} else {
ElMessage.error(res.msg);
}
}
function handleFormatError(file) {
if (formData.value.type === 'image') {
ElMessage.warning('请上传bmp/png/jpeg/jpg/gif格式的图片');
} else {
ElMessage.warning('请上传mp3/wma/wav/amr格式的语音');
}
}
function handleMaxSize(file) {
ElMessage.warning('请上传文件2M以内的文件');
}
// 上传头部token
function getToken() {
header.value['Authori-zation'] = 'Bearer ' + getCookies('token');
}
function selectMenu(name) {
formData.value.type = name;
}
// 获取分类
function getUserLabelAll() {
wechatQrcodeTree().then((res) => {
let data = res.data.data;
labelSort.value = data;
});
}
// 创建
function save() {
if (!formData.value.name) {
return ElMessage.error('请输入二维码名称');
}
if (!formData.value.cate_id) {
return ElMessage.error('请选择分组');
}
if (!dataLabel.value.length) {
return ElMessage.error('请选择用户标签');
} else {
let ids = [];
dataLabel.value.map((i) => {
ids.push(i.id);
});
formData.value.label_id = ids;
}
if (!formData.value.uid) {
return ElMessage.error('请选择推广员');
}
if (isReceiveTime.value) {
if (formData.value.time < 1) {
return ElMessage.error('使用有效期限不能小于1天');
}
} else {
formData.value.time = 0;
}
if (formData.value.type === 'text' || formData.value.type === 'url') {
if (!formData.value.content.content.trim()) {
return ElMessage.error('请输入内容');
}
}
if (formData.value.type === 'voice' || formData.value.type === 'image') {
if (!formData.value.content.src.trim()) {
return ElMessage.error('请先上传消息');
}
}
if (formData.value.type === 'news') {
if (!formData.value.content.list.title.trim()) {
return ElMessage.error('请选择图文消息');
}
}
disabled.value = false;
loading.value = true;
wechatQrcodeSaveApi(id.value, formData.value)
.then((res) => {
disabled.value = true;
ElMessage.success(res.msg);
setTimeout(() => {
router.push({
path: routePre + '/marketing/channel_code/channelCodeIndex',
});
}, 1000);
})
.catch((err) => {
disabled.value = true;
ElMessage.error(err.msg);
});
}
</script>
<style lang="scss" scoped>
.content_width {
width: 460px;
}
.ivu-row {
border: 1px solid #f2f2f2;
}
.ivu-form-item {
padding: 10px 0;
max-width: 1100px;
}
.ivu-form :deep(.ivu-form-item-label) {
font-weight: 700;
font-size: 14px !important;
}
.ivu-input-wrapper {
width: 320px;
}
.ivu-radio-wrapper {
margin-right: 30px;
font-size: 14px !important;
}
.ivu-radio-wrapper :deep(.ivu-radio) {
margin-right: 10px;
}
.ivu-input-number {
width: 160px;
}
.ivu-date-picker {
width: 320px;
}
.ivu-icon-ios-camera-outline {
width: 58px;
height: 58px;
border: 1px dotted rgba(0, 0, 0, 0.1);
border-radius: 4px;
background-color: rgba(0, 0, 0, 0.02);
line-height: 58px;
cursor: pointer;
vertical-align: middle;
}
.upload-list {
width: 58px;
height: 58px;
border: 1px dotted rgba(0, 0, 0, 0.1);
border-radius: 4px;
margin-right: 15px;
display: inline-block;
position: relative;
cursor: pointer;
vertical-align: middle;
}
:deep(.el-tag) {
margin-right: 5px;
}
.upload-list img {
display: block;
width: 100%;
height: 100%;
}
.ivu-icon-ios-close-circle {
position: absolute;
top: 0;
right: 0;
transform: translate(50%, -50%);
}
.modelBox :deep(.ivu-modal-body) {
padding: 0 16px 16px 16px !important;
}
.header-save {
display: flex;
justify-content: space-between;
}
.trip {
font-size: 12px;
color: #ccc;
}
textarea {
padding: 0 5px;
border-radius: 3px;
border-color: #c5c8ce;
outline-color: #2d8cf0;
font-size: 14px;
}
.picBox {
display: inline-block;
cursor: pointer;
.upLoad {
width: 58px;
height: 58px;
line-height: 58px;
border: 1px dotted rgba(0, 0, 0, 0.1);
border-radius: 4px;
background: rgba(0, 0, 0, 0.02);
}
.pictrue {
width: 60px;
height: 60px;
border: 1px dotted rgba(0, 0, 0, 0.1);
margin-right: 10px;
img {
width: 100%;
height: 100%;
}
}
.iconfont {
color: #898989;
}
}
.addfont {
display: inline-block;
font-size: 12px;
font-weight: 400;
color: var(--prev-color-primary);
margin-left: 14px;
cursor: pointer;
margin-left: 10px;
}
.iconxiayi {
font-size: 14px;
}
.ivu-page-header-title {
padding-bottom: 0;
}
.news-box {
width: 200px;
background-color: #f2f2f2;
padding: 10px;
border-radius: 10px;
margin-top: 20px;
position: relative;
.news_pic {
width: 100%;
height: 150px;
}
.del_icon {
position: absolute;
right: -8px;
top: -8px;
cursor: pointer;
}
}
.labelInput {
border: 1px solid #dcdee2;
width: 460px;
padding: 0 15px;
border-radius: 5px;
min-height: 30px;
cursor: pointer;
.span {
font-size: 12px;
color: #c5c8ce;
}
.ivu-icon-ios-arrow-down {
font-size: 14px;
color: #808695;
}
}
</style>

View File

@ -1,77 +0,0 @@
/**
* 渠道码扩展路由
* 兼容模式:既是 Vue Router 路由配置,也包含 section/action 元数据供扩展机制使用
*/
import setting from '@/setting';
const routePre = setting.routePre;
const pre = 'channel_code_';
export default [
{
path: routePre + '/marketing/channel_code/channelCodeIndex',
section: 'channel_code',
action: 'channelCodeIndex',
meta: {
auth: true,
title: '公众号渠道码',
keepAlive: true,
},
component: () => import('@/layout'),
children: [
{
path: '',
name: `${pre}channelCodeIndex`,
component: () => import('./pages/channelCodeIndex'),
meta: {
auth: true,
title: '公众号渠道码',
keepAlive: true,
},
},
],
},
{
path: routePre + '/marketing/channel_code/create',
section: 'channel_code',
action: 'createCode',
meta: {
auth: ['marketing-channel_code-create'],
title: '渠道码',
},
component: () => import('@/layout'),
children: [
{
path: '',
name: `${pre}createCode`,
component: () => import('./pages/createCode'),
meta: {
auth: ['marketing-channel_code-create'],
title: '渠道码',
activeMenu: routePre + '/marketing/channel_code/channelCodeIndex',
},
},
],
},
{
path: routePre + '/marketing/channel_code/code_statistic',
section: 'channel_code',
action: 'codeStatistic',
meta: {
auth: ['marketing-channel_code-statistic'],
title: '二维码统计',
},
component: () => import('@/layout'),
children: [
{
path: '',
name: `${pre}codeStatistic`,
component: () => import('./pages/codeStatistic'),
meta: {
auth: ['marketing-channel_code-statistic'],
title: '二维码统计',
activeMenu: routePre + '/marketing/channel_code/channelCodeIndex',
},
},
],
},
];

View File

@ -1,38 +0,0 @@
<template>
<div class="combination-app">
<div class="section-content">
<component :is="sectionComponent" :params="params" />
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import ProductList from './pages/product/index.vue';
import PinkList from './pages/pink/index.vue';
import CreateProduct from './pages/create/index.vue';
import Statistics from './pages/statistics/index.vue';
defineOptions({ name: 'CombinationApp' });
const props = defineProps({
section: { type: String, default: 'combination' },
action: { type: String, default: 'product' },
params: { type: Object, default: () => ({}) },
});
const actionComponents = {
product: ProductList,
pink: PinkList,
create: CreateProduct,
statistics: Statistics,
};
const sectionComponent = computed(() => actionComponents[props.action] || ProductList);
</script>
<style scoped>
.combination-app {
height: 100%;
}
</style>

View File

@ -1,130 +0,0 @@
/**
* 拼团模块 API
*/
import request from '@/libs/request';
/**
* 拼团商品列表
*/
export function combinationListApi(params) {
return request({
url: 'marketing/combination',
method: 'GET',
params,
});
}
/**
* 修改拼团商品状态
*/
export function combinationSetStatusApi(data) {
return request({
url: `marketing/combination/set_status/${data.id}/${data.status}`,
method: 'PUT',
});
}
/**
* 拼团统计(全局)
*/
export function statisticsApi() {
return request({
url: 'marketing/combination/statistics',
method: 'GET',
});
}
/**
* 拼团商品详情
*/
export function combinationInfoApi(id) {
return request({
url: `marketing/combination/${id}`,
method: 'GET',
});
}
/**
* 保存拼团商品(新增/编辑)
*/
export function combinationCreatApi(data) {
return request({
url: `marketing/combination/${data.id || 0}`,
method: 'POST',
data,
});
}
/**
* 拼团列表(已开团的)
*/
export function combineListApi(params) {
return request({
url: 'marketing/combination/combine/list',
method: 'GET',
params,
});
}
/**
* 拼团人列表
*/
export function orderPinkListApi(id) {
return request({
url: `marketing/combination/order_pink/${id}`,
method: 'GET',
});
}
/**
* 拼团导出
*/
export function storeCombinationApi(data) {
return request({
url: 'export/combination_list',
method: 'GET',
params: data,
});
}
/**
* 拼团统计-头部卡片
*/
export function getcombinationStatistics(id) {
return request({
url: `marketing/combination/statistics/head/${id}`,
method: 'GET',
});
}
/**
* 拼团统计-参与人员
*/
export function getcombinationStatisticsPeople(id, params) {
return request({
url: `marketing/combination/statistics/list/${id}`,
method: 'GET',
params,
});
}
/**
* 拼团统计-订单列表
*/
export function getcombinationStatisticsOrder(id, params) {
return request({
url: `marketing/combination/statistics/order/${id}`,
method: 'GET',
params,
});
}
/**
* 拼团立即成团
*/
export function combineJoinApi(id) {
return request({
url: `marketing/combination/immediately/${id}`,
method: 'GET',
});
}

View File

@ -1,11 +0,0 @@
{
"key": "combination",
"title": "拼团活动",
"version": "1.0.0",
"pages": [
{ "path": "product", "title": "拼团商品管理" },
{ "path": "pink", "title": "拼团列表" },
{ "path": "create/:id?/:copy?", "title": "添加/编辑拼团商品" },
{ "path": "statistics/:id", "title": "拼团统计" }
]
}

View File

@ -1,5 +0,0 @@
import CombinationApp from './App.vue';
import routes from './routes.js';
export default CombinationApp;
export { routes };

View File

@ -1,837 +0,0 @@
<template>
<div>
<pages-header
ref="pageHeader"
:title="$route.params.id && !$route.params.copy ? '编辑拼团商品' : '添加拼团商品'"
:backUrl="$routeProStr + '/marketing/combination/product'"
></pages-header>
<el-card :bordered="false" shadow="never" class="mt16">
<el-row class="mt30 acea-row row-middle row-center">
<el-col :span="20">
<steps :stepList="stepList" :isActive="current"></steps>
</el-col>
<el-col :span="23" v-loading="spinShow">
<el-form
class="form mt30"
ref="formRef"
:model="formValidate"
:rules="ruleValidate"
:label-width="labelWidth"
:label-position="labelPosition"
@submit.prevent
>
<el-form-item label="选择商品:" prop="image_input" v-if="current === 0">
<div class="picBox" v-db-click @click="changeGoods">
<div class="pictrue" v-if="formValidate.image">
<img v-lazy="formValidate.image" />
</div>
<div class="upLoad acea-row row-center-wrapper" v-else>
<i class="el-icon-goods" style="font-size: 24px"></i>
</div>
</div>
</el-form-item>
<el-row v-show="current === 1">
<el-col :span="24">
<el-form-item label="商品主图:" prop="image">
<div class="picBox" v-db-click @click="modalPicTap('dan', 'danFrom')">
<div class="pictrue" v-if="formValidate.image"><img v-lazy="formValidate.image" /></div>
<div class="upLoad acea-row row-center-wrapper" v-else>
<i class="el-icon-picture-outline" style="font-size: 24px"></i>
</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="商品轮播图:" prop="images">
<div class="acea-row">
<div
class="pictrue"
v-for="(item, index) in formValidate.images"
:key="index"
draggable="true"
@dragstart="handleDragStart($event, item)"
@dragover.prevent="handleDragOver($event, item)"
@dragenter="handleDragEnter($event, item)"
@dragend="handleDragEnd($event, item)"
>
<img v-lazy="item" />
<i class="el-icon-circle-close btndel" v-db-click @click="handleRemove(index)"></i>
</div>
<div
v-if="formValidate.images.length < 10"
class="upLoad acea-row row-center-wrapper"
v-db-click
@click="modalPicTap('duo')"
>
<i class="el-icon-picture-outline" style="font-size: 24px"></i>
</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-col v-bind="grid">
<el-form-item label="拼团名称:" prop="title">
<el-input
clearable
placeholder="请输入拼团名称"
v-model="formValidate.title"
class="content_width"
maxlength="80"
show-word-limit
/>
</el-form-item>
</el-col>
</el-col>
<el-col :span="24">
<el-col v-bind="grid">
<el-form-item label="拼团简介:" prop="info">
<el-input
placeholder="请输入拼团简介"
type="textarea"
:rows="4"
v-model="formValidate.info"
class="content_width"
maxlength="100"
show-word-limit
/>
</el-form-item>
</el-col>
</el-col>
<el-col :span="24">
<el-form-item label="拼团时间:" prop="section_time">
<div>
<el-date-picker
clearable
:editable="false"
type="datetimerange"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
@change="onchangeTime"
class="content_width"
v-model="formValidate.section_time"
></el-date-picker>
<div class="grey">设置活动开启结束时间,用户可以在设置时间内发起参与拼团</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24" v-if="formValidate.virtual_type == 0">
<el-form-item label="物流方式:" prop="logistics">
<el-checkbox-group v-model="formValidate.logistics">
<el-checkbox label="1">快递</el-checkbox>
<el-checkbox label="2">到店</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-col>
<el-col :span="24" v-if="formValidate.virtual_type == 0 && formValidate.logistics.includes('1')">
<el-form-item label="运费设置:">
<el-radio-group v-model="formValidate.freight">
<el-radio :label="2" :value="2">固定邮费</el-radio>
<el-radio :label="3" :value="3">运费模板</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col
:span="24"
v-if="
formValidate.freight != 3 &&
formValidate.freight != 1 &&
formValidate.virtual_type == 0 &&
formValidate.logistics.includes('1')
"
>
<el-form-item label="">
<div class="acea-row">
<el-input-number
:controls="false"
:min="0"
:max="9999999999"
v-model="formValidate.postage"
placeholder="请输入金额"
class="input-number-unit-class"
class-unit="元"
/>
</div>
</el-form-item>
</el-col>
<el-col :span="24" v-if="formValidate.freight == 3 && formValidate.virtual_type == 0">
<el-form-item label="" prop="temp_id">
<div class="acea-row">
<el-select
v-model="formValidate.temp_id"
clearable
placeholder="请选择运费模板"
class="content_width"
>
<el-option
v-for="(item, index) in templateList"
:value="item.id"
:key="index"
:label="item.name"
></el-option>
</el-select>
<span class="addfont" v-db-click @click="freight">新增运费模板</span>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="拼团时效:" prop="effective_time">
<div>
<el-input-number
:controls="false"
placeholder="请输入拼团时效"
class="input-number-unit-class"
class-unit="小时"
v-model="formValidate.effective_time"
/>
<div class="grey">
用户发起拼团后开始计时,需在设置时间内邀请到规定好友人数参团,超过时效时间则系统判定拼团失败,自动发起退款
</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="拼团人数:" prop="people">
<div>
<el-input-number
:controls="false"
:min="2"
:max="10000"
placeholder="请输入拼团人数"
:precision="0"
v-model="formValidate.people"
class="input-number-unit-class"
class-unit="人"
/>
<div class="grey">单次拼团需要参与的用户数</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="虚拟成团补齐人数:" prop="virtualPeople">
<div>
<el-input-number
:controls="false"
placeholder="设置虚拟成团的补齐人数"
:precision="0"
:max="10000"
:min="0"
v-model="formValidate.virtualPeople"
class="input-number-unit-class"
class-unit="人"
/>
<div class="grey">
如:5人团设置补齐2人,当团队成员大于等于3人时,拼团结束时自动补齐剩余最多2个位置,不开启虚拟成团请设置为0
</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="单位:" prop="unit_name">
<el-input clearable placeholder="请输入单位" v-model="formValidate.unit_name" class="content_width" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="总购买数量限制:" prop="num">
<div>
<el-input-number
:controls="false"
:min="1"
placeholder="请输入总数量限制"
:precision="0"
:max="10000"
v-model="formValidate.num"
class="input-number-unit-class"
:class-unit="formValidate.unit_name || '件'"
/>
<div class="grey">该商品活动期间内,用户可购买的最大数量</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="单次购买数量限制:" prop="once_num">
<div>
<el-input-number
:controls="false"
:min="1"
placeholder="请输入单次购买数量限制"
:precision="0"
:max="10000"
v-model="formValidate.once_num"
class="input-number-unit-class"
:class-unit="formValidate.unit_name || '件'"
/>
<div class="grey">用户参与拼团时,一次购买最大数量限制</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="团长返佣比例:" prop="head_commission">
<div>
<el-input-number
:controls="false"
:min="0"
:max="100"
placeholder="团长返佣比例"
:precision="0"
v-model="formValidate.head_commission"
class="input-number-unit-class"
class-unit="%"
/>
<div class="grey">
拼团成功后,如果团长是分销员,则订单确认收货时给团长返佣金,比例是实际支付金额的0-100%
</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="拼团是否参与分销:" props="is_commission">
<div>
<el-switch
class="defineSwitch"
:active-value="1"
:inactive-value="0"
v-model="formValidate.is_commission"
size="large"
active-text="开启"
inactive-text="关闭"
>
</el-switch>
<div class="grey">拼团商品是否参与商城分销返佣</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="排序:">
<el-input-number
:controls="false"
placeholder="请输入排序"
:precision="0"
:max="10000"
:min="0"
v-model="formValidate.sort"
class="content_width"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="热门推荐:" props="is_hot">
<el-switch
class="defineSwitch"
:active-value="1"
:inactive-value="0"
v-model="formValidate.is_host"
size="large"
active-text="开启"
inactive-text="关闭"
></el-switch>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="活动状态:" props="is_show">
<el-switch
class="defineSwitch"
:active-value="1"
:inactive-value="0"
v-model="formValidate.is_show"
size="large"
active-text="开启"
inactive-text="关闭"
></el-switch>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="规格选择:">
<el-table
ref="multipleTableRef"
:data="specsData"
:row-key="getRowKeys"
border
@selection-change="changeCheckbox"
>
<el-table-column type="selection" :reserve-selection="true" width="55"></el-table-column>
<el-table-column
:label="item.title"
:min-width="item.minWidth"
v-for="(item, index) in columns"
:key="index"
>
<template #default="scope">
<template v-if="item.key"
><span>{{ scope.row[item.key] }}</span></template
>
<template v-else-if="item.slot === 'pic'">
<div
class="acea-row row-middle row-center-wrapper"
v-db-click
@click="modalPicTap('dan', 'danTable', scope.$index)"
>
<div class="pictrue pictrueTab" v-if="scope.row.pic"><img v-lazy="scope.row.pic" /></div>
<div class="upLoad pictrueTab acea-row row-center-wrapper" v-else>
<i class="el-icon-picture-outline" style="font-size: 24px"></i>
</div>
</div>
</template>
<template v-else-if="item.slot === 'price'">
<el-input-number
:controls="false"
v-model="scope.row.price"
:min="0"
:precision="2"
class="priceBox"
></el-input-number>
</template>
<template v-if="item.slot === 'quota'">
<el-input-number
:controls="false"
v-model="scope.row.quota"
:min="1"
class="priceBox"
></el-input-number>
</template>
</template>
</el-table-column>
</el-table>
</el-form-item>
</el-col>
</el-row>
<el-row v-show="current === 2">
<el-col :span="24">
<el-form-item label="内容:">
<WangEditor
style="width: 90%"
:content="formValidate.description"
:ai-default-prompt="aiDefaultPrompt"
@editorContent="getEditorContent"
></WangEditor>
</el-form-item>
</el-col>
</el-row>
<el-form-item>
<el-button
class="submission"
v-db-click
@click="step"
:disabled="($route.params.id && current === 1) || current === 0"
>上一步</el-button
>
<el-button
:disabled="submitOpen && current === 2"
type="primary"
class="submission"
v-db-click
@click="next"
>{{ current === 2 ? '提交' : '下一步' }}</el-button
>
</el-form-item>
</el-form>
</el-col>
</el-row>
</el-card>
<el-dialog v-model="modals" title="商品列表" class="paymentFooter" width="1000px">
<goods-list ref="goodslistRef" @getProductId="getProductId"></goods-list>
</el-dialog>
<el-dialog v-model="modalPic" width="950px" title="上传商品图" :close-on-click-modal="false">
<uploadPictures
:isChoice="isChoice"
@getPic="getPic"
@getPicD="getPicD"
:gridBtn="gridBtn"
:gridPic="gridPic"
v-if="modalPic"
></uploadPictures>
</el-dialog>
<freight-template ref="templateRef" @addSuccess="productGetTemplate"></freight-template>
</div>
</template>
<script setup>
import { computed, nextTick, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import goodsList from '@/components/goodsList/index';
import WangEditor from '@/components/wangEditor/index.vue';
import uploadPictures from '@/components/uploadPictures';
import { combinationInfoApi, combinationCreatApi } from '../../api';
import { productAttrsApi } from '@/api/marketing';
import { productGetTemplateApi } from '@/api/product';
import freightTemplate from '@/components/freightTemplate/index';
import steps from '@/components/steps/index';
import setting from '@/setting';
import { useMediaStore } from '@/store/module/media';
defineOptions({ name: 'combinationProductCreate' });
const route = useRoute();
const router = useRouter();
const mediaStore = useMediaStore();
const routePre = setting.routePre;
const isMobile = computed(() => mediaStore.isMobile);
const labelWidth = computed(() => (isMobile.value ? undefined : '155px'));
const labelPosition = computed(() => (isMobile.value ? 'top' : 'right'));
const formRef = ref(null);
const goodslistRef = ref(null);
const multipleTableRef = ref(null);
const templateRef = ref(null);
const submitOpen = ref(false);
const spinShow = ref(false);
const isChoice = ref('');
const current = ref(0);
const modalPic = ref(false);
const grid = { xl: 12, lg: 20, md: 24, sm: 24, xs: 24 };
const grid2 = { xl: 8, lg: 8, md: 12, sm: 24, xs: 24 };
const gridPic = { xl: 6, lg: 8, md: 12, sm: 12, xs: 12 };
const gridBtn = { xl: 4, lg: 8, md: 8, sm: 8, xs: 8 };
const stepList = ['选择拼团商品', '填写基础信息', '修改商品详情'];
const modals = ref(false);
const modal_loading = ref(false);
const images = ref([]);
const templateList = ref([]);
const columns = ref([]);
const specsData = ref([]);
const picTit = ref('');
const tableIndex = ref(0);
const copy = ref(0);
const description = ref('');
let dragging = null;
const formValidate = ref({
images: [],
info: '',
title: '',
image: '',
unit_name: '',
price: 0,
effective_time: 24,
stock: 1,
sales: 0,
sort: 0,
is_postage: 0,
is_commission: 0,
is_host: 0,
is_show: 0,
section_time: [],
description: '',
id: 0,
product_id: 0,
people: 2,
once_num: 1,
num: 1,
temp_id: '',
attrs: [],
items: [],
virtual: 100,
virtualPeople: 0,
head_commission: 0,
logistics: ['1'],
freight: 2,
postage: 1,
});
// AI 写作默认提示词:优先带入当前商品标题(弹窗内未被用户修改过的提示词会跟随刷新)
const aiDefaultPrompt = computed(() => {
const title = (formValidate.value.title || '').trim();
return title
? `请围绕商品《${title}》撰写一篇产品推广文章,介绍产品亮点、使用场景与适用人群,结构清晰,800字左右。`
: '写一篇产品推广文章,介绍产品亮点、使用场景与适用人群,结构清晰,800字左右。';
});
const ruleValidate = {
image: [{ required: true, message: '请选择主图', trigger: 'change' }],
images: [{ required: true, type: 'array', message: '请选择轮播图', trigger: 'change', min: 1 }],
title: [{ required: true, message: '请输入拼团名称', trigger: 'blur' }],
info: [{ required: true, message: '请输入拼团简介', trigger: 'blur' }],
section_time: [{ required: true, type: 'array', message: '请选择活动时间', trigger: 'change' }],
unit_name: [{ required: true, message: '请输入单位', trigger: 'blur' }],
effective_time: [{ required: true, type: 'number', message: '请输入拼团时效(小时)', trigger: 'blur' }],
people: [{ required: true, type: 'number', message: '请输入拼团人数', trigger: 'blur' }],
num: [{ required: true, type: 'number', message: '请输入购买数量限制', trigger: 'blur' }],
once_num: [{ required: true, type: 'number', message: '请输入单次购买数量限制', trigger: 'blur' }],
virtualPeople: [{ required: true, type: 'number', message: '请输入虚拟成团补齐人数', trigger: 'blur' }],
temp_id: [{ required: true, message: '请选择运费模板', trigger: 'change', type: 'number' }],
};
onMounted(() => {
if (route.params.id) {
copy.value = route.params.copy;
current.value = 1;
getInfo();
}
productGetTemplate();
});
function getEditorContent(data) {
description.value = data;
}
function freight() {
templateRef.value.id = 0;
templateRef.value.isTemplate = true;
}
function productAttrs(row) {
productAttrsApi(row.id, 3)
.then((res) => {
let data = res.data.info;
specsData.value = data.attrs;
specsData.value.forEach(function (item, index) {
specsData.value[index]['id'] = index;
});
formValidate.value.items = data.items;
columns.value = data.header;
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function changeCheckbox(selection) {
formValidate.value.attrs = selection;
}
function productGetTemplate() {
productGetTemplateApi().then((res) => {
templateList.value = res.data;
});
}
function getProductId(row) {
modal_loading.value = false;
modals.value = false;
setTimeout(() => {
formValidate.value = {
images: row.slider_image,
info: row.store_info,
title: row.store_name,
image: row.image,
unit_name: row.unit_name,
price: 0,
effective_time: 24,
stock: row.stock,
sales: row.sales,
sort: row.sort,
is_postage: row.is_postage,
is_commission: 0,
is_host: row.is_hot,
is_show: 0,
section_time: [],
id: 0,
people: 2,
num: 1,
once_num: 1,
product_id: row.id,
temp_id: row.temp_id,
virtual: 100,
virtualPeople: 0,
logistics: row.logistics,
freight: row.freight,
postage: row.postage,
custom_form: row.custom_form,
virtual_type: row.virtual_type,
head_commission: 0,
description: row.description,
};
productAttrs(row);
}, 500);
}
function onchangeTime(e) {
formValidate.value.section_time = e;
}
function getInfo() {
spinShow.value = true;
combinationInfoApi(route.params.id)
.then(async (res) => {
let info = res.data.info;
formValidate.value = info;
formValidate.value.virtualPeople = parseInt(
formValidate.value.people - formValidate.value.people * (formValidate.value.virtual / 100),
);
formValidate.value['items'] = info.attrs.items;
columns.value = info.attrs.header;
specsData.value = info.attrs.value;
specsData.value.forEach(function (item, index) {
specsData.value[index]['id'] = index;
});
let attr = [];
for (let index in info.attrs.value) {
if (info.attrs.value[index]._checked) attr.push(info.attrs.value[index]);
}
formValidate.value.attrs = attr;
nextTick(() => {
attr.forEach((row) => {
multipleTableRef.value && multipleTableRef.value.toggleRowSelection(row, true);
});
});
spinShow.value = false;
})
.catch((res) => {
spinShow.value = false;
ElMessage.error(res.msg);
});
}
function getRowKeys(row) {
return row.id;
}
function next() {
if (current.value === 2) {
formValidate.value.description = description.value;
formRef.value.validate((valid) => {
if (valid) {
if (copy.value == 1) formValidate.value.copy = 1;
formValidate.value.id = Number(route.params.id) || 0;
submitOpen.value = true;
formValidate.value.virtual = parseInt(
((formValidate.value.people - formValidate.value.virtualPeople) / formValidate.value.people) * 100,
);
combinationCreatApi(formValidate.value)
.then(async (res) => {
submitOpen.value = false;
ElMessage.success(res.msg);
setTimeout(() => {
router.push({ path: routePre + '/marketing/combination/product' });
}, 500);
})
.catch((res) => {
submitOpen.value = false;
ElMessage.error(res.msg);
});
}
});
} else if (current.value === 1) {
formRef.value.validate((valid) => {
if (valid) {
if (formValidate.value.people < 2) return ElMessage.error('拼团人数必须大于2');
if (formValidate.value.num < 0) return ElMessage.error('购买数量限制必须大于0');
if (formValidate.value.once_num < 0) return ElMessage.error('单次购买数量限制必须大于0');
if (!formValidate.value.attrs) return ElMessage.error('请选择属性规格');
for (let index in formValidate.value.attrs) {
if (formValidate.value.attrs[index].quota <= 0) return ElMessage.error('拼团限量必须大于0');
if (formValidate.value.attrs[index].quota > formValidate.value.attrs[index]['stock'])
return ElMessage.error('拼团限量不能超过规格库存');
}
current.value += 1;
} else {
return ElMessage.warning('请完善您的信息');
}
});
} else {
if (formValidate.value.image) current.value += 1;
else ElMessage.warning('请选择商品');
}
}
function step() {
current.value--;
}
function modalPicTap(tit, picTitVal, index) {
modalPic.value = true;
isChoice.value = tit === 'dan' ? '单选' : '多选';
picTit.value = picTitVal;
tableIndex.value = index;
}
function getPic(pc) {
switch (picTit.value) {
case 'danFrom':
formValidate.value.image = pc.att_dir;
break;
default:
if (!!formValidate.value.attrs && formValidate.value.attrs.length)
specsData.value[tableIndex.value]['_checked'] = true;
specsData.value[tableIndex.value].pic = pc.att_dir;
}
modalPic.value = false;
}
function getPicD(pc) {
images.value = pc;
images.value.map((item) => {
formValidate.value.images.push(item.att_dir);
formValidate.value.images = formValidate.value.images.splice(0, 10);
});
modalPic.value = false;
}
function handleRemove(i) {
images.value.splice(i, 1);
formValidate.value.images.splice(i, 1);
}
function changeGoods() {
modals.value = true;
nextTick(() => {
if (!goodslistRef.value) return;
goodslistRef.value.formValidate.is_show = -1;
goodslistRef.value.formValidate.type = 3;
goodslistRef.value.getList();
goodslistRef.value.goodsCategory();
});
}
function handleDragStart(e, item) {
dragging = item;
}
function handleDragEnd(e, item) {
dragging = null;
}
function handleDragOver(e) {
e.dataTransfer.dropEffect = 'move';
}
function handleDragEnter(e, item) {
e.dataTransfer.effectAllowed = 'move';
if (item === dragging) return;
const newItems = [...formValidate.value.images];
const src = newItems.indexOf(dragging);
const dst = newItems.indexOf(item);
newItems.splice(dst, 0, ...newItems.splice(src, 1));
formValidate.value.images = newItems;
}
</script>
<style lang="scss" scoped>
.content_width {
width: 460px;
}
.grey {
font-size: 12px;
color: #999;
}
.priceBox {
width: 100%;
}
.addfont {
font-size: 12px;
color: var(--prev-color-primary);
margin-left: 10px;
cursor: pointer;
}
.form {
.picBox {
display: inline-block;
cursor: pointer;
}
.pictrue {
width: 60px;
height: 60px;
border: 1px dotted rgba(0, 0, 0, 0.1);
margin-right: 15px;
display: inline-block;
position: relative;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
.btndel {
position: absolute;
z-index: 9;
width: 20px !important;
height: 20px !important;
left: 46px;
top: -4px;
}
}
.upLoad {
width: 58px;
height: 58px;
line-height: 58px;
border: 1px dotted rgba(0, 0, 0, 0.1);
border-radius: 4px;
background: rgba(0, 0, 0, 0.02);
cursor: pointer;
}
}
</style>

View File

@ -1,263 +0,0 @@
<template>
<div class="article-manager">
<el-card :bordered="false" shadow="never" class="ivu-mb-16" :body-style="{ padding: 0 }">
<div class="padding-add">
<el-form
ref="formRef"
:model="formValidate"
:label-width="labelWidth"
:label-position="labelPosition"
@submit.prevent
inline
>
<el-form-item label="时间选择:">
<date-range-picker
clearable
v-model="timeVal"
:editable="false"
@change="onchangeTime"
format="YYYY/MM/DD"
value-format="YYYY/MM/DD"
class="mr20"
></date-range-picker>
</el-form-item>
<el-form-item label="拼团状态:">
<el-select
v-model="formValidate.status"
placeholder="请选择"
clearable
@change="userSearchs"
class="form_content_width"
>
<el-option :value="1" label="进行中"></el-option>
<el-option :value="2" label="已完成"></el-option>
<el-option :value="3" label="未完成"></el-option>
</el-select>
</el-form-item>
</el-form>
</div>
</el-card>
<cards-data :cardLists="cardLists" v-if="cardLists.length >= 0"></cards-data>
<el-card :bordered="false" shadow="never" class="ivu-mt">
<el-table :data="tableList" v-loading="loading" highlight-current-row>
<el-table-column label="头像" min-width="90">
<template #default="scope">
<div class="tabBox_img" v-viewer><img v-lazy="scope.row.avatar" /></div>
</template>
</el-table-column>
<el-table-column label="开团团长" min-width="150">
<template #default="scope"
><span>{{ scope.row.nickname + ' / ' + scope.row.uid }}</span></template
>
</el-table-column>
<el-table-column label="开团时间" min-width="150">
<template #default="scope"
><span>{{ $filters.formatDate(scope.row.add_time) }}</span></template
>
</el-table-column>
<el-table-column label="拼团商品" min-width="180">
<template #default="scope"
><span>{{ scope.row.title + ' / ' + scope.row.cid }}</span></template
>
</el-table-column>
<el-table-column label="几人团" min-width="80">
<template #default="scope"
><span>{{ scope.row.people }}</span></template
>
</el-table-column>
<el-table-column label="几人参加" min-width="80">
<template #default="scope"
><span>{{ scope.row.count_people }}</span></template
>
</el-table-column>
<el-table-column label="结束时间" min-width="120">
<template #default="scope"
><span>{{ $filters.formatDate(scope.row.stop_time) }}</span></template
>
</el-table-column>
<el-table-column label="状态" min-width="120">
<template #default="scope">
<el-tag type="info" v-show="scope.row.status === 1">进行中</el-tag>
<el-tag v-show="scope.row.status === 2">已完成</el-tag>
<el-tag type="warning" v-show="scope.row.status === 3">未完成</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="150">
<template #default="scope">
<a v-db-click @click="Info(scope.row)">查看详情</a>
<el-divider v-if="scope.row.status === 1" direction="vertical"></el-divider>
<a v-if="scope.row.status === 1" v-db-click @click="joinCombination(scope.row)">立即成团</a>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total"
:total="total"
v-model:page="formValidate.page"
v-model:limit="formValidate.limit"
@pagination="getList"
/>
</div>
</el-card>
<!-- 详情模态框-->
<el-dialog v-model="modals" class="tableBox" title="查看详情" :close-on-click-modal="false" width="720px">
<el-table
ref="selection"
:data="tabList3"
v-loading="loading2"
empty-text="暂无数据"
highlight-current-row
max-height="600"
>
<el-table-column label="ID" width="80">
<template #default="scope"
><span>{{ scope.row.id }}</span></template
>
</el-table-column>
<el-table-column label="用户名称" min-width="100">
<template #default="scope"
><span>{{ scope.row.nickname + ' / ' + scope.row.uid }}</span></template
>
</el-table-column>
<el-table-column label="用户头像" min-width="150">
<template #default="scope">
<div class="tabBox_img" v-viewer><img v-lazy="scope.row.avatar" /></div>
</template>
</el-table-column>
<el-table-column label="订单编号" min-width="100">
<template #default="scope"
><span>{{ scope.row.order_id }}</span></template
>
</el-table-column>
<el-table-column label="金额" min-width="100">
<template #default="scope"
><span>{{ scope.row.total_price }}</span></template
>
</el-table-column>
<el-table-column label="订单状态" min-width="100">
<template #default="scope">
<el-tag v-show="scope.row.is_refund != 0">已退款</el-tag>
<el-tag type="danger" v-show="scope.row.is_refund === 0">未退款</el-tag>
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import cardsData from '@/components/cards/cards';
import { combineListApi, orderPinkListApi, statisticsApi, combineJoinApi } from '../../api';
import { useMediaStore } from '@/store/module/media';
defineOptions({ name: 'combinationPinkList' });
const mediaStore = useMediaStore();
const isMobile = computed(() => mediaStore.isMobile);
const labelWidth = computed(() => (isMobile.value ? undefined : '80px'));
const labelPosition = computed(() => (isMobile.value ? 'top' : 'right'));
const formRef = ref(null);
const cardLists = ref([]);
const modals = ref(false);
const loading = ref(false);
const formValidate = ref({ status: '', data: '', page: 1, limit: 15 });
const tableList = ref([]);
const total = ref(0);
const timeVal = ref([]);
const loading2 = ref(false);
const tabList3 = ref([]);
const rows = ref({});
onMounted(() => {
getList();
getStatistics();
});
function getStatistics() {
statisticsApi()
.then(async (res) => {
cardLists.value = res.data.res;
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function Info(row) {
modals.value = true;
rows.value = row;
orderPinkListApi(row.id)
.then(async (res) => {
tabList3.value = res.data.list;
loading.value = false;
})
.catch((res) => {
loading.value = false;
ElMessage.error(res.msg);
});
}
function joinCombination(row) {
ElMessageBox.confirm('确认成团?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
combineJoinApi(row.id)
.then((res) => {
ElMessage.success(res.msg);
getList();
})
.catch((res) => {
ElMessage.error(res.msg);
});
})
.catch(() => {
ElMessage.info('已取消');
});
}
function onchangeTime(e) {
timeVal.value = e || [];
formValidate.value.data = timeVal.value[0] === '' ? '' : timeVal.value ? timeVal.value.join('-') : '';
formValidate.value.page = 1;
getList();
}
function getList() {
loading.value = true;
formValidate.value.status = formValidate.value.status || '';
combineListApi(formValidate.value)
.then(async (res) => {
tableList.value = res.data.list;
total.value = res.data.count;
loading.value = false;
})
.catch((res) => {
loading.value = false;
ElMessage.error(res.msg);
});
}
function userSearchs() {
formValidate.value.page = 1;
getList();
}
</script>
<style lang="scss" scoped>
.article-manager {
margin-top: 3px;
}
.tabBox_img {
width: 36px;
height: 36px;
border-radius: 4px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
</style>

View File

@ -1,288 +0,0 @@
<template>
<div>
<el-card :bordered="false" shadow="never" :body-style="{ padding: 0 }">
<div class="padding-add">
<el-form
ref="formRef"
:model="formValidate"
:label-width="labelWidth"
label-position="right"
@submit.prevent
inline
>
<el-form-item label="上架状态:">
<el-select
v-model="formValidate.is_show"
placeholder="请选择"
clearable
@change="userSearchs"
class="form_content_width"
>
<el-option :value="1" label="上架"></el-option>
<el-option :value="0" label="下架"></el-option>
</el-select>
</el-form-item>
<el-form-item label="拼团搜索:" prop="store_name">
<el-input
clearable
placeholder="请输入拼团名称/ID"
v-model="formValidate.store_name"
class="form_content_width"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" v-db-click @click="userSearchs">查询</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16">
<el-button
v-auth="['marketing-store_combination-create']"
class="action-row"
type="primary"
v-db-click
@click="add"
>
添加拼团商品
</el-button>
<el-button v-auth="['export-storeCombination']" class="action-row export" v-db-click @click="exports"
>导出</el-button
>
<el-table :data="tableList" v-loading="loading" highlight-current-row class="mt14">
<el-table-column label="ID" width="80">
<template #default="scope"
><span>{{ scope.row.id }}</span></template
>
</el-table-column>
<el-table-column label="拼团图片" min-width="90">
<template #default="scope">
<div class="tabBox_img" v-viewer><img v-lazy="scope.row.image" /></div>
</template>
</el-table-column>
<el-table-column label="拼团名称" min-width="130">
<template #default="scope">
<el-tooltip placement="top" :show-after="600">
<template #content
><div>{{ scope.row.title }}</div></template
>
<span class="line2">{{ scope.row.title }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="原价" min-width="100">
<template #default="scope"
><span>{{ scope.row.ot_price }}</span></template
>
</el-table-column>
<el-table-column label="拼团价" min-width="100">
<template #default="scope"
><span>{{ scope.row.price }}</span></template
>
</el-table-column>
<el-table-column label="拼团人数" min-width="80">
<template #default="scope"
><span>{{ scope.row.count_people }}</span></template
>
</el-table-column>
<el-table-column label="参与人数" min-width="80">
<template #default="scope"
><span>{{ scope.row.count_people_all }}</span></template
>
</el-table-column>
<el-table-column label="成团数量" min-width="80">
<template #default="scope"
><span>{{ scope.row.count_people_pink }}</span></template
>
</el-table-column>
<el-table-column label="限量" min-width="80">
<template #default="scope"
><span>{{ scope.row.quota_show }}</span></template
>
</el-table-column>
<el-table-column label="限量剩余" min-width="80">
<template #default="scope"
><span>{{ scope.row.quota }}</span></template
>
</el-table-column>
<el-table-column label="活动状态" min-width="100">
<template #default="scope">
<el-tag v-show="scope.row.start_name === '进行中'">进行中</el-tag>
<el-tag type="warning" v-show="scope.row.start_name === '未开始'">未开始</el-tag>
<el-tag type="info" v-show="scope.row.start_name === '已结束'">已结束</el-tag>
</template>
</el-table-column>
<el-table-column label="活动时间" min-width="180">
<template #default="scope">
<p>开始:{{ scope.row.start_time }}</p>
<p>结束:{{ scope.row.stop_time }}</p>
</template>
</el-table-column>
<el-table-column label="上架状态" min-width="120">
<template #default="scope">
<el-switch
class="defineSwitch"
:active-value="1"
:inactive-value="0"
v-model="scope.row.is_show"
@change="onchangeIsShow(scope.row)"
size="large"
active-text="上架"
inactive-text="下架"
>
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="170">
<template #default="scope">
<a v-if="scope.row.stop_status === 0" v-db-click @click="edit(scope.row)">编辑</a>
<el-divider direction="vertical" v-if="scope.row.stop_status === 0" />
<a v-db-click @click="copy(scope.row)">复制</a>
<el-divider direction="vertical"></el-divider>
<a v-db-click @click="del(scope.row, '删除拼团商品', scope.$index)">删除</a>
<el-divider direction="vertical"></el-divider>
<a v-db-click @click="viewInfo(scope.row)">统计</a>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total"
:total="total"
v-model:page="formValidate.page"
v-model:limit="formValidate.limit"
@pagination="getList"
/>
</div>
</el-card>
</div>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import { combinationListApi, combinationSetStatusApi, storeCombinationApi } from '../../api';
import exportExcel from '@/utils/newToExcel.js';
import { modalSure } from '@/utils/public';
import setting from '@/setting';
import { useMediaStore } from '@/store/module/media';
defineOptions({ name: 'combinationProductList' });
const route = useRoute();
const router = useRouter();
const mediaStore = useMediaStore();
const routePre = setting.routePre;
const isMobile = computed(() => mediaStore.isMobile);
const labelWidth = computed(() => (isMobile.value ? undefined : '80px'));
const labelPosition = computed(() => (isMobile.value ? 'top' : 'right'));
const formRef = ref(null);
const loading = ref(false);
const formValidate = ref({ is_show: '', store_name: '', page: 1, limit: 15 });
const tableList = ref([]);
const total = ref(0);
onMounted(() => {
getList();
});
async function exports() {
let [th, filekey, data, fileName] = [[], [], [], ''];
let excelData = JSON.parse(JSON.stringify(formValidate.value));
excelData.page = 1;
excelData.limit = 200;
for (let i = 0; i < excelData.page + 1; i++) {
let lebData = await getExcelData(excelData);
if (!fileName) fileName = lebData.filename;
if (!filekey.length) filekey = lebData.fileKey;
if (!th.length) th = lebData.header;
if (lebData.export.length) {
data = data.concat(lebData.export);
excelData.page++;
} else {
exportExcel(th, filekey, fileName, data);
return;
}
}
}
function getExcelData(excelData) {
return new Promise((resolve) => {
storeCombinationApi(excelData).then((res) => resolve(res.data));
});
}
function add() {
router.push({ path: routePre + '/marketing/combination/create' });
}
function edit(row) {
router.push({
path: routePre + '/marketing/combination/create/' + row.id + '/0',
});
}
function copy(row) {
router.push({
path: routePre + '/marketing/combination/create/' + row.id + '/1',
});
}
function del(row, tit, num) {
let delfromData = { title: tit, num: num, url: `marketing/combination/${row.id}`, method: 'DELETE', ids: '' };
modalSure(delfromData)
.then((res) => {
ElMessage.success(res.msg);
tableList.value.splice(num, 1);
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function viewInfo(row) {
router.push({
path: routePre + '/marketing/combination/statistics/' + row.id,
});
}
function getList() {
loading.value = true;
formValidate.value.product_id = route.params.product_id || '';
combinationListApi(formValidate.value)
.then(async (res) => {
tableList.value = res.data.list;
total.value = res.data.count;
loading.value = false;
})
.catch((res) => {
loading.value = false;
ElMessage.error(res.msg);
});
}
function userSearchs() {
formValidate.value.page = 1;
getList();
}
function onchangeIsShow(row) {
let data = { id: row.id, status: row.is_show };
combinationSetStatusApi(data)
.then(async (res) => {
ElMessage.success(res.msg);
})
.catch((res) => {
ElMessage.error(res.msg);
row.is_show = !row.is_show;
});
}
</script>
<style lang="scss" scoped>
.tabBox_img {
width: 36px;
height: 36px;
border-radius: 4px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
</style>

View File

@ -1,250 +0,0 @@
<template>
<div>
<pages-header
ref="pageHeader"
:title="$route.meta.title"
:backUrl="$routeProStr + '/marketing/combination/product'"
></pages-header>
<cards-data :cardLists="cardLists" v-if="cardLists.length >= 0" class="ivu-mt-16"></cards-data>
<el-card :bordered="false" shadow="never">
<el-form ref="paginationRef" :model="pagination" label-width="80px" label-position="right" @submit.prevent inline>
<el-form-item v-if="type == 1" label="订单状态:">
<el-select
v-model="pagination.status"
placeholder="请选择订单状态"
clearable
@change="searchList"
class="form_content_width"
>
<el-option value="1" label="待发货"></el-option>
<el-option value="2" label="待收货"></el-option>
<el-option value="3" label="待评价"></el-option>
<el-option value="4" label="交易完成"></el-option>
</el-select>
</el-form-item>
<el-form-item label="搜索:">
<el-input
v-model="pagination.real_name"
:placeholder="type == 1 ? '请输入用户|订单号|UID' : '请输入用户姓名|UID'"
class="form_content_width"
clearable
/>
</el-form-item>
<el-form-item>
<el-button type="primary" v-db-click @click="searchList">查询</el-button>
</el-form-item>
</el-form>
<el-tabs v-model="type" @tab-change="onClickTab">
<el-tab-pane v-for="(item, index) in tabs" :label="item.label" :name="item.type" :key="index" />
</el-tabs>
<el-table :data="tbody" ref="tableRef" v-loading="loading" highlight-current-row>
<el-table-column
:label="item.title"
:min-width="item.minWidth"
v-for="(item, index) in type == 1 ? thead2 : thead"
:key="index"
>
<template #default="scope">
<template v-if="item.key"
><span>{{ scope.row[item.key] }}</span></template
>
<template v-else-if="item.slot === 'avatar'">
<div class="tabBox_img" v-viewer><img v-lazy="scope.row.avatar" /></div>
</template>
<template v-else-if="item.slot === 'people'">
<span>{{ scope.row.count_people + ' / ' + scope.row.people }}</span>
</template>
<template v-else-if="item.slot === 'status'">
<el-tag type="info" v-show="scope.row.status === 1">进行中</el-tag>
<el-tag type="danger" v-show="scope.row.status === 3">已失败</el-tag>
<el-tag v-show="scope.row.status === 2">已成功</el-tag>
</template>
<template v-else-if="item.slot === 'action'">
<a v-db-click @click="Info(scope.row)">查看详情</a>
</template>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total"
:total="total"
v-model:page="pagination.page"
v-model:limit="pagination.limit"
@pagination="getList"
/>
</div>
</el-card>
<el-dialog v-model="modals" class="tableBox" title="查看详情" :close-on-click-modal="false" width="750px">
<el-table
ref="selection"
:data="tabList3"
v-loading="loading2"
empty-text="暂无数据"
highlight-current-row
max-height="600"
>
<el-table-column label="ID" width="80">
<template #default="scope"
><span>{{ scope.row.id }}</span></template
>
</el-table-column>
<el-table-column label="用户头像" min-width="90">
<template #default="scope">
<div class="tabBox_img" v-viewer><img v-lazy="scope.row.avatar" /></div>
</template>
</el-table-column>
<el-table-column label="用户名称" min-width="130">
<template #default="scope"
><span>{{ scope.row.nickname + ' / ' + scope.row.uid }}</span></template
>
</el-table-column>
<el-table-column label="订单编号" min-width="130">
<template #default="scope"
><span>{{ scope.row.order_id }}</span></template
>
</el-table-column>
<el-table-column label="金额" min-width="130">
<template #default="scope"
><span>{{ scope.row.total_price }}</span></template
>
</el-table-column>
<el-table-column label="订单状态" min-width="130">
<template #default="scope">
<el-tag v-show="scope.row.is_refund != 0">已退款</el-tag>
<el-tag type="danger" v-show="scope.row.is_refund === 0">未退款</el-tag>
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { ElMessage } from 'element-plus';
import cardsData from '@/components/cards/cards';
import {
getcombinationStatistics,
getcombinationStatisticsPeople,
getcombinationStatisticsOrder,
orderPinkListApi,
} from '../../api';
defineOptions({ name: 'combinationStatistics' });
const route = useRoute();
const paginationRef = ref(null);
const tableRef = ref(null);
const modals = ref(false);
const id = ref(0);
const tbody = ref([]);
const total = ref(0);
const loading = ref(false);
const loading2 = ref(false);
const tabList3 = ref([]);
const rows = ref({});
const tabs = [
{ type: '0', label: '活动参与人' },
{ type: '1', label: '活动订单' },
];
const type = ref('0');
const thead = [
{ title: '头像', slot: 'avatar' },
{ title: '发起用户', key: 'nickname' },
{ title: '开团时间', key: '_add_time' },
{ title: '拼团人数', slot: 'people' },
{ title: '结束时间', key: '_stop_time' },
{ title: '拼团状态', slot: 'status' },
{ title: '操作', slot: 'action' },
];
const thead2 = [
{ title: '订单号', key: 'order_id' },
{ title: '用户', key: 'real_name' },
{ title: '订单状态', key: 'status' },
{ title: '订单支付金额', key: 'pay_price' },
{ title: '订单商品数', key: 'total_num' },
{ title: '下单时间', key: 'add_time' },
{ title: '支付时间', key: 'pay_time' },
];
const cardLists = ref([
{ col: 4, count: 0, name: '活动参与人数(人)', className: 'iconcanyurenshu' },
{ col: 4, count: 0, name: '推广人数(人)', className: 'icontuiguangrenshu' },
{ col: 4, count: 0, name: '发起拼团数', className: 'iconfaqirenshu' },
{ col: 4, count: 0, name: '成团数', className: 'iconchengtuanshu' },
{ col: 4, count: 0, name: '支付订单额(元)', className: 'iconzhifudingdan' },
{ col: 4, count: 0, name: '支付人数(人)', className: 'iconxiadanrenshu' },
]);
const pagination = ref({ page: 1, limit: 15, real_name: '', status: '' });
onMounted(() => {
id.value = route.params.id;
getStatistics();
getList();
});
function getStatistics() {
getcombinationStatistics(id.value).then((res) => {
let arr = ['people_count', 'spread_count', 'start_count', 'success_count', 'pay_price', 'pay_count'];
cardLists.value.map((i, index) => {
i.count = res.data[arr[index]];
});
});
}
function getList() {
loading.value = true;
if (type.value == 0) {
getcombinationStatisticsPeople(id.value, pagination.value).then((res) => {
loading.value = false;
total.value = res.data.count;
tbody.value = res.data.list;
});
} else {
getcombinationStatisticsOrder(id.value, pagination.value).then((res) => {
loading.value = false;
total.value = res.data.count;
tbody.value = res.data.list;
});
}
}
// el-tabs 的 v-model 在 tab-click 前已更新,直接读 type
function onClickTab() {
pagination.value.page = 1;
pagination.value.real_name = '';
pagination.value.status = '';
getList();
}
function searchList() {
pagination.value.page = 1;
getList();
}
function Info(row) {
modals.value = true;
rows.value = row;
orderPinkListApi(row.id)
.then(async (res) => {
tabList3.value = res.data.list;
loading.value = false;
})
.catch((res) => {
loading.value = false;
ElMessage.error(res.msg);
});
}
</script>
<style lang="scss" scoped>
.tabBox_img {
width: 36px;
height: 36px;
border-radius: 4px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
</style>

View File

@ -1,124 +0,0 @@
/**
* 拼团扩展路由
*/
import setting from '@/setting';
const routePre = setting.routePre;
const pre = 'combination_';
export default [
{
path: routePre + '/marketing/combination/product',
section: 'combination',
action: 'product',
meta: {
auth: ['marketing-combination-product'],
title: '拼团商品',
},
component: () => import('@/layout'),
children: [
{
path: '',
name: `${pre}product`,
component: () => import('./pages/product/index.vue'),
meta: {
auth: ['marketing-combination-product'],
title: '拼团商品',
},
},
],
},
{
path: routePre + '/marketing/combination/create/:id?/:copy?',
section: 'combination',
action: 'create',
meta: {
auth: ['marketing-combination-create'],
title: '添加/编辑拼团商品',
},
component: () => import('@/layout'),
children: [
{
path: '',
name: `${pre}create`,
component: () => import('./pages/create/index.vue'),
meta: {
auth: ['marketing-combination-create'],
title: '添加/编辑拼团商品',
activeMenu: routePre + '/marketing/combination/product',
},
},
],
},
{
path: routePre + '/marketing/combination/pink',
section: 'combination',
action: 'pink',
meta: {
auth: ['marketing-combination-pink'],
title: '拼团列表',
},
component: () => import('@/layout'),
children: [
{
path: '',
name: `${pre}pink`,
component: () => import('./pages/pink/index.vue'),
meta: {
auth: ['marketing-combination-pink'],
title: '拼团列表',
},
},
],
},
{
path: routePre + '/marketing/combination/statistics/:id',
section: 'combination',
action: 'statistics',
meta: {
auth: ['marketing-combination-statistics'],
title: '拼团统计',
},
component: () => import('@/layout'),
children: [
{
path: '',
name: `${pre}statistics`,
component: () => import('./pages/statistics/index.vue'),
meta: {
auth: ['marketing-combination-statistics'],
title: '拼团统计',
activeMenu: routePre + '/marketing/combination/product',
},
},
],
},
// 兼容旧路由
{
path: routePre + '/marketing/store_combination/index',
section: 'combination',
action: 'product',
name: `${pre}legacyIndex`,
meta: { title: '拼团商品' },
},
{
path: routePre + '/marketing/store_combination/combina_list',
section: 'combination',
action: 'pink',
name: `${pre}legacyPink`,
meta: { title: '拼团列表' },
},
{
path: routePre + '/marketing/store_combination/create/:id?/:copy?',
section: 'combination',
action: 'create',
name: `${pre}legacyCreate`,
meta: { title: '添加/编辑拼团商品' },
},
{
path: routePre + '/marketing/store_combination/statistics/:id',
section: 'combination',
action: 'statistics',
name: `${pre}legacyStatistics`,
meta: { title: '拼团统计' },
},
];

View File

@ -1,133 +0,0 @@
<template>
<div class="coupon-setting">
<el-form :model="couponForm" label-width="120px" label-position="right">
<el-form-item label="优惠券面值:" prop="coupon_value">
<el-input-number
:controls="false"
:min="0"
v-model="couponForm.coupon_value"
placeholder="请输入面值"
class="input-number-unit-class"
class-unit="元"
></el-input-number>
</el-form-item>
<el-form-item label="优惠门槛:" prop="coupon_min">
<el-input-number
:controls="false"
:min="0"
v-model="couponForm.coupon_min"
placeholder="请输入门槛金额"
class="input-number-unit-class"
class-unit="元"
></el-input-number>
</el-form-item>
<el-form-item label="有效期:" prop="coupon_start_time">
<el-date-picker
v-model="couponForm.coupon_start_time"
type="date"
placeholder="开始日期"
style="width: 48%"
></el-date-picker>
<span style="margin: 0 10px">至</span>
<el-date-picker
v-model="couponForm.coupon_end_time"
type="date"
placeholder="结束日期"
style="width: 48%"
></el-date-picker>
</el-form-item>
<el-form-item label="使用范围:" prop="coupon_scope">
<el-radio-group v-model="couponForm.coupon_scope">
<el-radio :label="0" :value="0">全店通用</el-radio>
<el-radio :label="1" :value="1">指定分类</el-radio>
<el-radio :label="2" :value="2">指定商品</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="指定分类:" prop="coupon_cate_id" v-if="couponForm.coupon_scope === 1">
<el-cascader
v-model="couponForm.coupon_cate_id"
:options="treeSelect"
:props="{ multiple: true, emitPath: false, checkStrictly: true }"
clearable
></el-cascader>
</el-form-item>
<el-form-item label="指定商品:" prop="coupon_product_id" v-if="couponForm.coupon_scope === 2">
<el-select
v-model="couponForm.coupon_product_id"
multiple
clearable
placeholder="请选择商品"
style="width: 100%"
>
</el-select>
</el-form-item>
<el-form-item label="每人限领:" prop="coupon_limit">
<el-input-number
:controls="false"
:min="0"
v-model="couponForm.coupon_limit"
placeholder="0表示不限"
></el-input-number>
<span>张</span>
</el-form-item>
</el-form>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
import { cascaderListApi } from '@/api/product';
defineOptions({ name: 'couponSetting' });
const props = defineProps({
productId: {
type: Number,
default: 0,
},
});
const treeSelect = ref([]);
const couponForm = ref({
coupon_value: 0,
coupon_min: 0,
coupon_start_time: '',
coupon_end_time: '',
coupon_scope: 0,
coupon_cate_id: [],
coupon_product_id: [],
coupon_limit: 0,
});
loadCategory();
if (props.productId) {
loadData();
}
async function loadCategory() {
try {
const res = await cascaderListApi(1);
treeSelect.value = res.data;
} catch (error) {
ElMessage.error('加载分类失败');
}
}
async function loadData() {
try {
} catch (error) {
ElMessage.error('加载失败');
}
}
function getFormData() {
return { ...couponForm.value };
}
defineExpose({ getFormData });
</script>
<style scoped>
.coupon-setting {
padding: 20px;
}
</style>

View File

@ -1,132 +0,0 @@
<template>
<div class="product-add">
<el-card :bordered="false" shadow="never" class="ivu-mt" :body-style="{ padding: 0 }">
<div class="tab-container">
<el-tabs v-model="activeName" type="card" @tab-click="handleClick">
<el-tab-pane label="基本信息" name="basic">
<basicInfo
ref="basicInfoRef"
:productId="productId"
:productType="'coupon'"
@saveSuccess="saveSuccess"
></basicInfo>
</el-tab-pane>
<el-tab-pane label="优惠券设置" name="coupon">
<couponSetting ref="couponSettingRef" :productId="productId" @saveSuccess="saveSuccess"></couponSetting>
</el-tab-pane>
<el-tab-pane label="价格与佣金" name="price">
<priceCommission
ref="priceCommissionRef"
:productId="productId"
@saveSuccess="saveSuccess"
></priceCommission>
</el-tab-pane>
<el-tab-pane label="商品详情" name="detail">
<productDetail ref="productDetailRef" :productId="productId" @saveSuccess="saveSuccess"></productDetail>
</el-tab-pane>
<el-tab-pane label="营销设置" name="marketing">
<marketingSetting
ref="marketingSettingRef"
:productId="productId"
@saveSuccess="saveSuccess"
></marketingSetting>
</el-tab-pane>
<el-tab-pane label="其他设置" name="other">
<otherSetting ref="otherSettingRef" :productId="productId" @saveSuccess="saveSuccess"></otherSetting>
</el-tab-pane>
</el-tabs>
</div>
<div class="bottom-btn-box">
<el-button type="primary" class="save-btn" @click="saveProduct">保存商品</el-button>
<el-button @click="goBack">返回列表</el-button>
</div>
</el-card>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import basicInfo from '../../../../pages/product/productAdd/components/BasicInfo.vue';
import priceCommission from '../../../../pages/product/productAdd/components/PriceCommission.vue';
import productDetail from '../../../../pages/product/productAdd/components/ProductDetail.vue';
import marketingSetting from '../../../../pages/product/productAdd/components/MarketingSetting.vue';
import otherSetting from '../../../../pages/product/productAdd/components/OtherSetting.vue';
import couponSetting from './components/CouponSetting.vue';
import { couponProductSave, couponProductUpdate } from '@/pages/plugin/coupon/couponProduct';
import setting from '@/setting';
defineOptions({ name: 'couponProductAdd' });
const route = useRoute();
const router = useRouter();
const routePre = setting.routePre;
const basicInfoRef = ref(null);
const couponSettingRef = ref(null);
const priceCommissionRef = ref(null);
const productDetailRef = ref(null);
const marketingSettingRef = ref(null);
const otherSettingRef = ref(null);
const activeName = ref('basic');
const productId = ref(0);
const id = route.params.id;
if (id) {
productId.value = parseInt(id);
}
function handleClick(tab) {}
async function saveProduct() {
const basicData = basicInfoRef.value.getFormData();
const couponData = couponSettingRef.value.getFormData();
const priceData = priceCommissionRef.value.getFormData();
const detailData = productDetailRef.value.getFormData();
const marketingData = marketingSettingRef.value.getFormData();
const otherData = otherSettingRef.value.getFormData();
const formData = {
...basicData,
...couponData,
...priceData,
...detailData,
...marketingData,
...otherData,
product_type: 'coupon',
};
try {
if (productId.value) {
await couponProductUpdate(productId.value, formData);
ElMessage.success('修改成功');
} else {
await couponProductSave(formData);
ElMessage.success('添加成功');
}
goBack();
} catch (error) {
ElMessage.error(error.msg || '操作失败');
}
}
function saveSuccess() {
ElMessage.success('保存成功');
}
function goBack() {
router.push({ path: routePre + '/product/coupon/list' });
}
</script>
<style scoped>
.product-add {
.bottom-btn-box {
text-align: center;
padding: 20px 0;
border-top: 1px solid #ebf0f5;
.save-btn {
width: 120px;
}
}
}
</style>

View File

@ -1,39 +0,0 @@
import request from '@/libs/request';
export function couponProductList(params) {
return request({
url: 'coupon/product/list',
method: 'get',
params,
});
}
export function couponProductDetail(id) {
return request({
url: `coupon/product/${id}`,
method: 'get',
});
}
export function couponProductSave(data) {
return request({
url: 'coupon/product',
method: 'post',
data,
});
}
export function couponProductUpdate(id, data) {
return request({
url: `coupon/product/${id}`,
method: 'put',
data,
});
}
export function couponProductDelete(id) {
return request({
url: `coupon/product/${id}`,
method: 'delete',
});
}

View File

@ -1,750 +0,0 @@
<template>
<div class="article-manager">
<el-card :bordered="false" shadow="never" :body-style="{ padding: 0 }">
<div class="padding-add">
<el-form :model="artFrom" label-width="80px" label-position="right" inline @submit.prevent>
<div class="acea-row search-form">
<div class="search-form-box">
<el-form-item label="商品搜索:" label-for="store_name">
<el-input
clearable
placeholder="请输入商品名称/关键字/ID"
v-model="artFrom.store_name"
class="form_content_width"
/>
</el-form-item>
<el-form-item label="商品分类:" label-for="pid">
<el-cascader
v-model="artFrom.cate_id"
:options="treeSelect"
:props="{ multiple: false, emitPath: false, checkStrictly: true }"
clearable
class="form_content_width"
></el-cascader>
</el-form-item>
<template v-if="collapse">
<el-form-item label="会员专属:">
<el-select v-model="artFrom.vip_product" clearable placeholder="全部" class="form_content_width">
<el-option label="全部" value="" />
<el-option label="不限制" value="0" />
<el-option label="仅会员可见可买" value="1" />
<el-option label="所有人可见仅会员可买" value="2" />
</el-select>
</el-form-item>
<el-form-item label="添加时间:">
<date-range-picker
class="form_range_content_width"
clearable
v-model="timeVal"
:editable="false"
@change="onchangeTime"
format="YYYY/MM/DD"
value-format="YYYY/MM/DD"
></date-range-picker>
</el-form-item>
<el-form-item label="价格:" label-for="store_name">
<el-input
clearable
placeholder="最小值"
v-model="artFrom.price_s[0]"
class="form_range_content_width"
/>
~
<el-input
clearable
placeholder="最大值"
v-model="artFrom.price_s[1]"
class="form_range_content_width"
/>
</el-form-item>
<el-form-item label="销量:" label-for="store_name">
<el-input
clearable
placeholder="最小值"
v-model="artFrom.sales_s[0]"
class="form_range_content_width"
/>
~
<el-input
clearable
placeholder="最大值"
v-model="artFrom.sales_s[1]"
class="form_range_content_width"
/>
</el-form-item>
</template>
</div>
<div class="search-form-sub">
<el-button type="primary" v-db-click @click="userSearchs">查询</el-button>
<el-button class="ResetSearch" v-db-click @click="reset">重置</el-button>
<a class="ivu-ml-8 font12 ml10" v-db-click @click="collapse = !collapse">
<template v-if="!collapse"> 展开 <i class="el-icon-arrow-down" /> </template>
<template v-else> 收起 <i class="el-icon-arrow-up" /> </template>
</a>
</div>
</div>
</el-form>
</div>
</el-card>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16" :body-style="{ padding: '0 20px 20px' }">
<div class="Button">
<router-link v-auth="['admin-coupon-product-save']" :to="routePre + '/product/coupon/add'"
><el-button type="primary" class="mr14">添加优惠券商品</el-button></router-link
>
<el-dropdown class="bnt mr14" @command="batchSelect">
<el-button>批量修改<i class="el-icon-arrow-down el-icon--right"></i></el-button>
<template #dropdown
><el-dropdown-menu>
<el-dropdown-item :command="1">商品分类</el-dropdown-item>
<el-dropdown-item :command="3">购买送积分</el-dropdown-item>
<el-dropdown-item :command="4">购买送优惠券</el-dropdown-item>
<el-dropdown-item :command="5">关联用户标签</el-dropdown-item>
<el-dropdown-item v-if="artFrom.type === '1'" :command="7">批量下架</el-dropdown-item>
<el-dropdown-item v-if="artFrom.type === '2'" :command="8">批量上架</el-dropdown-item>
<el-dropdown-item :command="9">设置商品标签</el-dropdown-item>
<el-dropdown-item v-if="artFrom.type !== '6'" :command="11">移到回收站</el-dropdown-item>
<el-dropdown-item v-if="artFrom.type == '6'" :command="12">恢复商品</el-dropdown-item>
</el-dropdown-menu></template
>
</el-dropdown>
</div>
<el-table
ref="table"
:data="tableList"
class="mt14"
v-loading="loading"
highlight-current-row
:row-key="getRowKey"
@selection-change="handleSelectRow"
empty-text="暂无数据"
>
<el-table-column type="selection" width="60" :reserve-selection="true"> </el-table-column>
<el-table-column label="商品ID" width="80">
<template #default="scope">
<span>{{ scope.row.id }}</span>
</template>
</el-table-column>
<el-table-column label="商品图" min-width="90">
<template #default="scope">
<div class="tabBox_img" v-viewer>
<img v-lazy="scope.row.image" />
</div>
</template>
</el-table-column>
<el-table-column label="商品名称" min-width="250">
<template #default="scope">
<span>{{ scope.row.store_name }}</span>
</template>
</el-table-column>
<el-table-column label="优惠券面值" min-width="120">
<template #default="scope">
<span>{{ scope.row.coupon_value || 0 }}元</span>
</template>
</el-table-column>
<el-table-column label="优惠门槛" min-width="120">
<template #default="scope">
<span>{{ scope.row.coupon_min || 0 }}元</span>
</template>
</el-table-column>
<el-table-column label="有效期" min-width="150">
<template #default="scope">
<span>{{ scope.row.coupon_start_time || '-' }} ~ {{ scope.row.coupon_end_time || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="商品售价" min-width="100">
<template #default="scope">
<span>{{ scope.row.price }}</span>
</template>
</el-table-column>
<el-table-column label="销量" min-width="100">
<template #default="scope">
<span>{{ scope.row.sales }}</span>
</template>
</el-table-column>
<el-table-column label="排序" min-width="100">
<template #default="scope">
<span>{{ scope.row.sort }}</span>
</template>
</el-table-column>
<el-table-column label="状态" min-width="100">
<template #default="scope">
<el-switch
class="defineSwitch"
:active-value="1"
:inactive-value="0"
v-model="scope.row.is_show"
:value="scope.row.is_show"
:disabled="scope.row.stop_status ? true : false"
@change="changeSwitch(scope.row)"
size="large"
active-text="上架"
inactive-text="下架"
>
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" minWidth="100">
<template #default="scope">
<a v-db-click @click="edit(scope.row)">编辑</a>
<el-divider direction="vertical"></el-divider>
<el-dropdown>
<span class="el-dropdown-link">更多<i class="el-icon-arrow-down el-icon--right"></i> </span>
<template #dropdown
><el-dropdown-menu>
<el-dropdown-item>
<router-link :to="{ path: routePre + '/product/product_reply/' + scope.row.id }"
><a>查看评论</a></router-link
>
</el-dropdown-item>
<el-dropdown-item @click="openModal(scope.row, 'vipPriceSet')">会员价管理</el-dropdown-item>
<el-dropdown-item @click="openModal(scope.row, 'brokerageSet')">佣金管理</el-dropdown-item>
<el-dropdown-item v-if="artFrom.type === '6'" @click="del(scope.row, '恢复商品', scope.$index)"
>恢复商品</el-dropdown-item
>
<el-dropdown-item v-if="artFrom.type === '6'" @click="fullDel(scope.row, '彻底删除', scope.$index)"
>彻底删除</el-dropdown-item
>
<el-dropdown-item v-else @click="del(scope.row, '移入回收站', scope.$index)"
>移到回收站</el-dropdown-item
>
</el-dropdown-menu></template
>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total"
:total="total"
v-model:page="artFrom.page"
v-model:limit="artFrom.limit"
@pagination="getDataList"
/>
</div>
</el-card>
<brokerageSet ref="brokerageSetRef" :productId="productId"></brokerageSet>
<vipPriceSet ref="vipPriceSetRef" :productId="productId"></vipPriceSet>
<el-dialog
v-model="batchModal"
class="batch-box"
title="批量设置"
:show-close="true"
:close-on-click-modal="false"
width="540px"
>
<el-form
class="batchFormData"
:rules="ruleBatch"
:model="batchFormData"
label-width="90px"
label-position="right"
@submit.prevent
>
<el-row :gutter="24">
<el-col :span="24" v-if="batchType == 1">
<el-form-item label="商品分类:" prop="cate_id">
<el-cascader
v-model="batchFormData.cate_id"
:options="treeSelect"
:props="{ multiple: true, emitPath: false, checkStrictly: true }"
clearable
style="width: 400px"
></el-cascader>
</el-form-item>
</el-col>
<el-col :span="24" v-if="[3, 4, 5].includes(batchType)">
<el-form-item label="赠送积分:" prop="give_integral" v-if="batchType == 3">
<el-input-number
:controls="false"
v-model="batchFormData.give_integral"
:min="0"
:max="9999999999"
placeholder="请输入积分"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="赠送优惠券:" v-if="batchType == 4">
<div v-if="couponName.length" class="mb20">
<el-tag closable v-for="(item, index) in couponName" :key="index" @close="handleClose(item)">{{
item.title
}}</el-tag>
</div>
<el-button type="primary" v-db-click @click="addCoupon">添加优惠券</el-button>
</el-form-item>
<el-form-item label="关联标签:" prop="label_id" v-if="batchType == 5">
<div class="acea-row label_width">
<div class="labelInput acea-row row-between-wrapper" v-db-click @click="openLabel">
<div style="width: auto">
<div v-if="dataLabel.length">
<el-tag
class="m-r-2"
closable
v-for="(item, index) in dataLabel"
@close="closeLabel(item)"
:key="index"
>{{ item.label_name }}</el-tag
>
</div>
<span class="span" v-else>选择用户关联标签</span>
</div>
<div class="iconfont iconxiayi"></div>
</div>
</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer
><span class="dialog-footer">
<el-button v-db-click @click="clearBatchData">取 消</el-button>
<el-button type="primary" v-db-click @click="batchSub">确 定</el-button>
</span></template
>
</el-dialog>
<el-dialog
v-model="labelShow"
title="请选择用户标签"
width="540px"
:show-close="true"
:close-on-click-modal="false"
>
<userLabel @activeData="activeData" @close="labelClose"></userLabel>
</el-dialog>
<el-dialog v-model="tagShow" title="请选择商品标签" :show-close="true" width="540px" :close-on-click-modal="false">
<goodsLabel :defaultLabelList="goodsLabelList" @activeLabel="activeGoodsLabel" @close="labelClose"></goodsLabel>
</el-dialog>
<coupon-list ref="couponTemplates" @nameId="nameId" :couponids="batchFormData.coupon_ids"></coupon-list>
</div>
</template>
<script setup>
import { nextTick, onActivated, ref } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import { couponProductList } from '@/pages/plugin/coupon/couponProduct';
import {
PostgoodsIsShow,
cascaderListApi,
productShowApi,
productUnshowApi,
batchSetting,
productLabelUseListApi,
} from '@/api/product';
import userLabel from '@/components/labelList';
import goodsLabel from '@/components/goodsLabel';
import couponList from '@/components/couponList';
import brokerageSet from '../../../pages/product/components/brokerageSet.vue';
import vipPriceSet from '../../../pages/product/components/vipPriceSet.vue';
import { modalSure } from '@/utils/public';
import setting from '@/setting';
defineOptions({ name: 'couponProduct' });
const router = useRouter();
const routePre = setting.routePre;
const table = ref(null);
const brokerageSetRef = ref(null);
const vipPriceSetRef = ref(null);
const couponTemplates = ref(null);
const batchModal = ref(false);
const labelShow = ref(false);
const tagShow = ref(false);
const batchType = ref(1);
const batchFormData = ref({
cate_id: [],
give_integral: 0,
label_id: [],
coupon_ids: [],
});
const ruleBatch = ref({});
const couponName = ref([]);
const dataLabel = ref([]);
const artFrom = ref({
page: 1,
limit: 15,
store_name: '',
cate_id: '',
vip_product: '',
sales_s: ['', ''],
price_s: ['', ''],
time: '',
type: '1',
});
const tableList = ref([]);
const total = ref(0);
const loading = ref(false);
const treeSelect = ref([]);
const ids = ref([]);
const multipleSelection = ref([]);
const productId = ref(0);
const goodsLabelList = ref([]);
const timeVal = ref([]);
const collapse = ref(false);
onActivated(() => {
goodsCategory();
getLabelList();
getDataList();
});
function onchangeTime(e) {
timeVal.value = e;
artFrom.value.time = timeVal.value ? timeVal.value.join('-') : '';
artFrom.value.page = 1;
getDataList();
}
function getLabelList() {
productLabelUseListApi()
.then((res) => {
res.data.map((el) => {
if (el.list && el.list.length) {
el.list.map((label) => {
label.active = false;
});
}
});
goodsLabelList.value = res.data;
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function openModal(row, type) {
productId.value = row.id;
const modalRefs = { vipPriceSet: vipPriceSetRef, brokerageSet: brokerageSetRef };
modalRefs[type].value.visible = true;
}
function batchSub() {
let data = batchFormData.value;
data.ids = ids.value;
data.type = batchType.value;
let activeIds = [];
dataLabel.value.forEach((item) => {
activeIds.push(item.id);
});
data.label_id = activeIds;
batchSetting(data)
.then((res) => {
ElMessage.success(res.msg);
getDataList();
clearBatchData(false);
ids.value = [];
})
.catch((err) => {
ElMessage.error(err.msg);
});
}
function clearBatchData(status) {
if (!status) {
batchFormData.value = {
cate_id: [],
give_integral: null,
label_id: [],
coupon_ids: [],
label_list: [],
};
dataLabel.value = [];
}
batchModal.value = false;
table.value.clearSelection();
}
function batchSelect(type) {
if (!ids.value.length) {
ElMessage.warning('请选择要修改的商品');
} else if (type === 7) {
onDismount();
} else if (type === 8) {
onShelves();
} else if (type === 9) {
batchType.value = type;
tagShow.value = true;
} else if (type === 11) {
batchGoodsSetting('全部移到回收站', 1);
} else if (type === 12) {
batchGoodsSetting('恢复选中商品', 2);
} else {
batchType.value = type;
batchModal.value = true;
}
}
function batchGoodsSetting(tit, type) {
let url = type == 1 ? 'product/product/batch_delete' : 'product/product/batch_recover';
let delfromData = {
title: tit,
url,
method: 'post',
ids: {
ids: ids.value,
},
un: 1,
};
modalSure(delfromData)
.then((res) => {
ElMessage.success(res.msg);
getDataList();
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function activeData(val) {
labelShow.value = false;
dataLabel.value = val;
}
function nameId(id, names) {
batchFormData.value.coupon_ids = id;
couponName.value = unique(names);
}
function handleClose(name) {
let index = couponName.value.indexOf(name);
couponName.value.splice(index, 1);
batchFormData.value.coupon_ids.splice(index, 1);
}
function unique(arr) {
const res = new Map();
return arr.filter((arr) => !res.has(arr.id) && res.set(arr.id, 1));
}
function labelClose() {
labelShow.value = false;
tagShow.value = false;
}
function activeGoodsLabel(data) {
tagShow.value = false;
batchFormData.value.label_list = Array.from(new Set(data));
batchSub();
}
function openLabel() {
labelShow.value = true;
}
function closeLabel(label) {
let index = dataLabel.value.indexOf(dataLabel.value.filter((d) => d.id == label.id)[0]);
dataLabel.value.splice(index, 1);
}
function addCoupon() {
couponTemplates.value.isTemplate = true;
couponTemplates.value.tableList();
}
function onShelves() {
if (ids.value.length === 0) {
ElMessage.warning('请选择要上架的商品');
} else {
let data = {
ids: ids.value,
};
productShowApi(data)
.then((res) => {
ElMessage.success(res.msg);
getDataList();
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
}
function onDismount() {
if (ids.value.length === 0) {
ElMessage.warning('请选择要下架的商品');
} else {
let data = {
ids: ids.value,
};
productUnshowApi(data)
.then((res) => {
ElMessage.success(res.msg);
artFrom.value.page = 1;
getDataList();
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
}
function getRowKey(row) {
return row.id;
}
function handleSelectRow(selection) {
const uniqueArr = [];
const selIds = [];
for (let i = 0; i < selection.length; i++) {
const item = selection[i];
if (!selIds.includes(item.id)) {
uniqueArr.push(item);
selIds.push(item.id);
}
}
ids.value = selIds;
multipleSelection.value = uniqueArr;
}
function goodsCategory() {
cascaderListApi(1)
.then((res) => {
treeSelect.value = res.data;
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function getDataList() {
loading.value = true;
artFrom.value.cate_id = artFrom.value.cate_id || '';
couponProductList(artFrom.value)
.then((res) => {
tableList.value = res.data.list || [];
total.value = res.data.count || 0;
nextTick(() => {
showSelectData();
});
loading.value = false;
})
.catch((res) => {
loading.value = false;
ElMessage.error(res.msg || '获取列表失败');
});
}
function showSelectData() {
if (multipleSelection.value.length > 0) {
tableList.value.forEach((row) => {
multipleSelection.value.forEach((item) => {
if (row.id === item.id) {
table.value.toggleRowSelection(item, true);
}
});
});
}
}
function userSearchs() {
artFrom.value.page = 1;
getDataList();
}
function changeSwitch(row) {
PostgoodsIsShow(row.id, row.is_show)
.then((res) => {
ElMessage.success(res.msg);
getDataList();
})
.catch((res) => {
row.is_show = !row.is_show ? 1 : 0;
ElMessage.error(res.msg);
});
}
function edit(row) {
router.push({ path: routePre + '/product/coupon/add/' + row.id });
}
function del(row, tit, num) {
let delfromData = {
title: tit,
num: num,
url: `product/product/${row.id}`,
method: 'DELETE',
ids: '',
un: 1,
};
modalSure(delfromData)
.then((res) => {
ElMessage.success(res.msg);
tableList.value.splice(num, 1);
getDataList();
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function fullDel(row, tit, num) {
let delfromData = {
title: tit,
num: num,
url: `product/full_del/${row.id}`,
method: 'DELETE',
};
modalSure(delfromData)
.then((res) => {
ElMessage.success(res.msg);
tableList.value.splice(num, 1);
getDataList();
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function reset() {
artFrom.value = {
page: 1,
limit: 15,
store_name: '',
cate_id: '',
vip_product: '',
sales_s: ['', ''],
price_s: ['', ''],
time: '',
type: '1',
};
timeVal.value = [];
getDataList();
}
</script>
<style scoped>
.tabBox_img {
width: 36px;
height: 36px;
border-radius: 4px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
.labelInput {
border: 1px solid #dcdee2;
padding: 0 15px 0 10px;
border-radius: 5px;
min-height: 30px;
cursor: pointer;
font-size: 12px;
.span {
color: #c5c8ce;
}
.iconxiayi {
margin-left: 5px;
font-size: 12px;
}
}
.el-dropdown-link {
cursor: pointer;
color: var(--prev-color-primary);
font-size: 12px;
}
.el-icon-arrow-down {
font-size: 12px;
}
.el-dropdown-menu__item {
a {
color: #606266;
}
}
.label_width {
width: 400px;
}
.search-form {
display: flex;
justify-content: space-between;
.search-form-box {
display: flex;
flex-wrap: wrap;
flex: 1;
}
.search-form-sub {
display: flex;
align-items: baseline;
}
}
.batch-box {
:deep(.ivu-modal-body) {
overflow: auto;
min-height: 350px;
}
}
</style>

View File

@ -1,28 +0,0 @@
/**
* 优惠券商品路由
*/
import setting from '@/setting';
const routePre = setting.routePre;
const pre = 'product_';
export default [
{
path: routePre + '/product/coupon/list',
name: `${pre}couponProductList`,
meta: {
auth: ['admin-coupon-product-list'],
title: '优惠券商品',
},
component: () => import('@/pages/plugin/coupon/index'),
},
{
path: routePre + '/product/coupon/add/:id?',
name: `${pre}couponProductAdd`,
meta: {
auth: ['admin-coupon-product-add'],
title: '优惠券商品添加',
activeMenu: routePre + '/product/coupon/list',
},
component: () => import('@/pages/plugin/coupon/add'),
},
];

View File

@ -1,44 +0,0 @@
<template>
<div class="agent-app">
<div class="section-content">
<component :is="sectionComponent" />
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import AgentManage from './agentManage.vue';
import SpreadApply from './spread/apply.vue';
import DivisionList from './division/list/index.vue';
import AgentIndex from './division/agent/index.vue';
import AgentStatistics from './division/agent/statistics.vue';
import ApplyList from './division/agent/applyList.vue';
import Agreement from './division/agent/agreement.vue';
defineOptions({ name: 'AgentApp' });
const props = defineProps({
section: { type: String, default: 'distribution' },
action: { type: String, default: 'manage' },
params: { type: Object, default: () => ({}) },
});
const actionComponents = {
manage: AgentManage,
apply: SpreadApply,
list: DivisionList,
agent: AgentIndex,
statistics: AgentStatistics,
applyList: ApplyList,
agreement: Agreement,
};
const sectionComponent = computed(() => actionComponents[props.action] || AgentManage);
</script>
<style scoped>
.agent-app {
height: 100%;
}
</style>

View File

@ -1,557 +0,0 @@
<template>
<div>
<el-card :bordered="false" shadow="never" class="ivu-mb-16" :body-style="{ padding: 0 }">
<div class="padding-add">
<el-form
ref="formValidateRef"
:model="formValidate"
:label-width="labelWidth"
:label-position="labelPosition"
@submit.prevent
inline
>
<el-form-item label="时间选择:">
<date-range-picker
clearable
v-model="timeVal"
:editable="false"
@change="onchangeTime"
format="YYYY/MM/DD"
value-format="YYYY/MM/DD"
class="mr20"
></date-range-picker>
</el-form-item>
<el-form-item label="搜索:" label-for="status">
<el-input
clearable
placeholder="请输入姓名、电话、UID"
v-model="formValidate.nickname"
class="form_content_width"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" v-db-click @click="userSearchs">查询</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
<cards-data :cardLists="cardLists" v-if="cardLists.length >= 0"></cards-data>
<el-card :bordered="false" shadow="never">
<el-button v-auth="['export-userAgent']" class="export" v-db-click @click="exports">导出</el-button>
<el-table
ref="selection"
:data="tableList"
class="mt14"
v-loading="loading"
empty-text="暂无数据"
highlight-current-row
>
<el-table-column label="ID" width="80">
<template #default="scope">
<span>{{ scope.row.uid }}</span>
</template>
</el-table-column>
<el-table-column label="头像" min-width="90">
<template #default="scope">
<div class="tabBox_img" v-viewer>
<img v-lazy="scope.row.headimgurl ? scope.row.headimgurl : moren" />
</div>
</template>
</el-table-column>
<el-table-column label="用户信息" width="150">
<template #default="scope">
<div class="name">
<div class="item">昵称:{{ scope.row.nickname }}</div>
<div class="item">姓名:{{ scope.row.real_name }}</div>
<div class="item">电话:{{ scope.row.phone }}</div>
</div>
</template>
</el-table-column>
<el-table-column label="分销等级" min-width="120">
<template #default="scope">
<div>{{ scope.row.agentLevel ? scope.row.agentLevel.name : '--' }}</div>
</template>
</el-table-column>
<el-table-column label="推广用户数量" min-width="120">
<template #default="scope">
<span>{{ scope.row.spread_count }}</span>
</template>
</el-table-column>
<el-table-column label="推广订单数量" min-width="120">
<template #default="scope">
<div>{{ scope.row.spread_order.order_count }}</div>
</template>
</el-table-column>
<el-table-column label="推广订单金额" min-width="120">
<template #default="scope">
<div>{{ scope.row.spread_order.order_price || '0.00' }}</div>
</template>
</el-table-column>
<el-table-column label="佣金总金额" min-width="120">
<template #default="scope">
<div>{{ scope.row.brokerage_money }}</div>
</template>
</el-table-column>
<el-table-column label="已提现金额" min-width="120">
<template #default="scope">
<div>{{ scope.row.extract_count_price }}</div>
</template>
</el-table-column>
<el-table-column label="提现次数" min-width="120">
<template #default="scope">
<div>{{ scope.row.extract_count_num }}</div>
</template>
</el-table-column>
<el-table-column label="未提现金额" min-width="120">
<template #default="scope">
<div>{{ scope.row.new_money }}</div>
</template>
</el-table-column>
<el-table-column label="上级推广人" min-width="120">
<template #default="scope">
<div>{{ scope.row.spread_name }}</div>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="120">
<template #default="scope">
<a v-db-click @click="promoters(scope.row, 'man')">推广人</a>
<el-divider direction="vertical"></el-divider>
<el-dropdown @command="changeMenu(scope.row, $event, scope.$index)" :transfer="true">
<span class="el-dropdown-link">更多<i class="el-icon-arrow-down el-icon--right"></i> </span>
<template #dropdown
><el-dropdown-menu>
<el-dropdown-item command="1">推广订单</el-dropdown-item>
<el-dropdown-item command="2">推广二维码</el-dropdown-item>
<el-dropdown-item command="3">修改上级推广人</el-dropdown-item>
<el-dropdown-item command="4" v-if="scope.row.spread_uid">清除上级推广人</el-dropdown-item>
<el-dropdown-item command="5">取消推广资格</el-dropdown-item>
<el-dropdown-item command="6">修改分销等级</el-dropdown-item>
</el-dropdown-menu></template
>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total"
:total="total"
v-model:page="formValidate.page"
v-model:limit="formValidate.limit"
@pagination="getList"
/>
</div>
</el-card>
<!-- 推广人列表-->
<promoters-list ref="promotersLists"></promoters-list>
<!-- 推广二维码-->
<el-dialog v-model="modals" title="推广二维码" :close-on-click-modal="false" width="540px">
<div class="acea-row row-around" v-loading="spinShow">
<div class="acea-row row-column-around row-between-wrapper">
<div class="QRpic" v-if="code_src"><img v-lazy="code_src" /></div>
<span class="QRpic_sp1 mt10" v-db-click @click="getWeChat">公众号推广二维码</span>
</div>
<div class="acea-row row-column-around row-between-wrapper">
<div class="QRpic" v-if="code_xcx"><img v-lazy="code_xcx" /></div>
<span class="QRpic_sp2 mt10" v-db-click @click="getXcx">小程序推广二维码</span>
</div>
<div class="acea-row row-column-around row-between-wrapper">
<div class="QRpic" v-if="code_h5"><img v-lazy="code_h5" /></div>
<span class="QRpic_sp2 mt10" v-db-click @click="getH5">H5推广二维码</span>
</div>
</div>
</el-dialog>
<!--修改推广人-->
<el-dialog v-model="promoterShow" title="修改推广人" width="540px" :show-close="true">
<el-form ref="formInlineRef" :model="formInline" label-width="100px" @submit.prevent>
<el-form-item label="用户头像:" prop="image">
<div class="picBox" v-db-click @click="customer">
<div class="pictrue" v-if="formInline.image">
<img v-lazy="formInline.image" />
</div>
<div class="upLoad acea-row row-center-wrapper" v-else>
<i class="el-icon-picture-outline" style="font-size: 24px"></i>
</div>
</div>
</el-form-item>
</el-form>
<template #footer
><span class="dialog-footer">
<el-button v-db-click @click="cancel('formInline')">取 消</el-button>
<el-button type="primary" v-db-click @click="putSend('formInline')">提交</el-button>
</span></template
>
</el-dialog>
<el-dialog v-model="customerShow" title="请选择商城用户" :show-close="true" width="1000px">
<customerInfo v-if="customerShow" @imageObject="imageObject"></customerInfo>
</el-dialog>
</div>
</template>
<script setup>
import moren from '@/assets/images/moren.jpg';
import { computed, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { ElMessage } from 'element-plus';
import cardsData from '@/components/cards/cards';
import searchFrom from '@/components/publicSearchFrom';
import { useMediaStore } from '@/store/module/media';
import {
agentListApi,
statisticsApi,
lookCodeApi,
lookxcxCodeApi,
lookh5CodeApi,
userAgentApi,
agentSpreadApi,
} from './api';
import promotersList from './handle/promotersList';
import customerInfo from '@/components/customerInfo';
import { membershipDataAddApi } from '@/api/membershipLevel';
import modalForm from '@/utils/modalForm';
import { modalSure } from '@/utils/public';
defineOptions({ name: 'agentManage' });
const { isMobile } = storeToRefs(useMediaStore());
const labelWidth = computed(() => (isMobile.value ? undefined : '80px'));
const labelPosition = computed(() => (isMobile.value ? 'top' : 'right'));
const customerShow = ref(false);
const promoterShow = ref(false);
const modals = ref(false);
const spinShow = ref(false);
const rows = ref({});
const formValidate = ref({
nickname: '',
data: '',
page: 1,
limit: 15,
});
const date = ref('all');
const total = ref(0);
const cardLists = ref([]);
const loading = ref(false);
const tableList = ref([]);
const timeVal = ref([]);
const code_src = ref('');
const code_xcx = ref('');
const code_h5 = ref('');
const formInline = ref({
uid: 0,
spread_uid: 0,
image: '',
});
const formValidateRef = ref(null);
const formInlineRef = ref(null);
const selection = ref(null);
const promotersLists = ref(null);
// 提交
function putSend(name) {
formInlineRef.value.validate((valid) => {
if (valid) {
if (!formInline.value.spread_uid) {
return ElMessage.error('请上传用户');
}
agentSpreadApi(formInline.value)
.then((res) => {
promoterShow.value = false;
ElMessage.success(res.msg);
getList();
formInlineRef.value.resetFields();
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
});
}
// 导出
function exports() {
let data = {
data: formValidate.value.data,
nickname: formValidate.value.nickname,
};
userAgentApi(data)
.then((res) => {
location.href = res.data[0];
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
// 操作
function changeMenu(row, name, index) {
switch (name) {
case '1':
promoters(row, 'order'); //推广人订单
break;
case '2':
spreadQR(row); //推广方式二维码
break;
case '3':
editS(row); //修改上级推广人
break;
case '4': //清除上级推广人
del_parent(row, '清除【 ' + row.nickname + ' 】的上级推广人', index);
break;
case '5': //取消推广资格
del_agent(row, '取消【 ' + row.nickname + ' 】的推广资格', index);
break;
case '6': //修改推广等级
modalForm(membershipDataAddApi({ uid: row.uid }, '/distribution/get_level_form')).then(() => getList());
break;
default:
break;
}
}
function editS(row) {
promoterShow.value = true;
formInline.value.uid = row.uid;
}
function customer() {
customerShow.value = true;
}
function imageObject(e) {
customerShow.value = false;
formInline.value.spread_uid = e.uid;
formInline.value.image = e.image;
}
// 清除上级关系
function del_parent(rows2, titile, num) {
let delfromDatap = {
title: titile,
num: num,
url: `distribution/stair/delete_spread/${rows2.uid}`,
method: 'PUT',
ids: '',
};
modalSure(delfromDatap)
.then((res) => {
ElMessage.success(res.msg);
getList();
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
// 取消自己推广资格
function del_agent(row, tit, num) {
let delfromData = {
title: tit,
num: num,
url: `distribution/stair/delete_system_spread/${row.uid}`,
method: 'PUT',
ids: '',
};
modalSure(delfromData)
.then((res) => {
ElMessage.success(res.msg);
getList();
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function edit(row) {
promoterShow.value = true;
formInline.value.uid = row.uid;
}
function cancel(name) {
promoterShow.value = false;
formInlineRef.value.resetFields();
}
// 推广人列表 订单
function promoters(row, tit) {
promotersLists.value.modals = true;
promotersLists.value.getList(row, tit);
}
// 统计
function getStatistics() {
let data = {
nickname: formValidate.value.nickname,
data: formValidate.value.data,
};
statisticsApi(data)
.then(async (res) => {
let data = res.data;
cardLists.value = data.res;
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
// 具体日期
function onchangeTime(e) {
timeVal.value = e;
formValidate.value.data = timeVal.value ? timeVal.value.join('-') : '';
formValidate.value.page = 1;
if (!e[0]) {
formValidate.value.data = '';
}
getList();
getStatistics();
}
// 选择时间
function selectChange(tab) {
formValidate.value.page = 1;
formValidate.value.data = tab;
timeVal.value = [];
getList();
getStatistics();
}
// 列表
function getList() {
loading.value = true;
agentListApi(formValidate.value)
.then(async (res) => {
let data = res.data;
tableList.value = data.list;
total.value = res.data.count;
loading.value = false;
})
.catch((res) => {
loading.value = false;
ElMessage.error(res.msg);
});
}
// 表格搜索
function userSearchs() {
formValidate.value.page = 1;
getList();
getStatistics();
}
// 二维码
function spreadQR(row) {
modals.value = true;
rows.value = row;
getWeChat();
getXcx();
getH5();
}
// 公众号推广二维码
function getWeChat() {
spinShow.value = true;
let data = {
uid: rows.value.uid,
action: 'wechant_code',
};
lookCodeApi(data)
.then(async (res) => {
let data = res.data;
code_src.value = data.code_src;
spinShow.value = false;
})
.catch((res) => {
spinShow.value = false;
ElMessage.error(res.msg);
});
}
// 小程序推广二维码
function getXcx() {
spinShow.value = true;
let data = {
uid: rows.value.uid,
};
lookxcxCodeApi(data)
.then(async (res) => {
let data = res.data;
code_xcx.value = data.code_src;
spinShow.value = false;
})
.catch((res) => {
spinShow.value = false;
ElMessage.error(res.msg);
});
}
function getH5() {
spinShow.value = true;
let data = {
uid: rows.value.uid,
};
lookh5CodeApi(data)
.then(async (res) => {
let data = res.data;
code_h5.value = data.code_src;
spinShow.value = false;
})
.catch((res) => {
spinShow.value = false;
ElMessage.error(res.msg);
});
}
getList();
getStatistics();
</script>
<style lang="scss" scoped>
.picBox {
display: inline-block;
cursor: pointer;
.upLoad {
width: 58px;
height: 58px;
line-height: 58px;
border: 1px dotted rgba(0, 0, 0, 0.1);
border-radius: 4px;
background: rgba(0, 0, 0, 0.02);
}
.pictrue {
width: 60px;
height: 60px;
border: 1px dotted rgba(0, 0, 0, 0.1);
margin-right: 10px;
img {
width: 100%;
height: 100%;
}
}
.iconfont {
color: #898989;
}
}
.QRpic {
width: 180px;
height: 180px;
img {
width: 100%;
height: 100%;
}
}
.QRpic_sp1 {
font-size: 13px;
color: #19be6b;
cursor: pointer;
}
.QRpic_sp2 {
font-size: 13px;
color: #2d8cf0;
cursor: pointer;
}
img {
height: 36px;
display: block;
}
.ivu-mt .name .item {
margin: 3px 0;
}
.tabform {
margin-bottom: 10px;
}
.Refresh {
font-size: 12px;
color: var(--prev-color-primary);
cursor: pointer;
}
.ivu-form-item {
margin-bottom: 10px;
}
/* .ivu-mt :deep(.ivu-table-header) */
/* border-top:1px dashed #ddd!important */
</style>

View File

@ -1,232 +0,0 @@
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
import request from '@/libs/request';
/**
* @description 分销 -- 列表
* @param {Object} param params {Object} 传值参数
*/
export function agentListApi(params) {
return request({
url: 'distribution/index',
method: 'get',
params,
});
}
/**
* @description 修改上级用户
* @param {Object} param params {Object} 传值参数
*/
export function agentSpreadApi(data) {
return request({
url: 'distribution/spread',
method: 'PUT',
data,
});
}
/**
* @description 分销 -- 表头
* @param {Object} param params {Object} 传值参数
*/
export function statisticsApi(params) {
return request({
url: 'distribution/statistics',
method: 'get',
params,
});
}
/**
* @description 分销 -- 推广人,订单列表
* @param {Object} param params {Object} 传值参数
* @param {String} param url {String} 请求地址
*/
export function stairListApi(url, params) {
return request({
url: url,
method: 'get',
params,
});
}
/**
* @description 分销 -- 公众号推广二维码
* @param {Object} param params {Object} 传值参数
*/
export function lookCodeApi(params) {
return request({
url: 'distribution/look_code',
method: 'get',
params,
});
}
/**
* @description 分销 -- 小程序推广二维码
* @param {Object} param params {Object} 传值参数
*/
export function lookxcxCodeApi(params) {
return request({
url: 'distribution/look_xcx_code',
method: 'get',
params,
});
}
/**
* @description 分销 -- h5推广二维码
* @param {Object} param params {Object} 传值参数
*/
export function lookh5CodeApi(params) {
return request({
url: 'distribution/look_h5_code',
method: 'get',
params,
});
}
/**
* @description 分销 -- 用户推广列表导出
*/
export function userAgentApi(data) {
return request({
url: `export/userAgent`,
method: 'get',
params: data,
});
}
/**
* @description 事业部--列表
* @param {Object} param data {Object} 传值参数
*/
export function regionList(data) {
return request({
url: 'distribution/division/list',
method: 'get',
params: data,
});
}
/**
* @description 代理商申请--列表
* @param {Object} param data {Object} 传值参数
*/
export function divisionList(data) {
return request({
url: 'distribution/division/agent_apply/list',
method: 'get',
params: data,
});
}
/**
* @description 事业部统计--列表
* @param {Object} param data {Object} 传值参数
*/
export function divisionStatistics(data) {
return request({
url: 'distribution/division/statistics',
method: 'get',
params: data,
});
}
/**
* @description 代理商添加--表单
* @param {Object} param data {Object} 传值参数
*/
export function agentFrom(uid) {
return request({
url: `distribution/division/agent/create/${uid}`,
method: 'get',
});
}
/**
* @description 代理商审核
* @param {Object} param data {Object} 传值参数
*/
export function divisionFrom(id, type) {
return request({
url: `distribution/division/examine_apply/${id}/${type}`,
method: 'get',
});
}
/**
* @description 事业部添加--表单
* @param {Object} param data {Object} 传值参数
*/
export function regionFrom(uid) {
return request({
url: `distribution/division/create/${uid}`,
method: 'get',
});
}
/**
* @description 事业部列表
* @param {Object} param data {Object} 传值参数
*/
export function clerkList(data) {
return request({
url: `distribution/division/down_list`,
method: 'get',
params: data,
});
}
/**
* @description 事业部状态切换--列表
* @param {Object} param data {Object} 传值参数
*/
export function isShowApi(data) {
return request({
url: `distribution/division/set_status/${data.status}/${data.id}`,
method: 'put',
});
}
/**
* @description 员工添加--表单
* @param {Object} param data {Object} 传值参数
*/
export function staffAddFrom(uid) {
return request({
url: `distribution/division/staff/create/${uid}`,
method: 'get',
});
}
/**
* @description 申请列表--分销员
* @param {Object} param data {Object} 传值参数
*/
export function spreadList(data) {
return request({
url: 'distribution/spread/apply/list',
method: 'get',
params: data,
});
}
/**
* @description 审核--分销员
* @param {Object} param data {Object} 传值参数
*/
export function spreadFrom(id, uid, type, data) {
return request({
url: `distribution/spread/apply/examine/${id}/${uid}/${type}`,
method: 'post',
data,
});
}

View File

@ -1,86 +0,0 @@
<template>
<div>
<div class="i-layout-page-header header-title">
<span class="ivu-page-header-title">{{ $route.meta.title }}</span>
</div>
<el-card :bordered="false" shadow="never" class="ivu-mt">
<el-form label-width="85px" @submit.prevent v-loading="spinShow">
<el-form-item label="协议内容:">
<WangEditor
:content="agreement.content"
:ai-default-prompt="aiDefaultPrompt"
:ai-default-system-prompt="aiDefaultSystemPrompt"
@editorContent="getEditorContent"
></WangEditor>
</el-form-item>
<el-form-item>
<el-button type="primary" v-db-click @click="memberAgreementSave">保存</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
import WangEditor from '@/components/wangEditor/index.vue';
import { agentAgreement, agentAgreementSave } from '@/api/user';
const ueConfig = {
autoHeightEnabled: false,
initialFrameHeight: 500,
initialFrameWidth: '100%',
UEDITOR_HOME_URL: '/UEditor/',
serverUrl: '',
};
const agreement = ref({
content: '',
id: 0,
});
const spinShow = ref(false);
// AI 写作默认提示词随协议数据下发(user_prompt 为 AI 弹窗输入框默认提示词,
// system_prompt 为系统提示词,生成时按行拆分为 prompts 逐条下发)
const aiDefaultPrompt = ref('');
const aiDefaultSystemPrompt = ref('');
function getEditorContent(data) {
agreement.value.content = data;
}
function memberAgreement() {
spinShow.value = true;
agentAgreement()
.then((res) => {
spinShow.value = false;
const { title, content, status, id, system_prompt, user_prompt } = res.data;
agreement.value.content = content;
agreement.value.id = id || 0;
aiDefaultPrompt.value = user_prompt || '';
aiDefaultSystemPrompt.value = system_prompt || '';
})
.catch((err) => {
ElMessage.error(err.msg);
spinShow.value = false;
});
}
// 保存
function memberAgreementSave() {
agentAgreementSave(agreement.value)
.then((res) => {
ElMessage.success('保存成功');
memberAgreement();
})
.catch((err) => {
ElMessage.error(err.msg);
});
}
memberAgreement();
</script>
<style lang="scss" scoped>
:deep(.ivu-form-item-content) {
line-height: unset !important;
}
</style>

View File

@ -1,351 +0,0 @@
<template>
<div>
<el-card :bordered="false" shadow="never" :body-style="{ padding: 0 }">
<div class="padding-add">
<el-form
ref="formValidateRef"
:model="formValidate"
:label-width="labelWidth"
:label-position="labelPosition"
@submit.prevent
inline
>
<el-form-item label="搜索:">
<el-input
clearable
placeholder="请输入姓名、UID"
v-model="formValidate.keyword"
class="form_content_width"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" v-db-click @click="userSearchs">查询</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16" :body-style="{ padding: '0 20px 20px' }">
<el-row class="ivu-mt box-wrapper">
<el-col :xs="24" :sm="24" ref="rightBox">
<el-tabs v-model="formValidate.status" @tab-change="userSearchs">
<el-tab-pane name="all" label="全部"></el-tab-pane>
<el-tab-pane
v-for="(item, index) in statusList"
:key="index"
:label="item.status_name"
:name="item.id"
></el-tab-pane>
</el-tabs>
<el-table
:data="userLists"
ref="table"
v-loading="loading"
highlight-current-row
no-formValidate-text="暂无数据"
no-filtered-formValidate-text="暂无筛选结果"
>
<el-table-column label="用户UID" width="100">
<template #default="scope">
<span>{{ scope.row.uid }}</span>
</template>
</el-table-column>
<el-table-column label="代理商名称" min-width="150">
<template #default="scope">
<span>{{ scope.row.agent_name }}</span>
</template>
</el-table-column>
<el-table-column label="代理商电话" min-width="150">
<template #default="scope">
<span>{{ scope.row.phone }}</span>
</template>
</el-table-column>
<el-table-column label="事业部名称" min-width="150">
<template #default="scope">
<span>{{ scope.row.division_name }}</span>
</template>
</el-table-column>
<el-table-column label="申请图片" min-width="150">
<template #default="scope">
<div class="pictrue-box" v-if="scope.row.images.length">
<div v-viewer v-for="(item, index) in scope.row.images || []" :key="index">
<img class="pictrue mr10" v-lazy="item" :src="item" />
</div>
</div>
</template>
</el-table-column>
<el-table-column label="申请时间" min-width="150">
<template #default="scope">
<span>{{ scope.row.add_time }}</span>
</template>
</el-table-column>
<el-table-column label="申请状态" min-width="150">
<template #default="scope">
<el-tag>{{ scope.row.status == 0 ? '申请中' : scope.row.status == 1 ? '已同意' : '已拒绝' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="邀请码" min-width="150">
<template #default="scope">
<el-tag>{{ scope.row.division_invite }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="170">
<template #default="scope">
<a v-if="scope.row.status == 0" v-db-click @click="groupAdd(scope.row.id, 1)">同意</a>
<el-divider v-if="scope.row.status == 0" direction="vertical" />
<a v-if="scope.row.status == 0" v-db-click @click="groupAdd(scope.row.id, 0)">拒绝</a>
<el-divider direction="vertical" v-if="scope.row.status == 0" />
<a v-db-click @click="del(scope.row, '删除申请', scope.$index)">删除</a>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total"
:total="total"
v-model:page="formValidate.page"
v-model:limit="formValidate.limit"
@pagination="getList"
/>
</div>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { ElMessage } from 'element-plus';
import { useMediaStore } from '@/store/module/media';
import { divisionList, divisionFrom, isShowApi, clerkList } from '../../api';
import modalForm from '@/utils/modalForm';
import { modalSure } from '@/utils/public';
defineOptions({ name: 'agent_extra' });
const { isMobile } = storeToRefs(useMediaStore());
const labelWidth = computed(() => (isMobile.value ? undefined : '50px'));
const labelPosition = computed(() => (isMobile.value ? 'top' : 'right'));
const grid = {
xl: 7,
lg: 7,
md: 12,
sm: 24,
xs: 24,
};
const total = ref(0);
const total2 = ref(0);
const userLists = ref([]);
const formInline = ref({
uid: 0,
proportion: 0,
image: '',
});
const statusList = ref([
{
status_name: '申请中',
id: '0',
},
{
status_name: '已同意',
id: '1',
},
{
status_name: '已拒绝',
id: '2',
},
]);
const FromData = ref(null);
const loading = ref(false);
const current = ref(0);
const formValidate = ref({
page: 1,
limit: 15,
keyword: '',
status: 'all',
});
const staffModal = ref(false);
const clerkReqData = ref({
uid: 0,
page: 1,
limit: 15,
});
const clerkLists = ref([]);
const formValidateRef = ref(null);
const table = ref(null);
const rightBox = ref(null);
function userSearchs() {
formValidate.value.page = 1;
getList();
}
function jump(uid) {
clerkReqData.value.uid = uid;
getClerkList();
}
function getClerkList() {
clerkReqData.value.division_type = 3;
clerkList(clerkReqData.value).then((res) => {
clerkLists.value = res.data.list;
total2.value = res.data.count;
staffModal.value = true;
});
}
// 列表
function getList() {
loading.value = true;
divisionList(formValidate.value)
.then(async (res) => {
let data = res.data;
userLists.value = data.list;
total.value = data.count;
loading.value = false;
})
.catch((res) => {
loading.value = false;
ElMessage.error(res.msg);
});
}
function pageChange(index) {
formValidate.value.page = index;
getList();
}
function clerkPageChange() {
clerkReqData.value.page = index;
getClerkList();
}
// 添加表单
function groupAdd(id, type) {
modalForm(divisionFrom(id, type))
.then((res) => {
getList();
})
.catch((err) => {});
}
// 修改是否显示
function onchangeIsShow(row) {
let data = {
id: row.uid,
status: row.division_status,
};
isShowApi(data)
.then(async (res) => {
ElMessage.success(res.msg);
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
// 编辑
function edit(row) {}
// 删除
function del(row, tit, num) {
let delfromData = {
title: tit,
method: 'DELETE',
uid: row.id,
url: `distribution/division/del_apply/${row.id}`,
};
modalSure(delfromData)
.then((res) => {
ElMessage.success(res.msg);
userLists.value.splice(num, 1);
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
.ivu-form-item {
margin-bottom: 0;
}
.picBox {
display: inline-block;
cursor: pointer;
.upLoad {
width: 58px;
height: 58px;
line-height: 58px;
border: 1px dotted rgba(0, 0, 0, 0.1);
border-radius: 4px;
background: rgba(0, 0, 0, 0.02);
}
.pictrue {
width: 60px;
height: 60px;
border: 1px dotted rgba(0, 0, 0, 0.1);
margin-right: 10px;
img {
width: 100%;
height: 100%;
}
}
}
:deep(.ivu-menu-vertical .ivu-menu-item-group-title) {
display: none;
}
:deep(.ivu-menu-vertical.ivu-menu-light:after) {
display: none;
}
.left-wrapper {
height: 904px;
background: #fff;
border-right: 1px solid #f2f2f2;
}
.menu-item {
z-index: 50;
position: relative;
display: flex;
justify-content: space-between;
word-break: break-all;
}
:deep(.el-tabs__item) {
height: 54px;
line-height: 54px;
}
.icon-box {
z-index: 3;
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
display: none;
}
.right-menu {
z-index: 10;
position: absolute;
right: -106px;
top: -11px;
width: auto;
min-width: 121px;
}
.tabBox_img {
width: 36px;
height: 36px;
border-radius: 4px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
.pictrue-box {
display: flex;
align-item: center;
}
.pictrue {
width: 25px;
height: 25px;
}
</style>

View File

@ -1,389 +0,0 @@
<template>
<div>
<el-card :bordered="false" shadow="never" :body-style="{ padding: 0 }">
<div class="padding-add">
<el-form
ref="formValidateRef"
:model="formValidate"
:label-width="labelWidth"
:label-position="labelPosition"
@submit.prevent
inline
>
<el-form-item label="搜索:">
<el-input
clearable
placeholder="请输入姓名、UID"
v-model="formValidate.keyword"
class="form_content_width"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" v-db-click @click="userSearchs">查询</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16">
<el-row class="ivu-mt box-wrapper">
<el-col :xs="24" :sm="24" ref="rightBox">
<el-row>
<el-col v-bind="grid">
<el-button type="primary" v-db-click @click="groupAdd('0')" class="mr20">添加代理商</el-button>
</el-col>
</el-row>
<el-table
:data="userLists"
ref="table"
class="mt14"
v-loading="loading"
highlight-current-row
no-formValidate-text="暂无数据"
no-filtered-formValidate-text="暂无筛选结果"
>
<el-table-column label="用户UID" width="100">
<template #default="scope">
<span>{{ scope.row.uid }}</span>
</template>
</el-table-column>
<el-table-column label="头像" min-width="90">
<template #default="scope">
<div class="tabBox_img" v-viewer>
<img v-lazy="scope.row.avatar" />
</div>
</template>
</el-table-column>
<el-table-column label="名称" min-width="130">
<template #default="scope">
<div class="acea-row">
<div v-text="scope.row.division_name" class="ml10"></div>
</div>
</template>
</el-table-column>
<el-table-column label="分销比例" min-width="130">
<template #default="scope">
<span> {{ scope.row.division_percent }}%</span>
</template>
</el-table-column>
<el-table-column label="员工数量" min-width="130">
<template #default="scope">
<span>{{ scope.row.agent_count }}</span>
</template>
</el-table-column>
<el-table-column label="截止时间" min-width="130">
<template #default="scope">
<span>{{ scope.row.division_end_time }}</span>
</template>
</el-table-column>
<el-table-column label="状态" min-width="130">
<template #default="scope">
<el-switch
:active-value="1"
:inactive-value="0"
v-model="scope.row.division_status"
:value="scope.row.division_status"
@change="onchangeIsShow(scope.row)"
size="large"
>
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="220">
<template #default="scope">
<a v-db-click @click="staffAdd(scope.row.uid)">添加员工</a>
<el-divider direction="vertical"></el-divider>
<a v-db-click @click="jump(scope.row.uid)">查看员工</a>
<el-divider direction="vertical"></el-divider>
<a v-db-click @click="groupAdd(scope.row.uid)">编辑</a>
<el-divider direction="vertical"></el-divider>
<a v-db-click @click="del(scope.row, '删除代理商', scope.$index, 2)">删除</a>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total"
:total="total"
v-model:page="formValidate.page"
v-model:limit="formValidate.limit"
@pagination="getList"
/>
</div>
</el-col>
</el-row>
</el-card>
<el-dialog v-model="staffModal" title="员工列表" class="order_box" width="1000px">
<el-table
:data="clerkLists"
ref="table"
class="mt20"
v-loading="loading"
highlight-current-row
no-formValidate-text="暂无数据"
no-filtered-formValidate-text="暂无筛选结果"
>
<el-table-column label="用户UID" width="120">
<template #default="scope">
<span>{{ scope.row.uid }}</span>
</template>
</el-table-column>
<el-table-column label="头像" min-width="120">
<template #default="scope">
<div class="tabBox_img" v-viewer>
<img v-lazy="scope.row.avatar" />
</div>
</template>
</el-table-column>
<el-table-column label="姓名" min-width="130">
<template #default="scope">
<div class="acea-row">
<i class="el-icon-male mr10" v-show="scope.row.sex === '男'" style="color: #2db7f5; font-size: 15px"></i>
<i
class="el-icon-female mr10"
v-show="scope.row.sex === '女'"
style="color: #ed4014; font-size: 15px"
></i>
<div v-text="scope.row.nickname" class=""></div>
</div>
</template>
</el-table-column>
<el-table-column label="分销比例" min-width="130">
<template #default="scope">
<span> {{ scope.row.division_percent }}%</span>
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<template #default="scope">
<a v-db-click @click="del(scope.row, '删除员工', scope.$index, 3)">删除</a>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total2"
:total="total2"
v-model:page="clerkReqData.page"
v-model:limit="clerkReqData.limit"
@pagination="getClerkList"
/>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { ElMessage } from 'element-plus';
import { useMediaStore } from '@/store/module/media';
import { regionList, agentFrom, isShowApi, clerkList, staffAddFrom } from '../../api';
import modalForm from '@/utils/modalForm';
import { modalSure } from '@/utils/public';
defineOptions({ name: 'agent_extra' });
const { isMobile } = storeToRefs(useMediaStore());
const labelWidth = computed(() => (isMobile.value ? undefined : '50px'));
const labelPosition = computed(() => (isMobile.value ? 'top' : 'right'));
const grid = {
xl: 7,
lg: 7,
md: 12,
sm: 24,
xs: 24,
};
const total = ref(0);
const total2 = ref(0);
const userLists = ref([]);
const formInline = ref({
uid: 0,
proportion: 0,
image: '',
});
const FromData = ref(null);
const loading = ref(false);
const current = ref(0);
const formValidate = ref({
page: 1,
limit: 15,
keyword: '',
});
const staffModal = ref(false);
const clerkReqData = ref({
uid: 0,
page: 1,
limit: 15,
});
const clerkLists = ref([]);
const formValidateRef = ref(null);
const table = ref(null);
const rightBox = ref(null);
function userSearchs() {
formValidate.value.page = 1;
getList();
}
function jump(uid) {
clerkReqData.value.uid = uid;
getClerkList();
}
function getClerkList() {
clerkReqData.value.division_type = 3;
clerkList(clerkReqData.value).then((res) => {
clerkLists.value = res.data.list;
total2.value = res.data.count;
staffModal.value = true;
});
}
// 列表
function getList() {
loading.value = true;
formValidate.value.division_type = 2;
regionList(formValidate.value)
.then(async (res) => {
let data = res.data;
userLists.value = data.list;
total.value = data.count;
loading.value = false;
})
.catch((res) => {
loading.value = false;
ElMessage.error(res.msg);
});
}
// 添加表单
function groupAdd(id) {
modalForm(agentFrom(id))
.then((res) => {
getList();
})
.catch((err) => {});
}
//添加员工表单
function staffAdd(id) {
modalForm(staffAddFrom(id))
.then((res) => {
getList();
})
.catch((err) => {});
}
// 修改是否显示
function onchangeIsShow(row) {
let data = {
id: row.uid,
status: row.division_status,
};
isShowApi(data)
.then(async (res) => {
ElMessage.success(res.msg);
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
// 编辑
function edit(row) {}
// 删除
function del(row, tit, num, type) {
let delfromData = {
title: tit,
method: 'DELETE',
uid: row.uid,
url: `distribution/division/del/${type}/${row.uid}`,
};
modalSure(delfromData)
.then((res) => {
ElMessage.success(res.msg);
if (type == 2) {
userLists.value.splice(num, 1);
} else {
clerkLists.value.splice(num, 1);
}
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
.ivu-form-item {
margin-bottom: 0;
}
.picBox {
display: inline-block;
cursor: pointer;
.upLoad {
width: 58px;
height: 58px;
line-height: 58px;
border: 1px dotted rgba(0, 0, 0, 0.1);
border-radius: 4px;
background: rgba(0, 0, 0, 0.02);
}
.pictrue {
width: 60px;
height: 60px;
border: 1px dotted rgba(0, 0, 0, 0.1);
margin-right: 10px;
img {
width: 100%;
height: 100%;
}
}
}
:deep(.ivu-menu-vertical .ivu-menu-item-group-title) {
display: none;
}
:deep(.ivu-menu-vertical.ivu-menu-light:after) {
display: none;
}
.left-wrapper {
height: 904px;
background: #fff;
border-right: 1px solid #f2f2f2;
}
.menu-item {
z-index: 50;
position: relative;
display: flex;
justify-content: space-between;
word-break: break-all;
&:hover .icon-box {
display: block;
}
}
.icon-box {
z-index: 3;
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
display: none;
}
.right-menu {
z-index: 10;
position: absolute;
right: -106px;
top: -11px;
width: auto;
min-width: 121px;
}
.tabBox_img {
width: 36px;
height: 36px;
border-radius: 4px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
</style>

View File

@ -1,230 +0,0 @@
<template>
<div>
<el-card :bordered="false" shadow="never" :body-style="{ padding: 0 }">
<div class="padding-add">
<el-form
ref="formValidateRef"
:model="formValidate"
:label-width="labelWidth"
:label-position="labelPosition"
@submit.prevent
inline
>
<el-form-item label="时间范围:">
<date-range-picker
clearable
v-model="timeVal"
@change="onchangeTime"
format="YYYY/MM/DD"
value-format="YYYY/MM/DD"
class="mr20"
></date-range-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" v-db-click @click="userSearchs">查询</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16" :body-style="{ padding: '0 20px 20px' }">
<el-row class="ivu-mt box-wrapper">
<el-col :xs="24" :sm="24" ref="rightBox">
<el-tabs v-model="formValidate.type" @tab-change="userSearchs">
<el-tab-pane
v-for="(item, index) in statusList"
:key="index"
:label="item.status_name"
:name="item.id"
></el-tab-pane>
</el-tabs>
<el-table
:data="userLists"
ref="table"
v-loading="loading"
highlight-current-row
no-formValidate-text="暂无数据"
no-filtered-formValidate-text="暂无筛选结果"
@sort-change="handleSortChange"
>
<el-table-column label="用户UID" width="150">
<template #default="scope">
<span>{{ scope.row.uid }}</span>
</template>
</el-table-column>
<el-table-column label="头像" min-width="120">
<template #default="scope">
<div class="tabBox_img" v-viewer>
<img v-lazy="scope.row.avatar" />
</div>
</template>
</el-table-column>
<el-table-column label="名称" min-width="130">
<template #default="scope">
<div class="acea-row">
<div v-text="scope.row.name"></div>
</div>
</template>
</el-table-column>
<el-table-column
label="订单数"
min-width="150"
sortable="custom"
:sort-orders="['ascending', 'descending']"
prop="order_sum"
>
<template #default="scope">
<span>{{ scope.row.order_sum }}</span>
</template>
</el-table-column>
<el-table-column
label="订单金额"
min-width="150"
sortable="custom"
:sort-orders="['ascending', 'descending']"
prop="order_sum_price"
>
<template #default="scope">
<span>{{ scope.row.order_sum_price }}</span>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total"
:total="total"
v-model:page="formValidate.page"
v-model:limit="formValidate.limit"
@pagination="getList"
/>
</div>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { ElMessage } from 'element-plus';
import { useMediaStore } from '@/store/module/media';
import { divisionStatistics } from '../../api';
defineOptions({ name: 'agent_extra' });
const { isMobile } = storeToRefs(useMediaStore());
const labelWidth = computed(() => (isMobile.value ? undefined : '70px'));
const labelPosition = computed(() => (isMobile.value ? 'top' : 'right'));
const grid = {
xl: 7,
lg: 7,
md: 12,
sm: 24,
xs: 24,
};
const total = ref(0);
const total2 = ref(0);
const statusList = ref([
{
status_name: '事业部',
id: '1',
},
{
status_name: '代理商',
id: '2',
},
{
status_name: '员工',
id: '3',
},
]);
const userLists = ref([]);
const formInline = ref({
uid: 0,
proportion: 0,
image: '',
});
const timeVal = ref([]);
const FromData = ref(null);
const loading = ref(false);
const current = ref(0);
const formValidate = ref({
page: 1,
limit: 15,
time: '',
type: '1',
sort: '',
order: '',
});
const staffModal = ref(false);
const clerkReqData = ref({
uid: 0,
page: 1,
limit: 15,
});
const clerkLists = ref([]);
const formValidateRef = ref(null);
const table = ref(null);
const rightBox = ref(null);
// 具体日期
function onchangeTime(e) {
timeVal.value = e || [];
getList();
}
function userSearchs() {
formValidate.value.page = 1;
getList();
}
// 列表
function getList() {
loading.value = true;
formValidate.value.time = timeVal.value[0] ? (timeVal.value ? timeVal.value.join('-') : '') : '';
divisionStatistics(formValidate.value)
.then(async (res) => {
let data = res.data;
userLists.value = data.list;
total.value = data.count;
loading.value = false;
})
.catch((res) => {
loading.value = false;
ElMessage.error(res.msg);
});
}
function pageChange(index) {
formValidate.value.page = index;
getList();
}
// 添加排序方法
function handleSortChange({ prop, order }) {
formValidate.value.sort = prop;
formValidate.value.order = order === 'ascending' ? 'asc' : 'desc';
getList();
}
onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
:deep(.el-tabs__item) {
height: 54px;
line-height: 54px;
}
.tabBox_img {
width: 36px;
height: 36px;
border-radius: 4px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
</style>

View File

@ -1,382 +0,0 @@
<template>
<div>
<el-card :bordered="false" shadow="never" :body-style="{ padding: 0 }">
<div class="padding-add">
<el-form
ref="formValidateRef"
:model="formValidate"
:label-width="labelWidth"
:label-position="labelPosition"
@submit.prevent
inline
>
<el-form-item label="搜索:">
<el-input
clearable
placeholder="请输入姓名、UID"
v-model="formValidate.keyword"
class="form_content_width"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" v-db-click @click="userSearchs">查询</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16">
<el-row class="action-row mb14">
<el-col :xs="24" :sm="24" ref="rightBox">
<el-button type="primary" v-db-click @click="groupAdd('0')">添加事业部</el-button>
<el-tooltip placement="right-start">
<i class="el-icon-question ml10"></i>
<template #content
><div>
<div>
事业部层级说明:事业部-代理商-员工。事业部相当于总代理或者区域代理,设置成为事业部之后,关联的用户会清除上级推广人
</div>
<div>
添加时候的管理员身份需要在,设置-管理权限-角色管理中设置对应的角色,事业部可以使用添加时设置的管理员账号密码登录后台
</div>
</div></template
>
</el-tooltip>
<el-table
:data="userLists"
ref="table"
class="mt14"
v-loading="loading"
highlight-current-row
no-formValidate-text="暂无数据"
no-filtered-formValidate-text="暂无筛选结果"
>
<el-table-column label="用户UID" width="100">
<template #default="scope">
<span>{{ scope.row.uid }}</span>
</template>
</el-table-column>
<el-table-column label="头像" min-width="90">
<template #default="scope">
<div class="tabBox_img" v-viewer>
<img v-lazy="scope.row.avatar" />
</div>
</template>
</el-table-column>
<el-table-column label="名称" min-width="130">
<template #default="scope">
<div class="acea-row">
<div v-text="scope.row.division_name"></div>
</div>
</template>
</el-table-column>
<el-table-column label="邀请码" min-width="130">
<template #default="scope">
<span>{{ scope.row.division_invite }}</span>
</template>
</el-table-column>
<el-table-column label="分销比例" min-width="130">
<template #default="scope">
<span> {{ scope.row.division_percent }}%</span>
</template>
</el-table-column>
<el-table-column label="代理商数量" min-width="130">
<template #default="scope">
<span>{{ scope.row.agent_count }}</span>
</template>
</el-table-column>
<el-table-column label="截止时间" min-width="130">
<template #default="scope">
<span>{{ scope.row.division_end_time }}</span>
</template>
</el-table-column>
<el-table-column label="状态" min-width="130">
<template #default="scope">
<el-switch
:active-value="1"
:inactive-value="0"
v-model="scope.row.division_status"
:value="scope.row.division_status"
@change="onchangeIsShow(scope.row)"
size="large"
>
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="170">
<template #default="scope">
<a v-db-click @click="jump(scope.row.uid)">查看代理商</a>
<el-divider direction="vertical"></el-divider>
<a v-db-click @click="groupAdd(scope.row.uid)">编辑</a>
<el-divider direction="vertical"></el-divider>
<a v-db-click @click="del(scope.row, '删除事业部', scope.$index)">删除</a>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total"
:total="total"
v-model:page="formValidate.page"
v-model:limit="formValidate.limit"
@pagination="getList"
/>
</div>
</el-col>
</el-row>
</el-card>
<el-dialog v-model="staffModal" title="代理商列表" class="order_box" width="1000px">
<el-table
:data="clerkLists"
ref="table"
class="mt20"
v-loading="loading"
highlight-current-row
no-formValidate-text="暂无数据"
no-filtered-formValidate-text="暂无筛选结果"
>
<el-table-column label="用户UID" width="80">
<template #default="scope">
<span>{{ scope.row.uid }}</span>
</template>
</el-table-column>
<el-table-column label="头像" min-width="90">
<template #default="scope">
<div class="tabBox_img" v-viewer>
<img v-lazy="scope.row.avatar" />
</div>
</template>
</el-table-column>
<el-table-column label="名称" min-width="130">
<template #default="scope">
<div class="acea-row">
<div v-text="scope.row.division_name" class="ml10"></div>
</div>
</template>
</el-table-column>
<el-table-column label="分销比例" min-width="130">
<template #default="scope">
<span> {{ scope.row.division_percent }}%</span>
</template>
</el-table-column>
<el-table-column label="到期时间" min-width="130">
<template #default="scope">
<span> {{ $filters.formatDate(scope.row.division_end_time) }}</span>
</template>
</el-table-column>
<el-table-column label="员工数量" min-width="130">
<template #default="scope">
<span> {{ scope.row.agent_count }}</span>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total2"
:total="total2"
v-model:page="clerkReqData.page"
v-model:limit="clerkReqData.limit"
@pagination="getClerkList"
/>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { ElMessage } from 'element-plus';
import { useMediaStore } from '@/store/module/media';
import { regionList, regionFrom, isShowApi, clerkList } from '../../api';
import modalForm from '@/utils/modalForm';
import { modalSure } from '@/utils/public';
defineOptions({ name: 'agent_extra' });
const { isMobile } = storeToRefs(useMediaStore());
const labelWidth = computed(() => (isMobile.value ? undefined : '50px'));
const labelPosition = computed(() => (isMobile.value ? 'top' : 'right'));
const grid = {
xl: 7,
lg: 7,
md: 12,
sm: 24,
xs: 24,
};
const total = ref(0);
const total2 = ref(0);
const userLists = ref([]);
const formInline = ref({
uid: 0,
proportion: 0,
image: '',
});
const FromData = ref(null);
const loading = ref(false);
const current = ref(0);
const formValidate = ref({
page: 1,
limit: 15,
keyword: '',
});
const staffModal = ref(false);
const clerkReqData = ref({
uid: 0,
page: 1,
limit: 15,
});
const clerkLists = ref([]);
const formValidateRef = ref(null);
const table = ref(null);
const rightBox = ref(null);
// 搜索
function userSearchs() {
formValidate.value.page = 1;
getList();
}
function jump(uid) {
clerkReqData.value.uid = uid;
getClerkList();
}
function getClerkList() {
clerkReqData.value.division_type = 2;
clerkList(clerkReqData.value).then((res) => {
clerkLists.value = res.data.list;
total2.value = res.data.count;
staffModal.value = true;
});
}
// 列表
function getList() {
loading.value = true;
formValidate.value.division_type = 1;
regionList(formValidate.value)
.then(async (res) => {
let data = res.data;
userLists.value = data.list;
total.value = data.count;
loading.value = false;
})
.catch((res) => {
loading.value = false;
ElMessage.error(res.msg);
});
}
// 添加表单
function groupAdd(id) {
modalForm(regionFrom(id))
.then((res) => {
getList();
})
.catch((err) => {});
}
// 修改是否显示
function onchangeIsShow(row) {
let data = {
id: row.uid,
status: row.division_status,
};
isShowApi(data)
.then(async (res) => {
ElMessage.success(res.msg);
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
// 编辑
function edit(row) {}
// 删除
function del(row, tit, num) {
let delfromData = {
title: tit,
method: 'DELETE',
uid: row.uid,
url: `distribution/division/del/1/${row.uid}`,
};
modalSure(delfromData)
.then((res) => {
ElMessage.success(res.msg);
userLists.value.splice(num, 1);
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
.ivu-form-item {
margin-bottom: 0;
}
.picBox {
display: inline-block;
cursor: pointer;
.upLoad {
width: 58px;
height: 58px;
line-height: 58px;
border: 1px dotted rgba(0, 0, 0, 0.1);
border-radius: 4px;
background: rgba(0, 0, 0, 0.02);
}
.pictrue {
width: 60px;
height: 60px;
border: 1px dotted rgba(0, 0, 0, 0.1);
margin-right: 10px;
img {
width: 100%;
height: 100%;
}
}
}
:deep(.ivu-menu-vertical .ivu-menu-item-group-title) {
display: none;
}
:deep(.ivu-menu-vertical.ivu-menu-light:after) {
display: none;
}
.left-wrapper {
height: 904px;
background: #fff;
border-right: 1px solid #f2f2f2;
}
.menu-item {
z-index: 50;
position: relative;
display: flex;
justify-content: space-between;
word-break: break-all;
&:hover .icon-box {
display: block;
}
}
.right-menu {
z-index: 10;
position: absolute;
right: -106px;
top: -11px;
width: auto;
min-width: 121px;
}
.tabBox_img {
width: 36px;
height: 36px;
border-radius: 4px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
</style>

View File

@ -1,14 +0,0 @@
{
"key": "distribution",
"title": "分销管理",
"version": "1.0.0",
"pages": [
{ "path": "agent_manage/index", "title": "分销员管理" },
{ "path": "spread/apply", "title": "分销员申请" },
{ "path": "division/list/index", "title": "事业部列表" },
{ "path": "division/agent/index", "title": "代理商列表" },
{ "path": "division/agent/statistics", "title": "事业部统计" },
{ "path": "division/agent/applyList", "title": "代理商申请" },
{ "path": "division/agent/agreement", "title": "代理商规则" }
]
}

View File

@ -1,281 +0,0 @@
<template>
<div>
<el-dialog
v-model="modals"
:title="listTitle === 'man' ? '统计推广人列表' : '推广订单'"
:close-on-click-modal="false"
width="1000px"
@closed="onCancel"
>
<div class="table_box">
<el-form
ref="formValidateRef"
:model="formValidate"
:label-width="labelWidth"
:label-position="labelPosition"
@submit.prevent
inline
>
<el-form-item label="时间选择:">
<date-range-picker
clearable
:editable="false"
@change="onchangeTime"
v-model="timeVal"
value-format="YYYY/MM/DD"
></date-range-picker>
</el-form-item>
<el-form-item label="用户类型:">
<el-select v-model="formValidate.type" clearable class="form_content_width">
<el-option
v-for="(item, i) in listTitle === 'man' ? fromList.fromTxt2 : fromList.fromTxt3"
:key="i"
:value="item.val"
:label="item.text"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="搜索:" v-if="listTitle === 'man'">
<el-input
clearable
placeholder="请输入请姓名、电话、UID"
v-model="formValidate.nickname"
class="form_content_width"
></el-input>
</el-form-item>
<el-form-item label="订单号:" v-if="listTitle === 'order'">
<el-input
clearable
placeholder="请输入请订单号"
v-model="formValidate.order_id"
class="form_content_width"
></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" v-db-click @click="userSearchs">查询</el-button>
</el-form-item>
</el-form>
</div>
<el-table
ref="selection"
:data="tabList"
v-loading="loading"
empty-text="暂无数据"
highlight-current-row
max-height="400"
>
<template v-if="listTitle === 'man'">
<el-table-column label="UID" width="80">
<template #default="scope">
<span>{{ scope.row.uid }}</span>
</template>
</el-table-column>
<el-table-column label="头像" min-width="90">
<template #default="scope">
<div class="tabBox_img" v-viewer>
<img v-lazy="scope.row.avatar ? scope.row.avatar : moren" />
</div>
</template>
</el-table-column>
<el-table-column label="用户信息" min-width="130">
<template #default="scope">
<span>{{ scope.row.nickname }}</span>
</template>
</el-table-column>
<el-table-column label="是否推广员" min-width="130">
<template #default="scope">
<span>{{ scope.row.promoter_name }}</span>
</template>
</el-table-column>
<el-table-column label="推广人数" min-width="130">
<template #default="scope">
<span>{{ scope.row.spread_count }}</span>
</template>
</el-table-column>
<el-table-column label="订单数" min-width="130">
<template #default="scope">
<span>{{ scope.row.order_count }}</span>
</template>
</el-table-column>
<el-table-column label="绑定时间" min-width="130">
<template #default="scope">
<span>{{ $filters.formatDate(scope.row.spread_time) }}</span>
</template>
</el-table-column>
</template>
<template v-else>
<el-table-column label="订单ID" min-width="130">
<template #default="scope">
<span>{{ scope.row.order_id }}</span>
</template>
</el-table-column>
<el-table-column label="用户信息" min-width="130">
<template #default="scope">
<span>{{ scope.row.user_info }}</span>
</template>
</el-table-column>
<el-table-column label="时间" min-width="130">
<template #default="scope">
<span>{{ scope.row._add_time }}</span>
</template>
</el-table-column>
<el-table-column label="返佣金额" min-width="130">
<template #default="scope">
<span>{{ scope.row.brokerage_price || 0 }}</span>
</template>
</el-table-column>
<el-table-column label="事业部返佣金额" min-width="130" v-if="rowsList.division_type == 1">
<template #default="scope">
<span>{{ scope.row.division_brokerage || 0 }}</span>
</template>
</el-table-column>
<el-table-column label="代理商返佣金额" min-width="130" v-if="rowsList.division_type == 2">
<template #default="scope">
<span>{{ scope.row.agent_brokerage || 0 }}</span>
</template>
</el-table-column>
<el-table-column label="员工返佣金额" min-width="130" v-if="rowsList.division_type == 3">
<template #default="scope">
<span>{{ scope.row.staff_brokerage || 0 }}</span>
</template>
</el-table-column>
</template>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total"
:total="total"
v-model:page="formValidate.page"
v-model:limit="formValidate.limit"
@pagination="pageChange"
/>
</div>
</el-dialog>
</div>
</template>
<script setup>
import moren from '@/assets/images/moren.jpg';
import { computed, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { ElMessage } from 'element-plus';
import { useMediaStore } from '@/store/module/media';
import { stairListApi } from '../api';
defineOptions({ name: 'promotersList' });
const { isMobile } = storeToRefs(useMediaStore());
const labelWidth = computed(() => (isMobile.value ? undefined : '80px'));
const labelPosition = computed(() => (isMobile.value ? 'top' : 'right'));
const modals = ref(false);
const fromList = ref({
title: '选择时间',
custom: true,
fromTxt: [
{ text: '全部', val: '' },
{ text: '今天', val: 'today' },
{ text: '昨天', val: 'yesterday' },
{ text: '最近7天', val: 'lately7' },
{ text: '最近30天', val: 'lately30' },
{ text: '本月', val: 'month' },
{ text: '本年', val: 'year' },
],
fromTxt2: [
{ text: '全部', val: '' },
{ text: '一级推广人', val: 1 },
{ text: '二级推广人', val: 2 },
],
fromTxt3: [
{ text: '全部', val: '' },
{ text: '一级推广人订单', val: 1 },
{ text: '二级推广人订单', val: 2 },
{ text: '事业部推广订单', val: 3 },
{ text: '代理商推广订单', val: 4 },
],
});
const formValidate = ref({
limit: 15,
page: 1,
nickname: '',
data: '',
type: '',
order_id: '',
uid: 0,
});
const loading = ref(false);
const tabList = ref([]);
const total = ref(0);
const timeVal = ref([]);
const columns4 = ref([]);
const listTitle = ref('');
const rowsList = ref({
division_type: 0,
});
const formValidateRef = ref(null);
const selection = ref(null);
function onCancel() {
formValidate.value = {
limit: 15,
page: 1,
nickname: '',
data: '',
type: '',
order_id: '',
uid: 0,
};
timeVal.value = [];
}
// 具体日期
function onchangeTime(e) {
timeVal.value = e;
formValidate.value.data = timeVal.value ? timeVal.value.join('-') : '';
getList(rowsList.value, listTitle.value);
}
// 选择时间
function selectChange(tab) {
formValidate.value.data = tab;
timeVal.value = [];
getList(rowsList.value, listTitle.value);
}
// 列表
function getList(row, tit) {
listTitle.value = tit;
rowsList.value = row;
loading.value = true;
let url = '';
if (listTitle.value === 'man') {
url = 'distribution/stair';
} else {
url = 'distribution/stair/order';
}
formValidate.value.uid = row.uid;
stairListApi(url, formValidate.value)
.then(async (res) => {
let data = res.data;
tabList.value = data.list;
total.value = data.count;
loading.value = false;
})
.catch((res) => {
loading.value = false;
tabList.value = [];
ElMessage.error(res.msg);
});
}
function pageChange() {
getList(rowsList.value, listTitle.value);
}
// 搜索
function userSearchs() {
formValidate.value.page = 1;
getList(rowsList.value, listTitle.value);
}
// 页面通过 $refs.promotersLists 控制弹窗并调用 getList
defineExpose({ modals, getList });
</script>
<style scoped></style>

View File

@ -1,5 +0,0 @@
import AgentApp from './App.vue';
import routes from './routes.js';
export default AgentApp;
export { routes };

View File

@ -1,101 +0,0 @@
/**
* 分销扩展路由配置
* 兼容模式:既是 Vue Router 路由配置(带 component 的项由 routers.js 扫描注册),
* 也包含 section/action 元数据供扩展机制使用
*/
import setting from '@/setting';
const routePre = setting.routePre;
const pre = 'agent_';
export default [
{
path: 'agent_manage/index',
section: 'distribution',
action: 'manage',
name: `${pre}agentManage`,
meta: {
auth: ['agent-agent-manage'],
title: '分销员管理',
},
},
{
path: 'spread/apply',
section: 'distribution',
action: 'apply',
name: `${pre}spreadApply`,
meta: {
auth: ['admin-agent-spread-apply'],
title: '分销员申请',
},
},
/** 事业部 */
{
path: routePre + '/division',
name: 'division',
redirect: { name: 'division_list' },
component: () => import('@/layout'),
children: [
{
path: 'index',
section: 'distribution',
action: 'list',
name: 'division_list',
component: () => import('@/pages/system/plugin/PageContainer'),
props: { typeKey: 'distribution', section: 'distribution', action: 'list' },
meta: {
auth: ['agent-division-index'],
title: '事业部列表',
},
},
{
path: 'agent/index',
section: 'distribution',
action: 'agent',
name: 'division_agent',
component: () => import('@/pages/system/plugin/PageContainer'),
props: { typeKey: 'distribution', section: 'distribution', action: 'agent' },
meta: {
auth: ['agent-division-agent-index'],
title: '代理商列表',
},
},
{
path: 'agent/statistics',
section: 'distribution',
action: 'statistics',
name: 'division_statistics',
component: () => import('@/pages/system/plugin/PageContainer'),
props: { typeKey: 'distribution', section: 'distribution', action: 'statistics' },
meta: {
auth: ['agent-division-statistics'],
title: '事业部统计',
},
},
{
path: 'agent/applyList',
section: 'distribution',
action: 'applyList',
name: 'division_applyList',
component: () => import('@/pages/system/plugin/PageContainer'),
props: { typeKey: 'distribution', section: 'distribution', action: 'applyList' },
meta: {
auth: ['agent-division-agent-applyList'],
title: '代理商申请',
},
},
{
path: 'agent/agreement',
section: 'distribution',
action: 'agreement',
name: 'division_agreement',
component: () => import('@/pages/system/plugin/PageContainer'),
props: { typeKey: 'distribution', section: 'distribution', action: 'agreement' },
meta: {
auth: ['agent-division-agent-agreement'],
title: '代理商规则',
},
},
],
},
];

View File

@ -1,352 +0,0 @@
<template>
<div>
<el-card :bordered="false" shadow="never" :body-style="{ padding: 0 }">
<div class="padding-add">
<el-form
ref="formValidateRef"
:model="formValidate"
:label-width="labelWidth"
:label-position="labelPosition"
@submit.prevent
inline
>
<el-form-item label="搜索:">
<el-input
clearable
placeholder="请输入姓名、UID"
v-model="formValidate.keyword"
class="form_content_width"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" v-db-click @click="userSearchs">查询</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16" :body-style="{ padding: '0 20px 20px' }">
<el-row class="box-wrapper">
<el-col :xs="24" :sm="24" ref="rightBox">
<el-tabs v-model="formValidate.status" @tab-change="userSearchs">
<el-tab-pane name="all" label="全部"></el-tab-pane>
<el-tab-pane
v-for="(item, index) in statusList"
:key="index"
:label="item.status_name"
:name="item.id"
></el-tab-pane>
</el-tabs>
<el-table
:data="userLists"
ref="table"
v-loading="loading"
highlight-current-row
no-formValidate-text="暂无数据"
no-filtered-formValidate-text="暂无筛选结果"
>
<el-table-column label="编号" width="80">
<template #default="scope">
<span>{{ scope.row.id }}</span>
</template>
</el-table-column>
<el-table-column label="用户UID" width="80">
<template #default="scope">
<span>{{ scope.row.uid }}</span>
</template>
</el-table-column>
<el-table-column label="用户昵称" min-width="150">
<template #default="scope">
<span>{{ scope.row.nickname }}</span>
</template>
</el-table-column>
<el-table-column label="分销员电话" min-width="150">
<template #default="scope">
<span>{{ scope.row.phone }}</span>
</template>
</el-table-column>
<el-table-column label="分销员姓名" min-width="150">
<template #default="scope">
<span>{{ scope.row.real_name }}</span>
</template>
</el-table-column>
<el-table-column label="申请状态" min-width="150">
<template #default="scope">
<el-tag>{{ scope.row.status == 0 ? '申请中' : scope.row.status == 1 ? '已同意' : '已拒绝' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="申请时间" min-width="150">
<template #default="scope">
<span>{{ scope.row.add_time }}</span>
</template>
</el-table-column>
<el-table-column label="申请理由" min-width="150">
<template #default="scope">
<span>{{ scope.row.content }}</span>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="170">
<template #default="scope">
<a v-if="scope.row.status == 0" v-db-click @click="examine(scope.row.id, scope.row.uid, 1)">同意</a>
<el-divider v-if="scope.row.status == 0" direction="vertical" />
<a v-if="scope.row.status == 0" v-db-click @click="examine(scope.row.id, scope.row.uid, 2)">拒绝</a>
<el-divider direction="vertical" v-if="scope.row.status == 0" />
<a v-db-click @click="del(scope.row, '删除申请', scope.$index)">删除</a>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total"
:total="total"
v-model:page="formValidate.page"
v-model:limit="formValidate.limit"
@pagination="getList"
/>
</div>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { ElMessage, ElMessageBox } from 'element-plus';
import { useMediaStore } from '@/store/module/media';
import { spreadList, spreadFrom, clerkList } from '../api';
import { modalSure } from '@/utils/public';
defineOptions({ name: 'agent_extra' });
const { isMobile } = storeToRefs(useMediaStore());
const labelWidth = computed(() => (isMobile.value ? undefined : '50px'));
const labelPosition = computed(() => (isMobile.value ? 'top' : 'right'));
const grid = {
xl: 7,
lg: 7,
md: 12,
sm: 24,
xs: 24,
};
const total = ref(0);
const total2 = ref(0);
const userLists = ref([]);
const formInline = ref({
uid: 0,
proportion: 0,
image: '',
});
const statusList = ref([
{
status_name: '申请中',
id: '0',
},
{
status_name: '已同意',
id: '1',
},
{
status_name: '已拒绝',
id: '2',
},
]);
const FromData = ref(null);
const loading = ref(false);
const current = ref(0);
const formValidate = ref({
page: 1,
limit: 15,
keyword: '',
status: 'all',
});
const staffModal = ref(false);
const clerkReqData = ref({
uid: 0,
page: 1,
limit: 15,
});
const clerkLists = ref([]);
const formValidateRef = ref(null);
const table = ref(null);
const rightBox = ref(null);
function userSearchs() {
formValidate.value.page = 1;
getList();
}
// 列表
function getList() {
loading.value = true;
spreadList(formValidate.value)
.then(async (res) => {
let data = res.data;
userLists.value = data.list;
total.value = data.count;
loading.value = false;
})
.catch((res) => {
loading.value = false;
ElMessage.error(res.msg);
});
}
function pageChange(index) {
formValidate.value.page = index;
getList();
}
// 审核
function examine(id, uid, type) {
if (type == 1) {
let data = {
title: type == 1 ? '通过分销员申请' : '拒绝分销员申请',
url: `distribution/spread/apply/examine/${id}/${uid}/${type}`,
method: 'post',
ids: '',
};
modalSure(data)
.then((res) => {
ElMessage.success(res.msg);
getList();
})
.catch((res) => {
ElMessage.error(res.msg);
});
} else {
ElMessageBox.prompt('请输入拒绝理由', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
})
.then(({ value }) => {
let data = {
refusal_reason: value,
};
spreadFrom(id, uid, type, data).then((res) => {
ElMessage.success(res.msg);
getList();
});
})
.catch(() => {
ElMessage({
type: 'info',
message: '取消输入',
});
});
}
}
// 编辑
function edit(row) {}
// 删除
function del(row, tit, num) {
let delfromData = {
title: tit,
method: 'DELETE',
uid: row.id,
url: `distribution/spread/apply/del/${row.id}`,
};
modalSure(delfromData)
.then((res) => {
ElMessage.success(res.msg);
userLists.value.splice(num, 1);
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
onMounted(() => {
getList();
});
</script>
<style lang="scss" scoped>
.ivu-form-item {
margin-bottom: 0;
}
.picBox {
display: inline-block;
cursor: pointer;
.upLoad {
width: 58px;
height: 58px;
line-height: 58px;
border: 1px dotted rgba(0, 0, 0, 0.1);
border-radius: 4px;
background: rgba(0, 0, 0, 0.02);
}
.pictrue {
width: 60px;
height: 60px;
border: 1px dotted rgba(0, 0, 0, 0.1);
margin-right: 10px;
img {
width: 100%;
height: 100%;
}
}
}
:deep(.ivu-menu-vertical .ivu-menu-item-group-title) {
display: none;
}
:deep(.ivu-menu-vertical.ivu-menu-light:after) {
display: none;
}
.left-wrapper {
height: 904px;
background: #fff;
border-right: 1px solid #f2f2f2;
}
.menu-item {
z-index: 50;
position: relative;
display: flex;
justify-content: space-between;
word-break: break-all;
&:hover .icon-box {
display: block;
}
}
.icon-box {
z-index: 3;
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
display: none;
}
:deep(.el-tabs__item) {
height: 54px;
line-height: 54px;
}
.right-menu {
z-index: 10;
position: absolute;
right: -106px;
top: -11px;
width: auto;
min-width: 121px;
}
.tabBox_img {
width: 36px;
height: 36px;
border-radius: 4px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
.pictrue-box {
display: flex;
align-item: center;
}
.pictrue {
width: 25px;
height: 25px;
}
</style>

View File

@ -1,35 +0,0 @@
<template>
<div class="integral-app">
<div class="section-content">
<component :is="sectionComponent" :params="params" />
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import ProductList from './pages/product/index.vue';
import ProductCreate from './pages/product/create.vue';
import OrderList from './pages/order/index.vue';
const actionComponents = {
product: ProductList,
product_create: ProductCreate,
order: OrderList,
};
defineOptions({ name: 'IntegralApp' });
const props = defineProps({
section: { type: String, default: 'integral' },
action: { type: String, default: 'product' },
params: { type: Object, default: () => ({}) },
});
const sectionComponent = computed(() => actionComponents[props.action] || ProductList);
</script>
<style scoped>
.integral-app {
height: 100%;
}
</style>

View File

@ -1,232 +0,0 @@
/**
* 积分商城 API
*/
import request from '@/libs/request';
/**
* 积分商品列表
*/
export function integralProductListApi(params) {
return request({
url: 'marketing/integral_product',
method: 'GET',
params,
});
}
/**
* 积分商品保存(新增/编辑)
*/
export function integralAddApi(data) {
return request({
url: `marketing/integral/${data.id}`,
method: 'post',
data,
});
}
/**
* 积分商品详情
*/
export function integralInfoApi(id) {
return request({
url: `marketing/integral/${id}`,
method: 'GET',
});
}
/**
* 积分商品修改状态
*/
export function integralIsShowApi(data) {
return request({
url: `marketing/integral/set_show/${data.id}/${data.is_show}`,
method: 'put',
});
}
/**
* 积分商品删除
*/
export function integralDelApi(id) {
return request({
url: `marketing/integral/${id}`,
method: 'DELETE',
});
}
/**
* 获取商品规格
*/
export function productAttrsApi(id, type) {
return request({
url: `product/product/attrs/${id}/${type}`,
method: 'GET',
});
}
/**
* 积分订单列表
*/
export function integralOrderList(params) {
return request({
url: 'marketing/integral/order/list',
method: 'get',
params,
});
}
/**
* 积分订单统计数据(头部tab)
*/
export function integralGetOrdes(data) {
return request({
url: 'marketing/integral/order/chart',
method: 'get',
params: data,
});
}
/**
* 积分订单详情
*/
export function getIntegralOrderDataInfo(id) {
return request({
url: `marketing/integral/order/info/${id}`,
method: 'get',
});
}
/**
* 积分订单物流
*/
export function getExpress(id) {
return request({
url: `marketing/integral/order/express/${id}`,
method: 'get',
});
}
/**
* 获取快递公司
*/
export function getExpressData(status) {
return request({
url: `marketing/integral/order/express_list?status=` + status,
method: 'get',
});
}
/**
* 积分订单配送信息表单
*/
export function getIntegralOrderDistribution(id) {
return request({
url: `marketing/integral/order/distribution/${id}`,
method: 'get',
});
}
/**
* 积分订单发货
*/
export function integralOrderPutDelivery(data) {
return request({
url: `marketing/integral/order/delivery/${data.id}`,
method: 'put',
data: data.datas,
});
}
/**
* 积分订单修改配送信息
*/
export function integralOrderPutDistribution(data) {
return request({
url: `marketing/integral/order/distribution/${data.id}`,
method: 'put',
data: data.datas,
});
}
/**
* 积分订单备注
*/
export function integralOrderPutRemarkData(data) {
return request({
url: `marketing/integral/order/remark/${data.id}`,
method: 'put',
data: { remark: data.remark },
});
}
/**
* 积分订单状态记录
*/
export function getIntegralOrderRecord(data) {
return request({
url: `marketing/integral/order/status/${data.id}`,
method: 'get',
});
}
/**
* 获取配送员列表
*/
export function orderDeliveryList() {
return request({
url: 'marketing/integral/order/delivery/list',
method: 'get',
});
}
/**
* 电子面单模板
*/
export function orderExpressTemp(data) {
return request({
url: 'marketing/integral/order/express/temp',
method: 'get',
params: data,
});
}
/**
* 积分面单配置
*/
export function orderSheetInfo() {
return request({
url: 'marketing/integral/order/sheet_info',
method: 'get',
});
}
/**
* 积分订单确认收货
*/
export function integralOrderTake(id) {
return request({
url: `marketing/integral/order/take/${id}`,
method: 'put',
});
}
/**
* 积分订单删除
*/
export function integralOrderDel(id) {
return request({
url: `marketing/integral/order/del/${id}`,
method: 'DELETE',
});
}
/**
* 积分订单打印
*/
export function integralOrderPrint(id) {
return request({
url: `marketing/integral/order/print/${id}`,
method: 'post',
});
}

View File

@ -1,9 +0,0 @@
{
"key": "integral",
"title": "积分商城",
"version": "1.0.0",
"pages": [
{ "path": "product", "title": "积分商品管理" },
{ "path": "order", "title": "积分订单管理" }
]
}

View File

@ -1,5 +0,0 @@
import IntegralApp from './App.vue';
import routes from './routes.js';
export default IntegralApp;
export { routes };

View File

@ -1,363 +0,0 @@
<template>
<div v-loading="spinShow">
<el-card :bordered="false" shadow="never" :body-style="{ padding: 0 }">
<div class="padding-add">
<el-form :model="orderData" :label-width="labelWidth" label-position="right" @submit.prevent inline>
<el-form-item label="订单搜索:">
<el-input
placeholder="请输入订单号/用户昵称/UID"
v-model="orderData.real_name"
clearable
style="width: 350px"
>
<template #prepend
><el-select placeholder="请选择" clearable v-model="orderData.field_key" style="width: 100px">
<el-option value="all" label="全部"></el-option>
<el-option value="order_id" label="订单号"></el-option>
<el-option value="real_name" label="用户昵称"></el-option>
<el-option value="uid" label="UID"></el-option> </el-select
></template>
</el-input>
</el-form-item>
<el-form-item label="订单状态:">
<el-select
placeholder="请选择"
clearable
v-model="orderData.status"
@change="getList"
class="form_content_width"
>
<el-option :value="item.status" :label="item.name" v-for="(item, i) in statusList" :key="i"></el-option>
</el-select>
</el-form-item>
<el-form-item label="下单时间:">
<date-range-picker
clearable
v-model="timeVal"
:editable="false"
@change="onchangeTime"
format="YYYY/MM/DD"
value-format="YYYY/MM/DD"
></date-range-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" v-db-click @click="getList">查询</el-button>
<el-button v-db-click @click="reset">重置</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
<!-- tabs -->
<el-card :bordered="false" shadow="never" class="ivu-mt mt16">
<el-tag
class="mr10 mb10"
v-for="(item, i) in tablists"
:key="i"
:type="orderData.type === item.status ? '' : 'info'"
@click="onClickTab(item)"
style="cursor: pointer"
>
{{ item.name }} ({{ item.count }})
</el-tag>
</el-card>
<!-- 订单列表 -->
<el-card :bordered="false" shadow="never" class="ivu-mt mt16">
<el-table :data="orderList" v-loading="loading" highlight-current-row class="mt14">
<el-table-column label="订单号" min-width="150">
<template #default="scope">
<span>{{ scope.row.order_id }}</span>
<span v-if="scope.row.is_del == 1" style="color: #ed4014; display: block">用户已删除</span>
</template>
</el-table-column>
<el-table-column label="用户信息" min-width="100">
<template #default="scope"> {{ scope.row.nickname }}/{{ scope.row.uid }} </template>
</el-table-column>
<el-table-column label="商品信息" min-width="300">
<template #default="scope">
<div class="tabBox">
<div class="tabBox_img" v-viewer>
<img v-lazy="scope.row.image" />
</div>
<span class="tabBox_tit">{{ scope.row.store_name + ' | ' }}{{ scope.row.suk || '' }}</span>
<span class="tabBox_pice">{{ '积分' + scope.row.total_price + ' x ' + scope.row.total_num }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="兑换积分" min-width="80">
<template #default="scope">
<span>{{ scope.row.total_price }}</span>
</template>
</el-table-column>
<el-table-column label="订单状态" min-width="80">
<template #default="scope">
<span>{{ scope.row.status_name }}</span>
</template>
</el-table-column>
<el-table-column label="下单时间" min-width="100">
<template #default="scope">
<span>{{ scope.row.add_time }}</span>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="160">
<template #default="scope">
<a v-db-click @click="openSend(scope.row)" v-if="scope.row.status === 1">发送货</a>
<a v-db-click @click="openDistribution(scope.row)" v-if="scope.row.status === 2">配送信息</a>
<el-divider direction="vertical" v-if="scope.row.status === 1 || scope.row.status === 2" />
<el-dropdown @command="changeMenu(scope.row, $event)" :transfer="true">
<span class="el-dropdown-link">更多<i class="el-icon-arrow-down el-icon--right"></i></span>
<template #dropdown
><el-dropdown-menu>
<el-dropdown-item command="detail">订单详情</el-dropdown-item>
<el-dropdown-item command="record">订单记录</el-dropdown-item>
<el-dropdown-item command="remark" v-show="scope.row.status !== 4">订单备注</el-dropdown-item>
<el-dropdown-item command="take" v-show="scope.row.status === 2">已收货</el-dropdown-item>
<el-dropdown-item command="del" v-show="scope.row.is_del === 1">删除订单</el-dropdown-item>
</el-dropdown-menu></template
>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total"
:total="total"
v-model:page="page.pageNum"
v-model:limit="page.pageSize"
@pagination="getList"
/>
</div>
</el-card>
<!-- 弹窗组件 -->
<order-details ref="detailsRef" :orderDatalist="orderDatalist" :orderId="orderId"></order-details>
<order-remark ref="remarkRef" :orderId="orderId" @submitFail="submitFail"></order-remark>
<order-record ref="recordRef" :orderId="orderId"></order-record>
<order-send ref="sendRef" :orderId="orderId" @submitFail="submitFail"></order-send>
<!-- 配送信息编辑表单 -->
<edit-from ref="edits" :FromData="FromData" @submitFail="submitFail"></edit-from>
</div>
</template>
<script setup>
import { computed, onActivated, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { ElMessage } from 'element-plus';
import { integralOrderList, integralGetOrdes, getIntegralOrderDataInfo, getIntegralOrderDistribution } from '../../api';
import orderDetails from './orderDetails.vue';
import orderRemark from './orderRemark.vue';
import orderRecord from './orderRecord.vue';
import orderSend from './orderSend.vue';
import editFrom from '@/components/from/from';
import { modalSure } from '@/utils/public';
import { useMediaStore } from '@/store/module/media';
defineOptions({ name: 'integralOrderList' });
const route = useRoute();
const mediaStore = useMediaStore();
const detailsRef = ref(null);
const remarkRef = ref(null);
const recordRef = ref(null);
const sendRef = ref(null);
const edits = ref(null);
const isMobile = computed(() => mediaStore.isMobile);
const labelWidth = computed(() => (isMobile.value ? undefined : '80px'));
const labelPosition = computed(() => (isMobile.value ? 'top' : 'right'));
const spinShow = ref(false);
const loading = ref(false);
const orderList = ref([]);
const tablists = ref([]);
const statusList = ref([
{ status: '', name: '全部' },
{ status: 1, name: '未发货' },
{ status: 2, name: '待收货' },
{ status: 3, name: '已完成' },
]);
const orderData = ref({
status: '',
data: '',
real_name: '',
field_key: 'all',
product_id: '',
type: '',
});
const timeVal = ref([]);
const orderId = ref(0);
const orderDatalist = ref(null);
const FromData = ref(null);
const total = ref(0);
const page = ref({
pageNum: 1,
pageSize: 15,
});
onMounted(() => {
getTabs();
getList();
});
onActivated(() => {
getList();
});
function getTabs() {
spinShow.value = true;
integralGetOrdes({
data: orderData.value.data,
product_id: route.query.product_id || '',
})
.then((res) => {
tablists.value = res.data;
spinShow.value = false;
})
.catch((res) => {
spinShow.value = false;
ElMessage.error(res.msg);
});
}
function onClickTab(item) {
orderData.value.type = item.status;
page.value.pageNum = 1;
getList();
}
function getList() {
loading.value = true;
orderData.value.product_id = route.query.product_id || '';
integralOrderList({
page: page.value.pageNum,
limit: page.value.pageSize,
status: orderData.value.status,
data: orderData.value.data,
real_name: orderData.value.real_name,
field_key: orderData.value.field_key,
type: orderData.value.type === 0 ? '' : orderData.value.type,
product_id: orderData.value.product_id,
})
.then(async (res) => {
let data = res.data;
orderList.value = data.data;
total.value = data.count;
loading.value = false;
})
.catch((res) => {
loading.value = false;
ElMessage.error(res.msg);
});
}
function onchangeTime(e) {
timeVal.value = e;
orderData.value.data = timeVal.value ? timeVal.value.join('-') : '';
getList();
}
function openSend(row) {
orderId.value = row.id;
sendRef.value.modals = true;
sendRef.value.getList(1);
sendRef.value.getDeliveryList();
}
function openDistribution(row) {
getIntegralOrderDistribution(row.id)
.then(async (res) => {
FromData.value = res.data;
edits.value.modals = true;
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function changeMenu(row, name) {
orderId.value = row.id;
switch (name) {
case 'detail':
getIntegralOrderDataInfo(row.id)
.then(async (res) => {
orderDatalist.value = res.data;
detailsRef.value.modals = true;
})
.catch((res) => {
ElMessage.error(res.msg);
});
break;
case 'record':
recordRef.value.modals = true;
recordRef.value.getList(row.id);
break;
case 'remark':
remarkRef.value.modals = true;
remarkRef.value.formValidate.remark = row.remark;
break;
case 'take':
modalSure({
title: '修改确认收货',
url: `marketing/integral/order/take/${row.id}`,
method: 'put',
ids: '',
})
.then((res) => {
ElMessage.success(res.msg);
getList();
})
.catch((res) => {
ElMessage.error(res.msg);
});
break;
case 'del':
modalSure({
title: '删除订单',
url: `marketing/integral/order/del/${row.id}`,
method: 'DELETE',
ids: '',
})
.then((res) => {
ElMessage.success(res.msg);
getList();
})
.catch((res) => {
ElMessage.error(res.msg);
});
break;
}
}
function reset() {
orderData.value = {
status: '',
data: '',
real_name: '',
field_key: 'all',
product_id: route.query.product_id || '',
type: '',
};
timeVal.value = [];
page.value.pageNum = 1;
getList();
getTabs();
}
function submitFail() {
getList();
}
</script>
<style lang="scss" scoped>
.tabBox {
display: flex;
align-items: center;
.tabBox_img {
width: 36px;
height: 36px;
img {
width: 100%;
height: 100%;
}
}
.tabBox_tit {
width: 60%;
font-size: 12px;
margin: 0 2px 0 10px;
letter-spacing: 1px;
padding: 5px 0;
}
}
</style>

View File

@ -1,51 +0,0 @@
<template>
<el-dialog v-model="modals" title="订单详情" width="680px" class="order_box" :show-close="true">
<el-row v-if="orderDatalist">
<el-col :span="24">
<el-card :bordered="false" shadow="never" class="ivu-mt">
<template #header
><div><span>用户信息</span></div></template
>
<el-row>
<el-col :span="12">用户昵称:{{ orderDatalist.userInfo.nickname }}</el-col>
<el-col :span="12">用户电话:{{ orderDatalist.userInfo.phone }}</el-col>
</el-row>
</el-card>
</el-col>
<el-col :span="24">
<el-card :bordered="false" shadow="never" class="ivu-mt">
<template #header
><div><span>订单信息</span></div></template
>
<el-row>
<el-col :span="12">订单号:{{ orderDatalist.orderInfo.order_id }}</el-col>
<el-col :span="12">下单时间:{{ orderDatalist.orderInfo.add_time }}</el-col>
<el-col :span="12">商品名称:{{ orderDatalist.orderInfo.store_name }}</el-col>
<el-col :span="12">商品规格:{{ orderDatalist.orderInfo.suk }}</el-col>
<el-col :span="12">兑换积分:{{ orderDatalist.orderInfo.total_price }}</el-col>
<el-col :span="12">订单状态:{{ orderDatalist.orderInfo.status_name }}</el-col>
</el-row>
<el-row v-if="orderDatalist.orderInfo.user_address">
<el-col :span="24">收货地址:{{ orderDatalist.orderInfo.user_address }}</el-col>
</el-row>
</el-card>
</el-col>
</el-row>
<template #footer><span></span></template>
</el-dialog>
</template>
<script setup>
import { ref } from 'vue';
defineOptions({ name: 'integralOrderDetails' });
defineProps({
orderDatalist: Object,
orderId: Number,
});
const modals = ref(false);
defineExpose({ modals });
</script>

View File

@ -1,53 +0,0 @@
<template>
<el-dialog v-model="modals" title="订单记录" width="680px" class="order_box" :show-close="true">
<el-table :data="recordList" v-loading="loading" empty-text="暂无数据">
<el-table-column label="操作时间" min-width="160">
<template #default="scope">
<span>{{ scope.row.change_time }}</span>
</template>
</el-table-column>
<el-table-column label="操作类型" min-width="100">
<template #default="scope">
<span>{{ scope.row.change_type }}</span>
</template>
</el-table-column>
<el-table-column label="操作描述" min-width="200">
<template #default="scope">
<span>{{ scope.row.change_message }}</span>
</template>
</el-table-column>
</el-table>
<template #footer><span></span></template>
</el-dialog>
</template>
<script setup>
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
import { getIntegralOrderRecord } from '../../api';
defineOptions({ name: 'integralOrderRecord' });
defineProps({
orderId: Number,
});
const modals = ref(false);
const loading = ref(false);
const recordList = ref([]);
function getList(id) {
loading.value = true;
getIntegralOrderRecord({ id })
.then(async (res) => {
recordList.value = res.data;
loading.value = false;
})
.catch((res) => {
loading.value = false;
ElMessage.error(res.msg);
});
}
defineExpose({ modals, getList });
</script>

View File

@ -1,52 +0,0 @@
<template>
<el-dialog v-model="modals" title="订单备注" width="500px" class="order_box" :show-close="true">
<el-form ref="formValidate" :model="formValidate" label-width="80px">
<el-form-item label="备注内容:">
<el-input v-model="formValidate.remark" type="textarea" :rows="4" placeholder="请输入备注内容"></el-input>
</el-form-item>
</el-form>
<template #footer
><span class="dialog-footer">
<el-button v-db-click @click="modals = false">取消</el-button>
<el-button type="primary" v-db-click @click="submit">确定</el-button>
</span></template
>
</el-dialog>
</template>
<script setup>
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
import { integralOrderPutRemarkData } from '../../api';
defineOptions({ name: 'integralOrderRemark' });
const props = defineProps({
orderId: [Number, String],
});
const emit = defineEmits(['submitFail']);
const modals = ref(false);
const formValidate = ref({
remark: '',
});
function submit() {
let data = {
id: props.orderId,
remark: formValidate.value.remark,
};
integralOrderPutRemarkData(data)
.then(async (res) => {
ElMessage.success(res.msg);
modals.value = false;
emit('submitFail');
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
defineExpose({ modals, formValidate });
</script>

View File

@ -1,268 +0,0 @@
<template>
<el-dialog v-model="modals" title="订单发送货" width="720px" class="order_box" :show-close="true">
<el-form ref="formItemRef" :model="formItem" label-width="100px" @submit.prevent>
<el-form-item label="选择类型:">
<el-radio-group v-model="formItem.type" @input="changeRadio">
<el-radio label="1" value="1">发货</el-radio>
<el-radio label="2" value="2">送货</el-radio>
<el-radio label="3" value="3">虚拟</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-show="formItem.type == '1' && export_open" label="发货类型:">
<el-radio-group v-model="formItem.express_record_type" @input="changeExpress">
<el-radio label="1" value="1">手动填写</el-radio>
<el-radio label="2" value="2">电子面单打印</el-radio>
</el-radio-group>
</el-form-item>
<div v-show="formItem.type === '1'">
<el-form-item label="快递公司:">
<el-select
v-model="formItem.delivery_name"
filterable
placeholder="请选择快递公司"
style="width: 80%"
@change="expressChange"
>
<el-option
v-for="(item, i) in express"
:value="item.value"
:key="item.value"
:label="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item v-if="formItem.express_record_type === '1'" label="快递单号:">
<el-input v-model="formItem.delivery_id" placeholder="请输入快递单号" style="width: 80%"></el-input>
</el-form-item>
<template v-if="formItem.express_record_type === '2'">
<el-form-item label="电子面单:" class="express_temp_id">
<el-select v-model="formItem.express_temp_id" placeholder="请选择电子面单" style="width: 80%">
<el-option
v-for="(item, i) in expressTemp"
:value="item.temp_id"
:key="i"
:label="item.title"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="寄件人姓名:">
<el-input v-model="formItem.to_name" placeholder="请输入寄件人姓名" style="width: 80%"></el-input>
</el-form-item>
<el-form-item label="寄件人电话:">
<el-input v-model="formItem.to_tel" placeholder="请输入寄件人电话" style="width: 80%"></el-input>
</el-form-item>
<el-form-item label="寄件人地址:">
<el-input v-model="formItem.to_addr" placeholder="请输入寄件人地址" style="width: 80%"></el-input>
</el-form-item>
</template>
</div>
<div v-show="formItem.type === '2'">
<el-form-item label="送货人:">
<el-select
v-model="formItem.sh_delivery"
placeholder="请选择送货人"
style="width: 80%"
@change="shDeliveryChange"
>
<el-option
v-for="(item, i) in deliveryList"
:value="item.id"
:key="i"
:label="`${item.wx_name} (${item.phone})`"
></el-option>
</el-select>
</el-form-item>
</div>
<div v-show="formItem.type === '3'">
<el-form-item label="备注:">
<el-input
v-model="formItem.fictitious_content"
type="textarea"
:autosize="{ minRows: 2, maxRows: 5 }"
placeholder="备注"
style="width: 80%"
></el-input>
</el-form-item>
</div>
</el-form>
<template #footer
><span class="dialog-footer">
<el-button v-db-click @click="cancel">取消</el-button>
<el-button type="primary" v-db-click @click="putSend">提交</el-button>
</span></template
>
</el-dialog>
</template>
<script setup>
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
import {
getExpressData,
orderExpressTemp,
orderDeliveryList,
orderSheetInfo,
integralOrderPutDelivery,
} from '../../api';
defineOptions({ name: 'integralOrderSend' });
const props = defineProps({
orderId: Number,
});
const emit = defineEmits(['submitFail']);
const formItemRef = ref(null);
const formItem = ref({
type: '1',
express_record_type: '1',
delivery_name: '',
delivery_id: '',
express_temp_id: '',
to_name: '',
to_tel: '',
to_addr: '',
sh_delivery: '',
sh_delivery_name: '',
sh_delivery_id: '',
sh_delivery_uid: '',
fictitious_content: '',
});
const modals = ref(false);
const express = ref([]);
const expressTemp = ref([]);
const deliveryList = ref([]);
const export_open = ref(false);
function changeRadio(o) {
formItemRef.value.resetFields();
formItem.value.type = o;
if (o === '1') {
getList(1);
}
}
function changeExpress(j) {
if (j === '2') {
formItem.value.delivery_name = '';
formItem.value.express_temp_id = '';
expressTemp.value = [];
getList(2);
} else {
getList(1);
}
}
function reset() {
formItem.value = {
type: '1',
express_record_type: '1',
delivery_name: '',
delivery_id: '',
express_temp_id: '',
to_name: '',
to_tel: '',
to_addr: '',
sh_delivery: '',
sh_delivery_name: '',
sh_delivery_id: '',
sh_delivery_uid: '',
fictitious_content: '',
};
}
function getList(type) {
let status = type === 2 ? 1 : '';
getExpressData(status)
.then(async (res) => {
express.value = res.data;
getSheetInfo();
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function putSend() {
let data = { id: props.orderId, datas: formItem.value };
if (formItem.value.type === '1' && formItem.value.express_record_type === '1') {
if (!formItem.value.delivery_name) return ElMessage.error('快递公司不能为空');
if (!formItem.value.delivery_id) return ElMessage.error('快递单号不能为空');
}
if (formItem.value.type === '2' && !formItem.value.sh_delivery) {
return ElMessage.error('送货人不能为空');
}
integralOrderPutDelivery(data)
.then(async (res) => {
emit('submitFail');
modals.value = false;
ElMessage.success(res.msg);
reset();
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function cancel() {
modals.value = false;
reset();
}
function expressChange(value) {
let expressItem = express.value.find((item) => item.value === value);
if (!expressItem) return;
formItem.value.delivery_code = expressItem.code;
if (formItem.value.express_record_type === '2') {
expressTemp.value = [];
formItem.value.express_temp_id = '';
orderExpressTemp({ com: formItem.value.delivery_code })
.then((res) => {
expressTemp.value = res.data;
if (!res.data.length) {
ElMessage.error('请配置所选快递公司的电子面单');
}
})
.catch((err) => {
ElMessage.error(err.msg);
});
}
}
function getDeliveryList() {
orderDeliveryList()
.then((res) => {
deliveryList.value = res.data.list;
})
.catch((err) => {
ElMessage.error(err.msg);
});
}
function getSheetInfo() {
orderSheetInfo()
.then((res) => {
const data = res.data;
for (const key in data) {
if (data.hasOwnProperty(key)) {
formItem.value[key] = data[key];
}
}
export_open.value = data.export_open === undefined ? true : data.export_open;
if (!export_open.value) formItem.value.express_record_type = '1';
formItem.value.to_addr = data.to_add;
})
.catch((err) => {
ElMessage.error(err.msg);
});
}
function shDeliveryChange(value) {
let deliveryItem = deliveryList.value.find((item) => item.id === value);
if (deliveryItem) {
formItem.value.sh_delivery_name = deliveryItem.wx_name;
formItem.value.sh_delivery_id = deliveryItem.phone;
formItem.value.sh_delivery_uid = deliveryItem.uid;
}
}
defineExpose({ modals, getList, getDeliveryList });
</script>
<style scoped>
.express_temp_id {
position: relative;
}
</style>

View File

@ -1,616 +0,0 @@
<template>
<div>
<pages-header
:title="$route.params.id ? '编辑积分商品' : '添加积分商品'"
:backUrl="$routeProStr + '/marketing/integral/product'"
></pages-header>
<el-card :bordered="false" shadow="never" class="mt16">
<el-row class="mt30 acea-row row-middle row-center" v-loading="spinShow">
<el-col :span="20">
<steps :stepList="stepList" :isActive="current"></steps>
</el-col>
<el-col :span="23">
<el-form
class="form mt30"
ref="formValidateRef"
:model="formValidate"
:rules="ruleValidate"
:label-width="labelWidth"
:label-position="labelPosition"
@submit.prevent
>
<!-- 第一步:选择商品 -->
<el-form-item label="选择商品:" prop="image_input" v-if="current === 0">
<div class="picBox" v-db-click @click="changeGoods">
<div class="pictrue" v-if="formValidate.image">
<img v-lazy="formValidate.image" />
</div>
<div class="upLoad acea-row row-center-wrapper" v-else>
<i class="el-icon-goods" style="font-size: 24px"></i>
</div>
</div>
</el-form-item>
<!-- 第二步:基础信息 -->
<el-col v-show="current === 1">
<el-col :span="24">
<el-form-item prop="image">
<template #label
><div class="custom-label">
<div>
<div>商品主图</div>
<div>(750*750)</div>
</div>
<div>:</div>
</div></template
>
<div class="picBox" v-db-click @click="modalPicTap('dan', 'danFrom')">
<div class="pictrue" v-if="formValidate.image">
<img v-lazy="formValidate.image" />
</div>
<div class="upLoad acea-row row-center-wrapper" v-else>
<i class="el-icon-picture-outline" style="font-size: 24px"></i>
</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item prop="images">
<template #label
><div class="custom-label">
<div>
<div>商品轮播图</div>
<div>(750*750)</div>
</div>
<div>:</div>
</div></template
>
<div class="acea-row">
<div
class="pictrue"
v-for="(item, index) in formValidate.images"
:key="index"
draggable="true"
@dragstart="handleDragStart($event, item)"
@dragover.prevent="handleDragOver($event, item)"
@dragenter="handleDragEnter($event, item)"
@dragend="handleDragEnd($event, item)"
>
<img v-lazy="item" />
<i class="el-icon-circle-close btndel" v-db-click @click="handleRemove(index)"></i>
</div>
<div
v-if="formValidate.images.length < 10"
class="upLoad acea-row row-center-wrapper"
v-db-click
@click="modalPicTap('duo')"
>
<i class="el-icon-picture-outline" style="font-size: 24px"></i>
</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-col v-bind="grid">
<el-form-item label="商品标题:" prop="title" label-for="title">
<el-input
placeholder="请输入商品标题"
v-model="formValidate.title"
class="content_width"
maxlength="80"
show-word-limit
/>
</el-form-item>
</el-col>
</el-col>
<el-col :span="24">
<el-form-item label="用户兑换数量限制:" prop="num">
<div class="acea-row row-middle">
<el-input-number
:controls="false"
:min="1"
:max="9999999999"
placeholder="请输入数量限制"
element-id="num"
:precision="0"
v-model="formValidate.num"
class="content_width"
/>
<div class="ml10 grey">每个用户可购买该商品总数限制</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="单位:" prop="unit_name" label-for="unit_name">
<el-input
placeholder="请输入单位"
element-id="unit_name"
v-model="formValidate.unit_name"
class="content_width"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="排序:">
<el-input-number
:controls="false"
placeholder="请输入排序"
element-id="sort"
:precision="0"
v-model="formValidate.sort"
class="content_width"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="上架状态:" props="is_show" label-for="status">
<el-radio-group element-id="is_show" v-model="formValidate.is_show">
<el-radio :label="1" :value="1" class="radio">开启</el-radio>
<el-radio :label="0" :value="0">关闭</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col v-bind="grid2">
<el-form-item label="热门推荐:" props="is_host" label-for="is_host">
<el-radio-group element-id="is_host" v-model="formValidate.is_host">
<el-radio :label="1" :value="1" class="radio">开启</el-radio>
<el-radio :label="0" :value="0">关闭</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="规格选择:">
<el-table
ref="multipleTable"
:data="specsData"
border
:row-key="getRowKeys"
class="mt14"
highlight-current-row
@selection-change="changeCheckbox"
>
<el-table-column type="selection" :reserve-selection="true" width="55"> </el-table-column>
<el-table-column
:label="item.title"
:min-width="item.minWidth"
v-for="(item, index) in columns"
:key="index"
>
<template #default="scope">
<template v-if="item.key">
<div>
<span>{{ scope.row[item.key] }}</span>
</div>
</template>
<template v-else-if="item.slot === 'pic'">
<div
class="acea-row row-middle row-center-wrapper"
v-db-click
@click="modalPicTap('dan', 'danTable', scope.$index)"
>
<div class="pictrue pictrueTab" v-if="scope.row.pic">
<img v-lazy="scope.row.pic" />
</div>
<div class="upLoad pictrueTab acea-row row-center-wrapper" v-else>
<i class="el-icon-picture-outline" style="font-size: 24px"></i>
</div>
</div>
</template>
<template v-else-if="item.slot === 'price'">
<el-input-number
:controls="false"
v-model="scope.row.price"
:min="0"
:precision="0"
class="priceBox"
:active-change="false"
></el-input-number>
</template>
<template v-else-if="item.slot === 'quota'">
<el-input-number
:controls="false"
v-model="scope.row.quota"
:min="1"
class="priceBox"
></el-input-number>
</template>
</template>
</el-table-column>
</el-table>
</el-form-item>
</el-col>
</el-col>
<!-- 第三步:商品详情 -->
<el-row v-show="current === 2">
<el-col :span="24">
<el-form-item label="内容:">
<WangEditor
style="width: 90%"
:content="formValidate.description"
:ai-default-prompt="aiDefaultPrompt"
@editorContent="getEditorContent"
>
</WangEditor>
</el-form-item>
</el-col>
</el-row>
<el-col :span="24">
<el-form-item>
<el-button
class="submission"
v-db-click
@click="step"
:disabled="($route.params.id && current === 1) || current === 0"
>上一步</el-button
>
<el-button
:disabled="submitOpen && current === 2"
type="primary"
class="submission"
v-db-click
@click="next('formValidate')"
>{{ current === 2 ? '提交' : '下一步' }}</el-button
>
</el-form-item>
</el-col>
</el-form>
</el-col>
</el-row>
</el-card>
<!-- 选择商品 -->
<el-dialog v-model="modals" title="商品列表" class="paymentFooter" width="1000px">
<goods-list ref="goodslist" @getProductId="getProductId"></goods-list>
</el-dialog>
<!-- 上传图片 -->
<el-dialog v-model="modalPic" width="950px" title="上传商品图" :close-on-click-modal="false">
<uploadPictures
:isChoice="isChoice"
@getPic="getPic"
@getPicD="getPicD"
:gridBtn="gridBtn"
:gridPic="gridPic"
v-if="modalPic"
></uploadPictures>
</el-dialog>
</div>
</template>
<script setup>
import { computed, nextTick, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import goodsList from '@/components/goodsList/index';
import WangEditor from '@/components/wangEditor/index.vue';
import uploadPictures from '@/components/uploadPictures';
import steps from '@/components/steps/index';
import { integralAddApi, productAttrsApi, integralInfoApi } from '../../api';
import setting from '@/setting';
import { useMediaStore } from '@/store/module/media';
defineOptions({ name: 'integralProductCreate' });
const route = useRoute();
const router = useRouter();
const routePre = setting.routePre;
const mediaStore = useMediaStore();
const formValidateRef = ref(null);
const multipleTable = ref(null);
const goodslist = ref(null);
const isMobile = computed(() => mediaStore.isMobile);
const labelWidth = computed(() => (isMobile.value ? undefined : '135px'));
const labelPosition = computed(() => (isMobile.value ? 'top' : 'right'));
const submitOpen = ref(false);
const spinShow = ref(false);
const isChoice = ref('');
const current = ref(0);
const modalPic = ref(false);
const grid = { xl: 12, lg: 20, md: 24, sm: 24, xs: 24 };
const grid2 = { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 };
const gridPic = { xl: 6, lg: 8, md: 12, sm: 12, xs: 12 };
const gridBtn = { xl: 4, lg: 8, md: 8, sm: 8, xs: 8 };
const stepList = ref(['选择积分商品', '填写基础信息', '修改商品详情']);
const modals = ref(false);
const modal_loading = ref(false);
const formValidate = ref({
images: [],
title: '',
image: '',
unit_name: '',
price: 0,
sales: 0,
stock: 0,
sort: 0,
num: 1,
once_num: 1,
is_host: 0,
is_show: 1,
description: '',
id: 0,
product_id: 0,
attrs: [],
items: [],
});
// AI 写作默认提示词:优先带入当前商品标题(弹窗内未被用户修改过的提示词会跟随刷新)
const aiDefaultPrompt = computed(() => {
const title = (formValidate.value.title || '').trim();
return title
? `请围绕商品《${title}》撰写一篇产品推广文章,介绍产品亮点、使用场景与适用人群,结构清晰,800字左右。`
: '写一篇产品推广文章,介绍产品亮点、使用场景与适用人群,结构清晰,800字左右。';
});
const description = ref('');
const columns = ref([]);
const specsData = ref([]);
const picTit = ref('');
const tableIndex = ref(0);
const copy = ref(0);
const ruleValidate = ref({
image: [{ required: true, message: '请选择主图', trigger: 'change' }],
images: [
{ required: true, type: 'array', message: '请选择轮播图', trigger: 'change' },
{ type: 'array', min: 1, message: '至少选择一张轮播图', trigger: 'change' },
],
title: [{ required: true, message: '请输入商品标题', trigger: 'blur' }],
unit_name: [{ required: true, message: '请输入单位', trigger: 'blur' }],
num: [{ required: true, type: 'number', message: '请输入兑换数量限制', trigger: 'blur' }],
});
// 非响应式内部状态(原代码未在 data 中声明)
let dragging = null;
onMounted(() => {
if (route.params.id) {
copy.value = route.params.copy || 0;
current.value = 1;
getInfo();
}
});
function getEditorContent(data) {
description.value = data;
}
function productAttrs(rows) {
productAttrsApi(rows.id, 4)
.then((res) => {
let data = res.data.info;
specsData.value = data.attrs;
specsData.value.forEach(function (item, index) {
specsData.value[index]['id'] = index;
});
formValidate.value.items = data.items;
columns.value = data.header;
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function changeCheckbox(selection) {
formValidate.value.attrs = selection;
}
function getProductId(row) {
modal_loading.value = false;
modals.value = false;
setTimeout(() => {
formValidate.value = {
images: row.slider_image || [],
title: row.store_name,
image: row.image,
unit_name: row.unit_name,
price: 0,
sales: row.sales,
stock: row.stock,
sort: row.sort,
num: 1,
once_num: 1,
is_host: 0,
is_show: 1,
description: row.description || '',
id: 0,
product_id: row.id,
attrs: [],
items: [],
};
productAttrs(row);
}, 500);
}
function getInfo() {
spinShow.value = true;
integralInfoApi(route.params.id)
.then(async (res) => {
let info = res.data.info;
formValidate.value = info;
formValidate.value['items'] = info.attrs.items;
columns.value = info.attrs.header;
specsData.value = info.attrs.value;
specsData.value.forEach(function (item, index) {
specsData.value[index]['id'] = index;
});
let attr = [];
for (let index in info.attrs.value) {
if (info.attrs.value[index]._checked) {
attr.push(info.attrs.value[index]);
}
}
formValidate.value.attrs = attr;
attr.forEach((row) => {
multipleTable.value.toggleRowSelection(row, true);
});
spinShow.value = false;
})
.catch((res) => {
spinShow.value = false;
ElMessage.error(res.msg);
});
}
function getRowKeys(row) {
return row.id;
}
function next(name) {
if (current.value === 2) {
formValidate.value.description = description.value;
formValidateRef.value.validate((valid) => {
if (valid) {
if (copy.value == 1) formValidate.value.copy = 1;
formValidate.value.id = Number(route.params.id) || 0;
submitOpen.value = true;
integralAddApi(formValidate.value)
.then(async (res) => {
submitOpen.value = false;
ElMessage.success(res.msg);
setTimeout(() => {
router.push({ path: routePre + '/marketing/integral/product' });
}, 500);
})
.catch((res) => {
submitOpen.value = false;
ElMessage.error(res.msg);
});
} else {
return false;
}
});
} else if (current.value === 1) {
formValidateRef.value.validate((valid) => {
if (valid) {
if (!formValidate.value.attrs || !formValidate.value.attrs.length) {
return ElMessage.error('请选择属性规格');
}
for (let index in formValidate.value.attrs) {
if (formValidate.value.attrs[index].quota <= 0) {
return ElMessage.error('兑换次数必须大于0');
}
}
current.value += 1;
} else {
return ElMessage.warning('请完善您的信息');
}
});
} else {
if (formValidate.value.image) {
current.value += 1;
} else {
ElMessage.warning('请选择商品');
}
}
}
function step() {
current.value--;
}
function modalPicTap(tit, picTitVal, index) {
modalPic.value = true;
isChoice.value = tit === 'dan' ? '单选' : '多选';
picTit.value = picTitVal;
tableIndex.value = index;
}
function getPic(pc) {
switch (picTit.value) {
case 'danFrom':
formValidate.value.image = pc.att_dir;
break;
default:
if (formValidate.value.attrs && formValidate.value.attrs.length) {
specsData.value[tableIndex.value]['_checked'] = true;
}
specsData.value[tableIndex.value].pic = pc.att_dir;
}
modalPic.value = false;
}
function getPicD(pc) {
pc.map((item) => {
formValidate.value.images.push(item.att_dir);
formValidate.value.images = formValidate.value.images.splice(0, 10);
});
modalPic.value = false;
}
function handleRemove(i) {
formValidate.value.images.splice(i, 1);
}
function changeGoods() {
modals.value = true;
nextTick(() => {
goodslist.value.formValidate.is_show = -1;
goodslist.value.formValidate.type = 7;
goodslist.value.getList();
goodslist.value.goodsCategory();
});
}
function handleDragStart(e, item) {
dragging = item;
}
function handleDragEnd(e, item) {
dragging = null;
}
function handleDragOver(e) {
e.dataTransfer.dropEffect = 'move';
}
function handleDragEnter(e, item) {
e.dataTransfer.effectAllowed = 'move';
if (item === dragging) return;
const newItems = [...formValidate.value.images];
const src = newItems.indexOf(dragging);
const dst = newItems.indexOf(item);
newItems.splice(dst, 0, ...newItems.splice(src, 1));
formValidate.value.images = newItems;
}
</script>
<style lang="scss" scoped>
.content_width {
width: 460px;
}
.custom-label {
display: inline-flex;
line-height: 1.5;
}
.grey {
color: #999;
}
.tabBox_img {
width: 50px;
height: 50px;
margin: 0 auto;
}
.tabBox_img img {
width: 100%;
height: 100%;
}
.priceBox {
width: 100%;
}
.form {
.picBox {
display: inline-block;
cursor: pointer;
}
.pictrue {
width: 60px;
height: 60px;
border: 1px dotted rgba(0, 0, 0, 0.1);
margin-right: 15px;
display: inline-block;
position: relative;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
.btndel {
position: absolute;
z-index: 9;
width: 20px !important;
height: 20px !important;
left: 46px;
top: -4px;
}
}
.upLoad {
width: 58px;
height: 58px;
line-height: 58px;
border: 1px dotted rgba(0, 0, 0, 0.1);
border-radius: 4px;
background: rgba(0, 0, 0, 0.02);
cursor: pointer;
}
}
</style>

View File

@ -1,274 +0,0 @@
<template>
<div>
<el-card :bordered="false" shadow="never" :body-style="{ padding: 0 }">
<div class="padding-add">
<el-form :model="tableFrom" :label-width="labelWidth" label-position="right" @submit.prevent inline>
<el-form-item label="创建时间:">
<date-range-picker
clearable
v-model="timeVal"
:editable="false"
@change="onchangeTime"
format="YYYY/MM/DD"
value-format="YYYY/MM/DD"
></date-range-picker>
</el-form-item>
<el-form-item label="上架状态:">
<el-select
placeholder="请选择"
clearable
v-model="tableFrom.is_show"
@change="userSearchs"
class="form_content_width"
>
<el-option value="1" label="上架"></el-option>
<el-option value="0" label="下架"></el-option>
</el-select>
</el-form-item>
<el-form-item label="商品搜索:" label-for="store_name">
<el-input
placeholder="请输入商品名称,ID"
v-model="tableFrom.store_name"
clearable
class="form_content_width"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" v-db-click @click="userSearchs">查询</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
<el-card :bordered="false" shadow="never" class="ivu-mt mt16">
<el-button
v-auth="['marketing-store_integral-create']"
type="primary"
icon="md-add"
v-db-click
@click="add"
class="action-row mr10"
>添加积分商品</el-button
>
<el-table :data="tableList" v-loading="loading" highlight-current-row class="mt14">
<el-table-column label="ID" min-width="50">
<template #default="scope">
<span>{{ scope.row.id }}</span>
</template>
</el-table-column>
<el-table-column label="商品图片" min-width="90">
<template #default="scope">
<div class="tabBox_img" v-viewer>
<img v-lazy="scope.row.image" />
</div>
</template>
</el-table-column>
<el-table-column label="活动标题" min-width="130">
<template #default="scope">
<el-tooltip placement="top" :open-delay="600">
<template #content
><div>{{ scope.row.title }}</div></template
>
<span class="line2">{{ scope.row.title }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="兑换积分" min-width="60">
<template #default="scope">
<span>{{ scope.row.price }}</span>
</template>
</el-table-column>
<el-table-column label="限量" min-width="60">
<template #default="scope">
<span>{{ scope.row.quota_show }}</span>
</template>
</el-table-column>
<el-table-column label="限量剩余" min-width="60">
<template #default="scope">
<span>{{ scope.row.quota }}</span>
</template>
</el-table-column>
<el-table-column label="创建时间" min-width="100">
<template #default="scope">
<span>{{ scope.row.add_time }}</span>
</template>
</el-table-column>
<el-table-column label="排序" min-width="60">
<template #default="scope">
<span>{{ scope.row.sort }}</span>
</template>
</el-table-column>
<el-table-column label="状态" min-width="80">
<template #default="scope">
<el-switch
class="defineSwitch"
:active-value="1"
:inactive-value="0"
v-model="scope.row.is_show"
:value="scope.row.is_show"
@change="onchangeIsShow(scope.row)"
size="large"
active-text="上架"
inactive-text="下架"
>
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="200">
<template #default="scope">
<a v-db-click @click="orderList(scope.row)">兑换记录</a>
<el-divider direction="vertical"></el-divider>
<a v-db-click @click="edit(scope.row)">编辑</a>
<el-divider direction="vertical"></el-divider>
<a v-db-click @click="copy(scope.row)">复制</a>
<el-divider direction="vertical"></el-divider>
<a v-db-click @click="del(scope.row, '删除积分商品', scope.$index)">删除</a>
</template>
</el-table-column>
</el-table>
<div class="acea-row row-right page">
<pagination
v-if="total"
:total="total"
v-model:page="tableFrom.page"
v-model:limit="tableFrom.limit"
@pagination="getList"
/>
</div>
</el-card>
</div>
</template>
<script setup>
import { computed, onActivated, onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import { integralProductListApi, integralIsShowApi } from '../../api';
import { formatDate } from '@/utils/validate';
import { modalSure } from '@/utils/public';
import setting from '@/setting';
import { useMediaStore } from '@/store/module/media';
defineOptions({ name: 'integralProductList' });
const router = useRouter();
const routePre = setting.routePre;
const mediaStore = useMediaStore();
const isMobile = computed(() => mediaStore.isMobile);
const labelWidth = computed(() => (isMobile.value ? undefined : '80px'));
const labelPosition = computed(() => (isMobile.value ? 'top' : 'right'));
const loading = ref(false);
const tableList = ref([]);
const timeVal = ref([]);
const tableFrom = ref({
integral_time: '',
is_show: '',
store_name: '',
page: 1,
limit: 15,
});
const total = ref(0);
// 原 filters.formatDate
function filterFormatDate(time) {
if (time !== 0) {
let date = new Date(time * 1000);
return formatDate(date, 'yyyy-MM-dd');
}
}
onMounted(() => {
getList();
});
onActivated(() => {
getList();
});
function add() {
router.push({ path: routePre + '/marketing/integral/product/create' });
}
function orderList(row) {
router.push({
path: routePre + '/marketing/integral/order',
query: { product_id: row.id },
});
}
function edit(row) {
router.push({
path: routePre + '/marketing/integral/product/create/' + row.id + '/0',
});
}
function copy(row) {
router.push({
path: routePre + '/marketing/integral/product/create/' + row.id + '/1',
});
}
function del(row, tit, num) {
let delfromData = {
title: tit,
num: num,
url: `marketing/integral/${row.id}`,
method: 'DELETE',
ids: '',
};
modalSure(delfromData)
.then((res) => {
ElMessage.success(res.msg);
tableFrom.value.page = 1;
getList();
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function getList() {
loading.value = true;
tableFrom.value.is_show = tableFrom.value.is_show || '';
integralProductListApi(tableFrom.value)
.then(async (res) => {
let data = res.data;
tableList.value = data.list;
total.value = res.data.count;
loading.value = false;
})
.catch((res) => {
loading.value = false;
ElMessage.error(res.msg);
});
}
function userSearchs() {
tableFrom.value.page = 1;
getList();
}
function onchangeTime(e) {
timeVal.value = e;
tableFrom.value.integral_time = timeVal.value ? timeVal.value.join('-') : '';
getList();
}
function onchangeIsShow(row) {
let data = { id: row.id, is_show: row.is_show };
integralIsShowApi(data)
.then(async (res) => {
ElMessage.success(res.msg);
})
.catch((res) => {
ElMessage.error(res.msg);
row.is_show = !row.is_show;
});
}
</script>
<style lang="scss" scoped>
.tabBox_img {
width: 36px;
height: 36px;
border-radius: 4px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
</style>

View File

@ -1,85 +0,0 @@
/**
* 积分商城扩展路由
*
* 说明:
* - 仅保留插件特有的路由:积分商品、积分订单
* - 积分记录、积分统计、积分配置为系统内置路由,已迁移至 router/modules/marketing.js
* - 旧路由(store_integral/*、user_point/*)已移除
*/
import setting from '@/setting';
const routePre = setting.routePre;
const pre = 'integral_';
export default [
{
path: routePre + '/marketing/integral/product',
section: 'integral',
action: 'product',
meta: {
auth: ['marketing-integral-product'],
title: '积分商品',
keepAlive: true,
},
component: () => import('@/layout'),
children: [
{
path: '',
name: `${pre}product`,
name: 'integralProductList',
component: () => import('./pages/product/index.vue'),
meta: {
auth: ['marketing-integral-product'],
title: '积分商品',
keepAlive: true,
},
},
],
},
{
path: routePre + '/marketing/integral/product/create/:id?/:copy?',
section: 'integral',
action: 'product_create',
meta: {
auth: ['marketing-integral-save'],
title: '添加/编辑积分商品',
},
component: () => import('@/layout'),
children: [
{
path: '',
name: `${pre}productCreate`,
component: () => import('./pages/product/create.vue'),
meta: {
auth: ['marketing-integral-save'],
title: '添加/编辑积分商品',
activeMenu: routePre + '/marketing/integral/product',
},
},
],
},
{
path: routePre + '/marketing/integral/order',
section: 'integral',
action: 'order',
meta: {
auth: ['marketing-integral-order'],
title: '积分订单',
keepAlive: true,
},
component: () => import('@/layout'),
children: [
{
path: '',
name: `${pre}order`,
name: 'integralOrderList',
component: () => import('./pages/order/index.vue'),
meta: {
auth: ['marketing-integral-order'],
title: '积分订单',
keepAlive: true,
},
},
],
},
// 积分记录、积分统计、积分配置为系统内置路由,已移至 router/modules/marketing.js
];

View File

@ -1,380 +0,0 @@
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
import request from '@/libs/request';
/*
* 登录
* */
export function AccountLogin(data) {
return request({
url: '/kefu/login',
method: 'post',
data,
kefu: true,
});
}
/**
* 获取左侧客服会话列表(会话列表/会话结束,用户与游客混排)
* @param {Object} params nickname-搜索关键字 is_end-0=会话列表 1=会话结束
* @constructor
*/
export function record(params) {
return request({
url: '/kefu/user/record',
method: 'get',
params,
kefu: true,
});
}
/**
* 结束会话:移入「会话结束」列表,不再计入未读
* @param {Number} toUid 会话对方uid(用户或游客)
* @constructor
*/
export function endChatApi(toUid) {
return request({
url: `/kefu/user/end_chat/${toUid}`,
method: 'put',
kefu: true,
});
}
/**
* 会话列表(用户+游客)未读消息总数
* @constructor
*/
export function unreadNumApi() {
return request({
url: '/kefu/user/unread_num',
method: 'get',
kefu: true,
});
}
/**
* 获取左侧用户详情
* @constructor
*/
export function userInfo(id) {
return request({
url: '/kefu/user/info/' + id,
method: 'get',
kefu: true,
});
}
/**
* 客服话术
* @constructor
*/
export function speeChcraft(params) {
return request({
url: '/kefu/service/speechcraft',
method: 'get',
params,
kefu: true,
});
}
/**
* 客服转接列表
* @constructor
*/
export function transferList(params) {
return request({
url: '/kefu/service/transfer_list',
method: 'get',
params,
kefu: true,
});
}
/**
* 客服转接列表
* @constructor
*/
export function serviceTransfer(params) {
return request({
url: '/kefu/service/transfer',
method: 'post',
params,
kefu: true,
});
}
/**
* 客服用户标签
* @constructor
*/
export function userLabel(id) {
return request({
url: `/kefu/user/label/${id}`,
method: 'get',
kefu: true,
});
}
/**
* 客服用户标签更新
* @constructor
*/
export function userLabelPut(id, data) {
return request({
url: `/kefu/user/label/${id}`,
method: 'put',
data,
kefu: true,
});
}
/**
* 客服用户聊天列表
* @constructor
*/
export function serviceList(params) {
return request({
url: `/kefu/service/list`,
method: 'get',
params,
kefu: true,
});
}
/**
* 退出登录
* @constructor
*/
export function AccountLogoutKefu() {
return request({
url: `/kefu/user/logout`,
method: 'post',
kefu: true,
});
}
/**
* 获取扫码登录凭证
* @constructor
*/
export function getSanCodeKey() {
return request({
url: `/kefu/key`,
method: 'get',
kefu: true,
});
}
/**
* 获取轮播图和logo
*/
export function loginInfoApi() {
return request({
url: '/kefu/config',
method: 'get',
kefu: true,
});
}
/**
* 获取话术分类
*/
export function serviceCate(params) {
return request({
url: '/kefu/service/cate',
method: 'get',
params,
kefu: true,
});
}
/**
* 修改话术
*/
export function serviceCateUpdate(id, params) {
return request({
url: '/kefu/service/speechcraft/' + id,
method: 'PUT',
params,
kefu: true,
});
}
/**
* 添加话术
*/
export function addSpeeChcraft(data) {
return request({
url: '/kefu/service/speechcraft',
method: 'post',
data,
kefu: true,
});
}
/**
* 添加分类
*/
export function addServiceCate(data) {
return request({
url: '/kefu/service/cate',
method: 'post',
data,
kefu: true,
});
}
/**
* 修改分类
*/
export function editServiceCate(id, params) {
return request({
url: '/kefu/service/cate/' + id,
method: 'PUT',
params,
kefu: true,
});
}
/**
* 扫码登录情况
*/
export function scanStatus(key, params) {
return request({
url: '/kefu/scan/' + key,
method: 'get',
params,
kefu: true,
});
}
/**
* 客服用户分组
* @constructor
*/
export function userGroupApi() {
return request({
url: `/kefu/user/group`,
method: 'get',
kefu: true,
});
}
/**
* 客服设置用户分组
* @constructor
*/
export function putGroupApi(uid, id) {
return request({
url: `/kefu/user/group/${uid}/${id}`,
method: 'put',
kefu: true,
});
}
/**
* 客服配置
* @constructor
*/
export function kefuConfig() {
return request({
url: `/kefu/config`,
method: 'get',
kefu: true,
});
}
/**
* 客户端 随机客服
* @constructor
*/
export function serviceListApi(params) {
return request({
url: `kefu/tourist/user`,
method: 'get',
params,
kefu: true,
});
}
/**
* 客户端 广告位
* @constructor
*/
export function getAdvApi() {
return request({
url: `kefu/tourist/adv`,
method: 'get',
kefu: true,
});
}
/**
* 客户端 聊天记录
* @constructor
*/
export function chatListApi(params) {
return request({
url: `kefu/tourist/chat`,
method: 'get',
params,
kefu: true,
});
}
/**
* 客户端 客服反馈
* @constructor
*/
export function feedbackDataApi() {
return request({
url: `kefu/tourist/feedback`,
method: 'get',
kefu: true,
});
}
/**
* 客户端 反馈提示语
* @constructor
*/
export function feedbackFromApi(data) {
return request({
url: `kefu/tourist/feedback`,
method: 'post',
data,
kefu: true,
});
}
/**
* 客服链接获取
* @constructor
*/
export function getWorkermanUrl() {
return request({
url: `get_workerman_url`,
method: 'get',
});
}
/**
* 复制粘贴上传图片
*/
export function uploadImg(data) {
return request({
url: `kefu/upload`,
method: 'post',
data,
kefu: true,
});
}

View File

@ -1,23 +0,0 @@
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
import request from '@/libs/request';
/**
* 客服详细信息
* @constructor
*/
export function serviceInfo() {
return request({
url: 'kefu/service/info',
method: 'get',
kefu: true,
});
}

View File

@ -1,204 +0,0 @@
<template>
<div>
<div class="feedback" :class="change === true ? 'on' : ''">
<div class="feedback-header acea-row">
<span class="sp1">商城客服已离线</span>
<div>
<i class="el-icon-close" style="font-size: 18px" v-db-click @click="close"></i>
</div>
</div>
<div v-if="!isShow">
<div class="feedback-conent mb20">
<div class="ft" v-text="notice"></div>
</div>
<div>
<el-form :model="formItem" ref="formItemRef" class="pl15" :rules="ruleValidate">
<el-form-item prop="rela_name">
<el-input v-model="formItem.rela_name" placeholder="请输入您的姓名"></el-input>
</el-form-item>
<el-form-item prop="phone">
<el-input v-model="formItem.phone" placeholder="请输入您的联系电话"></el-input>
</el-form-item>
<el-form-item prop="content">
<el-input v-model="formItem.content" class="mb10" type="textarea" placeholder="请输入留言内容"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" v-db-click @click="handleSubmit()" style="width: 100%">提交留言</el-button>
</el-form-item>
</el-form>
</div>
</div>
<div class="sure" v-if="isShow">
<div class="sure-yuan"></div>
<div class="sp1 mb10">提交成功</div>
<div class="sp2 mb30">您的信息提交成功,我们会尽快与您取得联系!</div>
<el-button type="primary" v-db-click @click="close">好的</el-button>
</div>
</div>
<div class="maskModel" @touchmove.prevent v-show="change === true"></div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import { feedbackDataApi, feedbackFromApi } from '@/pages/plugin/kefu/api';
defineOptions({ name: 'feedback' });
const props = defineProps({
change: Boolean,
});
const emit = defineEmits(['closeChange']);
const formItemRef = ref(null);
const isShow = ref(false);
const formItem = ref({
rela_name: '',
content: '',
phone: '',
});
const notice = ref('');
const ruleValidate = {
rela_name: [{ required: true, message: '请输入您的姓名', trigger: 'blur' }],
content: [{ required: true, message: '请输入留言内容', trigger: 'blur' }],
phone: [
{ required: true, message: '请填写手机号码', trigger: 'change' },
{ pattern: /^1[3456789]\d{9}$/, message: '手机号码格式不正确', trigger: 'blur' },
],
};
onMounted(() => {
getNotice();
});
function handleSubmit() {
formItemRef.value.validate((valid) => {
if (valid) {
feedbackFromApi(formItem.value)
.then((res) => {
isShow.value = true;
})
.catch((err) => {
ElMessage.error(err.msg);
});
} else {
}
});
}
function close() {
emit('closeChange', false);
}
// 广告
function getNotice() {
feedbackDataApi()
.then((res) => {
notice.value = res.data.feedback;
})
.catch((err) => {
ElMessage.error(err.msg);
});
}
</script>
<style scoped lang="scss">
.maskModel {
z-index: 7777 !important;
}
.on {
opacity: 1 !important;
transform: scale(1) !important;
-webkit-transform: scale(1) !important;
-o-transform: scale(1) !important;
-moz-transform: scale(1) !important;
-ms-transform: scale(1) !important;
}
.pl15 {
padding: 0 15px;
}
.sure {
width: 100%;
height: 480px;
text-align: center;
&-yuan {
width: 70px;
height: 70px;
border-radius: 50%;
overflow: hidden;
background: #55d443;
margin: 54px auto;
line-height: 70px;
}
.sp1 {
color: #333333;
font-size: 16px;
}
.sp2 {
color: #999999;
font-size: 13px;
}
}
.feedback {
position: fixed;
width: 320px;
height: 530px;
border-radius: 2px;
background-color: #fff;
z-index: 9999;
top: 50%;
left: 50%;
margin-left: -150px;
margin-top: -237px;
transition: all 0.3s ease-in-out 0s;
-webkit-transition: all 0.3s ease-in-out 0s;
-o-transition: all 0.3s ease-in-out 0s;
-moz-transition: all 0.3s ease-in-out 0s;
-webkit-transform: scale(0);
-o-transform: scale(0);
-moz-transform: scale(0);
-ms-transform: scale(0);
transform: scale(0);
opacity: 0;
&-header {
width: 100%;
height: 50px;
line-height: 50px;
padding: 0 15px;
background: linear-gradient(270deg, #1890ff 0%, #3875ea 100%);
align-items: center;
justify-content: space-between;
.sp1 {
color: #fff;
font-size: 16px;
}
}
&-conent {
padding: 15px;
.ft {
color: #333333;
font-size: 13px;
}
}
}
@media (max-width: 767px) {
.feedback {
top: 0;
left: 0;
width: 100%;
height: 100dvh;
margin: 0;
border-radius: 0;
background: var(--prev-bg-white);
&-header {
background: var(--prev-color-primary);
}
&-conent .ft {
color: var(--prev-color-text-regular);
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +0,0 @@
<template>
<div class="Loads acea-row row-center-wrapper" v-if="loading && !loaded" style="margin-top: 0.2rem; font-size: 12px">
<template v-if="loading">
<div class="iconfontYI icon-jiazai loading acea-row row-center-wrapper"></div>
正在加载中
</template>
<template v-else> 上拉加载更多 </template>
</div>
</template>
<script setup>
defineOptions({ name: 'Loading' });
defineProps({
loaded: Boolean,
loading: Boolean,
});
</script>

View File

@ -1,51 +0,0 @@
<template>
<div class="empty-wrapper">
<!-- 用户列表 -->
<template v-if="status == 1">
<img src="@/assets/images/no_chat.png" alt="" />
</template>
<template v-if="status == 2">
<img src="@/assets/images/no_user.png" alt="" />
</template>
<template v-if="status == 3">
<img src="@/assets/images/no_all.png" alt="" />
</template>
<template v-if="status == 4">
<img src="@/assets/images/no_zf.png" alt="" />
</template>
<template v-if="status == 5">
<img src="@/assets/images/no_fh.png" alt="" />
</template>
<template v-if="status == 6">
<img src="@/assets/images/no_tk.png" alt="" />
</template>
<p>{{ msg }}</p>
</div>
</template>
<script setup>
defineOptions({ name: 'empty' });
defineProps({
status: {
type: [String, Number],
default: 1,
},
msg: {
type: String,
default: '',
},
});
</script>
<style lang="scss" scoped>
.empty-wrapper {
margin-top: 60px;
text-align: center;
font-size: 12px;
color: #666;
img {
width: 36%;
}
}
</style>

View File

@ -1,869 +0,0 @@
<template>
<div>
<!-- 常用语 -->
<div class="words-mask" v-if="isWords">
<div class="content">
<div class="title-box">
<div class="tab-box">
<div
class="tan-item"
:class="{ on: item.key == wordsTabCur }"
v-for="(item, index) in wordsTab"
:key="index"
v-db-click
@click.stop="bindTab(item)"
>
{{ item.title }}
</div>
<div class="right-icon">
<span class="iconfont iconbianji2" v-db-click @click.stop="isWordShow = true"></span>
<span class="iconfont iconcha" v-db-click @click.stop="closeBox"></span>
</div>
</div>
<div class="input-box">
<el-input v-model="wordsData.searchTxt" placeholder="搜索快捷回复" :search="true" @on-search="bindSearch" />
</div>
</div>
<div class="scroll-box">
<div class="scroll-left">
<div class="left-item add_cate" v-db-click @click.stop="openCate(0)" v-if="wordsTabCur">
<span class="iconfont iconjiahao"></span> 分组
</div>
<div
class="left-item"
:class="{ active: wordsData.cateId == item.id }"
v-for="item in wordsData.cate"
:key="item.id"
v-db-click
@click.stop="changeCate(item)"
>
{{ item.name }}
</div>
</div>
<div class="right-box">
<!-- vue-scroll 为 Vue2 专用库且未再全局注册,改用原生滚动容器(触底加载下一页) -->
<div class="right-scroll" @scroll="handleWordsScroll">
<div class="msg-item add-mg" v-show="wordsTabCur" v-db-click @click.stop="addMsg">
<span class="iconfont icontianjia11"></span>添加话术
</div>
<div
class="msg-item"
v-for="(item, index) in wordsList"
:key="index"
v-db-click
@click.stop="selectWords(item)"
>
<span class="title">{{ item.title }}</span
>{{ item.message }}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 添加分组 -->
<el-dialog
v-model="cateData.isCate"
width="470px"
:show-close="true"
custom-class="vertical-center-modal"
class="words-box"
>
<div class="mask-title">
{{ cateData.status ? '编辑分组' : '新增分组' }}
<span class="iconfont iconcha" v-db-click @click.stop="closeCate"></span>
</div>
<div class="input-box">
<el-input class="noinput" v-model="cateData.name" placeholder="请输入分组名称" />
</div>
<div class="input-box">
<el-input class="noinput" v-model="cateData.sort" placeholder="请输入分组排序" />
</div>
<el-button v-db-click @click.stop="cateConfirm" class="subBtn" type="primary" :disabled="cateStatus"
>确定</el-button
>
</el-dialog>
<!-- 添加话术 -->
<el-dialog
v-model="msgData.isCateMeg"
width="470px"
:show-close="true"
custom-class="vertical-center-modal"
class="words-box"
>
<div class="mask-title">
{{ msgData.status ? '修改话术' : '添加话术' }}
<span class="iconfont iconcha" v-db-click @click.stop="closeMsgBox"></span>
</div>
<div class="input-box">
<el-input class="noinput" v-model="msgData.title" placeholder="请输入标题名称 (选填)" />
</div>
<div class="input-box text-area">
<el-input class="noinput" :rows="4" type="textarea" v-model="msgData.message" placeholder="请输入您的话术" />
</div>
<div class="input-box">
<el-select v-model="msgData.msgCateId">
<el-option v-for="item in selectData" :value="item.id" :key="item.value" :label="item.name"></el-option>
</el-select>
</div>
<el-button v-db-click @click.stop="msgConfirm" class="subBtn" type="primary" :disabled="msgStatus"
>确定</el-button
>
</el-dialog>
<!-- 编辑弹窗 -->
<div class="edit-box" v-if="isWordShow">
<div class="head">
<div class="tit-bar">
{{ wordsTabCur ? '个人库' : '公共库' }}<span v-db-click @click.stop="isWordShow = false">完成</span>
</div>
<div class="input-box noinput">
<el-input v-model="wordsData.searchTxt" placeholder="搜索快捷回复" :search="true" @on-search="bindSearch" />
</div>
</div>
<div class="scroll-box">
<div class="scroll-left">
<div class="top">
<div class="left-item add_cate" v-db-click @click.stop="openCate(0)" v-if="wordsTabCur">
<span class="iconfont iconjiahao"></span> 分组
</div>
<div
class="left-item"
:class="{ active: wordsData.cateId == item.id }"
v-for="item in wordsData.cate"
:key="item.id"
v-db-click
@click.stop="changeCate(item)"
>
{{ item.name }}
</div>
</div>
<div class="bom" v-if="wordsTabCur">
<div class="left-item edits-box" v-db-click @click.stop="editList.status = true">编辑分组</div>
</div>
</div>
<div class="right-box">
<div class="right-scroll" @scroll="handleWordsScroll">
<div class="msg-item" v-for="(item, index) in wordsList" :key="index">
<span class="title">{{ item.title }}</span
>{{ item.message }}
<div class="edit-bar" v-if="wordsTabCur">
<span class="iconfont iconbianji1" v-db-click @click.stop="bindEdit(item)"></span>
<span class="iconfont iconshanchu1" v-db-click @click.stop="delMsg(item, '删除话术', index)"></span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 编辑分组列表 -->
<el-dialog
v-model="editList.status"
width="470px"
:show-close="true"
custom-class="vertical-center-modal"
class="words-box cate-list"
>
<div class="mask-title">
编辑分组
<span class="iconfont iconcha" v-db-click @click.stop="editList.status = false"></span>
</div>
<div class="list-box">
<div class="item" v-for="(item, index) in wordsData.cate" :key="index" :index="index">
<span>{{ item.name }}</span>
<div class="right-box">
<span class="iconfont iconbianji1" v-if="index > 0" v-db-click @click.stop="openCate(1, item)"></span>
<span
class="iconfont iconshanchu1"
v-if="index > 0"
v-db-click
@click.stop="delCate(item, '删除分组', index)"
></span>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import { modalSure } from '@/utils/public';
import {
speeChcraft,
serviceCate,
addServiceCate,
addSpeeChcraft,
serviceCateUpdate,
editServiceCate,
} from '@/pages/plugin/kefu/api';
defineOptions({ name: 'words' });
const props = defineProps({
isWords: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['selectMsg', 'closeBox']);
const isWordShow = ref(false); // 编辑窗
const wordsList = ref([]);
const wordsTab = ref([
{
title: '个人库',
key: 1,
},
{
title: '公共库',
key: 0,
},
]);
const wordsTabCur = ref(1);
const wordsData = reactive({
isScroll: true,
page: 1,
limit: 15,
searchTxt: '',
cate: [], // 分类
cateId: '', // 分类id
});
// 话术列表请求进行中标记:触底滚动高频触发,避免重复请求同一页
const wordsLoading = ref(false);
// 分组数据
const cateData = reactive({
status: 0, // 0 新增 1编辑
name: '',
sort: '',
isCate: false, // 分组状态开关
id: '',
});
// 编辑分组列表
const editList = reactive({
status: false,
});
// 话术添加数据
const msgData = reactive({
isCateMeg: false,
msgCateId: '',
message: '',
title: '',
status: 0, // 0 新增 1修改
editId: '',
});
const selectData = ref('');
const cateStatus = computed(() => {
if (cateData.name && cateData.sort) {
return false;
} else {
return true;
}
});
const msgStatus = computed(() => {
if (msgData.message) {
return false;
} else {
return true;
}
});
onMounted(() => {
Promise.all([getServiceCate()]);
});
// 关闭添加话术弹窗
function closeMsgBox() {
msgData.isCateMeg = false;
}
// 选择话术
function selectWords(item) {
emit('selectMsg', item.message);
}
// 关闭弹窗
function closeBox() {
emit('closeBox');
}
// 搜索
function bindSearch() {
wordsData.page = 1;
wordsData.isScroll = true;
wordsLoading.value = false;
wordsList.value = [];
getWordsList();
}
// 顶部切换
function bindTab(item) {
wordsTabCur.value = item.key;
wordsData.isScroll = true;
wordsLoading.value = false;
wordsData.page = 1;
wordsData.cate = [];
wordsList.value = [];
getServiceCate();
}
// 选择话术分类
function changeCate(item) {
wordsData.isScroll = true;
wordsLoading.value = false;
wordsList.value = [];
wordsData.page = 1;
wordsData.cateId = item.id;
msgData.msgCateId = item.id;
getWordsList();
}
// 获取话术分类
function getServiceCate() {
serviceCate({
type: wordsTabCur.value,
}).then((res) => {
let tempArr = JSON.parse(JSON.stringify(res.data.data));
wordsData.cateId = res.data.data.length ? res.data.data[0].id : '';
msgData.msgCateId = wordsData.cateId;
wordsData.cate = res.data.data;
selectData.value = tempArr;
getWordsList();
});
}
// 原生滚动触底:距底部不足 10px 时加载下一页
function handleWordsScroll(e) {
const el = e.target;
if (el.scrollTop + el.clientHeight >= el.scrollHeight - 10) {
getWordsList();
}
}
// 常用语
function getWordsList() {
if (!wordsData.isScroll || wordsLoading.value) return;
wordsLoading.value = true;
speeChcraft({
page: wordsData.page,
limit: wordsData.limit,
title: wordsData.searchTxt,
cate_id: wordsData.cateId,
type: wordsTabCur.value,
})
.then((res) => {
wordsLoading.value = false;
wordsData.isScroll = res.data.length >= wordsData.limit;
wordsList.value = wordsList.value.concat(res.data);
wordsData.page++;
})
.catch(() => {
wordsLoading.value = false;
});
}
// 打开分组弹窗
function openCate(key, item) {
cateData.status = key;
cateData.isCate = true;
if (key == 1) {
cateData.name = item.name;
cateData.id = item.id;
}
}
// 关闭分组弹窗
function closeCate() {
cateData.isCate = false;
cateData.name = '';
cateData.sort = '';
}
// 分组添加
function cateConfirm() {
if (!cateData.status) {
addServiceCate({
name: cateData.name,
sort: cateData.sort,
})
.then((res) => {
cateData.isCate = false;
cateData.name = '';
cateData.sort = '';
wordsData.isScroll = true;
wordsList.value = [];
ElMessage.success(res.msg);
getServiceCate();
})
.catch((error) => {
ElMessage.error(error.msg);
});
} else {
editServiceCate(cateData.id, {
name: cateData.name,
sort: cateData.sort,
}).then((res) => {
cateData.isCate = false;
cateData.name = '';
cateData.sort = '';
cateData.id = '';
wordsData.isScroll = true;
wordsList.value = [];
ElMessage.success(res.msg);
getServiceCate();
});
}
}
// 话术打开
function addMsg() {
msgData.isCateMeg = true;
msgData.status = 0;
}
// 话术添加
function msgConfirm() {
if (!msgData.status) {
addSpeeChcraft({
title: msgData.title,
cate_id: msgData.msgCateId,
message: msgData.message,
})
.then((res) => {
msgData.isCateMeg = false;
msgData.title = '';
msgData.message = '';
msgData.msgCateId = wordsData.cateId;
ElMessage.success(res.msg);
wordsData.isScroll = true;
wordsData.page = 1;
wordsList.value = [];
getWordsList();
})
.catch((error) => {
ElMessage.error(error.msg);
});
} else {
serviceCateUpdate(msgData.editId, {
title: msgData.title,
cate_id: msgData.msgCateId,
message: msgData.message,
}).then((res) => {
msgData.isCateMeg = false;
msgData.title = '';
msgData.message = '';
msgData.msgCateId = wordsData.cateId;
ElMessage.success(res.msg);
wordsData.isScroll = true;
wordsData.page = 1;
wordsList.value = [];
getWordsList();
});
}
}
// 编辑话术
function bindEdit(item) {
msgData.status = 1;
msgData.isCateMeg = true;
msgData.message = item.message;
msgData.title = item.title;
msgData.editId = item.id;
}
// 删除话术
function delMsg(row, tit, num) {
let delfromData = {
title: tit,
num: num,
url: `/kefu/service/speechcraft/${row.id}`,
method: 'DELETE',
ids: '',
kefu: true,
};
modalSure(delfromData)
.then((res) => {
wordsList.value.splice(num, 1);
ElMessage.success(res.msg);
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
function delCate(row, tit, num) {
let delfromData = {
title: tit,
num: num,
url: `/kefu/service/cate/${row.id}`,
method: 'DELETE',
ids: '',
kefu: true,
};
modalSure(delfromData)
.then((res) => {
wordsData.cate.splice(num, 1);
wordsData.isScroll = true;
wordsList.value = [];
ElMessage.success(res.msg);
getServiceCate();
})
.catch((res) => {
ElMessage.error(res.msg);
});
}
</script>
<style lang="scss" scoped>
// 话术列表滚动容器(替代未注册的 vue-scroll):话术弹层与编辑窗两处共用
.right-scroll {
height: 100%;
overflow-y: auto;
}
.words-mask {
z-index: 50;
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
.content {
position: absolute;
left: 0;
right: 0;
top: 1.14rem;
bottom: 0;
display: flex;
flex-direction: column;
background: #fff;
border-radius: 0.06rem 0.06rem 0px 0px;
.title-box {
padding: 0 0.3rem 0.3rem;
position: relative;
border-bottom: 1px solid #f5f6f9;
.tab-box {
position: relative;
display: flex;
justify-content: space-between;
padding: 0.4rem 2.2rem 0.3rem;
font-size: 0.32rem;
color: #9f9f9f;
.on {
color: #3875ea;
font-weight: bold;
}
.right-icon {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
.iconfont {
margin-left: 0.2rem;
font-size: 0.48rem;
color: #c8cad0;
}
}
}
.input-box {
display: flex;
align-items: center;
width: 6.9rem;
height: 0.64rem;
padding-right: 0.05rem;
margin-left: 0.18rem;
border-radius: 0.32rem;
overflow: hidden;
:deep(.el-input__inner) {
background: #f5f6f9;
}
:deep(.el-input__inner),
.el-input__inner:hover,
.el-input__inner:focus {
border: transparent;
box-shadow: none;
}
}
.icon-cha1 {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
}
}
.scroll-box {
flex: 1;
display: flex;
overflow: hidden;
.scroll-left {
width: 1.76rem;
height: 100%;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
background: #f5f6f9;
.left-item {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 1.09rem;
color: #282828;
font-size: 0.26rem;
&.active {
color: #3875ea;
background: #fff;
&:after {
content: ' ';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 0.06rem;
height: 0.46rem;
background: #3875ea;
}
}
&.add_cate {
color: #9f9f9f;
font-size: 0.26rem;
.iconfont {
margin-right: 0.1rem;
font-size: 0.24rem;
}
}
}
}
.right-box {
flex: 1;
overflow: scroll;
-webkit-overflow-scrolling: touch;
}
.msg-item {
padding: 0.25rem 0.3rem;
color: #888888;
font-size: 0.28rem;
.title {
margin-right: 0.2rem;
color: #282828;
}
&.add-mg {
display: flex;
align-items: center;
justify-content: flex-end;
font-size: 0.28rem;
padding: 0.15rem 0.3rem;
.iconfont {
font-size: 0.36rem;
margin-right: 0.1rem;
}
}
}
}
}
}
.words-box {
.mask-title {
position: relative;
text-align: center;
margin-bottom: 0.5rem;
color: #282828;
font-size: 0.32rem;
font-weight: bold;
.iconfont {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
color: #c8cad0;
font-size: 0.44rem;
font-weight: normal;
}
}
.input-box {
height: 0.68rem;
margin-top: 0.32rem;
background: #f5f5f5;
border-radius: 0.14rem;
&.text-area {
height: 1.92rem;
textarea {
display: block;
height: 100%;
}
}
}
.subBtn {
width: 100%;
height: 0.86rem;
margin-top: 0.6rem;
margin-bottom: 0.3rem;
font-size: 0.3rem !important;
border-radius: 0.43rem;
&[disabled] {
background: #c8cad0;
color: #fff;
font-size: 0.3rem !important;
}
}
}
.edit-box {
z-index: 99;
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
flex-direction: column;
background: #fff;
.head {
padding: 0.4rem 0.3rem 0.3rem;
.tit-bar {
position: relative;
text-align: center;
font-size: 0.32rem;
color: #282828;
font-weight: bold;
span {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
color: #3875ea;
font-size: 0.28rem;
font-weight: normal;
}
}
.input-box {
margin-top: 0.3rem;
background: #f5f6f9;
border-radius: 0.39rem;
}
}
.scroll-box {
flex: 1;
display: flex;
overflow: hidden;
.scroll-left {
display: flex;
flex-direction: column;
position: relative;
width: 1.76rem;
height: 100%;
background: #f5f6f9;
.top {
flex: 1;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
}
.left-item {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 1.09rem;
color: #282828;
font-size: 0.26rem;
&.active {
color: #3875ea;
background: #fff;
&:after {
content: ' ';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 0.06rem;
height: 0.46rem;
background: #3875ea;
}
}
&.add_cate {
color: #9f9f9f;
font-size: 0.26rem;
.iconfont {
margin-right: 0.1rem;
font-size: 0.24rem;
}
}
&.edits-box {
color: #3875ea;
}
}
}
.right-box {
flex: 1;
padding-left: 0.3rem;
}
.msg-item {
padding: 0.25rem 0.3rem 0.25rem 0;
color: #888888;
font-size: 0.28rem;
.title {
margin-right: 0.2rem;
color: #282828;
}
&.add-mg {
display: flex;
align-items: center;
justify-content: flex-end;
font-size: 0.28rem;
padding: 0.15rem 0.3rem;
.iconfont {
font-size: 0.36rem;
margin-right: 0.1rem;
}
}
.edit-bar {
display: flex;
align-items: center;
justify-content: flex-end;
margin-top: 0.25rem;
padding-bottom: 0.1rem;
border-bottom: 1px solid #f0f2f7;
.iconfont {
margin-left: 0.3rem;
font-size: 0.32rem;
}
}
}
}
}
.cate-list {
.list-box {
max-height: 7.5rem;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
.item {
display: flex;
align-items: center;
justify-content: space-between;
height: 1rem;
border-bottom: 1px solid #f0f2f7;
color: #282828;
font-size: 0.28rem;
.iconfont {
color: #9f9f9f;
font-size: 0.32rem;
margin-left: 0.4rem;
}
}
}
}
</style>
<style>
.kf_mobile .ivu-modal-wrap {
display: flex;
align-items: center;
justify-content: center;
}
.vertical-center-modal {
display: flex;
align-items: center;
justify-content: center;
}
.ivu-modal {
top: 0;
}
.noinput input,
.noinput textarea {
border-color: transparent !important;
background: transparent !important;
resize: none;
}
.noinput input:hover,
.noinput input:focus,
.noinput textarea:hover,
.noinput textarea:focus {
border-color: transparent !important;
box-shadow: none !important;
}
</style>

View File

@ -1,554 +0,0 @@
<template>
<div class="wrapper-box">
<div class="page-account kf">
<div class="content">
<img :src="copyrightImg" alt="" />
<div class="desc">
<p class="tit">让客户服务如此简单</p>
<p class="kefu">专业客服系统<br />助力企业打造一流的服务体验</p>
</div>
</div>
<div class="container" :class="[fullWidth > 768 ? 'containerSamll' : 'containerBig']">
<div class="index_from page-account-container">
<div :style="{ display: !loginType ? 'block' : 'none' }">
<div class="page-account-top">
<div class="page-account-top-logo">客服登录</div>
</div>
<el-form ref="formInlineRef" :model="formInline" :rules="ruleInline" @keyup.enter="handleSubmit()">
<el-form-item class="mb20" prop="username">
<el-input type="text" v-model="formInline.username" placeholder="请输入用户名" size="large" />
</el-form-item>
<el-form-item class="mb20" prop="password">
<el-input type="password" v-model="formInline.password" placeholder="请输入密码" size="large" />
</el-form-item>
<el-form-item>
<el-button type="primary" size="large" v-db-click @click="handleSubmit()" class="btn">登录 </el-button>
</el-form-item>
</el-form>
<div class="qh_box" v-if="!isMobile" v-db-click @click="bindScan">
<span class="iconfont iconerweima2"></span>
</div>
</div>
<div :style="{ display: loginType ? 'block' : 'none' }">
<div class="page-account-top">
<div class="page-account-top-logo">微信扫码登录</div>
</div>
<div class="code-box">
<div class="qrcode" ref="qrCodeUrl"></div>
<div class="rxpired-box" v-show="rxpired">
<p>已过期</p>
<el-button type="primary" v-db-click @click="bindRefresh">点击刷新</el-button>
</div>
</div>
<div class="qh_box" v-db-click @click="loginType = 0"><span class="iconfont iconzhanghaomima"></span></div>
</div>
</div>
</div>
</div>
<div class="foot-box" v-if="copyright">{{ copyright }}</div>
<div class="foot-box" v-else>
Copyright © 2016~2026 <a href="https://www.crmeb.com" target="_blank">{{ version }}</a>
</div>
</div>
</template>
<script setup>
import __asset_sw_0 from '@/assets/images/sw.jpg';
import __asset_logo_dark_1 from '@/assets/images/logo-dark.png';
import { ref, watch, onMounted, onBeforeUnmount } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import { AccountLogin, getSanCodeKey, scanStatus, kefuConfig } from '@/pages/plugin/kefu/api';
import Setting from '@/setting';
import QRCode from '@/vendor/qrcodejs2';
import { setCookies } from '@/libs/util';
import { useMediaStore } from '@/store/module/media';
import { useKefuStore } from '@/pages/plugin/kefu/store';
defineOptions({ name: 'kefu_index' });
const route = useRoute();
const router = useRouter();
const mediaStore = useMediaStore();
const kefuStore = useKefuStore();
const formInlineRef = ref(null);
const qrCodeUrl = ref(null);
const fullWidth = ref(document.documentElement.clientWidth);
const swiperOption = ref({
pagination: '.swiper-pagination',
autoplay: true,
});
const modals = ref(false);
const autoLogin = ref(true);
const imgcode = ref('');
const formInline = ref({
username: '',
password: '',
code: '',
});
const ruleInline = {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
code: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
};
const errorNum = ref(0);
const jigsaw = ref(null);
const login_logo = ref('');
const swiperList = ref([]);
const defaultSwiperList = ref(__asset_sw_0);
const loginType = ref(0); // 0 账号 1 扫码
const codeKey = ref('');
const scanTime = ref('');
const rxpired = ref(false); // 扫码是否过期
const isMobile = ref(false);
const version = ref(''); //版本号
const isScan = ref(false);
const timeNum = ref(0);
const copyright = ref('');
const copyrightImg = ref(__asset_logo_dark_1);
// created
kefuConfig().then((res) => {
version.value = res.data.version;
copyright.value = res.data.copyright;
if (res.data.site_name) {
document.title = res.data.site_name;
}
if (res.data.copyrightImg) {
copyrightImg.value = res.data.copyrightImg;
}
});
isMobile.value = mediaStore.isMobile;
top != window && (top.location.href = location.href);
document.onkeydown = function (e) {
if (route.name === 'login') {
let key = window.event.keyCode;
if (key === 13) {
handleSubmit();
}
}
};
window.addEventListener('resize', handleResize);
// 为了避免频繁触发resize函数导致页面卡顿,使用定时器(原实现中 screenWidth 从未被读取,此处仅保留节流语义)
let resizeTimer = false;
watch(fullWidth, (val) => {
if (!resizeTimer) {
resizeTimer = true;
setTimeout(function () {
resizeTimer = false;
}, 400);
}
});
watch(
() => route.path,
() => {
captchas();
},
);
onMounted(function () {
captchas();
});
onBeforeUnmount(function () {
timeNum.value = 0;
if (qrCodeUrl.value) qrCodeUrl.value.innerHTML = '';
window.clearInterval(scanTime.value);
window.removeEventListener('resize', handleResize);
});
// 切换扫码
function bindScan() {
if (!isScan.value) {
isScan.value = true;
getSanCodeKeyData();
}
loginType.value = 1;
}
// 生成二维码
function creatQrCode() {
let url = `${window.location.protocol}//${window.location.host}/pages/users/scan_login/index?key=${codeKey.value}`;
var qrcode = new QRCode(qrCodeUrl.value, {
text: url, // 需要转换为二维码的内容
width: 160,
height: 160,
colorDark: '#000000',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.H,
});
}
// 关闭模态框
function closeModel() {
AccountLogin({
account: formInline.value.username,
password: formInline.value.password,
imgcode: formInline.value.code,
})
.then(async (res) => {
let expires = getExpiresTime(res.data.exp_time);
// 记录用户登录信息
setCookies('kefu_uuid', res.data.kefuInfo.uid, expires);
setCookies('kefu_token', res.data.token, expires);
setCookies('kefu_expires_time', res.data.exp_time, expires);
// js-cookie 存对象会被 String() 成 "[object Object]",必须显式序列化为 JSON 字符串(对话页 JSON.parse 解析)
setCookies('kefuInfo', JSON.stringify(res.data.kefuInfo), expires);
// 记录用户信息
kefuStore.setInfo(res.data.kefuInfo);
if (mediaStore.isMobile) {
//手机页面
return router.replace({ path: route.query.redirect || Setting.routePre + '/kefu/mobile_list' });
} else {
// pc页面
return router.replace({ path: route.query.redirect || Setting.routePre + '/kefu/pc_list' });
}
})
.catch((res) => {
let data = res === undefined ? {} : res;
errorNum.value++;
captchas();
ElMessage.error(data.msg || '登录失败');
if (jigsaw.value) jigsaw.value.reset();
});
}
function getExpiresTime(expiresTime) {
let nowTimeNum = Math.round(new Date() / 1000);
let expiresTimeNum = expiresTime - nowTimeNum;
return parseFloat(parseFloat(parseFloat(expiresTimeNum / 60) / 60) / 24);
}
function closefail() {
if (jigsaw.value) jigsaw.value.reset();
ElMessage.error('校验错误');
}
function handleResize(event) {
fullWidth.value = document.documentElement.clientWidth;
}
function captchas() {
imgcode.value = Setting.apiBaseURL + '/captcha_pro?' + Date.parse(new Date());
}
function handleSubmit() {
formInlineRef.value.validate((valid) => {
if (valid) {
closeModel();
}
});
}
// 获取客服扫码key(原方法名与导入的 api 同名,改名避免遮蔽)
function getSanCodeKeyData() {
getSanCodeKey()
.then((res) => {
codeKey.value = res.data.key;
creatQrCode();
scanTime.value = setInterval(() => {
timeNum.value++;
if (timeNum.value >= 60) {
timeNum.value = 0;
window.clearInterval(scanTime.value);
rxpired.value = true;
} else {
getScanStatus();
}
}, 1000);
})
.catch((error) => {
timeNum.value = 0;
window.clearInterval(scanTime.value);
rxpired.value = true;
ElMessage.error(error.msg);
});
}
// 扫码登录情况
function getScanStatus() {
scanStatus(codeKey.value)
.then(async (res) => {
// 0 = 二维码过期需要重新获取授权凭证
if (res.data.status == 0) {
timeNum.value = 0;
window.clearInterval(scanTime.value);
rxpired.value = true;
}
// 1=正在扫描
if (res.data.status == 1) {
}
// 3 扫描成功正在登录
if (res.data.status == 3) {
window.clearInterval(scanTime.value);
let expires = getExpiresTime(res.data.exp_time);
// 记录用户登录信息
setCookies('kefu_uuid', res.data.kefuInfo.uid, expires);
setCookies('kefu_token', res.data.token, expires);
setCookies('kefu_expires_time', res.data.exp_time, expires);
setCookies('kefuInfo', res.data.kefuInfo, expires);
// 记录用户信息
kefuStore.setInfo(res.data.kefuInfo);
if (mediaStore.isMobile) {
//手机页面
return router.replace({ path: route.query.redirect || Setting.routePre + '/kefu/mobile_list' });
} else {
// pc页面
return router.replace({ path: route.query.redirect || Setting.routePre + '/kefu/pc_list' });
}
}
})
.catch((error) => {
ElMessage.error(error.msg);
timeNum.value = 0;
window.clearInterval(scanTime.value);
rxpired.value = true;
});
}
// 刷新二维码
function bindRefresh() {
qrCodeUrl.value.innerHTML = '';
rxpired.value = false;
getSanCodeKeyData();
}
</script>
<style lang="scss" scoped>
.page-account {
display: flex;
width: 100%;
background-image: url('@/assets/images/kfbg_2.jpg');
background-size: cover;
background-position: center;
justify-content: center;
align-items: center;
height: 100vh;
overflow: auto;
.content {
height: 400px;
margin-right: 100px;
.desc {
color: #fff;
.tit {
font-size: 40px;
font-weight: 600;
}
.kefu {
margin-top: 30px;
font-weight: 500;
font-size: 20px;
}
}
img {
width: 360px;
margin-left: -100px;
}
}
}
.code-box {
position: relative;
display: flex;
align-items: center;
justify-content: center;
.qrcode {
display: flex;
align-items: center;
justify-content: center;
width: 180px;
height: 180px;
border: 1px solid #e5e5e6;
}
.rxpired-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 160px;
height: 160px;
background: rgba(0, 0, 0, 0.6);
p {
margin-bottom: 10px;
font-size: 15px;
color: #fff;
}
}
}
.page-account-top-logo {
color: #000000;
font-size: 21px;
}
.wrapper-box {
display: flex;
flex-direction: column;
height: 100vh;
.foot-box {
padding: 20px 20px;
font-size: 14px;
color: #666666;
text-align: right;
box-sizing: border-box;
a {
margin-left: 0;
color: #666666;
}
}
}
.page-account {
display: flex;
flex: 1;
}
.page-account .code {
display: flex;
align-items: center;
justify-content: center;
}
.page-account .code .pictrue {
height: 40px;
}
.swiperPross {
border-radius: 6px 0px 0px 6px;
}
.swiperPross,
.swiperPic,
.swiperPic img {
width: 510px;
height: 100%;
}
.swiperPic img {
width: 100%;
height: 100%;
}
.container {
height: 400px !important;
padding: 0 !important;
/* overflow: hidden; */
border-radius: 6px;
z-index: 1;
display: flex;
}
.containerSamll {
width: 384px !important;
background: #fff !important;
}
.containerBig {
width: 90%;
padding-bottom: 20px;
margin-top: 84px;
background: #f7f7f7 !important;
height: auto !important;
box-shadow: 0px 3px 20px rgba(0, 20, 41, 0.06);
}
.index_from {
position: relative;
padding: 40px 40px 32px 40px;
height: 400px;
width: 100%;
box-sizing: border-box;
}
.containerBig .index_from {
padding: 20px;
height: auto !important;
}
.index_from .qh_box {
position: absolute;
right: 12px;
top: 0;
cursor: pointer;
.iconfont {
color: #265bed;
font-size: 36px;
}
}
.page-account-top {
padding: 20px 0 50px !important;
box-sizing: border-box !important;
display: flex;
justify-content: center;
}
.page-account-container {
border-radius: 0px 6px 6px 0px;
}
.btn {
width: 100%;
background: #265bed;
}
.captchaBox {
width: 310px;
}
input {
display: block;
width: 290px;
line-height: 40px;
margin: 10px 0;
padding: 0 10px;
outline: none;
border: 1px solid #c8cccf;
border-radius: 4px;
color: #6a6f77;
}
#msg {
width: 100%;
line-height: 40px;
font-size: 14px;
text-align: center;
}
a:link,
a:visited,
a:hover,
a:active {
margin-left: 100px;
color: #0366d6;
}
.index_from :deep(.ivu-input-large) {
font-size: 14px !important;
}
</style>
<style>
@media screen and (min-width: 320px) and (max-width: 960px) {
.page-account {
background-image: url('@/assets/images/m_bg.png') !important;
background-size: 100% auto !important;
background-repeat: no-repeat;
background-position: left top !important;
display: flex;
}
.wrapper-box .foot-box {
text-align: center !important;
}
.content {
display: none;
}
.index_from {
box-shadow: 0px 3px 20px rgba(0, 20, 41, 0.06);
background: #fff;
}
.wrapper-box .foot-box {
padding: 20px 66px !important;
color: #adadad !important;
font-size: 0.22rem !important;
}
.containerBig {
width: 86% !important;
border-radius: 0.2rem !important;
overflow: hidden;
}
.btn {
background: linear-gradient(90deg, #3875ea 0%, #1890fc 100%) !important;
border-radius: 0.41rem;
}
.ivu-input {
border: 1px solid #dcdee2;
-webkit-appearance: none; /*去除阴影边框*/
outline: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0); /*点击高亮的颜色*/
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More