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,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();
},
};