feat: 会员中心 hgcloud 主题初始化 + drone 部署步骤
All checks were successful
continuous-integration/drone/push Build is passing

- 解压官方默认主题 default_yfMBA.tar.gz 到 clientarea/hgcloud/
- .gitignore 排除压缩包和临时解压目录
- drone 新增步骤: 同步 hgcloud 到 /clientarea/template/pc/
This commit is contained in:
yiqiu
2026-03-19 17:56:44 +08:00
parent 75756e5a64
commit 3b41cffbc9
381 changed files with 386825 additions and 1 deletions

View File

@@ -0,0 +1,38 @@
// css 样式依赖common.css
const aliAsideMenu = {
template: ` <el-aside width="220px">
<img class="ali-logo" :src="logo"></img>
<div class="menu-list-top">
<div class="menu-item" :class="item.id === menuActiveId ? 'menu-active':''" v-for="item in menu1" :key="item.id" @click="toPage(item)">
<img :src="item.icon" class="item-img">
<span class="item-text">{{item.text}}</span>
</div>
</div>
</el-aside>`,
data() {
return {
activeId: 1,
menu1: [
{ icon: `${url}/img/common/menu1.png`, text: lang.menu_1, url: "./index.htm", id: 1 },
{ icon: `${url}/img/common/menu2.png`, text: lang.menu_4, url: "./account.htm", id: 2 },
],
logo:`${url}/img/ali/logo.png`
}
},
created() {
console.log(location.href);
},
methods: {
// 页面跳转
toPage(e) {
location.href = e.url
},
},
props: {
menuActiveId: {
type: Number,
default: 1
}
},
}

View File

@@ -0,0 +1,304 @@
// css 样式依赖common.css
const asideMenu = {
template: `
<el-aside width="190px">
<a :href="commonData.clientarea_logo_url || '/home.htm'" onclick="return false" class="menu-alink">
<img class="ali-logo" :src="logo" @click="goHome" v-show="logo"></img>
</a>
<el-menu class="menu-top" :default-active="menuActiveId" @select="handleSelect" background-color="transparent"
text-color="var(--color-menu-text)" active-text-color="var(--color-menu-text-active)">
<template v-for="(item,index) in menu1">
<!-- 只有一级菜单 -->
<template v-if="!item.child || item.child?.length === 0">
<a :href="getMenuUrl(item.id || item.url)" onclick="return false" class="menu-alink">
<el-menu-item :key="item.id ? item.id : item.url" :index="item.id ? item.id + '' : item.url">
<i class="iconfont" :class="item.icon"></i>
<span class="aside-menu-text" slot="title">{{item.name}}</span>
</el-menu-item>
</a>
</template>
<!-- 有二级菜单 -->
<el-submenu v-else :key="item.id ? item.id : item.url" :index="item.id ? item.id + '' : item.url">
<template slot="title">
<i class="iconfont" :class="item.icon"></i>
<span class="aside-menu-text" slot="title">{{item.name}}</span>
</template>
<template v-for="child in item.child">
<a :href="getMenuUrl(child.id || child.url)" onclick="return false" class="menu-alink">
<el-menu-item :index="child.id + ''" :key="child.id">
{{child.name}}
</el-menu-item>
</a>
</template>
</el-submenu>
</template>
</el-menu>
<div class="line" v-if="hasSeparate"></div>
<el-menu class="menu-top" :default-active="menuActiveId" @select="handleSelect" background-color="transparent"
text-color="var(--color-menu-text)" active-text-color="#FFF">
<template v-for="(item,index) in menu2">
<!-- 只有一级菜单 -->
<template v-if="!item.child || item.child?.length === 0">
<a :href="getMenuUrl(item.id || item.url)" onclick="return false" class="menu-alink">
<el-menu-item :key="item.id ? item.id + '' : item.url" :index="item.id ? item.id + '' : item.url">
<i class="iconfont" :class="item.icon"></i>
<span class="aside-menu-text" slot="title">{{item.name}}</span>
</el-menu-item>
</a>
</template>
<!-- 有二级菜单 -->
<el-submenu v-else :key="item.id ? item.id : item.url" :index="item.id ? item.id + '' : item.url">
<template slot="title">
<i class="iconfont" :class="item.icon"></i>
<span class="aside-menu-text" slot="title">{{item.name}}</span>
</template>
<template v-for="child in item.child">
<a :href="getMenuUrl(child.id || child.url)" onclick="return false" class="menu-alink">
<el-menu-item :index="child.id + ''" :key="child.id">
{{child.name}}
</el-menu-item>
</a>
</template>
</el-submenu>
</template>
</el-menu>
</el-aside>
`,
// 云服务器 当前
// 物理服务器 dcim
// 通用产品
data() {
return {
activeId: 1,
menu1: [],
menu2: [],
logo: "",
menuActiveId: "",
iconsData: [],
commonData: {},
noRepeat: [],
hasSeparate: false,
originMenu: [],
};
},
mounted() {
this.doGetMenu();
// const mainLoading = document.getElementById("mainLoading");
// if (mainLoading) {
// mainLoading.style.display = "none";
// }
// if (document.getElementsByClassName("template")[0]) {
// document.getElementsByClassName("template")[0].style.display = "block";
// }
},
created() {
this.getCommonSetting();
},
beforeUpdate() {},
mixins: [mixin],
updated() {
// // 关闭loading
// document.getElementsByClassName('template')[0].style.display = 'block'
const mainLoading = document.getElementById("mainLoading");
if (mainLoading) {
mainLoading.style.display = "none";
}
if (document.getElementsByClassName("template")[0]) {
document.getElementsByClassName("template")[0].style.display = "block";
}
},
methods: {
// 页面跳转
// toPage(e) {
// // 获取 当前点击导航的id 存入本地
// const id = e.id
// localStorage.setItem('frontMenusActiveId', id)
// // 跳转到对应路径
// location.href = '/' + e.url
// },
// 获取通用配置
async getCommonSetting() {
try {
const res = await getCommon();
this.commonData = res.data.data;
localStorage.setItem(
"common_set_before",
JSON.stringify(res.data.data)
);
this.logo = this.commonData.system_logo;
} catch (error) {}
},
// 判断当前菜单激活
setActiveMenu() {
const originUrl = location.pathname.slice(1);
const allUrl = originUrl + location.search;
let flag = false;
this.originMenu.forEach((item) => {
// 当前url下存在和导航菜单对应的路径
if (!item.child && item.url) {
const url = String(item.url).split("?");
if (
(url.length > 1 && item.url == allUrl) ||
(url.length == 1 && item.url == originUrl)
) {
this.menuActiveId = item.id + "";
flag = true;
}
}
// 当前url下存在二级菜单
if (item.child && item.child.length > 0) {
item.child.forEach((child) => {
const url = String(child.url).split("?");
if (
(url.length > 1 && child.url == allUrl) ||
(url.length == 1 && child.url == originUrl)
) {
this.menuActiveId = child.id + "";
flag = true;
}
});
}
});
if (!flag) {
this.menuActiveId = localStorage.getItem("frontMenusActiveId") || "";
}
},
goHome() {
localStorage.frontMenusActiveId = "";
const openUrl = this.commonData.clientarea_logo_url || "/home.htm";
if (this.commonData.clientarea_logo_url_blank == 1) {
window.open(openUrl);
} else {
location.href = openUrl;
}
},
// 获取前台导航
doGetMenu() {
getMenu().then((res) => {
if (res.data.status === 200) {
res.data.data.menu.forEach((item) => {
if (item.child && item.child.length > 0) {
this.originMenu.push(...item.child);
} else {
this.originMenu.push(item);
}
});
const menu = res.data.data.menu;
localStorage.setItem("frontMenus", JSON.stringify(menu));
let index = menu.findIndex((item) => item.name == "分隔符");
if (index != -1) {
this.hasSeparate = true;
this.menu1 = menu.slice(0, index);
this.menu2 = menu.slice(index + 1);
} else {
this.hasSeparate = false;
this.menu1 = menu;
}
this.setActiveMenu();
}
});
// 获取详情
accountDetail()
.then((res) => {
if (res.data.status == 200) {
let obj = res.data.data.account;
let id = res.data.data.account.id;
localStorage.setItem(
"is_sub_account",
obj.customfield?.is_sub_account
);
if (obj.customfield?.is_sub_account == 1) {
// 子账户
accountPermissions(id).then((relust) => {
let rule = relust.data.data.rule;
this.$emit("getruleslist", rule);
});
} else {
// 主账户
this.$emit("getruleslist", "all");
}
}
})
.catch((err) => {
console.log(err, "err----->");
});
},
arrFun(n) {
for (var i = 0; i < n.length; i++) {
//用typeof判断是否是数组
if (n[i].child && typeof n[i].child == "object") {
let obj = JSON.parse(JSON.stringify(n[i]));
delete obj.child;
this.noRepeat.push(obj);
this.arrFun(n[i].child);
} else {
this.noRepeat.push(n[i]);
}
}
},
/*
* 获取菜单url
* @param {Number} id 菜单id 或者url
* @return {String} url 菜单url
*/
getMenuUrl(id) {
const temp =
this.originMenu.find((item) => item.id == id || item.url == id) || {};
const reg =
/^(((ht|f)tps?):\/\/)([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/;
let url = "/" + temp.url;
if (reg.test(temp.url)) {
if (temp?.second_reminder === 1) {
url = `/transfer.htm?target=${encodeURIComponent(temp.url)}`;
} else {
url = temp.url;
}
}
return url;
},
handleSelect(id) {
localStorage.setItem("frontMenusActiveId", id);
const temp =
this.originMenu.find((item) => item.id == id || item.url == id) || {};
const reg =
/^(((ht|f)tps?):\/\/)([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/;
if (reg.test(temp.url)) {
if (temp?.second_reminder === 1) {
return window.open(
`/transfer.htm?target=${encodeURIComponent(temp.url)}`
);
} else {
return window.open(temp.url);
}
}
location.href = "/" + temp.url;
},
getAllIcon() {
let url = "/upload/common/iconfont/iconfont.json";
let _this = this;
// 申明一个XMLHttpRequest
let request = new XMLHttpRequest();
// 设置请求方法与路径
request.open("get", url);
// 不发送数据到服务器
request.send(null);
//XHR对象获取到返回信息后执行
request.onload = function () {
// 解析获取到的数据
let data = JSON.parse(request.responseText);
_this.iconsData = data.glyphs;
_this.iconsData.map((item) => {
item.font_class = "icon-" + item.font_class;
});
};
},
},
};

View File

@@ -0,0 +1,148 @@
/* 自动续费 */
const autoRenew = {
template: `
<div class="com-auto-renew">
<el-switch :value="isAutoRenew" active-color="var(--color-primary)"
:active-value="1" :inactive-value="0" @change="autoRenewChange">
</el-switch>
<el-dialog :visible.sync="showRenewDialog" :show-close="false" custom-class="common-renew-dialog"
width="6.2rem">
<div class="dialog-title">
{{calcTitle}}
</div>
<div class="con" v-loading="loading">
<p class="item">
<span class="label">ID</span>
<span class="value">{{host.id}}</span>
</p>
<p class="item">
<span class="label">{{lang.auto_renew_name}}</span>
<span class="value">{{host.name}}</span>
</p>
<p class="item" v-if="host.area">
<span class="label">{{lang.auto_renew_area}}</span>
<span class="value">{{host.country}}-{{host.city}}-{{host.area}}</span>
</p>
<p class="item" v-if="host.dedicate_ip">
<span class="label">IP</span>
<span class="value">
<span>{{host.dedicate_ip}}</span>
<el-popover placement="top" trigger="hover" v-if="host.ip_num > 1">
<div class="ips">
<p v-for="(item,index) in host.allIp" :key="index">
{{item}}
<i class="el-icon-document-copy base-color" @click="copyIp(item)"></i>
</p>
</div>
<span slot="reference" class="base-color">
({{host.ip_num}})
</span>
</el-popover>
<i class="el-icon-document-copy base-color" @click="copyIp(host.allIp)" v-if="host.ip_num > 0">
</i>
</span>
</p>
<p class="item">
<span class="label">{{lang.auto_renew_cycle}}</span>
<span class="value">{{commonData.currency_prefix}}{{host.renew_amount}}/{{host.billing_cycle_name}}</span>
</p>
<p class="item">
<span class="label">{{lang.auto_renew_due}}</span>
<span class="value">{{host.due_time | formateTime}}</span>
</p>
</div>
<div class="dialog-footer">
<el-button class="btn-ok" @click="handleAutoRenew" :loading="submitLoading">{{lang.auto_renew_sure}}</el-button>
<el-button class="btn-no" @click="showRenewDialog = false">{{lang.auto_renew_cancel}}</el-button>
</div>
</el-dialog>
</div>
`,
data() {
return {
showRenewDialog: false,
submitLoading: false,
calcTitle: "",
is_auto_renew: 0,
host: {},
loading: false,
commonData: JSON.parse(localStorage.getItem("common_set_before")) || {},
};
},
props: {
isAutoRenew: {
type: Number,
required: true,
default: 0,
},
id: {
type: Number | String,
required: true,
default: null,
},
},
filters: {
formateTime(time) {
if (time && time !== 0) {
return formateDate(time * 1000);
} else {
return "--";
}
},
},
methods: {
copyIp(ip) {
if (typeof ip !== "string") {
ip = ip.join(",");
}
const textarea = document.createElement("textarea");
textarea.value = ip.replace(/,/g, "\n");
document.body.appendChild(textarea);
textarea.select();
document.execCommand("copy");
document.body.removeChild(textarea);
this.$message.success(lang.index_text32);
},
async handleAutoRenew() {
try {
const params = {
id: this.host.id,
status: this.is_auto_renew,
};
this.submitLoading = true;
const res = await rennewAuto(params);
this.$message.success(res.data.msg);
this.showRenewDialog = false;
this.submitLoading = false;
this.$emit("update");
} catch (error) {
this.submitLoading = false;
this.showRenewDialog = false;
this.$message.error(error.data.msg);
}
},
async autoRenewChange(val) {
try {
this.showRenewDialog = true;
this.is_auto_renew = val ? 1 : 0;
this.loading = true;
const res = await getHostSpecific({id: this.id});
this.host = res.data.data;
this.host.allIp = (
this.host.dedicate_ip +
"," +
this.host.assign_ip
).split(",");
this.calcTitle =
lang.auto_renew_tip1 +
(this.isAutoRenew
? lang_obj.auto_renew_tip3
: lang_obj.auto_renew_tip2);
this.loading = false;
} catch (error) {
this.loading = false;
this.$message.error(error.data.msg);
}
},
},
};

View File

@@ -0,0 +1,651 @@
const batchRenewpage = {
template: /*html */ `
<div class="batch-op-btn">
<div class="search-btn" style="margin-right: 0.1rem;" v-if="show_quick_order === 1">
<a :href="quick_order_url" style="color: #fff; text-decoration: none;" target="_blank">{{lang.new_goods}}</a>
</div>
<el-dropdown split-button type="primary" @click="handleClick" @command="handleCommand" v-show="canOp">
{{opName}}
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="item in opList" :key="item.value" :command="item.value">{{item.label}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-dialog width="5.5rem" :visible.sync="confirmDialog" :title="lang.cart_tip_text15 + opName + '?'" class="branch-confirm-dialog">
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="handelOp" :loading="subLoading">{{lang.cart_tip_text9}}</el-button>
<el-button @click="confirmDialog = false;">{{lang.cart_tip_text10}}</el-button>
</div>
</el-dialog>
<el-dialog width="10rem" :visible.sync="isShow" @close="rzClose" class="branch-rennew-dialog">
<div class="dialag-content" v-loading="loading">
<h2 class="tips-title">{{title}}</h2>
<el-table :data="dataList" style="width: 100%" max-height="600">
<el-table-column prop="id" label="ID" width="120">
</el-table-column>
<el-table-column prop="product_name" :label="lang.cart_tip_text11" min-width="180"
:show-overflow-tooltip="true">
</el-table-column>
<el-table-column prop="billing_cycles" :label="lang.cart_tip_text12" min-width="180">
<template slot-scope="{row}">
<el-select v-model="row.select_cycles" @change="(val) =>changeCycles(row)">
<el-option v-for="(item,index) in row.billing_cycles" :key="index" :value="index" :label=" item.customfield?.multi_language?.billing_cycle || item.billing_cycle">
<span>{{currency_prefix + item.price}} /{{ item.customfield?.multi_language?.billing_cycle || item.billing_cycle }}</span>
</el-option>
</el-select>
</template>
</el-table-column>
<el-table-column prop="base_price" :label="lang.cart_tip_text13" width="180" align="right">
<template slot-scope="{row}">
<span>{{currency_prefix + row.cur_pirce}}</span>
</template>
</el-table-column>
</el-table>
<div class="total-price">{{lang.template_text87}}
<span class="pay-money">{{currency_prefix }} <span
class="font-26">{{ calcDiscountPrice }}</span> </span>
</div>
<div class="origin-price" v-if="hasDiscount && currentDiscount > 0">
<span class="pay-money">{{currency_prefix }} <span
class="font-26">{{ calcTotalPrice}}</span> </span>
</div>
<!-- 优惠码 -->
<div class="batch-discount-popover">
<el-popover placement="bottom" trigger="click" v-if="hasDiscount && !promo.promo_code " v-model="visibleShow"
:visible-arrow="false">
<div class="discount-content">
<div class="close-btn-img" @click="closePopver">
<img src="${url}/img/common/close_icon.png" alt="">
</div>
<div>
<el-input class="discount-input" clearable v-model="discountInputVal" :placeholder="lang.login_text10"
maxlength="9"></el-input>
<el-button class="discount-btn" :loading="isLoading"
@click="handelApplyPromoCode">{{lang.shoppingCar_tip_text9}}</el-button>
</div>
</div>
<span slot="reference" class="discount-text">{{lang.shoppingCar_tip_text12}}</span>
</el-popover>
<div v-if="promo.promo_code && currentDiscount > 0" class="used">
{{ promo.promo_code }}
<i class="el-icon-circle-close remove-discountCode" @click="removeDiscountCode"></i>
</div>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="handelRenew" :loading="subLoading">{{lang.cart_tip_text9}}</el-button>
<el-button @click="rzClose">{{lang.cart_tip_text10}}</el-button>
</div>
</el-dialog>
<!-- 重置密码弹窗 -->
<div class="branch-repass-dialog">
<el-dialog width="6.8rem" :visible.sync="isShowRePass" :show-close=false @close="rePassDgClose">
<div class="dialog-title">
{{lang.cart_tip_text28}}
</div>
<div class="dialog-main">
<el-input class="pass-input" v-model="rePassData.password" :placeholder="lang.account_tips47">
<div class="pass-btn" slot="suffix" @click="autoPass">{{lang.common_cloud_btn1}}</div>
</el-input>
</div>
<div slot="footer" class="dialog-footer">
<div class="btn-ok" @click="rePassSub" v-loading="subLoading">{{lang.cart_tip_text9}}</div>
<div class="btn-no" @click="rePassDgClose">{{lang.cart_tip_text10}}</div>
</div>
</el-dialog>
</div>
<!-- 修改备注弹窗 -->
<div class="branch-repass-dialog">
<el-dialog width="6.8rem" :visible.sync="isShowNote" :show-close=false>
<div class="dialog-title">
{{lang.cart_tip_text30}}
</div>
<div class="dialog-main">
<el-input class="pass-input" v-model="notes" :placeholder="lang.cart_tip_text31"></el-input>
</div>
<div slot="footer" class="dialog-footer">
<div class="btn-ok" @click="handelNoteSub" v-loading="subLoading">{{lang.cart_tip_text9}}</div>
<div class="btn-no" @click="isShowNote = false">{{lang.cart_tip_text10}}</div>
</div>
</el-dialog>
</div>
<pay-dialog ref="RennwPayDialog" @payok="paySuccess" @paycancel="payCancel"></pay-dialog>
<safe-confirm ref="safeRef" :password.sync="client_operate_password" @confirm="hadelSafeConfirm"></safe-confirm>
</div>
`,
props: {
title: {
type: String,
default: lang.cart_tip_text7,
},
ids: {
type: Array,
required: true,
default: () => {
return [];
},
},
tab: {
type: String,
required: true,
default: "", // using expiring // overdue // deleted // ""
},
moduleType: {
type: String,
required: true,
},
},
components: {
payDialog,
safeConfirm,
},
data() {
return {
currency_prefix:
(JSON.parse(localStorage.getItem("common_set_before")) || {})
?.currency_prefix || "¥",
isShow: false,
loading: false,
subLoading: false,
confirmDialog: false,
dataList: [],
opType: "renew",
client_operate_password: "",
allOpList: [
{
value: "renew",
label: lang.cart_tip_text7,
},
{
value: "on",
label: lang.cart_tip_text16,
},
{
value: "off",
label: lang.cart_tip_text17,
},
{
value: "reboot",
label: lang.cart_tip_text18,
},
{
value: "hard_off",
label: lang.cart_tip_text19,
},
{
value: "hard_reboot",
label: lang.cart_tip_text20,
},
],
cloud_moudle: ["mf_cloud", "remf_cloud", "remf_finance", "rewhmcs_cloud"],
dcim_moudle: [
"mf_dcim",
"remf_dcim",
"remf_finance_dcim",
"rewhmcs_dcim",
],
ip_moudle: ["mf_cloud_ip", "mf_cloud_disk", "huawei_eip"],
common_moudle: ["idcsmart_common"],
show_quick_order: 0,
quick_order_url: "",
isShowRePass: false,
rePassData: {
password: "",
},
promo: {
promo_code: "",
},
visibleShow: false,
discountInputVal: "",
isLoading: false,
currentDiscount: 0,
batchHostDiscount: {}, // 批量使用优惠码过后的折扣
notes: "",
isShowNote: false,
};
},
mixins: [mixin],
created() {
const addons = document.querySelector("#addons_js");
const addons_js_arr = JSON.parse(addons.getAttribute("addons_js")).map((item) => {
return item.name;
});
this.hasDiscount = addons_js_arr.includes("PromoCode");
this.setQuickOrderUrl();
},
computed: {
canOp() {
return this.tab !== "deleted";
},
opList() {
const isActive =
this.tab === "using" || this.tab === "expiring" || this.tab == "";
const isCloud = this.cloud_moudle.includes(this.moduleType);
const isDcim = this.dcim_moudle.includes(this.moduleType);
const isIp = this.ip_moudle.includes(this.moduleType);
const isCommon = this.common_moudle.includes(this.moduleType);
if (!isActive) {
return [
{
value: "renew",
label: lang.cart_tip_text7,
},
];
}
if (isIp) {
return [
{
value: "renew",
label: lang.cart_tip_text7,
},
{
value: "unlink",
label: lang.cart_tip_text21,
},
{
value: "note",
label: lang.cart_tip_text30,
},
];
} else if (isCloud) {
return [...this.allOpList, {
value: "note",
label: lang.cart_tip_text30,
},];
} else if (isDcim) {
return [
{
value: "renew",
label: lang.cart_tip_text7,
},
{
value: "on",
label: lang.cart_tip_text16,
},
{
value: "off",
label: lang.cart_tip_text17,
},
{
value: "reboot",
label: lang.cart_tip_text18,
},
{
value: "note",
label: lang.cart_tip_text30,
},
];
}
if (isCommon) {
return [
...this.allOpList,
{
value: "crack_pass",
label: lang.cart_tip_text27,
},
// {
// value: "reinstall",
// label: lang.cart_tip_text28,
// },
];
} else {
return [
{
value: "renew",
label: lang.cart_tip_text7,
},
{
value: "note",
label: lang.cart_tip_text30,
},
];
}
},
idsArr() {
return this.ids.map((item) => {
return item.id;
});
},
rewIdArr() {
return this.dataList.map((item) => {
return item.id;
});
},
calcTotalPrice() {
return this.dataList
.reduce((acc, cur) => {
return acc + cur.cur_pirce * 1;
}, 0)
.toFixed(2);
},
// 折扣价钱计算方式
calcDiscountPrice() {
return (this.dataList
.reduce((acc, cur) => {
let temp = 0;
// 折扣价 + 已减去的循环优惠的价格 - 批量使用优惠码的折扣
if (this.batchHostDiscount[cur.id] * 1 > 0) {
temp = cur.cur_pirce * 1 + cur.promo_code_discount * 1 - this.batchHostDiscount[cur.id] * 1;
} else {
temp = cur.cur_pirce * 1;
}
return acc + temp;
}, 0))
.toFixed(2);
},
opName() {
return (
this.opList.filter((item) => item.value === this.opType)[0]?.label ||
lang.cart_tip_text22
);
},
},
watch: {
opList(val) {
this.opType = val[0].value || "";
},
},
methods: {
removeDiscountCode() {
this.discountInputVal = "";
this.promo.promo_code = "";
this.currentDiscount = 0;
this.batchHostDiscount = {};
},
async handelApplyPromoCode(bol = true) {
try {
if (this.discountInputVal.length === 0) {
if (bol) {
this.$message.warning(lang.shoppingCar_tip_text13);
}
return;
}
// this.isLoading = true;
let discountArr = [];
this.dataList.forEach(item => {
const params = {
host_id: item.id,
product_id: item.product_id,
amount: item.billing_cycles[item.select_cycles].base_price,
billing_cycle_time: item.billing_cycles[item.select_cycles].duration,
promo_code: this.discountInputVal,
scene: "renew"
};
discountArr.push(params);
});
const res = await applyBatchRenewCode({ promo_codes: discountArr });
this.promo.promo_code = this.discountInputVal;
this.currentDiscount = res.data.data.discount;
this.batchHostDiscount = res.data.data.host_discount;
if (this.currentDiscount * 1 > 0) {
this.$message.success(res.data.msg);
} else {
this.removeDiscountCode();
}
} catch (error) {
this.$message.error(error.data.msg);
}
},
closePopver() {
this.visibleShow = false;
this.discountInputVal = "";
},
setQuickOrderUrl() {
// 拿到当前导航完整路径 去除location.origin
this.currentNav = location.href.replace(location.origin, "");
const params = getUrlParams();
let currentMenu;
const menuList = JSON.parse(localStorage.getItem("frontMenus") || "[]");
let isFindById = false;
// 找到当前导航
// 有id直接使用id
if (params.m) {
currentMenu = menuList.find(
(item) =>
item.id == params.m || item?.child?.some((el) => el.id == params.m)
);
isFindById = true;
}
if (!currentMenu) {
currentMenu = menuList.find(
(item) =>
(item.url !== "" && this.currentNav.includes(item.url)) ||
item?.child?.some(
(el) => el.url !== "" && this.currentNav.includes(el.url)
)
);
isFindById = false;
}
if (currentMenu?.child) {
if (isFindById) {
currentMenu = currentMenu?.child?.find((el) => el.id == params.m);
} else {
currentMenu = currentMenu.child.find(
(el) => el.url !== "" && this.currentNav.includes(el.url)
);
}
}
if (currentMenu) {
this.show_quick_order = currentMenu?.show_quick_order;
this.quick_order_url = currentMenu?.quick_order_url;
}
},
// 随机生成密码
autoPass() {
let passLen = 9;
let pass =
randomCoding(1) +
randomCoding(1).toLocaleLowerCase() +
0 +
genEnCode(passLen, 1, 1, 0, 1, 0);
this.rePassData.password = pass;
},
rePassSub() {
if (!this.rePassData.password) {
this.$message.error(lang.cart_tip_text29);
return;
}
this.handelOp();
},
rePassDgClose() {
this.isShowRePass = false;
this.rePassData.password = "";
},
hadelSafeConfirm(val) {
this[val]();
},
handleCommand(command) {
this.opType = command;
this.handleClick();
},
handleClick() {
if (this.opType === "") {
this.$message.warning(lang.cart_tip_text23);
return;
}
if (this.idsArr.length === 0) {
this.$message.warning(lang.cart_tip_text24);
return;
}
if (this.opType === "renew") {
this.openDia();
return;
}
if (this.opType === "crack_pass") {
this.isShowRePass = true;
return;
}
if (this.opType === "note") {
this.openNoteDia();
return;
}
this.confirmDialog = true;
},
handelNoteSub() {
this.subLoading = true;
apiBatchUpdateHostNotes({ ids: this.idsArr, notes: this.notes })
.then((res) => {
this.$message.success(res.data.msg);
this.subLoading = false;
this.isShowNote = false;
this.$emit("success");
})
.catch((err) => {
this.$message.error(err.data.msg);
this.subLoading = false;
});
},
openNoteDia() {
this.notes = "";
this.isShowNote = true;
},
handelOp(e, remember_operate_password = 0) {
// if (!this.client_operate_password) {
// this.$refs.safeRef.openDialog("handelOp");
// return;
// }
const client_operate_password = this.client_operate_password;
this.client_operate_password = "";
this.subLoading = true;
const params = {
id: this.idsArr,
action: this.opType,
client_operate_password,
client_operate_methods: "handelOp",
remember_operate_password,
};
if (this.opType === "crack_pass") {
params.password = this.rePassData.password;
}
batchOperation(this.moduleType, params)
.then((res) => {
const arr = res.data.data;
const tips = arr
.map((item) => {
return `<p>ID${item.id}${item.msg}</p>`;
})
.join("");
this.$notify({
title: lang.cart_tip_text25,
message: tips,
duration: 0,
dangerouslyUseHTMLString: true,
});
this.$emit("success");
this.subLoading = false;
this.confirmDialog = false;
if (this.opType === "crack_pass") {
this.isShowRePass = false;
}
})
.catch((err) => {
this.subLoading = false;
this.$message.error(err.data.msg);
if (err.data.data) {
if (
!client_operate_password &&
err.data.data.operate_password === 1
) {
this.$refs.safeRef.openDialog("handelOp");
return;
} else {
return this.$message.error(err.data.msg);
}
}
});
},
openDia() {
this.promo.promo_code = "";
this.discountInputVal = "";
this.currentDiscount = 0;
this.batchHostDiscount = {};
this.isShow = true;
this.getRenewList();
},
changeCycles(item) {
this.discountInputVal = this.promo.promo_code;
this.handelApplyPromoCode(false);
item.cur_pirce = this.calcPrice(item);
item.promo_code_discount = this.calcPrice(item, true);
},
calcPrice(row, bol = false) {
let temp = 'price';
if (bol) {
temp = 'promo_code_discount';
}
return (
row.billing_cycles.filter(
(item, index) => index == row.select_cycles
)[0]?.[temp] || 0
);
},
getRenewList() {
this.loading = true;
batchRenewList({ ids: this.idsArr })
.then((res) => {
this.dataList = res.data.data.list.map((item) => {
item.select_cycles = 0;
item.cur_pirce = item.billing_cycles[0].price;
item.promo_code_discount = item.billing_cycles[0].promo_code_discount || 0;
return item;
});
this.loading = false;
})
.catch((err) => {
this.loading = false;
this.$message.error(err.data.msg);
});
},
rzClose() {
this.isShow = false;
},
paySuccess(e) {
this.isShow = false;
this.$emit("success");
},
// 取消支付回调
payCancel(e) { },
handelRenew() {
this.subLoading = true;
const billing_cycles = {};
this.dataList.forEach((item) => {
billing_cycles[item.id] = item.billing_cycles.filter(
(items, index) => index == item.select_cycles
)[0].billing_cycle;
});
aipBatchRenew({
ids: this.rewIdArr,
billing_cycles,
customfield: {
promo_code: this.promo.promo_code,
},
})
.then((res) => {
this.subLoading = false;
if (res.data.code === "Unpaid") {
this.$refs.RennwPayDialog.showPayDialog(res.data.data.id);
} else {
this.isShow = false;
this.$emit("success");
}
})
.catch((err) => {
this.subLoading = false;
this.$message.error(err.data.msg);
});
},
},
};

View File

@@ -0,0 +1,116 @@
// 验证码通过 - 找到当前显示的验证码实例
function captchaCheckSuccsss(bol, captcha, token) {
if (bol) {
// 遍历所有注册的验证码实例,找到 DOM 中存在的那个v-if 确保存在即可见)
if (window.captchaInstances) {
for (let captchaId in window.captchaInstances) {
const element = document.getElementById(captchaId);
// v-if 保证:元素存在 = 元素可见
if (element) {
const instance = window.captchaInstances[captchaId];
if (instance && instance.getData) {
instance.getData(captcha, token);
break; // 找到就退出循环
}
}
}
}
}
}
// 取消验证码验证 - 找到当前显示的验证码实例
function captchaCheckCancel() {
if (window.captchaInstances) {
for (let captchaId in window.captchaInstances) {
const element = document.getElementById(captchaId);
// v-if 保证:元素存在 = 元素可见
if (element) {
const instance = window.captchaInstances[captchaId];
if (instance && instance.captchaCancel) {
instance.captchaCancel();
break; // 找到就退出循环
}
}
}
}
}
// css 样式依赖common.css
const captchaDialog = {
template: `
<div :id="captchaId" v-if="isShowCaptcha"></div>`,
created() {
// this.doGetCaptcha()
},
data() {
return {
captchaData: {
token: "",
captcha: "",
},
captchaHtml: "",
};
},
props: {
isShowCaptcha: {
type: Boolean,
},
captchaId: {
type: String,
default() {
// 生成唯一 ID避免多实例冲突
return `captchaHtml_${Date.now()}_${Math.random()
.toString(36)
.slice(2, 11)}`;
},
},
},
methods: {
// 获取图形验证码
doGetCaptcha() {
try {
getNewCaptcha().then((res) => {
if (res.data.status === 200) {
this.captchaHtml = res.data.data.html;
$(`#${this.captchaId}`).html(this.captchaHtml);
$(`#${this.captchaId}`).show();
}
});
} catch (e) {
console.log("获取图形验证码", e);
}
},
// 取消验证码
captchaCancel() {
this.$emit("captcha-cancel");
},
// 获取验证码数据
getData(captchaCode, token) {
this.captchaData.captcha = captchaCode;
this.captchaData.token = token;
this.$emit("get-captcha-data", captchaCode, token);
},
},
mounted() {
// 创建全局实例映射对象
if (!window.captchaInstances) {
window.captchaInstances = {};
}
// 将当前实例注册到全局映射表中,使用箭头函数保持 this 指向
window.captchaInstances[this.captchaId] = {
getData: (captchaCode, token) => {
this.getData(captchaCode, token);
},
captchaCancel: () => {
this.captchaCancel();
},
};
},
// 组件销毁时清理对应的实例引用
beforeDestroy() {
if (window.captchaInstances && window.captchaInstances[this.captchaId]) {
delete window.captchaInstances[this.captchaId];
}
},
};

View File

@@ -0,0 +1,104 @@
const cashBack = {
template: `
<el-dialog :visible.sync="showCash" :show-close="false" custom-class="common-cashback-dialog">
<div class="dialog-title">
{{lang.apply_cashback}}
</div>
<div class="con">
{{lang.cashback_tip1}}<span class="price">{{currency_prefix}}{{cashbackPrice | filterMoney}}</span>
<template v-if="cashbackTime">{{lang.cashback_tip2}} {{cashbackTime | formateTime}}</template>
</div>
<div class="dialog-footer">
<div class="tip">{{lang.cashback_tip}}</div>
<div class="opt">
<el-button class="btn-ok" @click="sureCashback" :loading="cashLoading">{{lang.ticket_btn6}}</el-button>
<el-button class="btn-no" @click="cancleDialog">{{lang.finance_btn7}}</el-button>
</div>
</div>
</el-dialog>
`,
filters: {
filterMoney(money) {
if (isNaN(money)) {
return "0.00";
} else {
const temp = `${money}`.split(".");
return parseInt(temp[0]).toLocaleString() + "." + (temp[1] || "00");
}
},
formateTime(time) {
if (time && time !== 0) {
return formateDate(time * 1000);
} else {
return "--";
}
},
},
data() {
return {
hasCash: false, // 是否安装了插件
isShowBtn: false, // 是否展示申请返现按钮
cashbackPrice: null,
cashbackTime: null,
cashLoading: false,
currency_prefix: (localStorage.common_set_before && JSON.parse(localStorage.common_set_before)?.currency_prefix) || "¥",
};
},
props: {
id: {
type: Number | String,
required: true,
},
showCash: {
type: Boolean,
},
},
mixins: [mixin],
mounted() {
this.hasCash = this.addons_js_arr.includes("ProductCashback");
this.hasCash && this.getCash();
},
created() { },
methods: {
async getCash() {
try {
const {
data: {
data: { cashback_support, is_cashback, expired, price },
},
} = await getCashbackInfo({ id: this.id });
if (
cashback_support &&
!is_cashback &&
price * 1 &&
(expired > new Date().getTime() / 1000 || expired === 0)
) {
this.cashbackPrice = price;
this.cashbackTime = expired;
this.isShowBtn = true;
} else {
this.isShowBtn = false;
}
this.$emit("showbtn", this.isShowBtn);
} catch (error) {
this.$message.error(error.data.msg);
}
},
async sureCashback() {
try {
this.cashLoading = true;
const res = await apllyCashback({ id: this.id });
this.$message.success(res.data.msg);
this.getCash();
this.cashLoading = false;
this.$emit("cancledialog", false);
} catch (error) {
this.cashLoading = false;
this.$message.error(error.data.msg);
}
},
cancleDialog() {
this.$emit("cancledialog", false);
},
},
};

View File

@@ -0,0 +1,84 @@
const cashCoupon = {
template: `
<div>
<el-popover placement="bottom" trigger="click" v-model="visibleShow" class="discount-popover" @show="popSow" :visible-arrow="false">
<div class="discount-content">
<div class="close-btn-img" @click="closePopver">
<img src="${url}/img/common/close_icon.png" alt="">
</div>
<div>
<el-select class="discount-input" v-model="value" clearable :placeholder="lang.shoppingCar_tip_text8" value-key="id">
<el-option v-for="item in options" :key="item.id" :label=" item.code + '--' + currency_prefix + item.price " :value="item"></el-option>
</el-select>
<el-button class="discount-btn" @click="handelApplyPromoCode">{{lang.shoppingCar_tip_text9}}</el-button>
</div>
</div>
<span slot="reference" class="cash-code">{{lang.shoppingCar_tip_text10}}</span>
</el-popover>
</div>
`,
data() {
return {
visibleShow: false, // 是否显示代金券弹窗
isLoading: false, // 确认按钮loading
value: {}, // 选择的对象
options: [], // 代金券数组
};
},
components: {},
props: {
scene: {
type: String, // new新购,renew续费,upgrade升降级
required: true,
},
product_id: {
type: Array, // 场景中的所有商品ID
required: true,
},
price: {
type: Number | String, // 需要支付的原价格
required: true,
},
currency_prefix: {
type: String,
default:
(localStorage.common_set_before &&
JSON.parse(localStorage.common_set_before)?.currency_prefix) ||
"¥",
},
},
created() {},
mounted() {},
methods: {
closePopver() {
this.visibleShow = false;
this.value = {};
},
handelApplyPromoCode() {
if (!this.value.id) {
this.$message.warning(lang.shoppingCar_tip_text11);
return;
}
this.$emit("use-cash", this.value);
this.visibleShow = false;
},
popSow() {
const params = {
scene: this.scene,
product_id: this.product_id,
price: Number(this.price),
};
this.getEnableList(params);
},
getEnableList(params) {
enableList(params)
.then((res) => {
this.options = res.data.data.list;
})
.catch((err) => {
this.$message.error(err.data.msg);
})
.finally(() => {});
},
},
};

View File

@@ -0,0 +1,36 @@
const certificationDialog = {
template: `
<!-- 未实名认证 dialog -->
<div class="rz-dialog">
<el-dialog width="10rem" :visible.sync="isShow" @close="rzClose">
<div class="dialag-content">
<h2 class="tips-title">您尚未进行实名认证,请先完成实名认证</h2>
<p class="tips-text">据我国2016年11月7日全国人民代表大会常务委员会通过的《中华人民共和国网络安全法》规定,用户不提供真实身份信息的,网络运营者不得为其提供相关服务。 为了符合国家法律法规,以及不影响您参与优惠活动,请您先实名认证。实名认证信息保密工作是统一管理,请放心填写。</p>
<div class="button-box">
<el-button @click="goCertification">立即认证</el-button>
<el-link @click ="rzClose()">以后再说</el-link>
</div>
</div>
</el-dialog>
</div>
`,
props: {
},
data() {
return {
isShow: false
}
},
methods: {
rzClose() {
this.isShow = false
},
goCertification() {
location.href = `plugin/${getPluginId("IdcsmartCertification")}/authentication_select.htm`;
},
},
watch: {}
}

View File

@@ -0,0 +1,128 @@
.coin-active-popover {
padding: 0 !important;
}
.coin-active-btn {
position: fixed;
right: 3px;
top: 35vh;
z-index: 99;
}
.coin-active-btn .coin-active-trigger {
position: relative;
display: inline-flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 8px 5px;
border-radius: 99px;
color: #fff;
background: var(--color-primary);
cursor: pointer;
user-select: none;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(26, 97, 214, 0.2);
}
.coin-active-btn .coin-active-trigger:hover {
transform: scale(1.05);
box-shadow: 0 4px 16px rgba(26, 97, 214, 0.4);
/* 由于@primary-color是CSS变量less的lighten函数无法直接处理改为使用filter实现亮色效果 */
background: var(--color-primary);
filter: brightness(1.08);
}
.coin-active-btn .coin-active-trigger:hover .trigger-icon {
transform: rotate(5deg);
background-color: #f8f9ff;
}
.coin-active-btn .coin-active-trigger:hover .trigger-text {
letter-spacing: 6px;
}
.coin-active-btn .coin-active-trigger .trigger-icon {
font-size: 0;
padding: 7px;
border-radius: 50%;
background-color: #fff;
transition: all 0.3s ease;
}
.coin-active-btn .coin-active-trigger .trigger-icon .icon-svg {
width: 16px;
height: 16px;
}
.coin-active-btn .coin-active-trigger .trigger-text {
writing-mode: vertical-rl;
text-orientation: mixed;
letter-spacing: 5px;
font-size: 12px;
transition: all 0.3s ease;
}
.coin-active-box .coin-title {
padding: 16px 24px;
font-size: 16px;
font-weight: 600;
color: #2B2B2B;
border-bottom: 1px solid #EAEAEA;
}
.coin-active-box .coin-active-tabs .el-tabs__nav-wrap {
padding-left: 24px;
}
.coin-active-box .coin-list {
padding: 0 24px;
max-height: 5rem;
overflow-y: auto;
width: 5rem;
}
.coin-active-box .coin-list .coin-list-item {
box-sizing: border-box;
padding: 16px 0;
border-bottom: 1px dashed #EAEAEA;
display: flex;
align-items: flex-start;
gap: 16px;
}
.coin-active-box .coin-list .coin-list-item:nth-last-of-type(1) {
border-bottom: none;
}
.coin-active-box .coin-list .coin-list-item .coin-item-icon {
border-radius: 50%;
background-color: #F3F3F3;
padding: 11px;
font-size: 0;
}
.coin-active-box .coin-list .coin-list-item .coin-item-icon .item-icon {
width: 24px;
height: 24px;
}
.coin-active-box .coin-list .coin-list-item .coin-item-content {
flex: 1;
}
.coin-active-box .coin-list .coin-list-item .coin-item-content .coin-item-content-top {
display: flex;
align-items: center;
justify-content: space-between;
column-gap: 0.3rem;
}
.coin-active-box .coin-list .coin-list-item .coin-item-content .coin-item-content-top .coin-item-content-title-text {
font-size: 14px;
font-weight: 600;
color: #2B2B2B;
}
.coin-active-box .coin-list .coin-list-item .coin-item-content .coin-item-content-top .coin-item-content-time {
font-size: 12px;
color: #A2A2A2;
}
.coin-active-box .coin-list .coin-list-item .coin-item-content .coin-item-content-desc {
text-align: right;
margin-top: 8px;
font-size: 14px;
cursor: pointer;
color: var(--color-primary);
}
.coin-active-box .coin-list .coin-list-item .coin-item-content .coin-item-detail .coin-detail-active-limit,
.coin-active-box .coin-list .coin-list-item .coin-item-content .coin-item-detail .coin-detail-use-limit {
font-size: 14px;
font-weight: 600;
color: #2B2B2B;
margin-bottom: 5px;
}
.coin-active-box .coin-list .coin-list-item .coin-item-content .coin-item-detail .coin-detail-use-limit {
margin-top: 10px;
}

View File

@@ -0,0 +1,467 @@
const coinActive = {
template: /* html*/ `
<el-popover placement="left-start" v-model="active_show" trigger="click" popper-class="coin-active-popover"
@show="visbleShow" v-if="home_show_coin_activity == 1">
<div class="coin-active-btn" slot="reference">
<div class="coin-active-trigger" role="button" :aria-label="lang.coin_text71 + coin_name">
<div class="trigger-icon" aria-hidden="true">
<svg class="icon-svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none"
version="1.1" width="16" height="16" viewBox="0 0 16 16">
<defs>
<clipPath id="master_svg0_783_42616">
<rect x="0" y="0" width="16" height="16" rx="0" />
</clipPath>
</defs>
<g clip-path="url(#master_svg0_783_42616)">
<g>
<path
d="M13.6663418125,13.347250952929688L13.6663418125,6.6666717529296875L2.3330078125,6.6666717529296875L2.3330078125,13.347250952929688C2.3330078125,14.075946852929688,2.9237326425,14.666671752929688,3.6524287125,14.666671752929688L12.3469208125,14.666671752929688C13.0756168125,14.666671752929688,13.6663418125,14.075946852929688,13.6663418125,13.347250952929688"
fill="var(--color-primary)" fill-opacity="1" style="mix-blend-mode:passthrough" />
<path
d="M2.4862143825,14.513465452929687Q2.9692757125,14.996526752929688,3.6524287125,14.996526752929688L12.3469208125,14.996526752929688Q13.0300728125,14.996526752929688,13.5131348125,14.513465452929687Q13.9961968125,14.030403652929689,13.9961968125,13.347250952929688L13.9961968125,6.6666717529296875Q13.9961968125,6.634183853929687,13.9898588125,6.602320201929688Q13.9835208125,6.570456549929688,13.9710878125,6.540441642929688Q13.9586548125,6.510426742929687,13.9406048125,6.483414042929687Q13.9225558125,6.456401352929688,13.8995838125,6.4334289129296875Q13.8766118125,6.410456512929687,13.8495988125,6.392407182929688Q13.8225858125,6.374357882929687,13.7925708125,6.361925302929688Q13.7625558125,6.349492732929687,13.7306918125,6.3431546429296874Q13.6988298125,6.336816552929688,13.6663418125,6.336816552929688L2.3330078125,6.336816552929688Q2.3005199135,6.336816552929688,2.2686562615,6.3431546429296874Q2.2367926095,6.349492732929687,2.2067777025,6.361925302929688Q2.1767628025,6.374357882929687,2.1497501025,6.392407182929688Q2.1227374125,6.410456512929687,2.0997649725,6.4334289129296875Q2.0767925725,6.456401352929688,2.0587432425,6.483414052929687Q2.0406939425,6.510426742929687,2.0282613625,6.540441642929688Q2.0158287925,6.570456549929688,2.0094907025,6.602320201929688Q2.0031526125,6.634183853929687,2.0031526125,6.6666717529296875L2.0031526125,13.347250952929688Q2.0031526125,14.030403652929689,2.4862143825,14.513465452929687ZM12.3469208125,14.336816752929687L3.6524287125,14.336816752929687Q3.2425374424999998,14.336816752929687,2.9527000825,14.046979452929687Q2.6628630125,13.757142552929688,2.6628630125,13.347250952929688L2.6628630125,6.996526952929687L13.3364868125,6.996526952929687L13.3364868125,13.347250952929688Q13.3364868125,13.757141552929689,13.0466488125,14.046979452929687Q12.7568108125,14.336816752929687,12.3469208125,14.336816752929687Z"
fill-rule="evenodd" fill="var(--color-primary)" fill-opacity="1" style="mix-blend-mode:passthrough" />
</g>
<g>
<path
d="M8.65971041,6.6666717529296875L8.660000029999999,6.6666717529296875L8.66000301,6.006668742929688L7.33999699,6.006668742929688L7.33999997,6.6666717529296875L7.34028959,6.6666717529296875L7.34028959,14.333338752929688L7.33999997,14.333338752929688L7.33999699,14.993341452929688L8.66000301,14.993341452929688L8.660000029999999,14.333338752929688L8.65971041,14.333338752929688L8.65971041,6.6666717529296875Z"
fill-rule="evenodd" fill="#FFFFFF" fill-opacity="1" style="mix-blend-mode:passthrough" />
</g>
<g>
<rect x="1.3330078125" y="4" width="13.333333969116211" height="2.6666667461395264"
rx="1.3194208145141602" fill="var(--color-primary)" fill-opacity="1"
style="mix-blend-mode:passthrough" />
<path
d="M1.0031526125,5.3472459L1.0031526125,5.3194208Q1.0031526125,4.6362685,1.4862143825,4.15320657Q1.9692761325,3.6701448,2.6524286125,3.6701448L13.3469218125,3.6701448Q14.0300738125,3.6701448,14.5131358125,4.1532066499999996Q14.9961968125,4.6362685,14.9961968125,5.3194208L14.9961968125,5.3472459Q14.9961968125,6.0303984,14.5131358125,6.5134602Q14.0300738125,6.996521899999999,13.3469218125,6.996521899999999L2.6524286125,6.996521899999999Q1.9692763725,6.996521899999999,1.4862143825,6.5134602Q1.0031526125,6.0303984,1.0031526125,5.3472459ZM1.6628630125,5.3472459Q1.6628630725,5.7571373,1.9527000825,6.0469744Q2.2425371425,6.3368115,2.6524286125,6.3368115L13.3469218125,6.3368115Q13.7568118125,6.3368115,14.0466488125,6.0469744Q14.3364858125,5.7571373,14.3364858125,5.3472459L14.3364858125,5.3194208Q14.3364858125,4.90952921,14.0466488125,4.61969221Q13.7568118125,4.3298552,13.3469218125,4.3298552L2.6524286125,4.3298552Q2.2425370225,4.32985526,1.9527000825,4.61969227Q1.6628630125,4.90952933,1.6628630125,5.3194208L1.6628630125,5.3472459Z"
fill="var(--color-primary)" fill-opacity="1" style="mix-blend-mode:passthrough" />
</g>
<g>
<path
d="M4.8803459725,1.8136378270703126Q4.7811028325,1.7201073770703126,4.7270553125,1.5949034670703126Q4.6730077825,1.4696996070703126,4.6730077825,1.3333282470703125Q4.6730078425,1.2683239280703125,4.6856895725,1.2045686570703125Q4.6983712925,1.1408133670703124,4.7232474125,1.0807571970703125Q4.7481235225,1.0207010470703124,4.7842379225,0.9666519170703125Q4.8203523725,0.9126027870703125,4.8663173625,0.8666377970703125Q4.9122823525,0.8206728070703125,4.9663314825,0.7845583570703125Q5.0203806125,0.7484439570703125,5.0804367625,0.7235678470703125Q5.1404929325,0.6986917270703125,5.2042482225,0.6860100070703125Q5.2680034935,0.6733282770703125,5.3330078125,0.6733282170703125Q5.4693791725,0.6733282170703125,5.5945830325,0.7273757470703125Q5.7197869425,0.7814232670703125,5.8133173925,0.8806664070703125L7.9996745125,3.0670235470703124L10.1860304125,0.8806677470703125Q10.2795610125,0.7814239870703125,10.4047651125,0.7273761070703125Q10.5299692125,0.6733282170703125,10.6663413125,0.6733282170703125Q10.7313456125,0.6733282770703125,10.7951012125,0.6860100070703125Q10.858856212500001,0.6986917270703125,10.9189119125,0.7235678470703125Q10.978967712500001,0.7484439570703125,11.0330171125,0.7845583570703125Q11.0870657125,0.8206728070703125,11.1330309125,0.8666377970703125Q11.1789961125,0.9126027870703125,11.2151103125,0.9666519170703125Q11.2512250125,1.0207010470703124,11.276101112500001,1.0807571970703125Q11.300977212500001,1.1408133670703124,11.3136592125,1.2045686570703125Q11.3263407125,1.2683239280703125,11.3263411125,1.3333282470703125Q11.3263416125,1.4696971770703124,11.272295912499999,1.5948992370703126Q11.2182503125,1.7201012670703126,11.1190100125,1.8136314470703125L8.4661603125,4.466480747070312Q8.4202154125,4.512425447070313,8.3661899125,4.548523947070313Q8.3121643125,4.5846223470703125,8.2521345125,4.609487547070312Q8.1921048125,4.634352647070313,8.1283774125,4.6470289470703126Q8.0646500125,4.659705147070312,7.9996743125,4.659705147070312Q7.9346986125,4.659705147070312,7.8709710125,4.6470289470703126Q7.807243812499999,4.634352647070313,7.7472138125,4.609487547070312Q7.687184112500001,4.5846223470703125,7.6331589125,4.548523947070313Q7.5791335125,4.512425447070313,7.533188812500001,4.466480747070312L4.8803459725,1.8136378270703126Z"
fill-rule="evenodd" fill="var(--color-primary)" fill-opacity="1" style="mix-blend-mode:passthrough" />
</g>
</g>
</svg>
</div>
<div class="trigger-text">{{lang.coin_text71}}{{coin_name}}</div>
</div>
</div>
<div class="coin-active-box" v-loading="loading">
<div class="coin-title">
{{coin_name}}{{lang.coin_text72}}
</div>
<template v-if="product_id || host_id">
<el-tabs v-model="activeTab" @tab-click="handleTabClick" class="coin-active-tabs">
<el-tab-pane :label="lang.coin_text76" name="current">
</el-tab-pane>
<el-tab-pane :label="lang.coin_text77" name="all">
</el-tab-pane>
</el-tabs>
</template>
<div class="coin-list">
<template v-if="coinActiveList.length === 0 && !loading">
<el-empty :description="lang.order_text15"></el-empty>
</template>
<template v-else>
<div class="coin-list-item" v-for="item in coinActiveList" :key="item.id">
<div class="coin-item-icon">
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
fill="none" version="1.1" width="24" height="24" viewBox="0 0 24 24">
<defs>
<clipPath id="master_svg0_783_43860">
<rect x="0" y="0" width="24" height="24" rx="0" />
</clipPath>
</defs>
<g clip-path="url(#master_svg0_783_43860)">
<g>
<path
d="M18.708139875,3.64892128671875C16.746531875000002,-0.54723961328125,13.474185875,0.72000699871875,12.082462875,1.87477598671875C11.011678675,4.83818338671875,8.189241875,9.15282158671875,6.912997675,10.94046338671875C9.057568575000001,10.38557438671875,10.998181375,13.12552738671875,11.700040775,14.56673738671875C11.914497375,13.85588038671875,16.461587875,6.99325228671875,18.708139875,3.64892128671875ZM18.132253875,5.46505788671875C18.618157875,6.10842898671875,20.269326875,7.25570008671875,22.995780875,6.69931168671875C20.890203875,8.653420486718751,16.440591875000003,12.96355938671875,15.489781875,14.56823938671875C14.762426875,14.65822038671875,13.100760875,14.78419538671875,12.274424875,14.56823938671875C13.549170875,12.33068538671875,16.509577874999998,7.37867598671875,18.132253875,5.46505788671875ZM4.994881375,16.10993138671875C4.4579884750000005,16.31688838671875,3.546170875,17.24520338671875,4.189542275,19.31178838671875C4.432493975,20.01964738671875,5.246830675,21.54184538671875,6.565067275,21.97375638671875C5.7852234750000004,22.15371938671875,3.999080375,21.87327738671875,3.079764475,19.31178838671875C3.040772275,19.27279838671875,2.236932905,15.64802238671875,4.994881375,16.10993138671875ZM4.802920074999999,13.40747038671875L5.990683075,11.44136338671875C7.151450675,11.05444038671875,9.804419075,11.17891638671875,11.121155775,14.75869938671875C12.485883875,15.16961838671875,15.930694875,15.53854338671875,18.780124875,13.71640938671875C18.499681875,14.07633538671875,17.860808875,14.85768038671875,17.554869875,15.10513038671875C18.001779875,15.16961838671875,19.002080875,15.33608438671875,19.429494875,15.49055438671875C18.601659875,16.06943838671875,16.245629875,17.21970938671875,13.457687875,17.18821538671875C14.083063875,17.54814338671875,15.501779875,18.26050038671875,16.176644875,18.22900538671875C15.882704875,18.28149638671875,15.203340875,18.38347638671875,14.835913875,18.38347638671875C15.000878875,18.70591138671875,15.432794875,19.40927138671875,15.831713875,19.65822238671875C14.222536875,19.46326038671875,10.746232075,18.33848538671875,9.703939875,15.37507838671875C9.270526875,14.34628438671875,7.682343975,12.51365138671875,4.802920074999999,13.40747038671875ZM12.115457875,18.77339738671875C11.748030675,18.68041638671875,11.094161975,18.06703738671875,10.813717875,17.77009738671875C10.629254375,18.14052438671875,10.072865475,19.80219038671875,9.817916875,20.58653438671875C9.297520675000001,22.03674538671875,8.553669975,22.81059038671875,8.247730775,23.01605038671875L9.511978175,23.13302638671875C9.601959274999999,22.63812438671875,11.286123275,20.02115038671875,12.115457875,18.77339738671875ZM9.092061475000001,15.06763738671875C7.469387075,12.87807538671875,5.072865675,13.58893538671875,4.075565575000001,14.21880838671875L2.469386425,16.34088338671875C2.008978501,17.20471038671875,1.971486052,18.24400338671875,2.008978501,18.65492038671875C2.592361685,22.20471238671875,5.290322574999999,23.19451338671875,6.566566975,23.24550438671875C8.129253875,23.18401538671875,9.107058075000001,21.39337338671875,9.401000475,20.50705138671875C9.553969875,19.88917338671875,10.153849575,18.19301438671875,10.434293775,17.42066738671875C9.699440975,16.77279638671875,9.234533775,15.58203438671875,9.093561675,15.06763638671875L9.092061475000001,15.06763738671875ZM8.019775875,19.58173538671875C7.215936675,16.367880386718753,5.422295775,15.60003138671875,4.255528975,15.61203038671875C3.8776046749999997,15.61503038671875,3.510177975,15.76200138671875,3.232733275,16.01694938671875C2.706338465,16.50585138671875,2.527874235,17.12222838671875,2.506878435,17.42066938671875C2.401899485,19.74220438671875,3.448690075,21.19691238671875,4.496980475,21.99625238671875C5.303818975,22.61263038671875,6.490082275,23.00105238671875,7.280423675,22.36067938671875C8.070765975,21.72031038671875,8.136752575,20.38407538671875,8.019775875,19.58173538671875Z"
fill="var(--color-primary)" fill-opacity="1" style="mix-blend-mode:passthrough" />
</g>
</g>
</svg>
</div>
<div class="coin-item-content">
<div class="coin-item-content-top">
<div class="coin-item-content-title-text">
{{ item.name }}
</div>
<div class="coin-item-content-time">
<template v-if="item.begin_time == 0 ">
{{lang.coin_text73}} - {{ item.end_time | formateTime }}
</template>
<template v-else>
{{ item.begin_time | formateTime }} - {{ item.end_time | formateTime }}
</template>
</div>
</div>
<div class="coin-item-content-desc">
<span @click="item.show_desc = !item.show_desc">{{ lang.coin_text78 }}</span>
<i class="el-icon-arrow-down" v-show="!item.show_desc" @click="item.show_desc = !item.show_desc"></i>
<i class="el-icon-arrow-up" v-show="item.show_desc" @click="item.show_desc = !item.show_desc"></i>
</div>
<div class="coin-item-detail" v-if="item.show_desc">
<div class="coin-detail-active-limit">
{{lang.coin_text79}}
</div>
<div class="coin-active-detail">
<template v-if="item.type === 'default'">
<!-- 公开送 -->
<p class="coin-detail-main-desc">
{{coin_name}}{{lang.coin_text80}}
</p>
</template>
<template v-if="item.type === 'property'">
<!-- 属性送 -->
<p class="coin-detail-main-desc">
<p>{{lang.coin_text81}}{{item.property_type === 'register' ? lang.coin_text82 : lang.coin_text83}}
</p>
<p>{{lang.coin_text84}}<template v-if="item.property_strategy === 'signal'">
{{lang.coin_text85}}
</template>
<template v-else>
{{lang.coin_text86}}{{item.property_cycle_num}}{{item.property_cycle_unit === 'day' ? lang.coin_text87 : lang.coin_text88}}{{lang.coin_text89}}
</template>
</p>
<p>{{lang.coin_text90}}{{coin_name}}{{lang.coin_text91}}
<template v-if="item.property_type === 'register'">
{{item.property_amount}}{{coin_name}}
</template>
<template v-else>
<p v-for="(property_item,index) in item.property_level" :key="index">
{{property_item.level_id_arr.map(item => item.name).join('、')}}{{lang.coin_text92}}{{property_item.award}}{{coin_name}}
</p>
</template>
</p>
</p>
</template>
<template v-if="item.type === 'recharge'">
<!-- 充值送 -->
<p>{{lang.coin_text93}}{{lang.coin_text94}}</p>
<p>{{lang.coin_text90}}{{coin_name}}{{lang.coin_text91}}</p>
<template v-if="item.recharge_type === 'gradient'">
<p v-for="recharge_item in item.recharge_return" :key="recharge_item.amount">
{{lang.coin_text94}}{{recharge_item.amount}}{{lang.coin_text92}}{{recharge_item.award}}{{coin_name}}
</p>
</template>
<template v-else>
<p>{{lang.coin_text94}}{{item.recharge_min}}{{lang.coin_text95}}{{lang.coin_text96}}
{{item.recharge_proportion}}% {{lang.coin_text97}}{{coin_name}}
</p>
</template>
</template>
<template v-if="item.type === 'total_consume'">
<!-- 累计消费送 -->
<p>{{lang.coin_text81}}{{lang.coin_text98}}</p>
<p v-if="item.total_consume_type === 'total'">{{lang.coin_text99}}{{lang.coin_text100}}</p>
<p v-if="item.total_consume_type === 'stage'">{{lang.coin_text99}}{{lang.coin_text101}}</p>
<p>{{lang.coin_text102}}{{lang.coin_text103}}</p>
<p>{{lang.coin_text90}}{{coin_name}}{{lang.coin_text91}}</p>
<template v-if="item.total_consume_type === 'total'">
<p v-for="total_consume_item in item.total_consume_total_return" :key="total_consume_item.amount">
{{lang.coin_text104}}{{total_consume_item.amount}}{{lang.coin_text105}}{{total_consume_item.award}}{{coin_name}}
</p>
</template>
<template v-else>
<p v-for="total_consume_item in item.total_consume_stage_return" :key="total_consume_item.amount">
{{lang.coin_text106}}{{total_consume_item.amount}}{{lang.coin_text105}}{{total_consume_item.award}}{{coin_name}}
</p>
</template>
</template>
<template v-if="item.type === 'scene'">
<!-- 场景送 -->
<p v-for="(action,index) in calculateSceneRange(item.rules)" :key="index">{{action}}</p>
<p>{{lang.coin_text102}}
<span v-if="item.gift_strategy === 'rule_once'">{{lang.coin_text145}}</span>
<span v-if="item.gift_strategy === 'activity_once'">{{lang.coin_text146}}</span>
<span v-if="item.gift_strategy === 'multiple'">{{lang.coin_text147}}</span>
</p>
</template>
<template v-if="item.type === 'single_consume'">
<!-- 单笔消费送 -->
<p>{{lang.coin_text93}}{{lang.coin_text98}}</p>
{{lang.coin_text107}}<template v-if="item.single_consume_able_product_ids_arr?.length > 0">
<span v-for="(product_item,index) in item.single_consume_able_product_ids_arr"
:key="product_item.id">
<a :href="'/cart/goods.htm?id=' + product_item.id" target="_blank"
style="color: var(--color-primary);">{{product_item.name}}
<span v-if="index !== item.single_consume_able_product_ids_arr.length - 1">、</span>
</a>
</span>
</template>
<template v-else>
{{lang.coin_text108}}
</template>
</p>
<p>{{lang.coin_text99}}{{lang.coin_text101}}</p>
<p>{{lang.coin_text102}}{{lang.coin_text103}}</p>
<p>{{lang.coin_text90}}{{coin_name}}{{lang.coin_text91}}</p>
<p v-for="single_consume_item in item.single_consume_return" :key="single_consume_item.amount">
{{lang.coin_text109}}{{single_consume_item.amount}}{{lang.coin_text105}}{{single_consume_item.award}}{{coin_name}}
</p>
</template>
<template v-if="item.type === 'order'">
<!-- 订购送 -->
<p>{{lang.coin_text81}}{{lang.coin_text110}}</p>
<p>{{lang.coin_text99}}{{lang.coin_text111}}</p>
<p v-if="item.order_same_once === 1">{{lang.coin_text102}}{{lang.coin_text112}}</p>
<p>{{lang.coin_text90}}{{coin_name}}{{lang.coin_text91}}</p>
<p v-for="(order_item,index) in item.order_return" :key="index">
{{lang.coin_text110}}
<template v-if="order_item.product_ids_arr?.length > 0">
<span v-for="(product_item,indexs) in order_item.product_ids_arr" :key="product_item.id">
<a :href="'/cart/goods.htm?id=' + product_item.id" target="_blank"
style="color: var(--color-primary);">{{product_item.name}}</a>
<span v-if="indexs !== order_item.product_ids_arr.length - 1">、</span>
</span>
</template>
<template v-else>
{{lang.coin_text108}}
</template>
{{lang.coin_text105}}{{order_item.award}}{{coin_name}}
</p>
</template>
<template v-if="item.type === 'full_gift'">
<!-- 每满送 -->
<p>{{lang.coin_text93}}
<template v-if="item.full_gift_send_scene_order == 1">
{{lang.coin_text110}}<template
v-if="item.full_gift_send_scene_renew == 1 || item.full_gift_send_scene_upgrade == 1">、</template>
</template>
<template v-if="item.full_gift_send_scene_renew == 1">
{{lang.coin_text113}}<template v-if="item.full_gift_send_scene_upgrade == 1">、</template>
</template>
<template v-if="item.full_gift_send_scene_upgrade == 1">
{{lang.coin_text114}}
</template>
</p>
<p>{{lang.coin_text115}}
<template v-if="item.full_gift_client_limit_open == 0">
{{lang.coin_text116}}
</template>
<template v-else>
{{item.full_gift_client_limit_type === 'client' ? lang.coin_text117 : lang.coin_text118}}
</template>
</p>
<p>{{lang.coin_text107}}<template v-if="item.full_gift_product_only_defence == 1">
({{lang.coin_text119}})
</template>
<template v-if="item.full_gift_send_product_ids_arr?.length > 0">
<span v-for="(product_item,index) in item.full_gift_send_product_ids_arr" :key="product_item.id">
<a :href="'/cart/goods.htm?id=' + product_item.id" target="_blank"
style="color: var(--color-primary);">{{product_item.name}}
<span v-if="index !== item.full_gift_send_product_ids_arr.length - 1">、</span>
</a>
</span>
</template>
<template v-else>
{{lang.coin_text108}}
</template>
</p>
<p>
{{lang.coin_text102}}{{lang.coin_text120}}{{item.full_gift_payment_threshold>0?item.full_gift_payment_threshold:item.full_gift_threshold_amount}}
<template v-if="item.full_gift_same_once == 1">{{lang.coin_text121}}</template>
</p>
<p>{{lang.coin_text90}}{{coin_name}}{{lang.coin_text91}}</p>
<p>
{{lang.coin_text122}}{{item.full_gift_threshold_amount}}{{lang.coin_text105}}{{item.full_gift_gift_amount}}{{coin_name}}
</p>
</template>
</div>
<div class="coin-detail-use-limit">{{coin_name}}{{lang.coin_text123}}</div>
<p>{{lang.coin_text124}}
<template v-if="item.type == 'default'">
{{item.effective_start_time | formateTime}}
</template>
<template v-else-if="item.immediately_effective == 1 || item.effective_time == 0">
{{lang.coin_text125}}
</template>
<template v-else>
{{lang.coin_text126}}{{item.effective_time}}{{item.effective_time_unit === 'day' ? lang.coin_text87 : lang.coin_text88}}{{lang.coin_text127}}
</template>
</p>
<p>{{lang.coin_text128}}
<template v-if="item.type == 'default'">
{{item.effective_end_time | formateTime}}
</template>
<template v-else-if="item.unlimit_efficient === 0">
{{item.efficient_time }} {{item.efficient_time_unit === 'day' ? lang.coin_text87 : lang.coin_text88}}
</template>
<template v-else>
{{lang.coin_text129}}
</template>
</p>
<p v-if="item.certification_can_use === 1">{{lang.coin_text48}}</p>
<p v-if="item.with_event_promotion_use === 0">{{lang.coin_text49}}</p>
<p v-if="item.with_promo_code_use === 0">{{lang.coin_text50}}</p>
<p v-if="item.with_client_level_use === 0">{{lang.coin_text51}}</p>
<p v-if="item.with_voucher_use === 0">{{lang.coin_text52}}</p>
<template
v-if="item.order_use_limit == 'product' || item.single_consume_use_limit == 'product' || item.full_gift_use_limit == 'product' || item.type == 'default' || item.type == 'property' || item.type == 'recharge' || item.type == 'total_consume'">
<p v-if="item.product_ids_arr?.length > 0">
{{lang.coin_text47}}
<span v-for="(el,index) in item.product_ids_arr" :key="el.id">
<a :href="'/cart/goods.htm?id=' + el.id" target="_blank"
style="color: var(--color-primary);">{{el.name}}</a>
<span v-if="index !== item.product_ids_arr.length - 1">、</span>
</span>
</p>
<p v-else>
{{lang.coin_text74}}
</p>
</template>
<p
v-if="item.order_use_limit == 'host' || item.single_consume_use_limit == 'host' || item.full_gift_use_limit == 'host'">
{{lang.coin_text130}}{{coin_name}}{{lang.coin_text131}}
</p>
<p v-if="item.product_only_defence == 1">{{lang.coin_text65}}</p>
<p
v-if="item.order_available == 1 || item.upgrade_available == 1 || item.renew_available == 1 || item.demand_available == 1">
{{lang.coin_text132}}
<template v-if="item.order_available == 1">
{{lang.coin_text133}}<span
v-if="item.upgrade_available == 1 || item.renew_available == 1 || item.demand_available == 1">、</span>
</template>
<template v-if="item.renew_available == 1">
{{lang.coin_text134}}<span v-if="item.upgrade_available == 1 || item.demand_available == 1">、</span>
</template>
<template v-if="item.upgrade_available == 1">
{{lang.coin_text135}}<span v-if="item.demand_available == 1">、</span>
</template>
<template v-if="item.demand_available == 1">
{{lang.coin_text136}}
</template>
</p>
<p v-if="item.cycle_limit === 1 ">
{{lang.coin_text5}}<span v-for="(cycle_item,index) in item.cycle"
:key="cycle_item">{{lang[cycle_item]}}
<span v-if="index !== item.cycle.length - 1">、</span></span>
</p>
</div>
</div>
</div>
</template>
</div>
</div>
</el-popover>
`,
data() {
return {
commonData: {},
coin_name: "",
coinClientInfo: {},
coinActiveList: [],
loading: false,
home_show_coin_activity: 0,
activeTab: "current",
product_id: 0,
host_id: 0,
active_show: false,
init: true,
};
},
filters: {
formateTime(time) {
if (time && time !== 0) {
return formateDate(time * 1000, "YYYY-MM-DD HH:mm");
} else {
return lang.voucher_effective;
}
},
},
created() {
if (!havePlugin("Coin")) {
return;
}
// 加载css
if (
!document.querySelector(
'link[href="' + url + 'components/coinActive/coinActive.css"]'
)
) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = `${url}components/coinActive/coinActive.css`;
document.head.appendChild(link);
}
this.commonData =
JSON.parse(localStorage.getItem("common_set_before")) || {};
this.getCoinInfo();
},
methods: {
visbleShow() {
this.coinActiveList.forEach((item) => {
item.show_desc = false;
});
this.getCoinActiveList();
},
handleTabClick() {
this.getCoinActiveList();
},
// 计算场景送的活动范围
calculateSceneRange(item) {
const allActions = [];
item.forEach(rule => {
rule.notice_actions_arr.forEach(action => {
if (action.name === 'host_renew') {
allActions.push(`${rule.trigger_products[0] ? rule.trigger_products[0].name : lang.coin_text108}${action.name_lang}${lang.coin_text105}${rule.amount}${this.coin_name}`);
} else if (action.name === 'order_pay') {
allActions.push(`${rule.trigger_products[0] ? rule.trigger_products[0].name : lang.coin_text108}${action.name_lang}${lang.coin_text105}${rule.amount}${this.coin_name}`);
} else {
if (!allActions.includes(action)) {
allActions.push(`${action.name_lang}${lang.coin_text105}${rule.amount}${this.coin_name}`);
}
}
});
});
return allActions;
},
setProductId() {
const nowUrl = location.href.split("/").pop();
const pageRouter =
nowUrl.indexOf("?") !== -1 ? nowUrl.split("?")[0] : nowUrl;
if (pageRouter == "goods.htm") {
this.product_id = getUrlParams().id;
} else if (pageRouter == "productdetail.htm") {
this.host_id = getUrlParams().id;
}
},
async getCoinInfo() {
try {
const res = await apiCoinClientCoupon();
this.coin_name = res.data.data.name;
this.coinClientInfo = res.data.data;
this.home_show_coin_activity = res.data.data.home_show_coin_activity;
if (this.home_show_coin_activity == 1) {
this.setProductId();
await this.getCoinActiveList(true);
}
} catch (error) {
console.log(error);
}
},
async getCoinActiveList(isInit = false) {
this.loading = true;
const params = {};
const isProduct = this.activeTab === "current" && this.product_id;
const isHost = this.activeTab === "current" && this.host_id;
params.product_id = isProduct ? this.product_id : undefined;
params.host_id = isHost ? this.host_id : undefined;
const isGoodPage = isProduct || isHost;
await apiCoinActiveList(params)
.then((res) => {
this.coinActiveList = res.data.data.list.map((item) => {
item.show_desc = false;
return item;
});
if (isGoodPage && isInit === true) {
this.active_show = this.coinActiveList.length > 0;
}
})
.finally(() => {
this.loading = false;
});
},
},
};

View File

@@ -0,0 +1,190 @@
// 变量定义
@primary-color: var(--color-primary);
@text-primary: #2B2B2B;
@text-secondary: #A2A2A2;
@border-color: #EAEAEA;
.coin-active-popover {
padding: 0 !important;
}
// 主容器
.coin-active-btn {
position: fixed;
right: 3px;
top: 35vh;
z-index: 99;
// 悬浮触发按钮
.coin-active-trigger {
position: relative;
display: inline-flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 8px 5px;
border-radius: 99px;
color: #fff;
background: @primary-color;
cursor: pointer;
user-select: none;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(26, 97, 214, 0.2);
&:hover {
transform: scale(1.05);
box-shadow: 0 4px 16px rgba(26, 97, 214, 0.4);
/* 由于@primary-color是CSS变量less的lighten函数无法直接处理改为使用filter实现亮色效果 */
background: @primary-color;
filter: brightness(1.08);
.trigger-icon {
transform: rotate(5deg);
background-color: #f8f9ff;
}
.trigger-text {
letter-spacing: 6px;
}
}
.trigger-icon {
font-size: 0;
padding: 7px;
border-radius: 50%;
background-color: #fff;
transition: all 0.3s ease;
.icon-svg {
width: 16px;
height: 16px;
}
}
.trigger-text {
writing-mode: vertical-rl;
text-orientation: mixed;
letter-spacing: 5px;
font-size: 12px;
transition: all 0.3s ease;
}
}
}
.coin-active-box {
.coin-title {
padding: 16px 24px;
font-size: 16px;
font-weight: 600;
color: @text-primary;
border-bottom: 1px solid @border-color;
}
.coin-active-tabs {
.el-tabs__nav-wrap {
padding-left: 24px;
}
}
.coin-list {
padding: 0 24px;
max-height: 5rem;
overflow-y: auto;
width: 5rem;
.coin-list-item {
box-sizing: border-box;
padding: 16px 0;
border-bottom: 1px dashed @border-color;
display: flex;
align-items: flex-start;
gap: 16px;
&:nth-last-of-type(1) {
border-bottom: none;
}
.coin-item-icon {
border-radius: 50%;
background-color: #F3F3F3;
padding: 11px;
font-size: 0;
.item-icon {
width: 24px;
height: 24px;
}
}
.coin-item-content {
flex: 1;
.coin-item-content-top {
display: flex;
align-items: center;
justify-content: space-between;
column-gap: .3rem;
.coin-item-content-title-text {
font-size: 14px;
font-weight: 600;
color: @text-primary;
}
.coin-item-content-time {
font-size: 12px;
color: @text-secondary;
}
}
.coin-item-content-desc {
text-align: right;
margin-top: 8px;
font-size: 14px;
cursor: pointer;
color: @primary-color;
}
.coin-item-detail {
.coin-detail-active-limit,
.coin-detail-use-limit {
font-size: 14px;
font-weight: 600;
color: @text-primary;
margin-bottom: 5px;
}
.coin-detail-use-limit {
margin-top: 10px;
}
}
}
// &:hover {
// cursor: pointer;
// .coin-item-content-top {
// .coin-item-content-title-text {
// color: @primary-color;
// }
// }
// .coin-item-content {
// background-color: #F3F3F3;
// }
// }
}
}
}

View File

@@ -0,0 +1,46 @@
// 父组件执行该组件的countDown() 实现倒计时
const countDownButton = {
template: `
<el-button :class="myClass" v-loading="loading" type="primary" :disabled="!flag">{{ flag? name : num + lang.second_try}}</el-button>
`,
data() {
return {
num: 60,
flag: true,
timer: null,
};
},
props: {
myClass: {
type: String,
default: "count-down-btn",
},
name: {
type: String,
default: lang.send_code,
},
loading: {
type: Boolean,
default: false,
},
},
created() {},
methods: {
countDown() {
this.flag = false;
this.num = --this.num;
this.timer = setInterval(() => {
if (this.num > 1) {
this.flag = false;
this.num = --this.num;
} else {
clearInterval(this.timer);
this.timer = null;
this.flag = true;
this.num = 60;
this.$emit("countend");
}
}, 1000);
},
},
};

View File

@@ -0,0 +1,84 @@
const creditNotice = {
template: /*html*/ `
<el-dialog width="7rem" :visible.sync="isShow" @close="diaClose" custom-class="credit-notice-dialog"
:show-close="false">
<div class="credit-content">
<div class="credit-title">
<span class="title-text">{{lang.coin_text67}}</span>
<span class="close-btn" @click="diaClose">
<i class="el-icon-close"></i>
</span>
</div>
<div class="credit-box">
<div class="credit-open">
{{lang.coin_text66}}
<el-switch v-model="credit_remind" :active-value="1" :inactive-value="0">
</el-switch>
</div>
<div class="credit-input">
{{lang.coin_text68}}
<el-input-number v-model="credit_remind_amount" :min="0" :precision="2" :step="0.01" :controls="false"
:placeholder="lang.coin_text70">
</el-input-number>
{{lang.coin_text69}}
</div>
</div>
<div class="credit-footer">
<el-button class="cancel-btn" @click="diaClose">{{lang.referral_btn7}}</el-button>
<el-button type="primary" @click="submitCredit" :loading="submitLoading">{{lang.referral_btn6}}</el-button>
</div>
</div>
</el-dialog>
`,
data() {
return {
isShow: false,
credit_remind_amount: undefined,
submitLoading: false,
credit_remind: 0,
coinClientCoupon: {},
};
},
methods: {
open() {
this.isShow = true;
accountDetail().then((res) => {
const {credit_remind, credit_remind_amount} = res.data.data.account;
this.credit_remind = credit_remind;
this.credit_remind_amount = credit_remind_amount;
});
},
diaClose() {
this.amount = undefined;
this.isShow = false;
},
submitCredit() {
if (!this.credit_remind_amount) {
return this.$message.error(lang.coin_text70);
}
this.submitLoading = true;
apiCreateCreditRemind({
credit_remind: this.credit_remind,
credit_remind_amount: this.credit_remind_amount,
})
.then((res) => {
if (res.data.status === 200) {
this.$message.success(res.data.msg);
this.diaClose();
this.$emit("success", {
credit_remind: this.credit_remind,
credit_remind_amount: this.credit_remind_amount,
});
}
})
.catch((error) => {
this.$message.error(error.data.msg);
})
.finally(() => {
this.submitLoading = false;
});
},
},
};

View File

@@ -0,0 +1,165 @@
const customGoods = {
template: `
<div class="custom-goods-box">
<el-form :model="ruleForm" ref="ruleForm" :label-position="labelPosition" :label-width="labelWidth" :rules="rules" :hide-required-asterisk="hideRequiredAsterisk">
<el-form-item :prop="item.id + ''" :label="item.field_name" v-for="item in customFieldList" :key="item.id">
<el-select v-model="ruleForm[item.id]" :placeholder="item.description" v-if="item.field_type === 'dropdown'">
<el-option :label="items" :value="items" v-for="(items,indexs) in calcFieldOption(item.field_option)" :key="indexs"></el-option>
</el-select>
<el-checkbox true-label="1" false-label="0" :label="item.field_name" v-model="ruleForm[item.id]"
v-else-if="item.field_type === 'tickbox'" :disabled="item.is_required === 1">
{{item.description}}
</el-checkbox>
<el-input type="textarea" v-model="ruleForm[item.id]" v-else-if="item.field_type === 'textarea'" :placeholder="item.description"></el-input>
<span v-html="item.explain_content" v-else-if="item.field_type === 'explain'" class="custom-explain"></span>
<el-input v-model="ruleForm[item.id]" :placeholder="item.description" v-else></el-input>
</el-form-item>
</el-form>
</div>
`,
data() {
return {
ruleForm: {},
rules: {},
customFieldList: [],
};
},
components: {},
props: {
id: {
type: Number | String,
required: true,
},
self_defined_field: {
type: Object,
required: true,
},
is_show_custom: {
type: Boolean,
required: false,
default: false,
},
labelWidth: {
type: String,
required: false,
default: "auto",
},
labelPosition: {
// :value right/left/top
type: String,
required: false,
default: "left",
},
hideRequiredAsterisk: {
type: Boolean,
required: false,
default: false,
},
},
created() {
this.getCustomFields();
},
watch: {},
mounted() {},
methods: {
getSelfDefinedField() {
let isValid = false;
this.$refs.ruleForm.validate((valid) => {
if (valid) {
this.$emit("update:self_defined_field", this.ruleForm);
isValid = true;
} else {
this.$message.error(lang.custom_goods_text3);
}
});
return isValid;
},
getCustomFields() {
customFieldsProduct(this.id).then((res) => {
const obj = {};
const rules = {};
this.customFieldList = res.data.data.data.map((item) => {
if (item.field_type === "dropdown" && item.is_required === 1) {
obj[item.id + ""] = this.calcFieldOption(item.field_option)[0];
} else {
obj[item.id + ""] = item.field_type === "tickbox" ? 0 : "";
if (item.field_type === "tickbox" && item.is_required === 1) {
obj[item.id + ""] = "1";
}
}
rules[item.id + ""] = this.calcRules(item);
return item;
});
this.$set(this, "ruleForm", obj);
if (Object.keys(this.self_defined_field).length > 0) {
// 设置默认值
for (let i = 0; i < this.customFieldList.length; i++) {
const item = this.customFieldList[i];
if (this.self_defined_field[item.id + ""] !== undefined) {
this.ruleForm[item.id + ""] =
this.self_defined_field[item.id + ""];
}
}
}
this.$emit("update:is_show_custom", this.customFieldList.length > 0);
this.$set(this, "rules", rules);
});
},
calcValidator(item, value, callback, regexpr) {
if (item.is_required === 1 && value === "") {
callback(new Error(lang.custom_goods_text1));
return;
}
if (
value !== "" &&
!new RegExp(regexpr.replace(/^\/|\/$/g, "")).test(value)
) {
callback(new Error(lang.custom_goods_text2));
return;
}
callback();
},
calcRules(item) {
const rules = [];
if (item.is_required === 1) {
rules.push({
required: true,
message: lang.custom_goods_text1,
trigger: ["blur", "change"],
});
} else {
rules.push({
required: false,
trigger: ["blur", "change"],
});
}
if (item.field_type === "link") {
// 类型为链接时需要校验url格式 http://www.baidu.com
const url =
"/^(((ht|f)tps?)://)?([^!@#$%^&*?.s-]([^!@#$%^&*?.s]{0,63}[^!@#$%^&*?.s])?.)+[a-z]{2,6}/?/";
rules.push({
validator: (rule, value, callback) =>
this.calcValidator(item, value, callback, url),
trigger: ["blur", "change"],
});
}
if (
item.field_type !== "dropdown" &&
item.field_type !== "tickbox" &&
item.regexpr
) {
rules.push({
validator: (rule, value, callback) =>
this.calcValidator(item, value, callback, item.regexpr),
trigger: ["blur", "change"],
});
}
return rules;
},
calcFieldOption(item) {
return item.split(",");
},
},
};

View File

@@ -0,0 +1,170 @@
const discountCode = {
template: `
<div>
<el-popover placement="bottom" trigger="click" v-model="visibleShow" class="discount-popover" :visible-arrow="false">
<div class="discount-content">
<div class="close-btn-img" @click="closePopver">
<img src="${url}/img/common/close_icon.png" alt="">
</div>
<div>
<el-input class="discount-input" clearable v-model="discountInputVal" :placeholder="lang.login_text10" maxlength="9"></el-input>
<el-button class="discount-btn" :loading="isLoading" @click="handelApplyPromoCode">{{lang.shoppingCar_tip_text9}}</el-button>
</div>
</div>
<span slot="reference" class="discount-text">{{lang.shoppingCar_tip_text12}}</span>
</el-popover>
</div>
`,
data () {
return {
discountInputVal: "", // 输入框Value
visibleShow: false, // 是否显示优惠弹窗
isLoading: false, // 确认按钮loading
discountMoney: 0, // 抵扣金额
};
},
components: {},
props: {
scene: {
type: String, // new新购,renew续费,upgrade升降级
required: true,
},
isNeedPromo_code: {
//优惠码
type: Boolean,
required: false,
default: true,
},
host_id: {
// 产品ID
type: String | Number,
required: false,
},
product_id: {
// 商品ID
type: String | Number,
required: true,
},
qty: {
// 数量 新购时必传
type: String | Number,
required: false,
},
amount: {
//单价
type: String | Number,
required: true,
},
billing_cycle_time: {
// 周期时间
type: String | Number,
required: true,
},
shopping_index: {
// 购物车位置
type: Number,
required: false,
},
multiple_product: {
// 订购页面多商品同时使用优惠
type: Array,
default: () => [],
required: false,
}
},
created () { },
mounted () { },
methods: {
closePopver () {
this.visibleShow = false;
this.discountInputVal = "";
},
handelApplyPromoCode () {
if (this.isNeedPromo_code && this.discountInputVal.length === 0) {
this.$message.warning(lang.shoppingCar_tip_text13);
return;
}
this.isLoading = true;
if (this.multiple_product.length === 0) {
const params = {
scene: this.scene,
product_id: this.product_id,
amount: this.amount,
billing_cycle_time: this.billing_cycle_time,
promo_code: this.discountInputVal,
};
if (this.qty) {
params.qty = this.qty;
}
if (this.host_id) {
params.host_id = this.host_id;
}
this.getCountMoney(params);
} else {
// 批量使用优惠码,目前只针对订购
let discountArr = [];
this.multiple_product.forEach(item => {
const params = {
scene: this.scene,
product_id: item.product_id,
amount: item.totalPrice,
billing_cycle_time: this.billing_cycle_time,
promo_code: this.discountInputVal,
};
if (item.qty) {
params.qty = item.qty;
}
if (this.host_id) {
params.host_id = this.host_id;
}
discountArr.push(applyPromoCode(params));
});
Promise.all(discountArr).then(res => {
this.discountMoney = res.reduce((all, cur) => {
all += Number(cur.data.data.discount);
return all;
}, 0);
this.$emit(
"get-discount",
this.discountMoney,
this.discountInputVal
);
this.$message.success(lang.shoppingCar_tip_text14);
this.closePopver();
}).catch(err => {
this.$message.error(err.data.msg);
}).finally(() => {
this.isLoading = false;
});
}
},
getCountMoney (params) {
applyPromoCode(params)
.then((res) => {
this.discountMoney = Number(res.data.data.discount);
if (this.shopping_index || this.shopping_index === 0) {
this.$emit(
"get-discount",
this.discountMoney,
this.discountInputVal,
this.shopping_index
);
} else {
this.$emit(
"get-discount",
this.discountMoney,
this.discountInputVal
);
}
this.$message.success(lang.shoppingCar_tip_text14);
this.closePopver();
})
.catch((err) => {
this.$message.error(err.data.msg);
})
.finally(() => {
this.isLoading = false;
});
},
},
};

View File

@@ -0,0 +1,171 @@
const eventCode = {
template: `
<div>
<el-popover placement="bottom" trigger="click" v-model="visibleShow" :visible-arrow="false" v-if="!disabled && options.length !==0">
<div class="event-content">
<el-select class="event-select" @change="changePromotion" v-model="eventId"
:placeholder="lang.goods_text5" >
<el-option v-for="item in options" :key="item.id" :value="item.id" :label="calcLebal(item)">
</el-option>
</el-select>
</div>
<span slot="reference" class="event-text">{{showText}}<i class="el-icon-caret-bottom"></i></span>
</el-popover>
<span class="event-text" v-if="disabled && options.length > 0">{{showText}}</span>
</div>
`,
data() {
return {
eventId: "", // 活动促销ID
options: [],
discount: 0,
visibleShow: false,
nowParams: {},
};
},
computed: {
showText() {
return this.eventId
? this.calcLebal(
this.options.filter((item) => item.id === this.eventId)[0]
)
: lang.goods_text6;
},
},
watch: {
billing_cycle_time() {
this.getEventList();
},
amount() {
this.getEventList();
},
qty() {
this.getEventList();
},
},
props: {
id: {
type: String | Number,
},
// 场景中的所有商品ID
product_id: {
type: String | Number,
required: true,
},
// 需要支付的原价格
amount: {
type: Number | String,
required: true,
},
// 购买数量
qty: {
type: Number | String,
default: 1,
required: true,
},
//周期时间
billing_cycle_time: {
type: Number | String,
required: true,
},
disabled: {
type: Boolean,
default: false,
},
},
created() {
this.getEventList();
},
mounted() {},
methods: {
calcLebal(item) {
if (!item) {
return "";
}
return item.type === "percent"
? lang.goods_text1 + " " + item.value + "%"
: item.type === "reduce"
? lang.goods_text2 + item.full + lang.goods_text3 + " " + item.value
: lang.goods_text6;
},
getEventList() {
const params = {
id: this.product_id,
billing_cycle_time: this.billing_cycle_time,
qty: this.qty,
amount: this.amount,
billing_cycle_time: this.billing_cycle_time,
};
if (
JSON.stringify(this.nowParams) == JSON.stringify(params) ||
!this.billing_cycle_time
) {
// 没有变化 防止重复请求
return;
}
this.nowParams = params;
eventPromotion(params)
.then((res) => {
const event_list = res.data.list;
const isTop =
res.data.addon_event_promotion_does_not_participate === "top";
if (event_list.length > 0) {
const no_select = {
id: 0,
type: "no",
value: 0,
full: 0,
};
if (isTop) {
event_list.unshift(no_select);
} else {
event_list.push(no_select);
}
this.options = event_list;
// 默认选中处理
if (
this.id &&
this.options.map((item) => item.id).includes(this.id)
) {
this.eventId = this.id;
} else {
this.eventId = this.options[0]?.id || "";
}
}
})
.catch((err) => {
this.$message.error(err.data.msg);
})
.finally(() => {
this.changePromotion();
});
},
changePromotion() {
this.$emit("change", {
discount: this.eventId ? this.discount : 0,
id: this.eventId ? this.eventId : "",
});
// applyEventPromotion({
// event_promotion: this.eventId,
// product_id: this.product_id,
// qty: this.qty,
// amount: this.amount,
// billing_cycle_time: this.billing_cycle_time,
// })
// .then((res) => {
// this.discount = res.data.data.discount;
// })
// .catch((err) => {
// this.discount = 0;
// console.log(err.data);
// })
// .finally(() => {
// });
},
clearPromotion() {
this.discount = 0;
this.$emit("change", { discount: this.discount, id: this.eventId });
},
},
};

View File

@@ -0,0 +1,362 @@
const flowPacket = {
template:
`
<div style="flex: 1">
<el-dialog :visible.sync="showPackage && packageList.length > 0" custom-class="common-package-dialog" :loading="packageLoading">
<i class="el-icon-close" @click="cancleDialog"></i>
<div class="dialog-title">
{{lang.buy_package}}
</div>
<!-- Radio 筛选:限时/不限时 -->
<div class="filter-radio" v-if="hasLimitTime || hasUnlimitedTime">
<el-radio-group v-model="filterType" @change="onFilterTypeChange">
<el-radio :label="0" v-if="hasLimitTime">{{lang.limit_time_flow}}</el-radio>
<el-radio :label="1" v-if="hasUnlimitedTime">{{lang.unlimited_traffic}}</el-radio>
</el-radio-group>
</div>
<div class="con">
<div class="items">
<div class="p-item" v-for="item in filteredPackageList" :key="item.id"
:class="{active: item.id === curPackageId}" @click="choosePackage(item)">
<p class="price">{{currencyPrefix}}{{getDisplayPrice(item) | filterMoney}}</p>
<p class="tit">
{{item.name}}
<template v-if="item.billing_mode === 0">
{{item.capacity}}G
</template>
<template v-else>{{item.min_capacity}}-{{item.max_capacity}}G</template>
</p>
<i class="el-icon-check"></i>
</div>
</div>
<div class="slider-container" v-if="showSlider">
<el-slider v-model="sliderValueComputed" :min="sliderMin" :max="sliderMax" :marks="sliderMarks"
show-input>
</el-slider>
</div>
</div>
<div class="dialog-footer">
<el-button class="btn-ok" @click="handlerPackage"
:loading="submitLoading">{{lang.ticket_btn6}}</el-button>
<el-button class="btn-no" @click="cancleDialog">{{lang.finance_btn7}}</el-button>
</div>
</el-dialog>
<!-- 弹窗模式下的触发按钮 -->
<el-button v-if="showFlowPacketList && displayMode === 'dialog' && hasFlow && lineType === 'flow'"
@click="openFlowListDialog" type="primary" size="small">
{{lang.flow_packet_list}}
</el-button>
<!-- 流量包列表 -->
<component
:is="listWrapper"
v-bind="listWrapperProps"
v-on="listWrapperEvents"
v-if="showFlowPacketList && hasFlow && lineType === 'flow' && (displayMode === 'inline' || showFlowListDialog)">
<div class="dialog-title" v-if="displayMode === 'dialog'">{{lang.flow_packet_list}}</div>
<div class="dialog-main">
<el-table :data="flowPacketList" v-loading="flowPacketLoading" style="width: 100%">
<el-table-column prop="id" :label="lang.flow_packet_id" min-width="100"></el-table-column>
<el-table-column prop="name" :label="lang.flow_packet_name" min-width="150"></el-table-column>
<el-table-column :label="lang.flow_packet_create_time" min-width="180">
<template slot-scope="scope">
<span>{{scope.row.create_time | formateTime}}</span>
</template>
</el-table-column>
<el-table-column :label="lang.flow_packet_size" min-width="150">
<template slot-scope="scope">
{{scope.row.size}}GB
</template>
</el-table-column>
<el-table-column :label="lang.flow_packet_used" min-width="120">
<template slot-scope="scope">
{{scope.row.used || 0}}GB
</template>
</el-table-column>
<el-table-column :label="lang.flow_packet_expire_time" min-width="180">
<template slot-scope="scope">
<span v-if="scope.row.expire_time === 0">/</span>
<span v-else>{{scope.row.expire_time | formateTime}}</span>
</template>
</el-table-column>
<el-table-column :label="lang.flow_packet_status" min-width="100">
<template slot-scope="scope">
<el-tag v-if="scope.row.status === 1" type="success" size="small">{{lang.flow_packet_status_valid}}</el-tag>
<el-tag v-else type="warning" size="small">{{lang.flow_packet_status_invalid}}</el-tag>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination :page-data="flowParams" v-if="flowParams.total > 0"
@sizechange="handleFlowPageSizeChange" @currentchange="handleFlowPageChange">
</pagination>
</div>
</component>
</div>
`,
mixins: [mixin],
components: {
pagination
},
filters: {
filterMoney (money) {
if (isNaN(money)) {
return '0.00';
} else {
const temp = `${money}`.split('.');
return parseInt(temp[0]).toLocaleString() + '.' + (temp[1] || '00');
}
},
formateTime (time) {
if (time && time !== 0) {
return formateDate(time * 1000);
} else {
return "--";
}
},
},
data () {
return {
hasFlow: true,
packageLoading: false,
submitLoading: false,
packageList: [],
curPackageId: '',
currencyPrefix: JSON.parse(localStorage.getItem("common_set_before")).currency_prefix,
filterType: 0, // 1: 不限时, 0: 限时
sliderValue: 10, // 滑动条的值
showSlider: false, // 是否显示滑动条
sliderMin: 0,
sliderMax: 100,
flowPacketList: [],
flowPacketLoading: false,
flowParams: {
page: 1,
limit: 20,
pageSizes: [20, 50, 100],
total: 0
},
showFlowListDialog: false, // 控制流量包列表弹窗
};
},
props: {
id: {
type: Number | String,
required: true,
},
showPackage: {
type: Boolean
},
module: {
type: String,
default: 'mf_cloud_mysql'
},
lineType: {
type: String,
default: 'bw'
},
displayMode: {
type: String,
default: 'inline' // inline | dialog (401使用dialog)
}
},
mounted () {
this.hasFlow = this.addons_js_arr.includes('FlowPacket');
if (this.hasFlow) {
this.getPackageList();
this.getFlowPacketList();
}
},
computed: {
showFlowPacketList() {
const hiddenModules = ['mf_dcim', 'remf_dcim'];
return !hiddenModules.includes(this.module);
},
// 处理滑动条空值问题
sliderValueComputed: {
get () {
return this.sliderValue === '' || this.sliderValue === null ? this.sliderMin : this.sliderValue;
},
set (val) {
this.sliderValue = val;
}
},
// 根据 filterType 筛选流量包
filteredPackageList () {
return this.packageList.filter(item => item.long_term_valid === this.filterType);
},
// 获取当前选中的流量包
currentPackage () {
return this.packageList.find(item => item.id === this.curPackageId);
},
// 滑动条标记
sliderMarks () {
return {
[this.sliderMin]: this.sliderMin + 'GB',
[this.sliderMax]: this.sliderMax + 'GB'
};
},
// 是否有限时流量包
hasLimitTime () {
return this.packageList.some(item => item.long_term_valid === 0);
},
// 是否有不限时流量包
hasUnlimitedTime () {
return this.packageList.some(item => item.long_term_valid === 1);
},
// 动态容器组件
listWrapper () {
return this.displayMode === 'dialog' ? 'el-dialog' : 'div';
},
// 动态容器属性
listWrapperProps () {
if (this.displayMode === 'dialog') {
return {
width: "1200px",
visible: this.showFlowListDialog,
'custom-class': 'flow-list-dialog'
};
}
return {
class: 'flow-packet-list'
};
},
// 动态容器事件
listWrapperEvents () {
if (this.displayMode === 'dialog') {
return {
'update:visible': (val) => { this.showFlowListDialog = val; }
};
}
return {};
},
},
watch: {
// 监听筛选类型变化,自动选中第一个
filteredPackageList (newList) {
if (newList.length > 0) {
this.curPackageId = newList[0].id;
this.choosePackage(newList[0]);
}
}
},
methods: {
async getPackageList () {
try {
this.packageLoading = true;
const res = await getFlowPacket({
id: this.id,
page: 1,
limit: 9999,
});
this.packageList = res.data.data.list;
if (this.packageList.length === 0) {
this.$emit('cancledialog', false);
return;
}
// 根据数据设置默认 filterType 和选中流量包
const limitTimeList = this.packageList.filter(item => item.long_term_valid === 0);
const unlimitedTimeList = this.packageList.filter(item => item.long_term_valid === 1);
// 优先选限时,只有不限时时才选不限时
if (limitTimeList.length > 0) {
this.filterType = 0;
this.curPackageId = limitTimeList[0].id;
this.choosePackage(limitTimeList[0]);
} else if (unlimitedTimeList.length > 0) {
this.filterType = 1;
this.curPackageId = unlimitedTimeList[0].id;
this.choosePackage(unlimitedTimeList[0]);
}
this.packageLoading = false;
} catch (error) {
this.packageLoading = false;
}
},
choosePackage (item) {
this.curPackageId = item.id;
// 如果 billing_mode = 1显示滑动条
if (item.billing_mode === 1) {
this.showSlider = true;
this.sliderMin = item.min_capacity;
this.sliderMax = item.max_capacity;
this.sliderValue = item.min_capacity;
} else {
this.showSlider = false;
}
},
async handlerPackage () {
try {
this.submitLoading = true;
const params = {
id: this.id,
flow_packet_id: this.curPackageId,
};
// 如果是范围流量包,传递滑动条的值
if (this.currentPackage && this.currentPackage.billing_mode === 1) {
params.selected_capacity = this.sliderValue;
}
const res = await buyFlowPacket(params);
this.$emit('sendpackid', res.data.data.id);
this.submitLoading = false;
} catch (error) {
this.submitLoading = false;
this.$message.error(error.data.msg);
}
},
cancleDialog () {
this.$emit('cancledialog', false);
},
// 获取流量包列表
async getFlowPacketList () {
// mf_dcim 和 remf_dcim 不请求流量包列表
if (!this.showFlowPacketList) return;
try {
this.flowPacketLoading = true;
const res = await getFlowPacketByModule(this.module, {
id: this.id,
...this.flowParams
});
this.flowPacketList = res.data.data.list || [];
this.flowParams.total = res.data.data.count || 0;
this.flowPacketLoading = false;
} catch (error) {
this.flowPacketLoading = false;
this.$message.error(error.data.msg);
}
},
// 分页变化
handleFlowPageChange (page) {
this.flowParams.page = page;
this.getFlowPacketList();
},
handleFlowPageSizeChange (e) {
this.flowParams.limit = e;
this.getFlowPacketList();
},
// 计算显示价格
getDisplayPrice (item) {
let price;
if (item.billing_mode === 0) {
price = item.price;
} else {
if (item.id === this.curPackageId) {
price = this.sliderValue * item.price;
} else {
price = item.min_capacity * item.price;
}
}
return Math.round(price * 100) / 100;
},
// 切换筛选类型时移除焦点
onFilterTypeChange () {
document.activeElement?.blur();
},
// 打开流量包列表弹窗
openFlowListDialog () {
this.showFlowListDialog = true;
this.getFlowPacketList();
}
},
};

View File

@@ -0,0 +1,47 @@
/* 处理产品转移中的状态 */
const hostStatus = {
template:
`
<div>
<div class="host-is-transfer" v-if="isTransferring">{{lang.host_transferring}}</div>
<slot v-else></slot>
</div>
`,
data () {
return {
isTransferring: false,
};
},
props: {
id: {
type: Number | String,
required: true,
default: null,
},
status: {
type: String,
required: true,
default: '',
}
},
methods: {
async getHostTransferStatus () {
try {
const res = await hostIsTransfer({ id: this.id * 1 });
const transferring = res.data.data.status;
// 状态为 Active 且 transferring === 1 的时候显示转移中
this.isTransferring = transferring && this.status === 'Active';
} catch (error) {
this.$message.error(error.data.msg);
}
}
},
mounted () {
const arr = JSON.parse(
document.querySelector("#addons_js").getAttribute("addons_js")
).map((item) => {
return item.name;
});
arr.includes("HostTransfer") && this.getHostTransferStatus();
}
};

View File

@@ -0,0 +1,827 @@
const ipDefase = {
template: /*html*/ `
<div>
<div class="ip-defase-box">
<div class="fire-list-search">
<el-select v-model="params.status" clearable :placeholder="lang.ipDefase_text1">
<el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
<el-input v-model="params.keywords" clearable @keyup.enter.native="search" :placeholder="lang.ipDefase_text2">
</el-input>
<el-button type="primary" @click="search">{{lang.ipDefase_text3}}</el-button>
</div>
<el-table v-loading="tabeLoading" :data="productList" @sort-change="sortChange"
style="width: 100%; margin:0.2rem 0;">
<el-table-column prop="id" label="ID">
</el-table-column>
<el-table-column prop="host_ip" :label="lang.ipDefase_text4" sortable>
</el-table-column>
<el-table-column prop="defense_peak" :label="lang.ipDefase_text14">
<template slot-scope="{row}">
<span>{{row.defense_peak || '--'}}</span>
</template>
</el-table-column>
<el-table-column prop="due_time" :label="lang.index_text13">
<template slot-scope="{row}">
<span>{{row.due_time | formateTime }}</span>
</template>
</el-table-column>
<el-table-column prop="status" :label="lang.ipDefase_text5" width="200">
<template slot-scope="{row}">
<template v-if="row.statusLoading">
<i class="primary-icon el-icon-loading"></i>
</template>
<template v-else>
{{getStatusLabel(row.status)}}
<i @click="getProductStatus(row)" class="primary-icon el-icon-refresh"></i>
</template>
</template>
</el-table-column>
<el-table-column prop="op" :label="lang.ipDefase_text6" width="160" fixed="right">
<template slot-scope="{row}">
<div class="operation">
<el-button type="text" @click="showRenew(row)" v-if="row.sub_host_id">{{lang.cloud_re_btn}}</el-button>
<el-button type="text" @click="openUpgradeDialog(row)">{{lang.ipDefase_text7}}</el-button>
</div>
</template>
</el-table-column>
</el-table>
<pagination :page-data="params" @sizechange="sizeChange" @currentchange="currentChange"></pagination>
</div>
<div class="upgrade-dialog" v-if="isShowUpgrade">
<el-dialog width="9.5rem" :visible.sync="isShowUpgrade" :show-close="false" @close="upgradeDgClose">
<div class="dialog-title">{{lang.ipDefase_text11}}</div>
<div class="dialog-main">
<div class="ipDefase-now-info">
<div class="now-text">{{lang.ipDefase_text15}}{{ipInfo.host_ip}}</div>
<div class="now-text">{{lang.ipDefase_text16}}{{getStatusLabel(ipInfo.status)}}</div>
<div class="now-text">{{lang.ipDefase_text17}}{{ipInfo.defense_peak || '--'}}</div>
</div>
<el-form ref="ipDefaseForm" label-position="left" label-width="100px" hide-required-asterisk>
<!-- 防御 -->
<el-form-item :label="lang.ipDefase_text12">
<el-radio-group v-model="defenseName">
<el-radio-button :label="c.desc" v-for="(c,cInd) in defenceList" :key="cInd" @click.native="chooseDefence($event,c)">
</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item :label="lang.cart_tip_text12" v-if="upDurationList.length > 0">
<el-radio-group v-model="upParams.duration_id">
<el-radio-button :label="c.id" v-for="c in upDurationList" :key="c.id" @click.native="changeDuration($event,c)">
{{c.name_show}}
</el-radio-button>
</el-radio-group>
</el-form-item>
</el-form>
</div>
<div class="dialog-footer">
<div class="footer-top">
<div class="money-text">{{lang.ipDefase_text13}}</div>
<div class="money" v-loading="upgradePriceLoading">
<span class="money-num">{{commonData.currency_prefix }} {{ upParams.totalPrice | filterMoney}}</span>
<el-popover placement="top-start" width="200" trigger="hover"
v-if="isShowLevel || (isShowPromo && upParams.isUseDiscountCode)">
<div class="show-config-list">
<p v-if="isShowLevel">
{{lang.shoppingCar_tip_text2}}{{commonData.currency_prefix}}
{{ upParams.clDiscount | filterMoney }}
</p>
<p v-if="isShowPromo && upParams.isUseDiscountCode">
{{lang.shoppingCar_tip_text4}}{{commonData.currency_prefix}}
{{ upParams.code_discount | filterMoney}}
</p>
</div>
<i class="el-icon-warning-outline total-icon" slot="reference"></i>
</el-popover>
<p class="original-price" v-if="upParams.totalPrice != upParams.original_price">
{{commonData.currency_prefix}} {{ upParams.original_price |
filterMoney}}
</p>
<div class="code-box" v-if="false">
<!-- 优惠码 -->
<discount-code v-show="isShowPromo && !upParams.customfield.promo_code "
@get-discount="getUpDiscount(arguments)" scene="upgrade" :product_id="product_id"
:amount="upParams.original_price" :billing_cycle_time="billing_cycle_time">
</discount-code>
</div>
<div class="code-number-text">
<div class="discount-codeNumber" v-show="upParams.customfield.promo_code">
{{ upParams.customfield.promo_code }}<i class="el-icon-circle-close remove-discountCode"
@click="removeUpDiscountCode()"></i>
</div>
</div>
</div>
</div>
<div class="footer-bottom">
<el-button class="btn-ok" @click="upgradeSub" :loading="loading4">
{{lang.security_btn5}}
</el-button>
<div class="btn-no" @click="upgradeDgClose">
{{lang.security_btn6}}
</div>
</div>
</div>
</el-dialog>
</div>
<!-- 续费弹窗 -->
<div class="renew-dialog">
<el-dialog width="6.9rem" :visible.sync="isShowRenew" :show-close="false" @close="renewDgClose">
<div class="dialog-title">{{lang.common_cloud_title10}}</div>
<div class="dialog-main">
<div class="renew-content">
<div class="renew-item" :class="renewActiveId==item.id?'renew-active':''" v-for="item in renewPageData"
:key="item.id" @click="renewItemChange(item)">
<div class="item-top">{{item.customfield?.multi_language?.billing_cycle || item.billing_cycle}}</div>
<div class="item-bottom" v-if="isShowPromo && renewParams.isUseDiscountCode">
{{commonData.currency_prefix + item.base_price}}
</div>
<div class="item-bottom" v-else>
{{commonData.currency_prefix + item.price}}
</div>
<div class="item-origin-price"
v-if="item.price*1 < item.base_price*1 && !renewParams.isUseDiscountCode">
{{commonData.currency_prefix + item.base_price}}
</div>
<i class="el-icon-check check" v-show="renewActiveId==item.id"></i>
</div>
</div>
<div class="pay-content">
<div class="pay-price">
<div class="money" v-loading="renewLoading">
<span class="text">{{lang.common_cloud_label11}}:</span>
<span>{{commonData.currency_prefix}}{{renewParams.totalPrice |
filterMoney}}</span>
<el-popover placement="top-start" width="200" trigger="hover"
v-if="(isShowLevel && renewParams.clDiscount*1 > 0) || (isShowPromo && renewParams.isUseDiscountCode)">
<div class="show-config-list">
<p v-if="isShowLevel && renewParams.clDiscount*1 > 0">
{{lang.shoppingCar_tip_text2}}{{commonData.currency_prefix}}
{{ renewParams.clDiscount | filterMoney}}
</p>
<p v-if="isShowPromo && renewParams.isUseDiscountCode">
{{lang.shoppingCar_tip_text4}}{{commonData.currency_prefix}}
{{ renewParams.code_discount | filterMoney }}
</p>
</div>
<i class="el-icon-warning-outline total-icon" slot="reference"></i>
</el-popover>
<p class="original-price"
v-if="renewParams.customfield.promo_code && renewParams.totalPrice != renewParams.base_price">
{{commonData.currency_prefix}} {{ renewParams.base_price |
filterMoney}}
</p>
<p class="original-price"
v-if="!renewParams.customfield.promo_code && renewParams.totalPrice != renewParams.original_price">
{{commonData.currency_prefix}} {{ renewParams.original_price
| filterMoney}}
</p>
<div class="code-box" v-if="false">
<!-- 优惠码 -->
<discount-code v-show="isShowPromo && !renewParams.customfield.promo_code"
@get-discount="getRenewDiscount(arguments)" scene="renew" :product_id="product_id"
:amount="renewParams.base_price" :billing_cycle_time="renewParams.duration">
</discount-code>
</div>
<div class="code-number-text">
<div class="discount-codeNumber" v-show="renewParams.customfield.promo_code">
{{ renewParams.customfield.promo_code }}<i class="el-icon-circle-close remove-discountCode"
@click="removeRenewDiscountCode()"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="dialog-footer">
<el-button class="btn-ok" @click="subRenew" :loading="subRenewLoading">
{{lang.common_cloud_btn30}}
</el-button>
<el-button class="btn-no" @click="renewDgClose">
{{lang.common_cloud_btn29}}
</el-button>
</div>
</el-dialog>
</div>
<pay-dialog ref="ipDefasePayDialog" @payok="paySuccess" @paycancel="payCancel"></pay-dialog>
</div>
`,
data() {
return {
commonData: {},
params: {
keywords: "",
status: "",
page: 1,
limit: 20,
pageSizes: [20, 50, 100],
total: 0,
orderby: "id",
sort: "desc",
},
tabeLoading: false,
statusOptions: [
{label: lang.ipDefase_text8, value: 0},
{label: lang.ipDefase_text9, value: 1},
{label: lang.ipDefase_text10, value: 2},
],
productList: [],
isShowUpgrade: false,
isShowLevel: false,
isShowPromo: false,
upParams: {
customfield: {
promo_code: "", // 优惠码
},
duration_id: "", // 周期
isUseDiscountCode: false, // 是否使用优惠码
clDiscount: 0, // 用户等级折扣价
code_discount: 0, // 优惠码折扣价
original_price: 0, // 原价
totalPrice: 0, // 现价
},
upgradePriceLoading: false,
loading4: false,
defenceList: [],
peak_defence: "",
defenseName: "",
ipInfo: {},
ip: "",
isInit: true,
isShowRenew: false, // 续费的总计loading
renewBtnLoading: false, // 续费按钮的loading
// 续费页面信息
renewPageData: [],
renewPriceList: [],
renewActiveId: "",
renewLoading: false, // 续费计算折扣loading
// 续费参数
renewParams: {
id: 0, //默认选中的续费id
isUseDiscountCode: false, // 是否使用优惠码
customfield: {
promo_code: "", // 优惠码
},
duration: "", // 周期
billing_cycle: "", // 周期时间
clDiscount: 0, // 用户等级折扣价
code_discount: 0, // 优惠码折扣价
original_price: 0, // 原价
base_price: 0,
totalPrice: 0, // 现价
},
sub_host_id: 0,
subRenewLoading: false,
upDurationList: [],
};
},
components: {
payDialog,
pagination,
discountCode,
cashBack,
},
props: {
id: {
// 产品id
type: String | Number,
required: true,
},
// 场景中的所有商品ID
product_id: {
type: String | Number,
required: true,
},
billing_cycle_time: {
type: String | Number,
required: true,
},
module: {
type: String,
required: true,
},
listmode: {
type: String,
required: true,
},
showip: {
type: String,
required: false,
},
},
watch: {
renewParams: {
handler() {
let n = 0;
// l:当前周期的续费价格
if (this.isShowPromo && this.renewParams.customfield.promo_code) {
// n: 算出来的价格
n =
(this.renewParams.base_price * 1000 -
this.renewParams.clDiscount * 1000 -
this.renewParams.code_discount * 1000) /
1000 >
0
? (this.renewParams.base_price * 1000 -
this.renewParams.clDiscount * 1000 -
this.renewParams.code_discount * 1000) /
1000
: 0;
} else {
// n: 算出来的价格
n =
(this.renewParams.original_price * 1000 -
this.renewParams.clDiscount * 1000 -
this.renewParams.code_discount * 1000) /
1000 >
0
? (this.renewParams.original_price * 1000 -
this.renewParams.clDiscount * 1000 -
this.renewParams.code_discount * 1000) /
1000
: 0;
}
let t = n;
// 如果当前周期和选择的周期相同,则和当前周期对比价格
// if (
// this.hostData.billing_cycle_time === this.renewParams.duration ||
// this.hostData.billing_cycle_name === this.renewParams.billing_cycle
// ) {
// // 谁大取谁
// t = n;
// }
this.renewParams.totalPrice =
t * 1000 > 0 ? ((t * 1000) / 1000).toFixed(2) : 0;
},
immediate: true,
deep: true,
},
},
created() {
this.getProductList();
this.getCommonData();
},
mounted() {
this.isShowLevel = havePlugin("IdcsmartClientLevel");
this.isShowPromo = havePlugin("PromoCode");
},
filters: {
formateTime(time) {
if (time && time !== 0) {
return formateDate(time * 1000);
} else {
return "--";
}
},
// 返回剩余到期时间
formateDueDay(time) {
return Math.floor((time * 1000 - Date.now()) / (1000 * 60 * 60 * 24));
},
filterMoney(money) {
if (isNaN(money) || money * 1 < 0) {
return "0.00";
} else {
return formatNuberFiexd(money);
}
},
},
methods: {
paySuccess(e) {
this.isShowUpgrade = false;
this.$emit("success");
setTimeout(() => {
this.getProductList();
}, 1000);
},
// 取消支付回调
payCancel(e) {},
openUpgradeDialog(row) {
this.ipInfo = row;
this.ip = row.host_ip;
this.getUpConfig();
},
getUpConfig() {
apiGetUpDefenceConfig(
{
id: this.id,
ip: this.ip,
},
this.module
)
.then((res) => {
this.defenceList = res.data.data?.defence || [];
this.peak_defence = res.data.data?.current_defence
? res.data.data?.current_defence
: res.data.data?.defence[0]?.value || "";
this.defenseName = this.defenceList.find(
(item) => item.value === this.peak_defence
)?.desc;
this.isShowUpgrade = true;
this.getCycleList();
})
.catch((err) => {
err.data.msg && this.$message.error(err.data.msg);
});
},
chooseDefence(e, c) {
this.defenseName = c.desc;
this.peak_defence = c.value;
this.getCycleList();
e.preventDefault();
},
changeDuration(e, c) {
this.upParams.duration_id = c.id;
this.getCycleList();
e.preventDefault();
},
// 关闭升降级弹窗
upgradeDgClose() {
this.isShowUpgrade = false;
this.removeUpDiscountCode(false);
},
// 升降级使用优惠码
getUpDiscount(data) {
this.upParams.customfield.promo_code = data[1];
this.upParams.isUseDiscountCode = true;
this.upParams.code_discount = Number(data[0]);
this.getCycleList();
},
// 移除升降级优惠码
removeUpDiscountCode(flag = true) {
this.upParams.isUseDiscountCode = false;
this.upParams.customfield.promo_code = "";
this.upParams.code_discount = 0;
if (flag) {
this.getCycleList();
}
},
// 获取升降级价格
getCycleList() {
this.upgradePriceLoading = true;
const params = {
id: this.id,
ip: this.ip,
peak_defence: this.peak_defence,
duration_id: this.upParams.duration_id,
};
apiCalculateUpDefencePrice(params, this.module)
.then(async (res) => {
if (res.data.status == 200) {
this.upDurationList = res.data.data.durations;
if (!this.upParams.duration_id) {
this.upParams.duration_id =
res.data.data.duration_id || this.upDurationList[0]?.id;
}
if (res.data.data.durations?.length === 0) {
this.upParams.duration_id = "";
}
let price = res.data.data.price; // 当前产品的价格
if (price < 0) {
this.upParams.original_price = 0;
this.upParams.totalPrice = 0;
this.upgradePriceLoading = false;
return;
}
this.upParams.original_price = price;
this.upParams.totalPrice = price;
// 开启了等级优惠
if (this.isShowLevel) {
await clientLevelAmount({id: this.product_id, amount: price})
.then((ress) => {
this.upParams.clDiscount = Number(ress.data.data.discount);
})
.catch(() => {
this.upParams.clDiscount = 0;
});
}
// 开启了优惠码插件
if (this.isShowPromo) {
// 更新优惠码
await applyPromoCode({
// 开启了优惠券
scene: "upgrade",
product_id: this.product_id,
amount: price,
billing_cycle_time: this.billing_cycle_time,
promo_code: this.upParams.customfield.promo_code,
host_id: this.id,
})
.then((resss) => {
this.upParams.isUseDiscountCode = true;
this.upParams.code_discount = Number(
resss.data.data.discount
);
})
.catch((err) => {
this.upParams.isUseDiscountCode = false;
this.upParams.customfield.promo_code = "";
this.upParams.code_discount = 0;
this.$message.error(err.data.msg);
});
}
this.upParams.totalPrice =
(price * 1000 -
this.upParams.clDiscount * 1000 -
this.upParams.code_discount * 1000) /
1000 >
0
? (
(price * 1000 -
this.upParams.clDiscount * 1000 -
this.upParams.code_discount * 1000) /
1000
).toFixed(2)
: 0;
this.upgradePriceLoading = false;
} else {
this.upParams.original_price = 0;
this.upParams.clDiscount = 0;
this.upParams.isUseDiscountCode = false;
this.upParams.customfield.promo_code = "";
this.upParams.code_discount = 0;
this.upParams.totalPrice = 0;
this.upgradePriceLoading = false;
}
})
.catch((error) => {
this.upDurationList = [];
this.upParams.duration_id = "";
this.upParams.original_price = 0;
this.upParams.clDiscount = 0;
this.upParams.isUseDiscountCode = false;
this.upParams.customfield.promo_code = "";
this.upParams.code_discount = 0;
this.upParams.totalPrice = 0;
this.upgradePriceLoading = false;
});
},
// 升降级提交
upgradeSub() {
const params = {
id: this.id,
ip: this.ip,
peak_defence: this.peak_defence,
duration_id: this.upParams.duration_id,
customfield: this.upParams.customfield,
};
this.loading4 = true;
apiGenerateUpDefenceOrder(params, this.module)
.then((res) => {
if (res.data.status === 200) {
this.$message.success(lang.common_cloud_text56);
const orderId = res.data.data.id;
// 调支付弹窗
this.$refs.ipDefasePayDialog.showPayDialog(orderId, 0);
} else {
this.$message.error(err.data.msg);
}
})
.catch((err) => {
this.$message.error(err.data.msg);
})
.finally(() => {
this.loading4 = false;
});
},
goProductDetail(row) {
window.open(`/productdetail.htm?id=${row.host_id}&showUp=1`);
},
getStatusLabel(status) {
return (
this.statusOptions.find((item) => item.value === status)?.label || "--"
);
},
search() {
this.params.page = 1;
this.getProductList();
},
// 每页展示数改变
sizeChange(e) {
this.params.limit = e;
this.params.page = 1;
// 获取列表
this.getProductList();
},
// 当前页改变
currentChange(e) {
this.params.page = e;
this.getProductList();
},
sortChange({prop, order}) {
this.params.orderby = order ? prop : "id";
this.params.sort = order === "ascending" ? "asc" : "desc";
this.getProductList();
},
// 获取产品状态
getProductStatus(item, isInit = false) {
!isInit && (item.statusLoading = true);
apiProductRefreshHostIpStatus({id: item.id}, this.listmode)
.then((res) => {
item.status = res.data.data.status;
if (item.statusLoading) {
this.$message.success(res.data.msg);
}
item.statusLoading = false;
})
.catch((err) => {
item.statusLoading = false;
this.$message.error(err.data.msg);
});
},
getProductList() {
this.tabeLoading = true;
apiProductGetHostIp({...this.params, host_id: this.id}, this.listmode)
.then(async (res) => {
this.tabeLoading = false;
this.productList = res.data.data.list.map((item) => {
item.statusLoading = false;
this.getProductStatus(item, true);
return item;
});
this.params.total = res.data.data.count;
if (this.showip && this.isInit) {
this.isInit = false;
const ipInfo = this.productList.find(
(item) => item.host_ip == this.showip
);
if (ipInfo) {
this.openUpgradeDialog(ipInfo);
}
}
})
.catch((err) => {
this.tabeLoading = false;
this.$message.error(err.data.msg);
});
},
getCommonData() {
this.commonData = JSON.parse(localStorage.getItem("common_set_before"));
},
// 续费使用优惠码
async getRenewDiscount(data) {
this.renewParams.customfield.promo_code = data[1];
this.renewParams.isUseDiscountCode = true;
this.renewParams.code_discount = Number(data[0]);
const price = this.renewParams.base_price;
const discountParams = {id: this.product_id, amount: price};
// 开启了等级折扣插件
if (this.isShowLevel) {
// 获取等级抵扣价格
await clientLevelAmount(discountParams)
.then((res2) => {
if (res2.data.status === 200) {
this.renewParams.clDiscount = Number(res2.data.data.discount); // 客户等级优惠金额
}
})
.catch((error) => {
this.renewParams.clDiscount = 0;
});
}
},
// 移除续费的优惠码
removeRenewDiscountCode() {
this.renewParams.isUseDiscountCode = false;
this.renewParams.customfield.promo_code = "";
this.renewParams.code_discount = 0;
this.renewParams.clDiscount = 0;
const price = this.renewParams.original_price;
},
// 显示续费弹窗
showRenew(row) {
this.sub_host_id = row.sub_host_id;
if (this.renewBtnLoading) return;
this.renewBtnLoading = true;
// 获取续费页面信息
const params = {
id: this.sub_host_id,
};
this.isShowRenew = true;
this.renewLoading = true;
renewPage(params)
.then((res) => {
if (res.data.status === 200) {
this.renewBtnLoading = false;
this.renewPageData = res.data.data.host;
this.renewActiveId = this.renewPageData[0].id;
this.renewParams.billing_cycle =
this.renewPageData[0].billing_cycle;
this.renewParams.duration = this.renewPageData[0].duration;
this.renewParams.original_price = this.renewPageData[0].price;
this.renewParams.base_price = this.renewPageData[0].base_price;
}
this.renewLoading = false;
})
.catch((err) => {
this.renewBtnLoading = false;
this.renewLoading = false;
this.$message.error(err.data.msg);
});
},
getRenewPrice() {
renewPage({id: this.id})
.then(async (res) => {
if (res.data.status === 200) {
this.renewPriceList = res.data.data.host;
}
})
.catch((err) => {
this.renewPriceList = [];
});
},
// 续费弹窗关闭
renewDgClose() {
this.isShowRenew = false;
this.removeRenewDiscountCode();
},
// 续费提交
subRenew() {
this.subRenewLoading = true;
const params = {
id: this.sub_host_id,
billing_cycle: this.renewParams.billing_cycle,
customfield: this.renewParams.customfield,
};
renew(params)
.then((res) => {
this.subRenewLoading = false;
if (res.data.status === 200) {
this.$message.success(res.data.msg);
this.isShowRenew = false;
if (res.data.code == "Paid") {
this.getProductList();
} else {
this.$refs.ipDefasePayDialog.showPayDialog(res.data.data.id);
}
}
})
.catch((err) => {
this.subRenewLoading = false;
this.$message.error(err.data.msg);
});
},
// 续费周期点击
async renewItemChange(item) {
this.renewLoading = true;
this.renewActiveId = item.id;
this.renewParams.duration = item.duration;
this.renewParams.billing_cycle = item.billing_cycle;
this.renewParams.original_price = item.price;
this.renewParams.base_price = item.base_price;
// 开启了优惠码插件
if (this.isShowPromo && this.renewParams.isUseDiscountCode) {
const discountParams = {id: this.product_id, amount: item.base_price};
// 开启了等级折扣插件
if (this.isShowLevel) {
// 获取等级抵扣价格
await clientLevelAmount(discountParams)
.then((res2) => {
if (res2.data.status === 200) {
this.renewParams.clDiscount = Number(res2.data.data.discount); // 客户等级优惠金额
}
})
.catch((error) => {
this.renewParams.clDiscount = 0;
});
}
// 更新优惠码
await applyPromoCode({
scene: "renew",
product_id: this.product_id,
amount: item.base_price,
billing_cycle_time: this.renewParams.duration,
promo_code: this.renewParams.customfield.promo_code,
})
.then((resss) => {
price = item.base_price;
this.renewParams.isUseDiscountCode = true;
this.renewParams.code_discount = Number(resss.data.data.discount);
})
.catch((err) => {
this.$message.error(err.data.msg);
this.removeRenewDiscountCode();
});
}
this.renewLoading = false;
},
},
};

View File

@@ -0,0 +1,452 @@
// 验证码通过
function captchaCheckSuccsss(bol, captcha, token) {
if (bol) {
// 验证码验证通过
getData(captcha, token)
}
};
// 取消验证码验证
function captchaCheckCancel() {
captchaCancel()
};
const loginDialog = {
template:
`
<div>
<!-- 验证码 -->
<captcha-dialog :is-show-captcha="isShowCaptcha" ref="captcha"></captcha-dialog>
<el-dialog width="16.95rem" custom-class='login-dialog' :visible.sync="visible" :show-close=true>
<div class="login-left">
<div class="login-text">
<div class="login-text-title">{{lang.login}}</div>
<div class="login-text-regist" v-if="commonData.register_email == 1 || commonData.register_phone == 1">
{{lang.login_no_account}}<a @click="toRegist">{{lang.login_regist_text}}</a>
</div>
</div>
<div class="login-form">
<div class="login-top">
<div v-show="isPassOrCode" class="login-email" :class="isEmailOrPhone? 'active':null" @click="isEmailOrPhone = true">{{lang.login_email}}
</div>
<div class="login-phone" :class="!isEmailOrPhone? 'active':null" @click="isEmailOrPhone = false">{{lang.login_phone}}
</div>
</div>
<div class="form-main">
<div class="form-item">
<el-input v-if="isEmailOrPhone" v-model="formData.email" :placeholder="lang.login_placeholder_pre + lang.login_email"></el-input>
<el-input v-else class="input-with-select select-input" v-model="formData.phone" :placeholder="lang.login_placeholder_pre + lang.login_phone">
<el-select filterable slot="prepend" v-model="formData.countryCode">
<el-option v-for="item in countryList" :key="item.name" :value="item.phone_code" :label="item.name_zh + '+' + item.phone_code"></el-option>
</el-select>
</el-input>
</div>
<div v-if="isPassOrCode" class="form-item">
<el-input :placeholder="lang.login_pass" v-model="formData.password" type="password">
</el-input>
</div>
<div v-else class="form-item code-item">
<!-- 邮箱验证码 -->
<el-input v-if="isEmailOrPhone" v-model="formData.emailCode" :placeholder="lang.email_code">
</el-input>
<count-down-button ref="emailCodebtn" @click.native="sendEmailCode" v-if="isEmailOrPhone" my-class="code-btn"></count-down-button>
<!-- <el-button v-if="isEmailOrPhone" class="code-btn" type="primary">获取验证码</el-button> -->
<!-- 手机验证码 -->
<el-input v-if="!isEmailOrPhone" v-model="formData.phoneCode" :placeholder="lang.login_phone_code">
</el-input>
<count-down-button ref="phoneCodebtn" @click.native="sendPhoneCode" v-if="!isEmailOrPhone" my-class="code-btn"></count-down-button>
<!-- <el-button v-if="!isEmailOrPhone" class="code-btn" type="primary">获取验证码</el-button> -->
</div>
<div class="form-item rember-item">
<el-checkbox v-model="formData.isRemember">{{lang.login_remember}}</el-checkbox>
<a @click="toForget">{{lang.login_forget}}</a>
</div>
<div class="read-item" v-if="errorText.length !== 0">
<el-alert :title="errorText" type="error" show-icon :closable="false">
</el-alert>
</div>
<div class="form-item">
<el-button type="primary" class="login-btn" @click="doLogin">{{lang.login}}</el-button>
</div>
<div class="form-item read-item">
<el-checkbox v-model="checked">
{{lang.login_read}}<a @click="toService">{{lang.read_service}}</a>{{lang.read_and}}<a @click="toPrivacy">{{lang.read_privacy}}</a>
</el-checkbox>
</div>
<div class="form-item line-item" v-if="commonData.login_phone_verify == 1">
<el-divider><span class="text">or</span></el-divider>
</div>
<div class="form-item" v-if="commonData.login_phone_verify == 1">
<el-button v-if="isPassOrCode" :disabled="commonData.login_phone_verify == 0" @click="isPassOrCode = false;isEmailOrPhone = false" class="type-btn">{{lang.login_code_login}}
</el-button>
<el-button v-else @click="isPassOrCode = true" class="type-btn">
{{lang.login_pass_login}}
</el-button>
</div>
</div>
</div>
</div>
<div class="login-right">
<img src="${url}/img/common/login_back.png" class="login-back-img">
</div>
</el-dialog>
</div>
`,
data() {
return {
visible: true,
// 登录是否需要验证
isCaptcha: false,
isShowCaptcha: false, //登录是否显示验证码弹窗
checked: getCookie("checked") == "1" ? true : false,
isEmailOrPhone: getCookie("isEmailOrPhone") == "1" ? true : false, // true:电子邮件 false:手机号
isPassOrCode: getCookie("isPassOrCode") == "1" ? true : false, // true:密码登录 false:验证码登录
errorText: "",
formData: {
email: getCookie("email") ? getCookie("email") : null,
phone: getCookie("phone") ? getCookie("phone") : null,
password: getCookie("password") ? getCookie("password") : null,
phoneCode: "",
emailCode: "",
isRemember: getCookie("isRemember") == "1" ? true : false,
countryCode: 86
},
token: "",
captcha: '',
countryList: [],
commonData: {}
}
},
components: {
countDownButton,
captchaDialog
},
props: {
isShowLogin: {
default: false,
type: Boolean,
}
},
created() {
this.getCommonData()
this.getCountryList()
},
mounted() {
window.captchaCancel = this.captchaCancel
window.getData = this.getData
window.showLoginDialog = this.showLoginDialog
},
methods: {
// 获取通用配置
getCommonData() {
this.commonData = JSON.parse(localStorage.getItem('common_set_before'))
if (this.commonData.login_phone_verify == 0) {
this.isPassOrCode = true
}
if (this.commonData.captcha_client_login == 1) {
this.isCaptcha = true
}
},
showLoginDialog() {
this.visible = true
},
toRegist() {
// location.href = 'regist.html'
console.log("显示注册弹窗");
},
toForget() {
// location.href = 'forget.html'
console.log("显示忘记密码弹窗");
},
// 验证码验证成功后的回调
getData(captchaCode, token) {
this.isCaptcha = false
this.token = token
this.captcha = captchaCode
this.isShowCaptcha = false
// 判断是否密码登录 是执行登录
// 否则判断发送手机验证码还是邮箱验证码
if (this.isPassOrCode) {
this.doLogin()
} else {
if (this.isEmailOrPhone) {
// 发送邮箱验证码
this.sendEmailCode()
} else {
// 发送手机验证码
this.sendPhoneCode()
}
}
},
// 登录
doLogin() {
let isPass = true;
const form = { ...this.formData };
console.log(form);
// 邮件登录验证
if (this.isEmailOrPhone) {
if (!form.email) {
isPass = false
this.errorText = "请输入邮箱"
} else if (
form.email.search(
/^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/
) === -1
) {
isPass = false
this.errorText = "邮箱格式不正确"
}
// 邮件 密码登录 验证
if (this.isPassOrCode) { // 密码登录
if (!form.password) {
isPass = false
this.errorText = "请输入密码"
}
} else {
// 邮件 验证码登录 验证
if (!form.emailCode) {
isPass = false
this.errorText = "请输入邮箱验证码"
} else {
if (form.emailCode.length !== 6) {
isPass = false
this.errorText = "邮箱验证码应为6位"
}
}
}
}
// 手机号码登录 验证
if (!this.isEmailOrPhone) {
if (!form.phone) {
isPass = false
this.errorText = "请输入手机号码"
} else {
// 设置正则表达式的手机号码格式 规则 ^起点 $终点 1第一位数是必为1 [3-9]第二位数可取3-9的数字 \d{9} 匹配9位数字
const reg = /^1[3-9]\d{9}$/;
if (!reg.test(form.phone)) {
isPass = false
this.errorText = "请输入正确的手机号"
}
}
// 手机号 密码登录 验证
if (this.isPassOrCode) { // 密码登录
if (!form.password) {
isPass = false
this.errorText = "请输入密码"
}
} else {
// 手机 验证码登录 验证
if (!form.phoneCode) {
isPass = false
this.errorText = "请输入手机验证码"
} else {
if (form.phoneCode.length !== 6) {
isPass = false
this.errorText = "手机验证码应为6位"
}
}
}
}
// 勾选协议
if (!this.checked) {
isPass = false
this.errorText = "请勾选服务协议书!"
}
if (isPass && this.isCaptcha) {
this.isShowCaptcha = true
this.$refs.captcha.doGetCaptcha()
return
}
// 验证通过
if (isPass) {
this.errorText = ""
let code = "";
if (!this.isPassOrCode) {
if (this.isEmailOrPhone) {
code = form.emailCode
} else {
code = form.phoneCode
}
}
const params = {
type: this.isPassOrCode ? "password" : "code",
account: this.isEmailOrPhone ? form.email : form.phone,
phone_code: form.countryCode.toString(),
code,
password: this.isPassOrCode ? this.encrypt(form.password) : "",
remember_password: form.isRemember ? "1" : "0",
captcha: this.captcha,
token: this.token
}
//调用登录接口
logIn(params).then(res => {
if (res.data.status === 200) {
this.$message.success(res.data.msg);
// 存入 jwt
localStorage.setItem("jwt", res.data.data.jwt);
if (form.isRemember) {// 记住密码
console.log(form);
if (this.isEmailOrPhone) {
console.log("email");
setCookie("email", form.email, 30)
} else {
console.log("phone");
setCookie("phone", form.phone, 30)
}
setCookie("password", form.password, 30)
setCookie("isRemember", form.isRemember ? "1" : "0")
setCookie("checked", this.checked ? "1" : "0")
// 保存登录方式
setCookie("isEmailOrPhone", this.isEmailOrPhone ? "1" : "0")
setCookie("isPassOrCode", this.isPassOrCode ? "1" : "0")
} else {
// 未勾选记住密码
delCookie("email")
delCookie("phone")
delCookie("password")
delCookie("isRemember")
delCookie("checked")
}
location.reload();
}
}).catch(err => {
if (err.data.msg === "请输入图形验证码" || err.data.msg === "图形验证码错误") {
this.isShowCaptcha = true
this.$refs.captcha.doGetCaptcha()
} else {
this.errorText = err.data.msg
// this.$message.error(err.data.msg);
}
})
}
},
// 获取国家列表
getCountryList() {
getCountry({}).then(res => {
console.log(res);
if (res.data.status === 200) {
this.countryList = res.data.data.list
}
})
},
// 加密方法
encrypt(str) {
const key = CryptoJS.enc.Utf8.parse("idcsmart.finance");
const iv = CryptoJS.enc.Utf8.parse("9311019310287172");
var encrypted = CryptoJS.AES.encrypt(str, key, {
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
iv: iv,
}).toString();
return encrypted;
},
// 发送邮箱验证码
sendEmailCode() {
let isPass = true
const form = this.formData
if (!form.email) {
isPass = false
this.errorText = "请输入邮箱"
} else if (
form.email.search(
/^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/
) === -1
) {
isPass = false
this.errorText = "邮箱格式不正确"
}
if (isPass) {
this.errorText = ""
const params = {
action: "login",
email: form.email,
token: this.token,
captcha: this.captcha
}
emailCode(params).then(res => {
if (res.data.status === 200) {
// 执行倒计时
this.$refs.emailCodebtn.countDown()
}
}).catch(err => {
if (err.data.msg === "请输入图形验证码" || err.data.msg === "图形验证码错误") {
this.isShowCaptcha = true
this.$refs.captcha.doGetCaptcha()
} else {
this.errorText = err.data.msg
// this.$message.error(err.data.msg);
}
})
}
},
// 发送手机短信
sendPhoneCode() {
let isPass = true
const form = this.formData
if (!form.phone) {
isPass = false
this.errorText = "请输入手机号码"
} else {
// 设置正则表达式的手机号码格式 规则 ^起点 $终点 1第一位数是必为1 [3-9]第二位数可取3-9的数字 \d{9} 匹配9位数字
const reg = /^1[3-9]\d{9}$/;
if (!reg.test(form.phone)) {
isPass = false
this.errorText = "请输入正确的手机号"
}
}
if (isPass) {
this.errorText = ""
const params = {
action: "login",
phone_code: form.countryCode,
phone: form.phone,
token: this.token,
captcha: this.captcha
}
phoneCode(params).then(res => {
if (res.data.status === 200) {
// 执行倒计时
this.$refs.phoneCodebtn.countDown()
}
}).catch(err => {
if (err.data.msg == "请输入图形验证码" || err.data.msg == "图形验证码错误") {
this.isShowCaptcha = true
this.$refs.captcha.doGetCaptcha()
} else {
this.errorText = err.data.msg
// this.$message.error(err.data.msg);
}
})
}
},
toService() {
const url = this.commonData.terms_service_url
window.open(url);
},
toPrivacy() {
const url = this.commonData.terms_privacy_url
window.open(url);
},
// 验证码 关闭
captchaCancel() {
this.isShowCaptcha = false
}
},
}

View File

@@ -0,0 +1,79 @@
// css 样式依赖common.css
const pagination = {
inheritAttrs: false,
template: `
<div class="myPage custom-pagination">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageData.page"
:page-sizes="pageData.pageSizes" :page-size="pageData.limit"
:layout="layoutToUse"
:total="pageData.total"
:pager-count=5
v-bind="$attrs"
:disabled="pageData.page <= 1 && isNextPageDisabled && showCustomButtons"
>
<span class="page-total">{{lang.total}} {{pageData.total}} {{lang.pieces}}</span>
</el-pagination>
<div class="manual-btn" v-if="showCustomButtons">
<el-button type="primary" size="small" :disabled="pageData.page <= 1" @click="handleChange(0)">{{lang.prev_page}}</el-button>
<el-button type="primary" size="small" :disabled="isNextPageDisabled" @click="handleChange(1)">{{lang.next_page}}</el-button>
</div>
</div>
`,
data () {
return {};
},
computed: {
layoutToUse () {
return this.$attrs.layout || "slot, sizes, prev, pager,jumper, next";
},
isNextPageDisabled () {
return this.curPageLength < this.pageData.limit;
},
},
props: {
pageData: {
default: function () {
return {
page: 1,
pageSizes: [20, 50, 100],
limit: 20,
total: 400,
};
},
},
showCustomButtons: {
type: Boolean,
default: false,
},
curPageLength: {
type: Number,
default: 0,
}
},
methods: {
handleChange (direction) {
if (direction === 0) {
if (this.pageData.page > 1) {
this.pageData.page -= 1;
this.$emit('currentchange', this.pageData.page);
}
} else if (direction === 1) {
if (!this.isNextPageDisabled) {
this.pageData.page += 1;
this.$emit('currentchange', this.pageData.page);
}
}
},
handleSizeChange (e) {
this.pageData.limit = e;
this.$emit("sizechange", e);
},
handleCurrentChange (e) {
this.pageData.page = e;
this.$emit("currentchange", e);
},
},
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,98 @@
const productFilter = {
template: /*html */ `
<div class="product-tab-list">
<el-tabs v-model="select_tab" @tab-click="handelTab" v-if="isInit">
<el-tab-pane v-for="(item,index) in tabList" :key="item.tab" :name="item.tab">
<template #label>
<span>{{item.name}}</span>
<span v-if="count[item.countName] > 0">({{count[item.countName]}})</span>
</template>
</el-tab-pane>
</el-tabs>
</div>
`,
data() {
return {
select_tab: "all",
isInit: false,
};
},
props: {
tabList: {
type: Array,
required: false,
default: () => {
return [
{
name: lang.product_list_status1,
tab: "using",
countName: "using_count",
},
{
name: lang.product_list_status2,
tab: "expiring",
countName: "expiring_count",
},
{
name: lang.product_list_status3,
tab: "overdue",
countName: "overdue_count",
},
{
name: lang.product_list_status4,
tab: "deleted",
countName: "deleted_count",
},
{
name: lang.finance_btn5,
tab: "all",
countName: "all_count",
},
];
},
},
tab: {
type: String,
required: false,
default: "",
},
count: {
type: Object,
required: false,
default: {
all_count: 0, // 全部产品数量
deleted_count: 0, // 已删除产品数量
expiring_count: 0, // 即将到期产品数量
overdue_count: 0, // 已逾期产品数量
using_count: 0, // 正常使用产品数量
},
},
},
// 监听count 数据变化了代表有数据 只执行一次 初始化不执行
watch: {
count: {
handler(newVal) {
if (newVal.list) {
this.isInit = true;
}
},
deep: true,
immediate: true,
},
},
mounted() {
if (this.tab) {
this.select_tab = this.tab;
}
if (this.tab == "") {
this.select_tab = "all";
}
},
methods: {
handelTab({name}) {
const tab = name === "all" ? "" : name;
this.$emit("update:tab", tab);
this.$emit("change");
},
},
};

View File

@@ -0,0 +1,358 @@
const proofDialog = {
template: `
<!-- 上传凭证 -->
<div>
<el-dialog custom-class='pay-dialog proof-dailog' :class='{look: isLook}' :visible.sync="proofDialog" :show-close="false"
@close="proofClose">
<div class="pc-pay">
<div class="dia-title">
<div class="title-text" v-if="isLook">{{lang.finance_custom19}}</div>
<div class="title-text" v-else>{{lang.finance_custom6}}{{zfData.orderId}}</div>
<div class="title-text" v-show="!isLook">{{lang.pay_text2}}
<span class="pay-money">{{ commonData.currency_prefix }}
<span class="font-26">{{ Number(zfData.amount).toFixed(2)}}</span>
</span>
<i class="el-icon-circle-close close" @click="proofDialog = false"></i>
</div>
</div>
<div class="dia-content">
<div class="item" v-show="!isLook">
<div class="pay-top">
<div class="pay-type" ref="payListRef">
<div class="type-item active">
<img :src="bankImg" alt="" />
</div>
</div>
</div>
<div class="qr-money">
<span>{{lang.finance_custom11}}</span>
<span class="pay-money">{{ commonData.currency_prefix}}
<span class="font-26">
{{orderInfo.amount_unpaid}}{{commonData.currency_code}}
</span>
</span>
</div>
<p class="des">
({{lang.finance_custom12}}{{commonData.currency_prefix}}{{orderInfo.credit}}{{commonData.currency_code}})
</p>
<div class="custom-text">
<div class="qr-content" v-loading="payLoading" v-html="payHtml" id="payBox"></div>
<i class="el-icon-document-copy" v-if="payHtml" @click="copyText(payHtml)"></i>
</div>
<el-steps :space="200" :active="stepNum" finish-status="success" :align-center="true"
class="custom-step">
<el-step :title="lang.finance_custom7"></el-step>
<el-step :title="lang.finance_custom4"></el-step>
<el-step>
<template slot="title">
<span class="txt" :class="{ fail: orderStatus === 'ReviewFail'}">{{orderStatus === 'ReviewFail'
? lang.finance_custom9 : lang.finance_custom8}}</span>
<el-popover placement="top-start" trigger="hover" :title="review_fail_reason">
<span class="help" slot="reference" v-if="orderStatus === 'ReviewFail'">?</span>
</el-popover>
</template>
</el-step>
<el-step :title="lang.finance_custom10"></el-step>
</el-steps>
</div>
<div class="item">
<p v-show="!isLook">{{lang.finance_custom4}}</p>
<el-upload class="upload-demo" ref="fileupload" drag
action="/console/v1/upload" :headers="{Authorization: jwt}"
:before-remove="beforeRemove" multiple :file-list="fileList" :on-success="handleSuccess"
:on-preview="clickFile"
:limit="10"
accept="image/*, .pdf, .PDF" v-if="!isLook">
<div class="el-upload__text">
<p>{{lang.finance_custom16}}<em>{{lang.finance_custom17}}</em></p>
<p>{{lang.finance_custom18}}</p>
</div>
</el-upload>
<div v-else class="view-box">
<p class="item" v-for="(item, index) in fileList" :key="index" @click="clickFile(item)">
{{item.name}}
</p>
</div>
<div class="dia-fotter" v-if="!isLook">
<el-button class="cancel-btn" @click="changeWay">{{lang.finance_custom14}}</el-button>
<el-button @click="submitProof" :disabled="formData.voucher.length === 0" class="submit-btn" :loading="submitLoading">
{{orderStatus === 'WaitUpload' ? lang.finance_custom4 : lang.finance_custom5}}
</el-button>
</div>
<div class="dia-fotter" v-else>
<el-button class="cancel-btn" @click="proofClose">{{lang.finance_text58}}</el-button>
</div>
</div>
</div>
</div>
</el-dialog>
<!-- 图片预览 -->
<div style="height: 0;">
<img id="proofViewer" :src="preImg" alt="">
</div>
<!-- 变更支付方式 -->
<div class="delete-dialog">
<el-dialog width="4.35rem" :visible.sync="showChangeWay" :show-close=false @close="showChangeWay=false">
<div class="delete-box">
<div class="delete-content">{{lang.finance_custom15}}</div>
<div class="delete-btn">
<el-button class="confirm-btn btn" @click="handelChangeWay" :loading="changeLoading">{{lang.finance_btn8}}</el-button>
<el-button class="cancel-btn btn" @click="showChangeWay=false">{{lang.finance_btn7}}</el-button>
</div>
</div>
</el-dialog>
</div>
</div>
`,
created() {},
mounted() {
// 引入 jquery
// const script = document.createElement("script");
// script.src = `${url}js/common/jquery.mini.js`;
// document.body.appendChild(script);
// this.initViewer();
},
// components: {
// payDialog
// },
computed: {
srcList() {
return this.formData.voucher;
},
},
destroyed() {},
props: {},
data() {
return {
proofDialog: false,
zfData: {
orderId: 0,
amount: 0,
},
commonData: {
currency_prefix: "¥",
},
orderInfo: {},
stepNum: 0,
orderStatus: "",
review_fail_reason: "",
payLoading: false,
payHtml: "",
fileList: [],
jwt: `Bearer ${localStorage.jwt}`,
formData: {
id: "",
voucher: [],
},
submitLoading: false,
showChangeWay: false,
changeLoading: false,
viewer: null,
preImg: "",
isLook: false,
bankImg: `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGoAAAAcCAYAAACJWipLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ
bWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdp
bj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6
eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ1IDc5LjE2
MzQ5OSwgMjAxOC8wOC8xMy0xNjo0MDoyMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJo
dHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlw
dGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAv
IiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RS
ZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpD
cmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKFdpbmRvd3MpIiB4bXBNTTpJbnN0
YW5jZUlEPSJ4bXAuaWlkOkRDQ0EyQzUzQTNFOTExRUE5OENBOTNGMERBMUM4MkM0IiB4bXBNTTpE
b2N1bWVudElEPSJ4bXAuZGlkOkRDQ0EyQzU0QTNFOTExRUE5OENBOTNGMERBMUM4MkM0Ij4gPHht
cE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6RENDQTJDNTFBM0U5MTFF
QTk4Q0E5M0YwREExQzgyQzQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6RENDQTJDNTJBM0U5
MTFFQTk4Q0E5M0YwREExQzgyQzQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94
OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz48YXx4AAAEzUlEQVR42uxaO2/TUBR2SiqxoKYS
MMDQAGKuO9IONQsSYmiQGBBCqvsLcOcMSQWZm1asqC4TEgNhYGGJI9GygOqOFUW4bEyEAYFQUDhH
fJee3th5tUpi4SMdXz+u7+N85+kkZSjKNy7Q8SbxGWM49Jt4xyil3/BFs9k0EjqkNEC6R8cnxKeH
vqJ84xUd7xD/TOA5pDESzBVq3ZEA6S/dIi4m0OhAGcZd4vERW9f9BJpWoM6N4LouJtC0ApVQAlRC
J5n1PSauxHHx1tycyW1te9vvd4z52VmLmoDGCHp8L0ONzYkYvVvXnvG66t2MKcsQ2g+vxfe2tuqt
QJXS+9TujxwKj7qqoxxiE8wCKqvzsL46oBA2K6lH5w4EH0WuJngW6iq/y8LV+nrIXMshANtyHgLH
ADg8f5X4Ot5XwOX4WTquroA2kaVmkXgNVlEXIHla9wJxJgQkTwCegfCjqIL3eF7mHPE3fk/MnwFP
CGs1YF0KTPW+KwA3I7xFRa0xtkCJWusBuKZAIqEUNVAKESBNswYLS7G6mNcG8IqqaHn+eXF/VZzX
tLEDzJ9Fa4WA5KGfHVugsBG2piUSsisA4M3ZQpMj3RhAWmJtpv58beqxJoxYCag/a/oO8TKsRa6B
z7N0r9MaLLBu/TmAw3M4Kl6ljXyjDI0cNUp10WeDBLOB8xXh9lytXzXEjZWhsT6uGbBOIHnCqg7g
5liwLmJPFsIP6LrYIcZFEc9RJ4CKetYXO6JN+GRVl+D+TMSYQAlJCFRpuS5wV1igilFfe1AeG3PL
uGdi/imxlpYY18ZLqOzpBa6le12JbYwisALaTAA34wlACnpMikivXcSUGlxeKiRtryKGedr9CYCV
QTKhMj8es9iF2wuj65hvGWP7wjMEcY1RlnRnJKimSCY2kYGxO7+Nc2V5ngBpEVlbr+RDqMqyTIxf
V+m4sNR/7xB4TgfF81SqrhINvhf3LxNKWAzKLs4dkVGVNXc0j1hiiBR5KaT+6UhsfWxhsLIAABmw
LgfzFMG+lgkamqv028SpBZQgsY5RLBwPlqW7PhYmB/NNpNFsNct0ryyEbYnis58vGaruMuEGq0gu
KsKysrC4Tc2aXPRjXmCFof1Y2v4qtLcDjJMbtEW9Jp5sw2+P8RnIQcoshVmHtdVDUuDjUBYgqbjE
c0wSGFlYmCPcFyc2tmaRgUg6XjIY6lOYcO1FAMpW5QzaoqaMUjq6Tsk39uh4rccxTbgW5jURMwqI
WTY2vAMLK7f7LoivDraoZ8Jcn6uCPFJwS9VfbLVQGJWo+Fxcy/qMrnMohtfgHj14B0fLDi0oxSq7
wEFa1GUC41Sb5x/6GDMDjZ6Bm5hQ8YvdG0CxUGPl9M9IEVQAT2PsnuIYWwxc6woSFiek2N5ldwgA
LdzLiPhkcglCbANQb5AWNQ4N+RjxfK+PMXMqwENbZ3SLwbOiEf7zvhPillI9zO9G1Uf4ghFW5Joi
AVHx1oHLCzg+afFq4K6P6WoboHr+gh/yc0CvWZx/zP0EfTwP2uwnsigeGwJQxkkB9T8RW9SXAc63
TnFqvYt+nxNoWi3qGfGvEVvX0wQaHahS+hO1/AfM7yOypufEDxNojlJK1DFn6XiD+PyQ1vKD+B0p
znu+SP7SfJT+CDAAdKMBqP61EkMAAAAASUVORK5CYII=
`,
};
},
methods: {
proofClose() {
this.proofDialog = false;
this.viewer && this.viewer.destroy();
},
changeWay() {
this.showChangeWay = true;
},
initViewer() {
this.viewer = new Viewer(document.getElementById("proofViewer"), {
button: true,
inline: false,
zoomable: true,
title: true,
tooltip: true,
minZoomRatio: 0.5,
maxZoomRatio: 100,
movable: true,
interval: 2000,
navbar: true,
loading: true,
});
},
// 附件下载
clickFile(item) {
const name = item.name;
const imgUrl = item.url || item.response.data.image_url;
const type = name.substring(name.lastIndexOf(".") + 1);
if (
[
"png",
"jpg",
"jepg",
"bmp",
"webp",
"PNG",
"JPG",
"JEPG",
"BMP",
"WEBP",
].includes(type)
) {
this.preImg = imgUrl;
if (!this.viewer) {
this.initViewer();
}
setTimeout(() => {
this.viewer.show();
$("#proofViewer").attr("src", imgUrl);
}, 10);
} else {
const downloadElement = document.createElement("a");
downloadElement.href = url;
downloadElement.download = item.name; // 下载后文件名
document.body.appendChild(downloadElement);
downloadElement.click(); // 点击下载
}
},
emitRefresh(isChange = false) {
this.$emit("refresh", isChange, this.orderInfo.id);
},
async handelChangeWay() {
try {
this.changeLoading = true;
const res = await changePayType(this.orderInfo.id);
this.$message.success(res.data.msg);
this.showChangeWay = false;
this.proofDialog = false;
this.changeLoading = false;
this.emitRefresh(true);
} catch (error) {
console.log("error", error);
this.changeLoading = false;
this.showChangeWay = false;
this.$message.error(error.data.msg);
}
},
onProgress(event) {
console.log(event);
},
async submitProof() {
try {
if (this.formData.voucher.length === 0) {
return this.$message.warning(lang.finance_custom13);
}
this.submitLoading = true;
const params = {
id: this.zfData.orderId,
voucher: this.formData.voucher,
};
const res = await uploadProof(params);
this.submitLoading = false;
this.$message.success(res.data.msg);
this.proofDialog = false;
this.emitRefresh();
} catch (error) {
this.submitLoading = false;
this.$message.error(error.data.msg);
}
},
beforeRemove(file, fileList) {
// 获取到删除的 save_name
let save_name = file.save_name || file.response.data.save_name;
this.formData.voucher = this.formData.voucher.filter((item) => {
return item != save_name;
});
},
// 上传文件相关
handleSuccess(response, file, fileList) {
if (response.status != 200) {
this.$message.error(response.msg);
// 清空上传框
let uploadFiles = this.$refs["fileupload"].uploadFiles;
let length = uploadFiles.length;
uploadFiles.splice(length - 1, length);
} else {
this.formData.voucher = [];
this.formData.voucher = fileList.map(
(item) => item.response?.data?.save_name || item.save_name
);
}
},
async getOrderDetails(orderId) {
try {
const res = await orderDetails(orderId);
this.orderInfo = res.data.data.order;
const {id, amount, status, review_fail_reason} = res.data.data.order;
this.zfData.orderId = Number(id);
this.zfData.amount = amount;
this.proofDialog = true;
this.orderStatus = status;
this.review_fail_reason = review_fail_reason;
this.isLook = status === "Paid" && this.orderInfo.voucher.length > 0;
if (status === "WaitUpload") {
this.stepNum = 2;
} else {
this.stepNum = 3;
}
// 获取转账信息
this.payLoading = true;
let result = "";
if (!this.isLook) {
result = await pay({
id,
gateway: "UserCustom",
});
this.payLoading = false;
this.payHtml = result.data.data.html;
$("#payBox").html(res.data.data.html);
}
this.fileList = this.orderInfo.voucher;
this.formData.voucher = this.orderInfo.voucher.map(
(item) => item.save_name
);
} catch (error) {
console.log("error", error);
this.$message.error(error.data.msg);
this.payLoading = false;
}
},
},
};

View File

@@ -0,0 +1,255 @@
const rechargeDialog = {
template: /*html*/ `
<div>
<el-dialog width="7.5rem" :visible.sync="isShowCz" @close="czClose" custom-class="recharge-dialog"
:show-close="false">
<div class="recharge-content">
<div class="recharge-title">
<span class="title-text">{{lang.finance_title4}}</span>
<span class="close-btn" @click="czClose">
<i class="el-icon-close"></i>
</span>
</div>
<div class="recharge-box">
<div class="recharge-input">
<el-input-number v-model="amount" :min="0" :precision="2" :step="0.01" :controls="false"
:placeholder="lang.finance_text130">
</el-input-number>
<el-button @click="handleSubmit" type="primary" :loading="submitLoading">{{lang.finance_btn6}}
</el-button>
</div>
<template v-if="rechargeActive.length > 0">
<div class="recharge-tip">
<template v-for="(item, index) in rechargeTip">
<span>{{item}}</span>
<template v-if="index !== rechargeTip.length - 1">
<br />
</template>
</template>
</div>
<div class="recharge-active">
<div class="active-title">{{lang.coin_text12}}<el-tooltip effect="dark" placement="top"
v-if="coinClientCoupon.coin_description_open == 1">
<div slot="content" v-html="coinClientCoupon.coin_description"></div>
<svg t="1745803081479" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="14138" width="16" height="16"
xmlns:xlink="http://www.w3.org/1999/xlink">
<path
d="M512 97.52381c228.912762 0 414.47619 185.563429 414.47619 414.47619s-185.563429 414.47619-414.47619 414.47619S97.52381 740.912762 97.52381 512 283.087238 97.52381 512 97.52381z m0 73.142857C323.486476 170.666667 170.666667 323.486476 170.666667 512s152.81981 341.333333 341.333333 341.333333 341.333333-152.81981 341.333333-341.333333S700.513524 170.666667 512 170.666667z m45.32419 487.619047v73.142857h-68.510476l-0.024381-73.142857h68.534857z m-4.047238-362.008381c44.251429 8.923429 96.889905 51.126857 96.889905 112.518096 0 61.415619-50.151619 84.650667-68.120381 96.134095-17.993143 11.50781-24.722286 24.771048-24.722286 38.863238V609.52381h-68.534857v-90.672762c0-21.504 6.89981-36.571429 26.087619-49.883429l4.315429-2.852571 38.497524-25.6c24.551619-16.530286 24.210286-49.712762 9.020952-64.365715a68.998095 68.998095 0 0 0-60.391619-15.481904c-42.715429 8.387048-47.640381 38.521905-47.932952 67.779047v16.554667H390.095238c0-56.953905 6.534095-82.773333 36.912762-115.395048 34.03581-36.449524 81.993143-42.300952 126.268952-33.328762z"
p-id="14139" fill="currentColor"></path>
</svg>
</el-tooltip></div>
<div class="active-main">
<div class="active-list">
<div class="active-item" v-for="item in rechargeActive" :key="item.id">
<div class="active-name">
<span>{{item.name}}</span>
<span class="active-time">
<template v-if="item.begin_time == 0">
{{item.begin_time | formateTime}}
</template>
<template v-else>
{{item.begin_time | formateTime}} - {{item.end_time | formateTime}}
</template>
</span>
</div>
<div class="gradient-content" v-if="item.type === 'gradient'">
<div class="gradient-item"
:class="{active: amount *1 >= items.amount * 1 && (index == item.return.length - 1 || amount * 1 < item.return[index + 1].amount * 1)}"
v-for="(items, index) in item.return" :key="items.id"
@click="amount = items.amount">
<div class="gradient-money">
<span class="s-12">{{currency_prefix}}</span>{{items.amount}}
</div>
<div class="gradient-award ">
{{lang.coin_text13}}{{items.amount}}{{lang.coin_text14}}{{items.award}}{{coinClientCoupon.name}}
<i class="el-icon-check active-icon"></i>
</div>
</div>
</div>
<div class="gradient-content" v-if="item.type === 'proportion'">
<div class="gradient-item" :class="{active:amount * 1 >= item.recharge_min * 1}"
@click="amount = item.recharge_min">
<div class="gradient-money">
<span class="s-12">{{currency_prefix}}</span>{{item.recharge_min}}
</div>
<div class="gradient-award ">
{{lang.coin_text15}}{{item.recharge_min}}<br>{{lang.coin_text16}}{{Number(item.recharge_proportion)}}%{{coinClientCoupon.name}}
<i class="el-icon-check active-icon"></i>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<div v-if="commonData.recharge_money_notice_content" v-html="commonData.recharge_money_notice_content"
class="cz-notice">
</div>
</div>
</div>
</el-dialog>
<pay-dialog ref="payDialog" @payok="paySuccess"></pay-dialog>
</div>
`,
data() {
return {
isShowCz: false,
currency_prefix: "",
currency_suffix: "",
commonData: {},
amount: undefined,
submitLoading: false,
rechargeActive: [],
coinClientCoupon: {},
};
},
components: {
payDialog,
},
created() {
this.getCommon();
},
filters: {
formateTime(time) {
if (time && time !== 0) {
return formateDate(time * 1000);
} else if (time === 0) {
return lang.coin_text17;
} else {
return "--";
}
},
},
computed: {
rechargeTip() {
if (this.rechargeActive.length === 0 || !this.amount) {
return [];
}
const rechargeTipList = [];
const maxCoinAward = Number(
this.coinClientCoupon.per_recharge_get_coin_max
);
this.rechargeActive.forEach((item) => {
if (item.type === "proportion") {
if (this.amount * 1 >= item.recharge_min * 1) {
const award = Number(item.recharge_proportion * 0.01 * this.amount);
const tip = `${item.name}${lang.coin_text18}${Number(
award > maxCoinAward ? maxCoinAward : award
).toFixed(2)}${this.coinClientCoupon.name}`;
rechargeTipList.push(tip);
} else {
const tip = `${item.name}${lang.coin_text19}${Number(
item.recharge_min - this.amount
).toFixed(2)}${this.currency_suffix}${lang.coin_text20}${Number(
item.recharge_proportion * 0.01 * item.recharge_min
).toFixed(2)}${this.coinClientCoupon.name}`;
rechargeTipList.push(tip);
}
}
if (item.type === "gradient") {
// 找出最大的阶梯金额
const maxAmount = Math.max(
...item.return.map((items) => items.amount)
);
if (this.amount * 1 >= maxAmount * 1) {
const maxAward = item.return.find(
(items) => items.amount === maxAmount
)?.award;
const tip = `${item.name}${lang.coin_text18}${Number(
maxAward > maxCoinAward ? maxCoinAward : maxAward
).toFixed(2)}${this.coinClientCoupon.name}`;
rechargeTipList.push(tip);
} else {
// 找出当前充值金额对应的阶梯 和 下一个阶梯
const currentIndex = item.return.findIndex((items, index) => {
return (
this.amount * 1 >= items.amount * 1 &&
this.amount * 1 < item.return[index + 1]?.amount * 1
);
});
const currentAmount = item.return[currentIndex];
const nextAmount = item.return[currentIndex + 1];
if (currentAmount && nextAmount) {
const currentAward =
currentAmount.award > maxCoinAward
? maxCoinAward
: currentAmount.award;
const nextAward =
nextAmount.award > maxCoinAward
? maxCoinAward
: nextAmount.award;
const tip = `${item.name}${lang.coin_text21}${Number(
currentAward
).toFixed(2)}${this.coinClientCoupon.name}${
lang.coin_text22
}${Number(nextAmount.amount - this.amount).toFixed(2)}${
this.currency_suffix
}${lang.coin_text20}${Number(nextAward).toFixed(2)}${
this.coinClientCoupon.name
}`;
rechargeTipList.push(tip);
}
}
}
});
return rechargeTipList;
},
},
methods: {
getCoinClientCoupon() {
apiCoinClientCoupon().then((res) => {
this.coinClientCoupon = res.data.data;
});
},
getRechargeDetail() {
apiCoinRechargeDetail().then((res) => {
this.rechargeActive = res.data.data.coins;
});
},
open() {
if (havePlugin("Coin")) {
this.getRechargeDetail();
this.getCoinClientCoupon();
}
this.isShowCz = true;
},
czClose() {
this.amount = undefined;
this.isShowCz = false;
},
handleSubmit() {
if (!this.amount) {
return this.$message.error(lang.finance_text130);
}
this.submitLoading = true;
apiRecharge({amount: this.amount})
.then((res) => {
if (res.data.status === 200) {
this.isShowCz = false;
const orderId = res.data.data.id;
this.$refs.payDialog.czPay(orderId);
}
})
.catch((error) => {
this.$message.error(error.data.msg);
})
.finally(() => {
this.submitLoading = false;
});
},
paySuccess() {
this.$emit("success");
},
getCommon() {
this.commonData = JSON.parse(localStorage.getItem("common_set_before"));
this.currency_prefix = this.commonData.currency_prefix;
this.currency_suffix = this.commonData.currency_suffix;
},
},
};

View File

@@ -0,0 +1,10 @@
const refundDialog = {
template: `
<div>这是退款弹窗</div>
`,
created() {},
data() {
return {};
},
methods: {},
};

View File

@@ -0,0 +1,96 @@
.common-renew-dialog .dialog-main .renew-content {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 105%;
}
.common-renew-dialog .dialog-main .renew-content .renew-item {
width: 1.6rem;
min-height: 0.83rem;
border: 1px solid #E6E7EB;
border-radius: 3px;
display: flex;
flex-direction: column;
align-items: center;
margin-right: 0.2rem;
margin-bottom: 0.2rem;
cursor: pointer;
position: relative;
box-sizing: border-box;
}
.common-renew-dialog .dialog-main .renew-content .renew-item:nth-child(3) {
margin-right: 0;
}
.common-renew-dialog .dialog-main .renew-content .renew-item:nth-child(6) {
margin-right: 0;
}
.common-renew-dialog .dialog-main .renew-content .renew-item .item-top {
font-size: 0.15rem;
color: #1E2736;
margin-top: 0.08rem;
}
.common-renew-dialog .dialog-main .renew-content .renew-item .item-bottom {
font-size: 0.15rem;
color: var(--color-primary);
margin-top: 0.04rem;
}
.common-renew-dialog .dialog-main .renew-content .renew-item .check {
position: absolute;
right: 0;
bottom: 0;
z-index: 3;
color: #FFF;
}
.common-renew-dialog .dialog-main .renew-content .renew-item .item-origin-price {
text-decoration: line-through;
font-size: 0.15rem;
color: #8692B0;
}
.common-renew-dialog .dialog-main .renew-content .renew-active {
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.12);
}
.common-renew-dialog .dialog-main .renew-content .renew-active::after {
content: '';
position: absolute;
bottom: 0;
right: 0;
z-index: 2;
width: 0;
height: 0;
border-bottom: 0.24rem solid var(--color-primary);
border-left: 0.24rem solid transparent;
}
.common-renew-dialog .dialog-main .pay-content {
border: 1px solid #E6E7EB;
border-radius: 3px;
padding: 0.2rem 0;
padding-right: 0.3rem;
}
.common-renew-dialog .dialog-main .pay-content .pay-price {
display: flex;
text-align: right;
flex-direction: row;
}
.common-renew-dialog .dialog-main .pay-content .pay-price .text {
font-size: 0.14rem;
margin-top: 0.1rem;
color: #1E2736;
margin-right: 1.43rem;
}
.common-renew-dialog .dialog-main .pay-content .pay-price .money {
width: 100%;
font-size: 0.28rem;
font-weight: bold;
color: var(--color-price-text);
text-align: right;
}
.common-renew-dialog .dialog-main .pay-content .pay-price .total-icon {
font-size: 0.16rem;
color: #1E2736;
}
.common-renew-dialog .dialog-main .pay-content .pay-price .original-price {
text-decoration: line-through;
font-size: 0.2rem;
font-weight: 500;
color: #999999;
}

View File

@@ -0,0 +1,325 @@
const renewDialog = {
template: /*html*/ `
<div>
<el-dialog width="6.9rem" :visible.sync="isShowRenew" :show-close="false" @close="renewDgClose" class="common-renew-dialog">
<div class="dialog-title">{{demand ? '转包年包月' : '续费'}}</div>
<div class="dialog-main">
<div class="renew-content">
<div class="renew-item" :class="selected_id==item.id?'renew-active':''" v-for="item in renewList"
:key="item.id" @click="renewItemChange(item)">
<div class="item-top">{{item.customfield?.multi_language?.billing_cycle || item.billing_cycle}}</div>
<div class="item-bottom" v-if="hasShowPromo && useDiscount">
{{commonData.currency_prefix + item.base_price}}
</div>
<div class="item-bottom" v-else>{{commonData.currency_prefix + item.price}}</div>
<div class="item-origin-price"
v-if="item.price*1 < item.base_price*1 && !useDiscount">
{{commonData.currency_prefix + item.base_price}}
</div>
<i class="el-icon-check check" v-show="selected_id==item.id"></i>
</div>
</div>
<div class="pay-content">
<div class="pay-price">
<div class="money" v-loading="renewLoading">
<span class="text">{{lang.common_cloud_label11}}:</span>
<span>{{commonData.currency_prefix}}{{totalPrice | filterMoney}}</span>
<el-popover placement="top-start" width="200" trigger="hover" v-if="level_discount_amount * 1 || code_discount_amount * 1">
<div class="show-config-list">
<p v-if="level_discount_amount*1 > 0">
{{lang.shoppingCar_tip_text2}}{{commonData.currency_prefix}}
{{ level_discount_amount | filterMoney}}
</p>
<p v-if="code_discount_amount * 1 > 0">
{{lang.shoppingCar_tip_text4}}{{commonData.currency_prefix}}
{{ code_discount_amount | filterMoney }}
</p>
</div>
<i class="el-icon-warning-outline total-icon" slot="reference"></i>
</el-popover>
<p class="original-price"
v-if="customfield.promo_code && totalPrice != base_price">
{{commonData.currency_prefix}} {{ base_price | filterMoney}}
</p>
<p class="original-price"
v-if="!customfield.promo_code && totalPrice != original_price">
{{commonData.currency_prefix}} {{ original_price | filterMoney}}
</p>
<div class="code-box">
<!-- 优惠码 -->
<discount-code v-show="hasShowPromo && !customfield.promo_code"
@get-discount="getRenewDiscount(arguments)"
:scene="demand ? 'change_billing_cycle' : 'renew'" :product_id="product_id"
:amount="base_price" :billing_cycle_time="duration"></discount-code>
</div>
<div class="code-number-text">
<div class="discount-codeNumber" v-show="customfield.promo_code">
{{ customfield.promo_code }}<i class="el-icon-circle-close remove-discountCode"
@click="removeRenewDiscountCode"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="dialog-footer">
<el-button :loading="submitLoading" type="primary" class="btn-ok" @click="subRenew">
{{demand ? lang.mf_demand_tip9 : lang.common_cloud_btn30}}
</el-button>
<div class="btn-no" @click="renewDgClose">{{lang.common_cloud_btn29}}</div>
</div>
</el-dialog>
</div>
`,
data() {
return {
isShowRenew: false,
currency_prefix: "",
currency_suffix: "",
commonData: {},
submitLoading: false,
renewList: [],
hasClientLevel: false,
hasShowPromo: false,
hasShowCash: false,
useDiscount: false,
level_discount_amount: 0, // 用户等级优惠金额
code_discount_amount: 0, // 优惠码优惠金额
duration: 0, // 续费周期
customfield: {
promo_code: "",
voucher_get_id: "",
},
billing_cycle: "",
selected_id: 0,
original_price: 0,
base_price: 0,
renewLoading: false,
};
},
props: {
demand: {
type: Boolean,
default: false,
},
id: {
type: Number,
default: 0,
required: true,
},
product_id: {
type: Number,
default: 0,
required: true,
},
renew_amount: {
type: Number,
default: 0,
},
billing_cycle_time: {
type: Number,
default: 0,
},
billing_cycle_name: {
type: String,
default: "",
},
},
created() {
// 加载css
if (
!document.querySelector(
'link[href="' + url + 'components/renewDialog/renewDialog.css"]'
)
) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = `${url}components/renewDialog/renewDialog.css`;
document.head.appendChild(link);
}
this.hasClientLevel = havePlugin("IdcsmartClientLevel");
this.hasShowPromo = havePlugin("PromoCode");
this.hasShowCash = havePlugin("IdcsmartVoucher");
this.getCommon();
},
computed: {
totalPrice() {
const goodsPrice =
this.hasShowPromo && this.customfield.promo_code
? this.base_price
: this.original_price;
const discountPrice =
this.level_discount_amount + this.code_discount_amount;
const totalPrice = goodsPrice - discountPrice;
const nowPrice = totalPrice > 0 ? totalPrice.toFixed(2) : 0;
const showPirce = Number(nowPrice);
return showPirce > 0 ? showPirce.toFixed(2) : 0;
},
},
methods: {
// 显示续费弹窗
showRenew() {
if (this.isShowRenew) return;
// 获取续费页面信息
const params = {
id: this.id,
};
this.renewLoading = true;
this.submitLoading = true;
this.isShowRenew = true;
const getRenewApi = this.demand
? apiGetDemandToPrepaymentPrice
: renewPage;
getRenewApi(params)
.then(async (res) => {
this.submitLoading = false;
this.renewLoading = false;
if (res.data.status === 200) {
this.renewList = res.data.data.host || res.data.data.duration || [];
this.selected_id = this.renewList[0].id;
this.billing_cycle = this.renewList[0].billing_cycle;
this.duration = this.renewList[0].duration;
this.original_price = this.renewList[0].price;
this.base_price = this.renewList[0].base_price;
}
})
.catch((err) => {
this.submitLoading = false;
this.renewLoading = false;
this.isShowRenew = false;
this.$message.error(err.data.msg);
});
},
// 续费使用优惠码
async getRenewDiscount(data) {
this.customfield.promo_code = data[1];
this.useDiscount = true;
this.code_discount_amount = Number(data[0]);
this.level_discount_amount = await this.getClientLevelAmount(
this.base_price
);
},
// 移除续费的优惠码
removeRenewDiscountCode() {
this.useDiscount = false;
this.customfield.promo_code = "";
this.code_discount_amount = 0;
this.level_discount_amount = 0;
},
// 续费弹窗关闭
renewDgClose() {
this.isShowRenew = false;
this.selected_id = 0;
this.duration = 0;
this.billing_cycle = "";
this.original_price = 0;
this.base_price = 0;
this.renewList = [];
this.customfield = {
promo_code: "",
voucher_get_id: "",
};
this.code_discount_amount = 0;
this.level_discount_amount = 0;
this.removeRenewDiscountCode();
},
async getClientLevelAmount(amount) {
try {
if (!this.hasClientLevel) {
return 0;
}
const params = {id: this.product_id, amount: amount};
const res = await apiClientLevelAmount(params);
return Number(res.data.data.discount);
} catch (error) {
this.$message.error(error.data.msg);
return 0;
}
},
async getPromoDiscount(amount) {
try {
if (!this.hasShowPromo || !this.useDiscount) {
return 0;
}
const params = {
scene: this.demand ? "change_billing_cycle" : "renew",
product_id: this.product_id,
amount: amount,
billing_cycle_time: this.duration,
promo_code: this.customfield.promo_code,
};
// 更新优惠码
const res = await applyPromoCode(params);
return Number(res.data.data.discount);
} catch (error) {
this.useDiscount = false;
this.customfield.promo_code = "";
this.level_discount_amount = 0;
this.$message.error(error.data.msg);
return 0;
}
},
// 续费周期点击
async renewItemChange(item) {
this.submitLoading = true;
this.selected_id = item.id;
this.duration = item.duration;
this.billing_cycle = item.billing_cycle;
this.original_price = item.price;
this.base_price = item.base_price;
// 开启了优惠码插件
this.level_discount_amount = await this.getClientLevelAmount(
item.base_price
);
this.code_discount_amount = await this.getPromoDiscount(item.base_price);
this.submitLoading = false;
},
// 续费提交
subRenew() {
this.submitLoading = true;
const params = {
id: this.id,
billing_cycle: this.billing_cycle,
customfield: this.customfield,
duration_id: this.selected_id,
};
const subApi = this.demand ? apiDemandToPrepayment : apiRenew;
subApi(params)
.then((res) => {
this.submitLoading = false;
if (res.data.status === 200) {
if (res.data.code == "Paid") {
this.isShowRenew = false;
this.$message.success(res.data.msg);
this.$emit("success");
} else {
this.isShowRenew = false;
this.$emit("pay", res.data.data.id);
}
}
})
.catch((err) => {
this.submitLoading = false;
this.$message.error(err.data.msg);
});
},
getCommon() {
this.commonData = JSON.parse(localStorage.getItem("common_set_before"));
this.currency_prefix = this.commonData.currency_prefix;
this.currency_suffix = this.commonData.currency_suffix;
},
},
};

View File

@@ -0,0 +1,122 @@
.common-renew-dialog {
.dialog-main {
.renew-content {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 105%;
.renew-item {
width: 1.6rem;
min-height: .83rem;
border: 1px solid #E6E7EB;
border-radius: 3px;
display: flex;
flex-direction: column;
align-items: center;
margin-right: .2rem;
margin-bottom: .2rem;
cursor: pointer;
position: relative;
box-sizing: border-box;
&:nth-child(3) {
margin-right: 0;
}
&:nth-child(6) {
margin-right: 0;
}
.item-top {
font-size: .15rem;
color: #1E2736;
margin-top: .08rem;
}
.item-bottom {
font-size: .15rem;
color: var(--color-primary);
margin-top: .04rem;
}
.check {
position: absolute;
right: 0;
bottom: 0;
z-index: 3;
color: #FFF;
}
.item-origin-price {
text-decoration: line-through;
font-size: 0.15rem;
color: #8692B0;
}
}
.renew-active {
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.12);
&::after {
content: '';
position: absolute;
bottom: 0;
right: 0;
z-index: 2;
width: 0;
height: 0;
border-bottom: 0.24rem solid var(--color-primary);
border-left: 0.24rem solid transparent;
}
}
}
.pay-content {
border: 1px solid #E6E7EB;
border-radius: 3px;
padding: 0.2rem 0;
padding-right: 0.3rem;
.pay-price {
display: flex;
text-align: right;
flex-direction: row;
.text {
font-size: 0.14rem;
margin-top: .1rem;
color: #1E2736;
margin-right: 1.43rem;
}
.money {
width: 100%;
font-size: .28rem;
font-weight: bold;
color: var(--color-price-text);
text-align: right;
}
.total-icon {
font-size: 0.16rem;
color: #1E2736;
}
.original-price {
text-decoration: line-through;
font-size: 0.2rem;
font-weight: 500;
color: #999999;
}
}
}
}
}

View File

@@ -0,0 +1,171 @@
.qrcode-status {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.qrcode-status.loading {
color: #999;
}
.qrcode-status.error {
color: #f56c6c;
}
.common-auth-info .top {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.common-auth-info .top .tit {
font-size: 16px;
font-weight: 500;
margin: 0;
}
.common-auth-info .top .reset-auth {
cursor: pointer;
font-size: 14px;
}
.common-auth-info .top .reset-auth:hover {
opacity: 0.8;
}
.common-auth-info .bot-info {
display: flex;
align-items: center;
}
.common-auth-info .bot-info .label {
color: #606266;
}
.common-auth-info .bot-info .status.reject {
color: #f56c6c;
}
.common-auth-info .bot-info .status.pending {
color: #e6a23c;
}
.common-auth-info .bot-info .status.pending .cancel-auth {
margin-left: 10px;
color: var(--color-primary);
cursor: pointer;
}
.common-auth-info .bot-info .status.pending .cancel-auth:hover {
opacity: 0.8;
}
.common-auth-info .bot-info .status.approved {
color: #67c23a;
}
.common-auth-dialog .dialog-title {
font-size: 18px;
font-weight: 500;
text-align: center;
margin-bottom: 0.6rem;
}
.common-auth-dialog .tip {
color: #606266;
font-size: 14px;
margin-bottom: 15px;
}
.common-auth-dialog .tip.primary-color {
color: var(--color-primary);
}
.common-auth-dialog .auth-info-display .base-info {
display: flex;
gap: 40px;
margin-bottom: 20px;
}
.common-auth-dialog .auth-info-display .base-info .item .s-tit {
font-weight: 500;
margin-bottom: 10px;
}
.common-auth-dialog .auth-info-display .base-info .item .info-row {
margin-bottom: 8px;
}
.common-auth-dialog .auth-info-display .base-info .item .info-row .label {
color: #909399;
}
.common-auth-dialog .auth-info-display .base-info .item .info-row .value {
color: #303133;
}
.common-auth-dialog .auth-info-display .base-info .item .info-row .value.primary-color {
color: var(--color-primary);
}
.common-auth-dialog .auth-info-display .domain-tip {
color: #909399;
font-size: 12px;
margin-top: 5px;
}
.common-auth-dialog .reason-section {
margin-top: 20px;
}
.common-auth-dialog .method-selection {
display: flex;
gap: 20px;
margin-top: 20px;
}
.common-auth-dialog .method-selection .method-card {
flex: 1;
padding: 20px;
border: 1px solid #dcdfe6;
border-radius: 8px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
}
.common-auth-dialog .method-selection .method-card:hover {
border-color: var(--color-primary);
box-shadow: 0 2px 12px rgba(64, 158, 255, 0.2);
}
.common-auth-dialog .method-selection .method-card h4 {
margin: 0 0 10px;
font-size: 16px;
color: #303133;
}
.common-auth-dialog .method-selection .method-card p {
color: #909399;
margin-bottom: 15px;
}
.common-auth-dialog .verify-content .user-info {
background: #f5f7fa;
padding: 15px;
border-radius: 8px;
margin: 15px 0;
}
.common-auth-dialog .verify-content .user-info .info-row {
margin-bottom: 8px;
}
.common-auth-dialog .verify-content .user-info .info-row:last-child {
margin-bottom: 0;
}
.common-auth-dialog .verify-content .user-info .info-row .label {
color: #909399;
}
.common-auth-dialog .verify-content .user-info .info-row .value {
color: #303133;
}
.common-auth-dialog .verify-content .user-info .info-row .value.primary-color {
color: var(--color-primary);
}
.common-auth-dialog .verify-content .qr-section {
text-align: center;
margin-top: 20px;
}
.common-auth-dialog .verify-content .qr-section > p {
color: #606266;
margin-bottom: 15px;
}
.common-auth-dialog .verify-content .qr-section .qr-code {
display: flex;
justify-content: center;
margin-bottom: 15px;
}
.common-auth-dialog .verify-content .qr-section .qr-code #qrcode-container {
width: 150px;
height: 150px;
display: flex;
align-items: center;
justify-content: center;
}
.common-auth-dialog .verify-content .qr-section .verify-status {
margin-top: 10px;
}
.common-auth-dialog .dialog-footer {
margin-top: 30px;
text-align: center;
}

View File

@@ -0,0 +1,567 @@
// 加载组件样式
(function () {
if (!document.querySelector('link[href*="resetAuth.css"]')) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = `${url}components/resetAuth/resetAuth.css`;
document.head.appendChild(link);
}
})();
const resetAuth = {
template: `
<div>
<!-- 授权 -->
<div class="common-auth-info">
<div class="top">
<p class="tit">{{lang.reset_auth_detail_title}}</p>
<span class="reset-auth" @click="handleResetAuth" v-if="hasRestAuth && authDialog.status !== 'pending'">{{lang.reset_auth_reset_button}}</span>
</div>
<div class="bot-info" v-if="hasRestAuth">
<span class="label">{{lang.reset_auth_status_label}}</span>
<div class="status">
<div class="status reject" v-if="authDialog.status === 'rejected'">{{lang.reset_auth_status_failed}}{{authDialog.reject_reason}}
</div>
<div class="status pending" v-if="authDialog.status === 'pending'">{{lang.reset_auth_status_pending}}<span class="cancel-auth"
@click="handleCancelAuth">{{lang.reset_auth_cancel_reset}}</span></div>
<div class="status approved" v-if="authDialog.status === 'approved'">{{lang.reset_auth_status_normal}}</div>
</div>
</div>
</div>
<!-- 授权重置弹窗 -->
<el-dialog :visible.sync="authDialog.visible" width="800px"
:close-on-click-modal="false" custom-class="common-auth-dialog " @close="closeAuthDialog">
<div class="dialog-title">
{{authDialogTitle}}
</div>
<div v-if="authDialog.currentStep === 'confirm'">
<p class="tip">{{lang.reset_auth_modify_tip}}</p>
</div>
<el-form :model="authDialog" status-icon :rules="rules" ref="ruleForm" label-width="100px"
label-position="top" class="demo-ruleForm">
<template v-if="authDialog.currentStep !== 'autoVerify'">
<div class="auth-info-display">
<div class="base-info">
<div class="item">
<p class="s-tit">{{lang.reset_auth_current_info}}</p>
<div class="info-row">
<span class="label">{{lang.reset_auth_ip_label}}</span>
<span class="value">{{authDialog.original_ip || '--'}}</span>
</div>
<div class="info-row">
<span class="label">{{lang.reset_auth_domain_label}}</span>
<span class="value">{{authDialog.original_domain || '--'}}</span>
</div>
</div>
<div class="item"
v-if="authDialog.currentStep === 'selectMethod' || authDialog.currentStep === 'manualReview'">
<p class="s-tit">{{lang.reset_auth_new_info}}</p>
<div class="info-row">
<span class="label">{{lang.reset_auth_ip_label}}</span>
<span class="value">{{authDialog.new_ip || '--'}}</span>
</div>
<div class="info-row">
<span class="label">{{lang.reset_auth_domain_label}}</span>
<span class="value">{{authDialog.new_domain || '--'}}</span>
</div>
</div>
</div>
<template v-if="authDialog.currentStep === 'confirm'">
<el-form-item :label="lang.reset_auth_new_ip" prop="new_ip">
<el-input v-model="authDialog.new_ip" :placeholder="lang.reset_auth_ip_placeholder"></el-input>
</el-form-item>
<el-form-item :label="lang.reset_auth_new_domain" prop="new_domain">
<el-input v-model="authDialog.new_domain" :placeholder="lang.reset_auth_domain_placeholder"></el-input>
</el-form-item>
<p class="domain-tip">{{lang.reset_auth_domain_tip}}</p>
<p class="domain-tip">{{lang.reset_auth_domain_example}}</p>
</template>
</div>
</template>
<!-- 申请理由 -->
<div class="reason-section" v-if="authDialog.currentStep !== 'confirm' && authDialog.currentStep !== 'autoVerify'">
<el-form-item :label="lang.reset_auth_apply_reason" prop="reset_reason">
<el-input type="textarea" v-model="authDialog.reset_reason" :rows="4" :maxlength="300"
show-word-limit :placeholder="lang.reset_auth_reason_placeholder">
</el-input>
</el-form-item>
</div>
<!-- 方式选择 -->
<div v-if="authDialog.currentStep === 'selectMethod'">
<p class="tip">{{lang.reset_auth_select_verify_tip}}</p>
<div class="method-selection">
<div class="method-card" @click="selectVerifyMethod('manual')">
<h4>{{lang.reset_auth_manual_review}}</h4>
<p>{{lang.reset_auth_manual_review_desc}}</p>
<el-button>{{lang.reset_auth_submit_manual}}</el-button>
</div>
<div class="method-card" @click="selectVerifyMethod('auto')">
<h4>{{lang.reset_auth_auto_reset}}</h4>
<p>{{lang.reset_auth_auto_reset_desc}}</p>
<el-button>{{lang.reset_auth_start_auto}}</el-button>
</div>
</div>
</div>
<!-- 自动扫码认证 -->
<div class="verify-content" v-if="authDialog.currentStep === 'autoVerify'">
<p class="tip">{{lang.reset_auth_verify_tip}}</p>
<p class="tip primary-color">{{lang.reset_auth_current_verify_info}}</p>
<div class="user-info">
<div class="info-row">
<span class="label">{{lang.reset_auth_current_user}}</span>
<span class="value primary-color">{{authDialog.userInfo.username}}</span>
</div>
<div class="info-row">
<span class="label">{{lang.reset_auth_real_name}}</span>
<span class="value primary-color">{{authDialog.userInfo.card_name}}</span>
</div>
<div class="info-row">
<span class="label">{{lang.reset_auth_id_card}}</span>
<span class="value primary-color">{{authDialog.userInfo.card_number}}</span>
</div>
</div>
<div class="qr-section">
<p>{{lang.reset_auth_scan_tip}}</p>
<div class="qr-code">
<div id="qrcode-container">
<!-- 二维码将在这里生成 -->
</div>
</div>
<div class="verify-status">
<el-link :loading="verifying" type="primary" :underline="false" v-if="realnameStatus === 2">
<i class="el-icon-loading"></i>
{{lang.reset_auth_waiting_verify}}
</el-link>
<el-link type="success" :underline="false" v-if="realnameStatus === 1">{{lang.reset_auth_verify_success}}</el-link>
</div>
</div>
</div>
<!-- 动态底部按钮 -->
<div class="dialog-footer">
<template v-if="authDialog.currentStep === 'confirm'">
<el-button type="primary" @click="confirmAuthChange" :loading="submitLoading">{{lang.reset_auth_confirm_modify}}</el-button>
<el-button @click="closeAuthDialog">{{lang.reset_auth_cancel}}</el-button>
</template>
<template v-if="authDialog.currentStep === 'selectMethod'">
<el-button @click="goBackStep">{{lang.reset_auth_back}}</el-button>
<el-button @click="closeAuthDialog">{{lang.reset_auth_cancel}}</el-button>
</template>
<div v-show="authDialog.currentStep === 'autoVerify'">
<el-button type="primary" @click="handleSure" :disabled="realnameStatus === 2" :loading="submitLoading">{{lang.reset_auth_confirm_apply}}</el-button>
<el-button @click="closeAuthDialog">{{lang.reset_auth_cancel}}</el-button>
</div>
<!-- 人工审核提交转移到选择方式的时候直接提交
<template v-if="authDialog.currentStep === 'manualReview'">
<el-button type="primary" @click="submitReset" :loading="submitLoading">{{lang.reset_auth_submit_apply}}</el-button>
<el-button @click="closeAuthDialog">{{lang.reset_auth_cancel}}</el-button>
</template>
-->
</div>
</el-form>
</el-dialog>
</div>
`,
data() {
return {
hasRestAuth: false,
authDialog: {
visible: false,
currentStep: "confirm", // confirm -> selectMethod -> autoVerify/manualReview
host_id: "",
original_ip: "",
original_domain: "",
status: "",
application_id: "",
new_ip: "",
new_domain: "",
userInfo: {
username: "",
card_name: "",
card_number: "",
url: "",
certify_id: "",
},
reset_reason: "",
verifying: false,
},
// 实名状态
realnameStatus: 2, // 1通过 2未通过
verifying: true,
rules: {
new_ip: [
{
required: false,
pattern:
/^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
message: this.lang.reset_auth_ip_format_error,
trigger: "blur",
},
],
new_domain: [
{
required: false,
pattern:
/^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\.[a-zA-Z]{2,}$/,
message: this.lang.reset_auth_domain_format_error,
trigger: "blur",
},
],
reset_reason: [
{
required: true,
message: this.lang.reset_auth_reason_required,
trigger: "change",
},
],
},
submitLoading: false,
pollingTimer: null, // 轮询定时器
pollingTimeout: null, // 轮询超时定时器
qrCodeLibLoaded: false, // QRCode库是否已加载
};
},
components: {},
props: {
info: {
type: Object,
required: true,
default: () => {
return {
reject_reason: "", // 授权信息里面回显的拒绝理由
host_id: "",
original_ip: "",
original_domain: "",
status: "",
reset_reason: "", // 申请重置的理由
application_id: "",
};
},
},
},
created() {
if (this.info && Object.keys(this.info).length > 0) {
Object.assign(this.authDialog, this.info);
}
},
watch: {
info: {
handler(newVal) {
if (newVal && Object.keys(newVal).length > 0) {
Object.assign(this.authDialog, newVal);
}
},
deep: true,
immediate: false,
},
},
computed: {
authDialogTitle() {
const titleMap = {
confirm: this.lang.reset_auth_modify_title,
selectMethod: this.lang.reset_auth_modify_title,
autoVerify: this.lang.reset_auth_auto_reset,
manualReview: this.lang.reset_auth_manual_review,
};
return (
titleMap[this.authDialog.currentStep] ||
this.lang.reset_auth_modify_title
);
},
// 检查是否有授权信息变更
hasAuthChanges() {
const hasIPChange =
this.authDialog.new_ip.trim() !== "" &&
this.authDialog.new_ip !== this.authDialog.original_ip;
const hasDomainChange =
this.authDialog.new_domain.trim() !== "" &&
this.authDialog.new_domain !== this.authDialog.original_domain;
return hasIPChange || hasDomainChange;
},
},
mixins: [mixin],
mounted() {
this.hasRestAuth = this.addons_js_arr.includes("IdcsmartAuthreset");
},
methods: {
/* 授权相关方法 */
// 打开授权重置弹窗
handleResetAuth() {
this.authDialog.visible = true;
this.authDialog.currentStep = "confirm";
// 重置表单数据
this.authDialog.new_ip = this.authDialog.original_ip;
this.authDialog.new_domain = this.authDialog.original_domain;
this.authDialog.reset_reason = "";
this.authDialog.verifying = false;
},
/* 撤销重置 */
handleCancelAuth() {
this.$confirm(this.lang.reset_auth_cancel_confirm)
.then(async () => {
try {
const res = await apiCancelRest({
application_id: this.authDialog.application_id,
});
this.$message.success(res.data.msg);
this.$emit("cancel-auth");
} catch (error) {
this.$message.error(error.data?.msg);
}
})
.catch((_) => {});
},
// 确认授权修改
confirmAuthChange() {
if (!this.hasAuthChanges) {
this.submitReset();
} else {
this.$refs.ruleForm.validate((valid) => {
if (!valid) {
this.$message.error("请检查输入信息格式");
return;
}
this.authDialog.currentStep = "selectMethod";
});
}
},
// 选择验证方式
selectVerifyMethod(method) {
this.authDialog.reset_method = method;
this.submitReset();
},
// 开始自动验证
async startAutoVerify() {
try {
// application_id 提交申请过后返回的 application_id
const params = {
application_id: this.authDialog.application_id,
};
const res = await apiGetRealName(params);
this.authDialog.userInfo = res.data.data;
if (!this.authDialog.userInfo.url) {
this.realnameStatus = 2;
return this.$message.error("获取认证链接失败");
}
// 1. 根据 this.authDialog.userInfo.url生成二维码
this.generateQRCode(this.authDialog.userInfo.url);
// 2. 开始轮询状态
this.startPolling();
} catch (error) {
this.$message.error(error.data?.msg);
}
},
// 加载QRCode库
loadQRCodeLib() {
return new Promise((resolve) => {
if (typeof QRCode !== "undefined") {
this.qrCodeLibLoaded = true;
resolve();
return;
}
// 检查是否已有加载中的脚本
if (document.querySelector('script[src*="qrcode.min.js"]')) {
const checkLoaded = setInterval(() => {
if (typeof QRCode !== "undefined") {
clearInterval(checkLoaded);
this.qrCodeLibLoaded = true;
resolve();
}
}, 100);
return;
}
const script = document.createElement("script");
script.src = `${url}js/common/qrcode.min.js`;
script.onload = () => {
this.qrCodeLibLoaded = true;
resolve();
};
document.body.appendChild(script);
});
},
// 生成二维码
async generateQRCode(qrUrl) {
if (!qrUrl) return;
const container = document.getElementById("qrcode-container");
if (!container) return;
// 显示加载状态
container.innerHTML = '<div class="qrcode-status loading">生成中...</div>';
try {
// 确保QRCode库已加载
if (!this.qrCodeLibLoaded) {
await this.loadQRCodeLib();
}
if (typeof QRCode !== "undefined") {
container.innerHTML = "";
new QRCode(container, {
text: qrUrl,
width: 150,
height: 150,
colorDark: "#000000",
colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.M,
});
} else {
container.innerHTML = '<div class="qrcode-status error">二维码库加载失败</div>';
}
} catch (error) {
console.error("二维码生成错误:", error);
container.innerHTML = '<div class="qrcode-status error">二维码生成失败</div>';
}
},
// 开始轮询认证状态
startPolling() {
this.verifying = true;
this.realnameStatus = 2; // 重置为未通过状态
// 清除之前的轮询
if (this.pollingTimer) {
clearInterval(this.pollingTimer);
}
// 开始轮询每3秒检查一次
this.pollingTimer = setInterval(async () => {
try {
const result = await apiGetRealNameStatus({
application_id: this.authDialog.application_id,
certify_id: this.authDialog.userInfo.certify_id,
});
if (result.data && result.data.code === 1) {
// 认证成功
this.realnameStatus = 1;
this.verifying = false;
this.stopPolling();
}
} catch (error) {
this.verifying = false;
this.stopPolling();
this.$message.error("认证状态检查失败");
}
}, 3000);
// 设置最大轮询时间5分钟后自动停止
this.pollingTimeout = setTimeout(() => {
if (this.pollingTimer) {
this.stopPolling();
this.verifying = false;
this.realnameStatus = 2;
this.$message.warning("认证超时,请重试");
}
}, 300000);
},
// 停止轮询
stopPolling() {
if (this.pollingTimer) {
clearInterval(this.pollingTimer);
this.pollingTimer = null;
}
if (this.pollingTimeout) {
clearTimeout(this.pollingTimeout);
this.pollingTimeout = null;
}
},
async handleSure() {
try {
this.submitLoading = true;
const res = await apiAutoResetSure({
application_id: this.authDialog.application_id,
});
this.$message.success(res.data.msg);
this.submitLoading = false;
this.closeAuthDialog();
} catch (error) {
this.submitLoading = false;
this.$message.error(error.data?.msg);
}
},
// 提交审核申请
submitReset() {
this.$refs.ruleForm.validate(async (valid) => {
if (valid) {
try {
let params = {};
if (this.authDialog.currentStep !== "confirm") {
params = {
new_ip: this.authDialog.new_ip,
new_domain: this.authDialog.new_domain,
reset_reason: this.authDialog.reset_reason,
reset_method: this.authDialog.reset_method,
};
}
this.submitLoading = true;
params.host_id = this.authDialog.host_id;
const res = await apiRestAuth(params);
this.$message.success(res.data.msg);
const {application_id} = res.data.data;
this.authDialog.application_id = application_id;
this.submitLoading = false;
if (params.reset_method === "auto") {
this.authDialog.currentStep = "autoVerify";
this.startAutoVerify();
} else {
this.closeAuthDialog();
}
} catch (error) {
this.submitLoading = false;
this.$message.error(error.data?.msg);
}
} else {
return false;
}
});
},
// 返回上一步
goBackStep() {
const stepFlow = {
selectMethod: "confirm",
autoVerify: "selectMethod",
manualReview: "selectMethod",
};
this.authDialog.currentStep =
stepFlow[this.authDialog.currentStep] || "confirm";
},
// 关闭弹窗
closeAuthDialog() {
// 避免重复触发
if (!this.authDialog.visible) return;
// 停止轮询
this.stopPolling();
// 重置状态
this.verifying = false;
this.realnameStatus = 2;
// 清空二维码
const container = document.getElementById("qrcode-container");
if (container) {
container.innerHTML = "";
}
// 关闭弹窗
this.authDialog.visible = false;
this.authDialog.currentStep = "confirm";
// 重新拉取授权信息
this.$emit("cancel-auth");
},
/* 授权相关方法 end */
},
// 组件销毁时清理定时器
beforeDestroy() {
this.stopPolling();
},
};

View File

@@ -0,0 +1,230 @@
// 授权重置组件样式
// 二维码状态样式
.qrcode-status {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
&.loading {
color: #999;
}
&.error {
color: #f56c6c;
}
}
.common-auth-info {
.top {
display: flex;
align-items: center;
margin-bottom: 10px;
.tit {
font-size: 16px;
font-weight: 500;
margin: 0;
}
.reset-auth {
cursor: pointer;
font-size: 14px;
&:hover {
opacity: 0.8;
}
}
}
.bot-info {
display: flex;
align-items: center;
.label {
color: #606266;
}
.status {
&.reject {
color: #f56c6c;
}
&.pending {
color: #e6a23c;
.cancel-auth {
margin-left: 10px;
color: var(--color-primary);
cursor: pointer;
&:hover {
opacity: 0.8;
}
}
}
&.approved {
color: #67c23a;
}
}
}
}
// 授权重置弹窗
.common-auth-dialog {
.dialog-title {
font-size: 18px;
font-weight: 500;
text-align: center;
margin-bottom: 0.6rem;
}
.tip {
color: #606266;
font-size: 14px;
margin-bottom: 15px;
&.primary-color {
color: var(--color-primary);
}
}
.auth-info-display {
.base-info {
display: flex;
gap: 40px;
margin-bottom: 20px;
.item {
.s-tit {
font-weight: 500;
margin-bottom: 10px;
}
.info-row {
margin-bottom: 8px;
.label {
color: #909399;
}
.value {
color: #303133;
&.primary-color {
color: var(--color-primary);
}
}
}
}
}
.domain-tip {
color: #909399;
font-size: 12px;
margin-top: 5px;
}
}
.reason-section {
margin-top: 20px;
}
// 方式选择
.method-selection {
display: flex;
gap: 20px;
margin-top: 20px;
.method-card {
flex: 1;
padding: 20px;
border: 1px solid #dcdfe6;
border-radius: 8px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
&:hover {
border-color: var(--color-primary);
box-shadow: 0 2px 12px rgba(64, 158, 255, 0.2);
}
h4 {
margin: 0 0 10px;
font-size: 16px;
color: #303133;
}
p {
color: #909399;
margin-bottom: 15px;
}
}
}
// 自动验证
.verify-content {
.user-info {
background: #f5f7fa;
padding: 15px;
border-radius: 8px;
margin: 15px 0;
.info-row {
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
.label {
color: #909399;
}
.value {
color: #303133;
&.primary-color {
color: var(--color-primary);
}
}
}
}
.qr-section {
text-align: center;
margin-top: 20px;
> p {
color: #606266;
margin-bottom: 15px;
}
.qr-code {
display: flex;
justify-content: center;
margin-bottom: 15px;
#qrcode-container {
width: 150px;
height: 150px;
display: flex;
align-items: center;
justify-content: center;
}
}
.verify-status {
margin-top: 10px;
}
}
}
.dialog-footer {
margin-top: 30px;
text-align: center;
}
}

View File

@@ -0,0 +1,100 @@
const safeConfirm = {
template: `
<div>
<el-dialog width="6.8rem" :visible.sync="visible" :show-close=false @close="closeDialog" custom-class="withdraw-dialog">
<div class="dialog-title">
{{lang.account_tips_text3}}
</div>
<div class="dialog-main">
<el-form label-width="80px" ref="ruleForm" :rules="rules" :model="dataForm" label-position="top" @submit.native.prevent>
<el-form-item :label="lang.account_tips_text3" prop="password">
<el-input class="input-select" type="password" v-model="dataForm.password" autocomplete="off" :placeholder="lang.account_tips_text2"></el-input>
<el-checkbox v-model="remember" style="margin-top: 5px;color: #999;}">{{lang.account_tips_text13}}</el-checkbox>
</el-form-item>
</el-form>
</div>
<div slot="footer" class="dialog-footer">
<el-button class="btn-ok" type="primary" @click="save" v-loading="submitLoading">{{lang.cart_tip_text9}}</el-button>
<el-button class="btn-no" @click="closeDialog">{{lang.cart_tip_text10}}</el-button>
</div>
</el-dialog>
</div>
`,
data() {
return {
visible: false,
submitLoading: false,
passData: "",
callbackFun: "",
home_enforce_safe_method: [],
dataForm: {
password: "",
},
rules: {
password: [
{required: true, message: lang.account_tips_text2, trigger: "blur"},
],
},
remember: false,
};
},
computed: {},
props: {
password: {
type: String,
default: "",
},
isLogin: {
type: Boolean,
default: false,
},
},
watch: {},
created() {
this.home_enforce_safe_method =
JSON.parse(localStorage.getItem("common_set_before"))
?.home_enforce_safe_method || [];
},
methods: {
/**
* @param {String} callbackFun 回调函数名称
*/
openDialog(callbackFun) {
this.callbackFun = callbackFun;
this.dataForm.password = "";
this.$emit("update:password", this.dataForm.password);
// if (
// !this.home_enforce_safe_method.includes("operate_password") &&
// !this.isLogin
// ) {
// this.$emit("update:password", "noNeed");
// // 执行父级方法
// this.$emit("confirm", this.callbackFun);
// } else {
// this.visible = true;
// setTimeout(() => {
// this.$refs.ruleForm.resetFields();
// }, 0);
// }
this.visible = true;
setTimeout(() => {
this.$refs.ruleForm.resetFields();
}, 0);
},
closeDialog() {
this.visible = false;
},
save() {
this.$refs.ruleForm.validate((valid) => {
if (!valid) {
return false;
}
this.$emit("update:password", this.dataForm.password);
// 执行父级方法
this.$emit("confirm", this.callbackFun, this.remember ? 1 : 0);
this.closeDialog();
});
},
},
};

View File

@@ -0,0 +1,93 @@
const scrollText = {
template: /* html*/ `
<div class="scroll-container" ref="container">
<div
class="scroll-text"
ref="text"
:class="{ 'centered': !isOverflow }"
:style="textStyle"
@mouseenter="pause"
@mouseleave="resume"
>
<slot></slot>
<!-- loop 模式下复制一份实现无缝 -->
<span v-if="mode === 'loop' && isOverflow" class="clone">
<slot></slot>
</span>
</div>
</div>
`,
data() {
return {
duration: 0,
isOverflow: false,
paused: false,
containerWidth: 0,
textWidth: 0,
};
},
props: {
speed: {type: Number, default: 50}, // 像素/秒
pauseTime: {type: Number, default: 2}, // once 模式停顿时间(秒)
mode: {
type: String,
default: "loop", // 可选: loop, once, pause
},
},
computed: {
textStyle() {
// 不超出:静止居中
if (!this.isOverflow) {
return {transform: "translateX(0)"};
}
let animation = "";
if (this.mode === "loop") {
animation = `scroll-loop ${this.duration}s linear infinite`;
} else if (this.mode === "once") {
// 每次滚动结束,停 pauseTime 秒
const totalTime = this.duration + this.pauseTime;
animation = `scroll-once ${totalTime}s linear infinite`;
} else if (this.mode === "pause") {
animation = `scroll-pause ${this.duration}s linear forwards`;
}
return {
animation,
"animation-play-state": this.paused ? "paused" : "running",
};
},
},
mounted() {
this.checkOverflow();
window.addEventListener("resize", this.checkOverflow);
},
beforeDestroy() {
window.removeEventListener("resize", this.checkOverflow);
},
methods: {
checkOverflow() {
this.$nextTick(() => {
const container = this.$refs.container;
const text = this.$refs.text;
if (!container || !text) return;
this.containerWidth = container.clientWidth;
this.textWidth = text.scrollWidth;
this.isOverflow = this.textWidth > this.containerWidth;
if (this.isOverflow) {
this.duration = this.textWidth / this.speed;
}
});
},
pause() {
this.paused = true;
},
resume() {
this.paused = false;
},
},
};

View File

@@ -0,0 +1,423 @@
const securityVerification = {
template: `
<div>
<el-dialog width="6rem" :visible.sync="securityVisible" @close="closeSecurityDialog" custom-class="security-dialog"
:show-close="false">
<div class="security-title">
<span class="title-text">安全验证</span>
<span class="close-btn" @click="closeSecurityDialog">
<i class="el-icon-close"></i>
</span>
</div>
<div class="security-content">
<el-form label-width="80px" ref="securityForm" :model="securityForm" label-position="top" :rules="currentRules">
<el-form-item :label="lang.security_verify_text2" prop="method_id">
<el-select v-model="securityForm.method_id" style="width: 100%;" @change="methodChange" :placeholder="lang.security_verify_text7">
<el-option v-for="item in availableMethods" :key="item.value" :value="item.value" :label="item.label">
</el-option>
</el-select>
</el-form-item>
<el-form-item :label="calcCurrentMethod?.label" v-if="securityForm.method_id === 'phone_code'" prop="phone_code">
<div style="display: flex; align-items: center;gap: 10px;">
<el-input v-model="securityForm.phone_code" :placeholder="calcCurrentMethod?.tip">
</el-input>
<count-down-button ref="securityPhoneCodeBtnRef" :loading="phoneCodeLoading" @click.native="sendPhoneCode" my-class="code-btn" >
</count-down-button>
</div>
</el-form-item>
<el-form-item :label="calcCurrentMethod?.label" v-if="securityForm.method_id === 'email_code'" prop="email_code">
<div style="display: flex; align-items: center;gap: 10px;">
<el-input v-model="securityForm.email_code" :placeholder="calcCurrentMethod?.tip">
</el-input>
<count-down-button ref="securityEmailCodeBtnRef" :loading="emailCodeLoading" @click.native="sendEmailCode" my-class="code-btn" >
</count-down-button>
</div>
</el-form-item>
<el-form-item :label="calcCurrentMethod?.label" v-if="securityForm.method_id === 'operate_password'" prop="operate_password">
<el-input v-model="securityForm.operate_password" :placeholder="calcCurrentMethod?.placeholder"></el-input>
</el-form-item>
<el-form-item :label="calcCurrentMethod?.label" v-if="securityForm.method_id === 'certification'" prop="certification">
<div class="realname-verify-box">
<div ref="realnameVerifyRef" id="realnameVerify" style="display: flex; align-items: center; justify-content: center;"></div>
<p style="text-align: center;margin-top: 10px;">{{calcCurrentMethod?.tip}}</p>
</div>
</el-form-item>
</el-form>
</div>
<div class="security-footer">
<el-button type="primary" @click="confirmSecurity" :loading="confirmSecurityLoading">{{lang.finance_btn8}}</el-button>
<el-button type="info" class="cancel-btn" @click="closeSecurityDialog">{{lang.finance_btn7}}</el-button>
</div>
</el-dialog>
<captcha-dialog :is-show-captcha="isShowCaptcha" ref="securityCaptchaRef" captcha-id="security-captcha"
@get-captcha-data="getData" @captcha-cancel="captchaCancel">
</captcha-dialog>
</div>
`,
components: {
captchaDialog,
countDownButton,
},
props: {
actionType: {
type: String,
default: "exception_login",
},
},
data() {
return {
commonData: {},
phoneCodeLoading: false, // 手机验证码loading
emailCodeLoading: false, // 邮箱验证码loading
securityVisible: false, // 安全验证弹窗是否显示
confirmSecurityLoading: false, // 确认按钮loading
realnameStatus: false, // true通过 false未通过
verifying: false, // 是否正在验证
pollingTimer: null, // 轮询定时器
pollingTimeout: null, // 轮询超时定时器
securityForm: {
method_id: "", // 验证方式
phone_code: "", // 手机验证码
email_code: "", // 邮箱验证码
operate_password: "", // 操作密码
certification: "", // 实名验证
},
callbackFun: "", // 回调函数名称
availableMethods: [], // 可用验证方式
certify_id: "", // 认证ID
certify_url: "", // 认证URL
isShowCaptcha: false, //登录是否显示验证码弹窗
codeAction: "",
};
},
computed: {
currentRules() {
const {method_id} = this.securityForm;
const base = {
method_id: [{required: true, message: lang.security_verify_text7}],
};
if (method_id === "phone_code") {
base.phone_code = [
{required: true, message: lang.security_verify_text8},
];
}
if (method_id === "email_code") {
base.email_code = [
{required: true, message: lang.security_verify_text9},
];
}
if (method_id === "operate_password") {
base.operate_password = [
{required: true, message: lang.security_verify_text10},
];
}
if (method_id === "certification") {
base.certification = [
{required: true, message: lang.security_verify_text11},
];
}
return base;
},
calcCurrentMethod() {
return (
this.availableMethods.find(
(item) => item.value === this.securityForm.method_id
) || {}
);
},
},
methods: {
/**
* @param {String} callbackFun 回调函数名称
*/
openDialog(callbackFun, availableMethods = []) {
this.callbackFun = callbackFun;
this.availableMethods = availableMethods;
this.account = availableMethods[0]?.account || "";
this.phone_code = availableMethods[0]?.phone_code || "";
this.confirmSecurityLoading = false;
this.securityForm = {
method_id: availableMethods[0]?.value || "",
phone_code: "",
email_code: "",
operate_password: "",
certification: "",
};
this.stopPolling();
this.realnameStatus = false; // 重置为未通过状态
this.verifying = false; // 重置为未验证状态
this.securityVisible = true; // 显示安全验证弹窗
this.$nextTick(() => {
this.methodChange(availableMethods[0]?.value || "");
});
},
closeSecurityDialog() {
this.$refs.securityForm.resetFields();
if (this.$refs.realnameVerifyRef) {
this.$refs.realnameVerifyRef.innerHTML = "";
}
this.securityVisible = false;
this.stopPolling();
},
confirmSecurity() {
this.$refs.securityForm.validate((valid) => {
if (valid) {
if (this.securityForm.method_id === "certification") {
// 判断实名状态
if (!this.realnameStatus) {
this.$message.error(lang.security_verify_text11);
return;
}
}
this.confirmSecurityLoading = true;
this.$emit("confirm", this.callbackFun, {
security_verify_method: this.securityForm.method_id,
security_verify_value:
this.securityForm[this.securityForm.method_id],
certify_id:
this.securityForm.method_id === "certification"
? this.certify_id
: "",
security_verify_token:
this.calcCurrentMethod?.security_verify_token || "",
});
this.confirmSecurityLoading = false;
this.closeSecurityDialog();
}
});
},
methodChange(value) {
this.stopPolling();
this.securityForm.phone_code = "";
this.securityForm.email_code = "";
this.securityForm.operate_password = "";
this.securityForm.certification = "";
this.account =
this.availableMethods.find((item) => item.value === value)?.account ||
"";
this.phone_code =
this.availableMethods.find((item) => item.value === value)
?.phone_code || "";
if (value === "certification") {
this.$nextTick(async () => {
try {
const res = await apiCreateCertification({
account: this.account,
phone_code: this.phone_code,
security_verify_token:
this.calcCurrentMethod?.security_verify_token || "",
});
this.certify_id = res.data.data.certify_id;
this.certify_url = res.data.data.certify_url;
this.generateQRCode(this.certify_url);
this.startPolling();
} catch (error) {
console.error("创建认证失败:", error);
this.$message.error(error?.data?.msg || "创建认证失败,请重试");
}
});
}
},
sendPhoneCode(isAuto = false) {
if (this.phoneCodeLoading) return;
if (isAuto !== true) {
this.token = "";
this.captcha = "";
}
if (
this.commonData.captcha_client_security_verify == 1 &&
!this.captcha
) {
this.codeAction = "phoneCode";
this.isShowCaptcha = true;
this.$refs.securityCaptchaRef.doGetCaptcha();
return;
}
this.phoneCodeLoading = true;
const params = {
action: this.actionType,
phone_code: this.phone_code ?? undefined,
phone: this.account ?? undefined,
token: this.token,
captcha: this.captcha,
};
phoneCode(params)
.then((res) => {
if (res.data.status === 200) {
this.phoneCodeLoading = false;
// 执行倒计时
this.$refs.securityPhoneCodeBtnRef.countDown();
this.$message.success(res.data.msg);
}
})
.catch((err) => {
this.phoneCodeLoading = false;
this.$message.error(err.data.msg);
});
},
sendEmailCode(isAuto = false) {
if (this.emailCodeLoading) return;
if (isAuto !== true) {
this.token = "";
this.captcha = "";
}
if (
this.commonData.captcha_client_security_verify == 1 &&
!this.captcha
) {
this.codeAction = "emailCode";
this.isShowCaptcha = true;
this.$refs.securityCaptchaRef.doGetCaptcha();
return;
}
this.emailCodeLoading = true;
const params = {
action: this.actionType,
email: this.account,
token: this.token,
captcha: this.captcha,
};
emailCode(params)
.then((res) => {
this.$message.success(res.data.msg);
this.emailCodeLoading = false;
this.$refs.securityEmailCodeBtnRef.countDown();
})
.catch((err) => {
this.emailCodeLoading = false;
this.$message.error(err.data.msg);
});
},
loadQRCodeLib() {
return new Promise((resolve) => {
const script = document.createElement("script");
script.src = `${url}js/common/qrcode.min.js`;
script.onload = resolve;
document.body.appendChild(script);
});
},
// 生成二维码
generateQRCode(url) {
if (!url) return;
// 清空之前的二维码
const container = this.$refs.realnameVerifyRef;
if (container) {
// 显示加载状态
container.innerHTML =
'<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: #999;">生成中...</div>';
try {
// 使用QRCode.js库生成二维码需要确保已引入
if (typeof QRCode !== "undefined") {
container.innerHTML = "";
new QRCode(container, {
text: url,
width: 200,
height: 200,
colorDark: "#000000",
colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.M,
});
}
} catch (error) {
console.error("二维码生成错误:", error);
container.innerHTML =
'<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: #f56c6c;">二维码生成失败</div>';
}
}
},
// 开始轮询认证状态
startPolling() {
this.verifying = true;
this.realnameStatus = false; // 重置为未通过状态
// 清除之前的轮询
if (this.pollingTimer) {
clearInterval(this.pollingTimer);
}
// 开始轮询每3秒检查一次
this.pollingTimer = setInterval(async () => {
try {
const result = await apiGetCertificationStatus({
certify_id: this.certify_id,
account: this.account,
phone_code: this.phone_code,
security_verify_token:
this.calcCurrentMethod?.security_verify_token || "",
});
if (result?.data?.data?.verify_status === 1) {
// 认证成功
this.realnameStatus = true;
this.verifying = false;
this.stopPolling();
this.securityForm.certification = this.certify_id;
this.confirmSecurity();
}
} catch (error) {
this.verifying = false;
this.stopPolling();
this.$message.error("认证状态检查失败");
}
}, 3000);
// 设置最大轮询时间5分钟后自动停止
this.pollingTimeout = setTimeout(() => {
if (this.pollingTimer) {
this.stopPolling();
this.verifying = false;
this.realnameStatus = false;
this.$message.warning("认证超时,请重试");
}
}, 300000);
},
// 停止轮询
stopPolling() {
if (this.pollingTimer) {
clearInterval(this.pollingTimer);
this.pollingTimer = null;
}
if (this.pollingTimeout) {
clearTimeout(this.pollingTimeout);
this.pollingTimeout = null;
}
},
// 获取通用配置
getCommonData() {
this.commonData = JSON.parse(
localStorage.getItem("common_set_before") || "{}"
);
},
// 验证码验证成功后的回调
getData(captchaCode, token) {
this.isShowCaptcha = false;
this.token = token;
this.captcha = captchaCode;
if (this.codeAction === "emailCode") {
this.sendEmailCode(true);
} else if (this.codeAction === "phoneCode") {
this.sendPhoneCode(true);
}
},
// 验证码 关闭
captchaCancel() {
this.isShowCaptcha = false;
},
},
async mounted() {
this.getCommonData();
await this.loadQRCodeLib();
},
// 组件销毁时清理定时器
beforeDestroy() {
this.stopPolling();
},
};

View File

@@ -0,0 +1,39 @@
const shadowContent = {
template: /* html*/ `
<div ref="shadow"></div>
`,
props: {
content: {
type: String,
default: "",
},
},
data() {
return {
shadowRoot: null,
};
},
mounted() {
this.shadowRoot = this.$refs.shadow.attachShadow({mode: "open"});
this.renderShadow();
},
// 内容变动时更新 Shadow DOM
watch: {
content() {
this.renderShadow();
},
},
methods: {
renderShadow() {
this.shadowRoot.innerHTML = `
<style>
img {
max-width: 100%;
height: auto;
}
</style>
${this.content}
`;
},
},
};

View File

@@ -0,0 +1,616 @@
// css 样式依赖common.css
ELEMENT.Dialog.props.closeOnClickModal.default = false;
const topMenu = {
template: /*html*/ `
<div>
<el-drawer :visible.sync="isShowMenu" direction="ltr" :before-close="handleClose" :with-header="false" size="3.8rem"
custom-class="drawer-menu">
<div class="drawer-menu-top">
<a :href="commonData.clientarea_logo_url || '/home.htm'" onclick="return false" class="menu-alink">
<img class="drawer-menu-logo" @click="goHome" :src="logo"></img>
</a>
</div>
<div class="drawer-menu-list-top">
<el-menu class="menu-top" :default-active="menuActiveId" @select="handleSelect" background-color="transparent"
text-color="var(--color-menu-text)" active-text-color="var(--color-menu-text-active)">
<template v-for="item in menu1">
<!-- 只有一级菜单 -->
<el-menu-item v-if="!item.child || item.child?.length === 0" :key="item.id"
:index="item.id ? item.id + '' : item.url" :id="item.url">
<a :href="getMenuUrl(item.id || item.url)" onclick="return false" class="menu-alink">
<i class="iconfont" :class="item.icon"></i>
<span class="aside-menu-text" slot="title">{{item.name}}</span>
</a>
</el-menu-item>
<!-- 有二级菜单 -->
<el-submenu v-else :key="item.id" :index="item.id ? item.id + '' : item.url" :id="item.url">
<template slot="title">
<i class="iconfont" :class="item.icon"></i>
<span class="aside-menu-text" slot="title">{{item.name}}</span>
</template>
<template v-for="child in item.child">
<el-menu-item :index="child.id ? child.id + '' : child.url" :key="child.id">
<a :href="getMenuUrl(child.id || child.url)" onclick="return false" class="menu-alink">
{{child.name}}
</a>
</el-menu-item>
</template>
</el-submenu>
</template>
</el-menu>
<div class="line" v-if="hasSeparate"></div>
<el-menu class="menu-top" :default-active="menuActiveId " @select="handleSelect" background-color="transparent"
text-color="var(--color-menu-text)" active-text-color="var(--color-menu-text-active)">
<template v-for="item in menu2">
<!-- 只有一级菜单 -->
<el-menu-item v-if="!item.child || item.child?.length === 0" :key="item.id"
:index="item.id ? item.id + '' : item.url" :id="item.url">
<a :href="getMenuUrl(item.id || item.url)" onclick="return false" class="menu-alink">
<i class="iconfont" :class="item.icon"></i>
<span class="aside-menu-text" slot="title">{{item.name}}</span>
</a>
</el-menu-item>
<!-- 有二级菜单 -->
<el-submenu v-else :key="item.id" :index="item.id ? item.id + '' : item.url" :id="item.url">
<template slot="title">
<i class="iconfont" :class="item.icon"></i>
<span class="aside-menu-text" slot="title">{{item.name}}</span>
</template>
<template v-for="child in item.child">
<el-menu-item :index="child.id ? child.id + '' : child.url" :key="child.id">
<a :href="getMenuUrl(child.id || child.url)" onclick="return false" class="menu-alink">
{{child.name}}
</a>
</el-menu-item>
</template>
</el-submenu>
</template>
</el-menu>
</div>
</el-drawer>
<el-header>
<div class="header-left">
<img src="${url}/img/common/menu.png" class="menu-img" @click="showMenu">
<img v-if="isShowMore" src="${url}/img/common/search.png" class="left-img">
<el-autocomplete v-if="isShowMore" v-model="topInput" :fetch-suggestions="querySearchAsync" placeholder="请输入内容"
@select="handleSelect">
<template slot-scope="{ item }">
<div class="search-value">{{ item.value }}</div>
<div class="search-name">{{ item.name }}</div>
</template>
</el-autocomplete>
</div>
<div class="header-right">
<div class="header-right-item car-item">
<div v-if="isShowCart" class="right-item" @click="goShoppingCar">
<el-badge :value="shoppingCarNum" class="item" :max="999" :hidden="shoppingCarNum === 0 ? true : false">
<img src="${url}/img/common/cart.svg">
</el-badge>
</div>
</div>
<div class="header-right-item car-item" v-plugin="'ClientCare'">
<el-popover placement="bottom-start" trigger="hover" :visible-arrow="false">
<div class="top-msg-box">
<div class="msg-top">
<span class="msg-top-left">{{lang.subaccount_text56}}</span>
<span class="msg-top-right" @click="goAccount">{{lang.subaccount_text57}}</span>
</div>
<div class="msg-list" v-if="msgList.length !== 0">
<div class="msg-item" v-for="item in msgList" :key="item.id" @click="goMsgDetail(item.id)">
<div class="msg-item-left" :style="{color: item.read === 1 ? '#8692b0' : 'var(--color-primary)' }">
<svg t="1750123872473" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12811" width="16" height="16">
<path d="M440.35072 623.04768 46.04416 373.1456l0 393.62048c0 64.63488 52.53632 117.39136 117.31968 117.39136l706.71872 0c64.70656 0 117.31456-52.57728 117.31456-117.39136l0-387.1232-368.99328 242.10944C568.1408 654.72 491.07968 655.19104 440.35072 623.04768L440.35072 623.04768 440.35072 623.04768z" fill="currentColor" p-id="12812"></path>
<path d="M870.08256 158.51008 163.35872 158.51008c-64.70656 0-117.31968 52.57728-117.31968 117.39136l0 0.90112 443.30496 280.96c20.83328 13.2096 58.27584 12.95872 78.93504-0.57856l419.1488-275.00032 0-6.28224C987.39712 211.3024 934.8608 158.51008 870.08256 158.51008L870.08256 158.51008 870.08256 158.51008z" fill="currentColor" p-id="12813"></path>
</svg>
</div>
<div class="msg-item-right">
<div class="msg-item-right-top">{{item.title}}</div>
<div class="msg-item-right-bottom">
<span>{{msgType[item.type]}}</span>
<span>{{item.create_time | formateTime}}</span>
</div>
</div>
</div>
</div>
<el-empty v-else :description="lang.subaccount_text55"></el-empty>
</div>
<div class="right-item" slot="reference">
<el-badge :value="msgCount" class="item" :max="999" :hidden="msgCount === 0 ? true : false">
<img src="${url}/img/common/msg.svg">
</el-badge>
</div>
</el-popover>
</div>
<div class="header-right-item hg-24">
<el-dropdown @command="changeLang" trigger="click" :disabled="commonData.lang_home_open * 1 ? false : true">
<div class="el-dropdown-country">
<img :src="curSrc" alt="">
<i class="right-icon el-icon-arrow-down el-icon--right"></i>
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="item in commonData.lang_list" :key="item.display_lang"
:command="item.display_lang">{{item.display_name}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<div class="header-right-item cloum-line-item" v-if="isGetData">
<div class="cloum-line"></div>
</div>
<div class="header-right-item" v-show="unLogin && isGetData">
<div class="un-login" @click="goLogin">
<img src="${url}/img/common/login_icon.png">{{lang.topMenu_text1}}
</div>
</div>
<div class="header-right-item" v-show="!unLogin && isGetData">
<el-dropdown @command="handleCommand" trigger="click">
<div class="el-dropdown-header">
<div class="right-item head-box" ref="headBoxRef" v-show="firstName">{{firstName}}</div>
<i class="right-icon el-icon-arrow-down el-icon--right"></i>
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="account">{{lang.topMenu_text2}}</el-dropdown-item>
<el-dropdown-item command="quit">{{lang.topMenu_text3}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<coin-active ref="coinActive" v-plugin="'Coin'"></coin-active>
</div>
</el-header>
</div>
`,
directives: {
plugin: {
inserted: function (el, binding) {
const addonsDom = document.querySelector("#addons_js");
let addonsArr = [];
let arr = [];
if (addonsDom) {
addonsArr = JSON.parse(addonsDom.getAttribute("addons_js")) || []; // 插件列表
// 判断是否安装了某插件
arr = addonsArr.filter((item) => item.name === binding.value);
if (arr.length === 0) {
// 未安装 移除该元素的dom
el.parentNode.removeChild(el);
}
} else {
el.parentNode.removeChild(el);
}
},
},
},
components: {
coinActive,
},
data () {
return {
topInput: "",
// curSrc: url+'/img/common/'+lang_obj.countryImg+'.png' ,
curSrc: `/upload/common/country/${lang_obj.countryImg}.png`,
isShowMenu: false,
logo: `/upload/logo.png`,
menu1: [],
menu2: [],
menuActiveId: "",
firstName: "",
hasSeparate: false,
originMenu: [],
produclData: [],
selectValue: "",
shoppingCarNum: 0,
headBgcList: [
"#3699FF",
"#57C3EA",
"#5CC2D7",
"#EF8BA2",
"#C1DB81",
"#F1978C",
"#F08968",
],
commonData: {
lang_list: [],
},
unLogin: true,
isGetData: false,
msgList: [],
msgCount: 0,
accountData: {},
msgType: {
official: lang.subaccount_text54,
host: lang.finance_info,
finance: lang.finance_text123,
},
predefineColors: [
"#ff4500",
"#ff8c00",
"#ffd700",
"#90ee90",
"#00ced1",
"#1e90ff",
"#c71585",
"rgba(255, 69, 0, 0.68)",
"rgb(255, 120, 0)",
"hsv(51, 100, 98)",
"hsva(120, 40, 94, 0.5)",
"hsl(181, 100%, 37%)",
"hsla(209, 100%, 56%, 0.73)",
"#c7158577",
],
};
},
props: {
isShowMore: {
type: Boolean,
default: false,
},
isShowCart: {
type: Boolean,
default: true,
},
num: {
type: Number,
default: 0,
},
},
watch: {
num (val) {
if (val) {
this.shoppingCarNum = val;
}
},
},
filters: {
formateTime (time) {
if (time && time !== 0) {
const date = new Date(time * 1000);
Y = date.getFullYear() + "-";
M =
(date.getMonth() + 1 < 10
? "0" + (date.getMonth() + 1)
: date.getMonth() + 1) + "-";
D = (date.getDate() < 10 ? "0" + date.getDate() : date.getDate()) + " ";
h =
(date.getHours() < 10 ? "0" + date.getHours() : date.getHours()) +
":";
m =
date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes();
return Y + M + D + h + m;
} else {
return "--";
}
},
},
created () {
this.GetIndexData();
this.doGetMenu();
this.getCartList();
this.getCommonSetting();
},
mounted () {
// 不生效
this.color1 = getComputedStyle(document.documentElement)
.getPropertyValue("--color-primary")
.trim();
if (this.getPluginId("ClientCare")) {
this.getMessageList();
}
},
methods: {
getPluginId (pluginName) {
const addonsDom = document.querySelector("#addons_js");
if (addonsDom) {
const addonsArr = JSON.parse(addonsDom.getAttribute("addons_js")); // 插件列表
for (let index = 0; index < addonsArr.length; index++) {
const element = addonsArr[index];
if (pluginName === element.name) {
return element.id;
}
}
} else {
console.log("请检查页面是否有插件dom");
}
},
goAccount () {
location.href = "/account.htm?type=3";
},
getMessageList () {
messageInfo().then((res) => {
this.msgList = res.data.data.credit_limit.list;
this.msgCount = res.data.data.credit_limit.count;
this.msgType = res.data.data.credit_limit.type.reduce((all, cur) => {
all[cur.name] = cur.name_lang;
return all;
}, {});
});
},
goMsgDetail (id) {
location.href = `/plugin/${getPluginId(
"ClientCare"
)}/msgDetail.htm?id=${id}`;
},
// 退出登录
logOut () {
this.$confirm(lang.topMenu_text4, lang.topMenu_text5, {
confirmButtonText: lang.topMenu_text6,
cancelButtonText: lang.topMenu_text7,
type: "warning",
})
.then(() => {
//const res = await Axios.post('/logout')
Axios.post("/logout").then((res) => {
localStorage.removeItem("jwt");
setTimeout(() => {
location.href = "/login.htm";
}, 300);
});
})
.catch(() => { });
},
goLogin () {
location.href = "/login.htm";
},
goHome () {
localStorage.frontMenusActiveId = "";
const openUrl = this.commonData.clientarea_logo_url || "/home.htm";
if (this.commonData.clientarea_logo_url_blank == 1) {
window.open(openUrl);
} else {
location.href = openUrl;
}
},
// 获取购物车数量
getCartList () {
cartList()
.then((res) => {
this.shoppingCarNum = res.data.data.list.filter(
(item) => item.customfield?.is_domain !== 1
).length;
})
.catch((err) => {
this.$message.error(err.data.msg);
});
},
GetIndexData () {
accountDetail()
.then((res) => {
if (res.data.status == 200) {
this.accountData = res.data.data.account;
localStorage.lang = res.data.data.account.language || "zh-cn";
this.firstName = res.data.data.account.username
.substring(0, 1)
.toUpperCase();
this.unLogin = false;
if (sessionStorage.headBgc) {
this.$refs.headBoxRef.style.background = sessionStorage.headBgc;
} else {
const index = Math.round(
Math.random() * (this.headBgcList.length - 1)
);
this.$refs.headBoxRef.style.background = this.headBgcList[index];
sessionStorage.headBgc = this.headBgcList[index];
}
}
})
.finally(() => {
this.isGetData = true;
});
},
goShoppingCar () {
localStorage.frontMenusActiveId = "";
location.href = "/cart/shoppingCar.htm";
},
goAccountpage () {
location.href = "/account.htm";
},
// 语言切换
changeLang (e) {
if (localStorage.getItem("lang") !== e || !localStorage.getItem("lang")) {
localStorage.setItem("lang", e);
sessionStorage.setItem("brow_lang", e);
let jwt = localStorage.getItem("jwt") || "";
if (jwt) {
this.accountData.language = e;
this.saveAccount();
} else {
this.changeLangHandle(e);
}
}
},
async changeLangHandle (e) {
try {
const res = await changeLanguage({
language: e,
});
this.$message.success(res.data.msg);
window.location.reload();
} catch (error) {
this.$message.error(error.data.msg);
}
},
// 编辑基础资料
saveAccount () {
const params = {
...this.accountData,
};
updateAccount(params)
.then((res) => {
if (res.data.status === 200) {
this.$message.success(res.data.msg);
window.location.reload();
}
})
.catch((error) => {
this.$message.error(error.data.msg);
});
},
handleCommand (e) {
if (e == "account") {
this.goAccountpage();
}
if (e == "quit") {
this.logOut();
}
console.log(e);
},
// 全局搜索
querySearchAsync (queryString, cb) {
if (queryString.length == 0) {
return false;
}
const params = {
keywords: queryString,
};
globalSearch(params).then((res) => {
if (res.data.status === 200) {
const data = res.data.data.hosts;
const result = [];
data.map((item) => {
let value = item.product_name + "#/" + item.id;
result.push({
id: item.id,
value,
name: item.name,
});
});
cb(result);
}
});
},
/*
* 获取菜单url
* @param {Number} id 菜单id 或者url
* @return {String} url 菜单url
*/
getMenuUrl (id) {
const temp =
this.originMenu.find((item) => item.id == id || item.url == id) || {};
const reg =
/^(((ht|f)tps?):\/\/)([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/;
let url = "/" + temp.url;
if (reg.test(temp.url)) {
if (temp?.second_reminder === 1) {
url = `/transfer.htm?target=${encodeURIComponent(temp.url)}`;
} else {
url = temp.url;
}
}
return url;
},
handleSelect (id) {
localStorage.setItem("frontMenusActiveId", id);
const temp =
this.originMenu.find((item) => item.id == id || item.url == id) || {};
const reg =
/^(((ht|f)tps?):\/\/)([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/;
if (reg.test(temp.url)) {
if (temp?.second_reminder === 1) {
return window.open(
`/transfer.htm?target=${encodeURIComponent(temp.url)}`
);
} else {
return window.open(temp.url);
}
}
location.href = "/" + temp.url;
},
showMenu () {
this.isShowMenu = true;
},
handleClose () {
this.isShowMenu = false;
},
// 获取前台导航
doGetMenu () {
getMenu().then((res) => {
if (res.data.status === 200) {
res.data.data.menu.forEach((item) => {
if (item.child && item.child.length > 0) {
this.originMenu.push(...item.child);
} else {
this.originMenu.push(item);
}
});
const menu = res.data.data.menu;
localStorage.setItem("frontMenus", JSON.stringify(menu));
let index = menu.findIndex((item) => item.name == "分隔符");
if (index != -1) {
this.hasSeparate = true;
this.menu1 = menu.slice(0, index);
this.menu2 = menu.slice(index + 1);
} else {
this.hasSeparate = false;
this.menu1 = menu;
}
this.setActiveMenu();
}
});
},
// 判断当前菜单激活
setActiveMenu () {
const originUrl = location.pathname.slice(1);
const allUrl = originUrl + location.search;
let flag = false;
this.originMenu.forEach((item) => {
// 当前url下存在和导航菜单对应的路径
if (!item.child && item.url) {
const url = String(item.url).split("?");
if (
(url.length > 1 && item.url == allUrl) ||
(url.length == 1 && item.url == originUrl)
) {
this.menuActiveId = item.id + "";
flag = true;
}
}
// 当前url下存在二级菜单
if (item.child && item.child.length > 0) {
item.child.forEach((child) => {
const url = String(child.url).split("?");
if (
(url.length > 1 && child.url == allUrl) ||
(url.length == 1 && child.url == originUrl)
) {
this.menuActiveId = child.id + "";
flag = true;
}
});
}
});
if (!flag) {
this.menuActiveId = localStorage.getItem("frontMenusActiveId") || "";
}
},
// 页面跳转
toPage (e) {
location.href = "/" + e.url;
},
// 获取通用配置
async getCommonSetting () {
try {
if (!localStorage.getItem("common_set_before")) {
const res = await getCommon();
this.commonData = res.data.data;
localStorage.setItem(
"common_set_before",
JSON.stringify(res.data.data)
);
}
this.commonData = JSON.parse(localStorage.getItem("common_set_before"));
this.logo = this.commonData.system_logo;
} catch (error) { }
},
},
};

View File

@@ -0,0 +1,108 @@
/* 流量预警 */
const trafficWarning = {
template: `
<div class="traffic-warning">
<div class="search-btn" @click="handleConfig">{{lang.flow_warn_text3}}</div>
<span v-if="tempValue" class="tip">{{lang.flow_warn_text1}}{{tempValue}}%{{lang.flow_warn_text2}}</span>
<el-dialog width="6.8rem" :visible.sync="visible" :show-close="false" @close="closeDialog" custom-class="withdraw-dialog">
<div class="dialog-title">{{lang.flow_warn_text3}}</div>
<div class="dialog-main">
<el-form label-width="120px" ref="ruleForm" :rules="rules" :model="warningForm" label-position="right">
<el-form-item :label="lang.flow_warn_text4" prop="warning_switch">
<el-switch v-model="warningForm.warning_switch" active-color="var(--color-primary)"
:active-value="1" :inactive-value="0">
</el-switch>
</el-form-item>
<el-form-item :label="lang.flow_warn_text1" prop="leave_percent" v-if="warningForm.warning_switch">
<el-select v-model="warningForm.leave_percent" :placeholder="lang.placeholder_pre2">
<el-option :value="5" label="5%"></el-option>
<el-option :value="10" label="10%"></el-option>
<el-option :value="15" label="15%"></el-option>
<el-option :value="20" label="20%"></el-option>
</el-select>
<span class="warning-text">{{lang.flow_warn_text5}}</span>
</el-form-item>
</el-form>
</div>
<div slot="footer" class="dialog-footer">
<el-button class="btn-ok" type="primary" @click="submit" :loading="submitLoading">{{lang.cart_tip_text9}}</el-button>
<el-button class="btn-no" @click="closeDialog">{{lang.cart_tip_text10}}</el-button>
</div>
</el-dialog>
</div>
`,
data() {
return {
visible: false,
submitLoading: false,
rules: {
leave_percent: [
{required: true, message: lang.placeholder_pre2, trigger: "change"},
],
},
warningForm: {
module: "",
warning_switch: 1,
leave_percent: "",
},
tempValue: 0,
tempData: {},
};
},
computed: {},
props: {
module: {
type: String,
default: "mf_cloud", // 目前支持 mf_cloud mf_dcim
},
},
watch: {},
created() {
this.getWarningConfig();
},
methods: {
async getWarningConfig() {
try {
const res = await getTrafficWarning({
module: this.module,
});
const temp = res.data.data;
this.tempValue = temp.leave_percent;
temp.leave_percent = temp.leave_percent || "";
this.warningForm = temp;
this.tempData = JSON.parse(JSON.stringify(temp));
} catch (error) {
this.$message.error(error.message);
}
},
handleConfig() {
this.visible = true;
Object.assign(this.warningForm, this.tempData);
},
closeDialog() {
this.visible = false;
},
submit() {
this.$refs.ruleForm.validate(async (valid) => {
if (valid) {
const params = {
module: this.module,
warning_switch: this.warningForm.warning_switch,
leave_percent: this.warningForm.leave_percent,
};
if (params.warning_switch === 0) {
params.leave_percent = 0;
}
this.submitLoading = true;
const res = await saveTrafficWarning(params);
this.submitLoading = false;
this.visible = false;
this.$message.success(res.data.msg);
this.getWarningConfig();
} else {
return false;
}
});
},
},
};

View File

@@ -0,0 +1,238 @@
const withdrawDialog = {
template: /*html */ `
<el-dialog width="6.8rem" :visible.sync="withdrawVisible" :show-close=false @close="withdrawCancel" custom-class="withdraw-dialog">
<div class="dialog-title">
{{lang.withdraw_title}}
</div>
<div class="dialog-main">
<el-form label-width="80px" :model="withdrawForm" label-position="top">
<el-form-item :label="lang.withdraw_label1" >
<el-select class="input-select" v-model="withdrawForm.method_id" @change="methodChange" :placeholder="lang.withdraw_placeholder1">
<el-option v-for="item in ruler.method" :key="item.id" :value="item.id" :label="item.name"></el-option>
</el-select>
</el-form-item>
<template v-if="withdrawForm.method_id != 0">
<el-form-item :label="lang.withdraw_label2" v-if="isBank">
<el-input class="input-select" v-model="withdrawForm.card_number"
:placeholder="lang.withdraw_placeholder3"></el-input>
</el-form-item>
<el-form-item :label="lang.withdraw_label3" v-else>
<el-input class="input-select" v-model="withdrawForm.account"
:placeholder="lang.withdraw_placeholder2"></el-input>
</el-form-item>
<el-form-item :label="lang.withdraw_label4">
<el-input class="input-select" v-model="withdrawForm.name" :placeholder="lang.withdraw_placeholder4"></el-input>
</el-form-item>
</template>
<el-form-item :label="lang.withdraw_label5" >
<el-input @keyup.native="withdrawForm.amount=oninput(withdrawForm.amount)" class="input-select amount-input" v-model="withdrawForm.amount" :placeholder="lang.withdraw_placeholder5 + currency_prefix + ruler.withdrawable_amount">
<el-button class="all-btn" type="text" slot="suffix" @click="withdrawForm.amount=ruler.withdrawable_amount">{{lang.withdraw_btn3}}
</el-button>
</el-input>
</el-form-item>
<el-form-item v-if="errText">
<el-alert :title="errText" type="error" :closable="false" show-icon>
</el-alert>
</el-form-item>
</el-form>
</div>
<div class="withdraw-rule">
<div class="label">{{lang.withdraw_title2}}</div>
<div class="rules">
<div class="rules-item" v-if="ruler.withdraw_min || ruler.withdraw_max">
{{lang.withdraw_text1}}
<span v-if="ruler.withdraw_min">{{lang.withdraw_text2}}{{currency_prefix}}{{ruler.withdraw_min}}</span>
<span v-if="ruler.withdraw_min && ruler.withdraw_max">,</span>
<span v-if="ruler.withdraw_max">{{lang.withdraw_text3}}{{currency_prefix}}{{ruler.withdraw_max}}</span>
</div>
<div class="rules-item" v-if="ruler.withdraw_handling_fee || ruler.percent_min">
{{lang.withdraw_text4}}
<span v-if="ruler.withdraw_handling_fee">{{ruler.withdraw_handling_fee}}</span>
<!-- 最低手续费 -->
<span v-if="ruler.percent_min">{{lang.withdraw_text5}}{{currency_prefix}}{{ruler.percent_min}}</span>
<span>{{lang.withdraw_text6}}</span>
</div>
<div class="rules-item">
{{lang.withdraw_text7}}
</div>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button class="btn-ok" type="primary" @click="doApplyWithdraw()" v-loading="withdrawLoading">{{lang.withdraw_btn1}}</el-button>
<el-button class="btn-no" @click="withdrawCancel">{{lang.withdraw_btn2}}</el-button>
</span>
</el-dialog>
`,
data() {
return {
// 提现弹窗开始
// 是否显示提现弹窗
currency_prefix: "¥",
withdrawVisible: false,
withdrawForm: {
source: "",
method_id: "",
amount: "",
card_number: "",
name: "",
account: "",
notes: "",
},
isBank: false,
withdrawLoading: false,
errText: "",
ruler: {
// 提现来源
source: "",
// 提现方式
method: [],
// 第一个提现方式
method_id: "",
// 可提现金额
withdrawable_amount: "",
// 单次提现最提金额
withdraw_min: "",
// 单次提现最高金额
withdraw_max: "",
// 提现手续费 百分比的带上“%” 固定金额 保留两位数
withdraw_handling_fee: "",
// 最低提现手续费
percent_min: "",
},
};
},
created() {
this.currency_prefix = JSON.parse(
localStorage.getItem("common_set_before")
).currency_prefix;
},
methods: {
// 提现弹窗开始
// 提现方式变化
methodChange(e) {
const method = this.ruler.method;
this.isBank = false;
method.forEach((item) => {
if (item.id == e && item.name == "银行卡") {
this.isBank = true;
}
});
},
// 显示提现弹窗
shwoWithdrawal(ruler) {
this.withdrawForm = {
source: ruler.source,
method_id: ruler.method_id,
amount: "",
card_number: "",
name: "",
account: "",
notes: "",
};
// 默认选择第一个
// this.withdrawForm.method_id = ruler.method[0].id
this.methodChange(this.withdrawForm.method_id);
this.ruler = ruler;
this.withdrawVisible = true;
this.errText = "";
},
// 申请提现
doApplyWithdraw() {
let isPass = true;
this.errText = "";
const params = {
...this.withdrawForm,
};
if (this.isBank && !params.card_number) {
this.errText = lang.withdraw_placeholder3;
isPass = false;
return;
}
if (!params.method_id && params.method_id != 0) {
this.errText = lang.withdraw_placeholder1;
isPass = false;
return;
}
if (!params.amount) {
this.errText = lang.withdraw_placeholder6;
isPass = false;
return;
} else {
// 提现金额小于最小金额
if (
this.ruler.withdraw_min &&
Number(this.ruler.withdraw_min) > Number(params.amount)
) {
this.errText =
lang.withdraw_tips1 +
this.currency_prefix +
this.ruler.withdraw_min;
isPass = false;
return;
}
if (Number(params.amount) > Number(this.ruler.withdrawable_amount)) {
this.errText = lang.withdraw_tips2;
isPass = false;
return;
}
if (
this.ruler.withdraw_max &&
Number(this.ruler.withdraw_max) < Number(params.amount)
) {
this.errText =
lang.withdraw_tips3 +
this.currency_prefix +
this.ruler.withdraw_max;
isPass = false;
return;
}
}
if (isPass) {
this.withdrawLoading = true;
this.errText = "";
this.$emit("dowithdraw", params);
}
},
oninput(value) {
let str = value;
let len1 = str.substr(0, 1);
let len2 = str.substr(1, 1);
//如果第一位是0第二位不是点就用数字把点替换掉
if (str.length > 1 && len1 == 0 && len2 != ".") {
str = str.substr(1, 1);
}
//第一位不能是.
if (len1 == ".") {
str = "";
}
if (len1 == "+") {
str = "";
}
if (len1 == "-") {
str = "";
}
//限制只能输入一个小数点
if (str.indexOf(".") != -1) {
let str_ = str.substr(str.indexOf(".") + 1);
if (str_.indexOf(".") != -1) {
str = str.substr(0, str.indexOf(".") + str_.indexOf(".") + 1);
}
}
//正则替换
str = str.replace(/[^\d^\.]+/g, ""); // 保留数字和小数点
str = str.replace(/^\D*([0-9]\d*\.?\d{0,2})?.*$/, "$1"); // 小数点后只能输 2 位
return str;
},
withdrawCancel() {
this.withdrawVisible = false;
this.withdrawLoading = false;
},
},
};